how to convert string function to string method?

  • Thread starter Dr. Phillip M. Feldman
  • Start date
D

Dr. Phillip M. Feldman

I wrote a handy-dandy function (see below) called "strip_pairs" for stripping
matching pairs of characters from the beginning and end of a string. This
function works, but I would like to be able to invoke it as a string method
rather than as a function. Is this possible?

def strip_pairs(s=None, open='([{\'"', close=')]}\'"'):
"""This function strips matching pairs of characters from the beginning
and
end of the input string `s`. `open` and `close` specify corresponding
pairs
of characters, and must be equal-length strings. If `s` begins with a
character in `open` and ends with the corresponding character in `close`,
both are removed from the string. This process continues until no
further
matching pairs can be removed."""

if len(open) != len(close): raise Exception, \
'\'open\' and \'close\' arguments must be strings of equal length.'

# If input is missing or is not of type `str` (or `unicode`), return
None:
if s is None or not isinstance(s,(str,unicode)): return None

while len(s) >= 2:

# Check whether first character of `s` is in `open`:
i= open.find(s[0])

# If `s` does not begin with a character from `open`, there are no
more
# pairs to be stripped:
if i == -1: break

# If `s` does not begin and end with matching characters, there are no
# more pairs to be stripped:
if s[-1] != close: break

# Strip the first and last character from `s`:
s= s[1:-1]

return s
 
P

Peter Otten

Dr. Phillip M. Feldman said:
I wrote a handy-dandy function (see below) called "strip_pairs" for
stripping
matching pairs of characters from the beginning and end of a string. This
function works, but I would like to be able to invoke it as a string
method
rather than as a function. Is this possible?

This requires a feature called "open classes" (Ruby has them). It is not
possible in Python, at least for built-in types like str.

Peter
 
R

r0g

Dr. Phillip M. Feldman said:
I wrote a handy-dandy function (see below) called "strip_pairs" for stripping
matching pairs of characters from the beginning and end of a string. This
function works, but I would like to be able to invoke it as a string method
rather than as a function. Is this possible?

def strip_pairs(s=None, open='([{\'"', close=')]}\'"'):
"""This function strips matching pairs of characters from the beginning
and
end of the input string `s`. `open` and `close` specify corresponding
pairs
of characters, and must be equal-length strings. If `s` begins with a
character in `open` and ends with the corresponding character in `close`,
both are removed from the string. This process continues until no
further
matching pairs can be removed."""

if len(open) != len(close): raise Exception, \
'\'open\' and \'close\' arguments must be strings of equal length.'

# If input is missing or is not of type `str` (or `unicode`), return
None:
if s is None or not isinstance(s,(str,unicode)): return None

while len(s) >= 2:

# Check whether first character of `s` is in `open`:
i= open.find(s[0])

# If `s` does not begin with a character from `open`, there are no
more
# pairs to be stripped:
if i == -1: break

# If `s` does not begin and end with matching characters, there are no
# more pairs to be stripped:
if s[-1] != close: break

# Strip the first and last character from `s`:
s= s[1:-1]

return s



I've never tried it but I think it is possible to inject new methods
into existing classes, see...

http://www.daniel-lemire.com/blog/archives/2005/12/21/metaclass-programming-in-python/

Not sure how good an idea it would be though, I have heard it refered to
as "monkey patching" and I can imagine doing it to classes as
fundamental as the string class could have negative performace consequences.

Roger.
 
B

Bruno Desthuilliers

Dr. Phillip M. Feldman a écrit :
I wrote a handy-dandy function (see below) called "strip_pairs" for stripping
matching pairs of characters from the beginning and end of a string. This
function works, but I would like to be able to invoke it as a string method
rather than as a function. Is this possible?

No. Basic type (strings/unicode, numerics, lists, tuples, dicts etc) are
not "opened" to monkeypatching. Partly for performance reasons, partly
to avoid the insanity that we've seen in the Ruby world where everyone
and it's little sisters extends basic types with tons of more or less
usefull and often conflicting features.
def strip_pairs(s=None, open='([{\'"', close=')]}\'"'):

Why a default value of None for the first param ???
"""This function strips matching pairs of characters from the beginning
and
end of the input string `s`. `open` and `close` specify corresponding
pairs
of characters, and must be equal-length strings. If `s` begins with a
character in `open` and ends with the corresponding character in `close`,
both are removed from the string. This process continues until no
further
matching pairs can be removed."""

if len(open) != len(close): raise Exception, \
'\'open\' and \'close\' arguments must be strings of equal length.'


Please use proper exception types. Here you want a ValueError. Also, you
just dont care whether open and close are proper strings - any sequence
of characters would do.
# If input is missing or is not of type `str` (or `unicode`), return
None:
if s is None or not isinstance(s,(str,unicode)): return None

If s is None, it won't be a str or unicode instance, so the test is
redundant !-)

But anyway : if this function is supposed to operate on strings, don't
accept None (and even less provide it as default), and don't fail
silently - you should really raise a TypeError here IMHO.
while len(s) >= 2:

# Check whether first character of `s` is in `open`:
i= open.find(s[0])

# If `s` does not begin with a character from `open`, there are no
more
# pairs to be stripped:
if i == -1: break

# If `s` does not begin and end with matching characters, there are no
# more pairs to be stripped:
if s[-1] != close: break

# Strip the first and last character from `s`:
s= s[1:-1]

return s


HTH
 
S

Steven D'Aprano

I wrote a handy-dandy function (see below) called "strip_pairs" for
stripping matching pairs of characters from the beginning and end of a
string. This function works, but I would like to be able to invoke it
as a string method rather than as a function. Is this possible?

Not exactly. You can subclass string and add such a method:

class MyString(str):
def strip_pairs(self, ...):
...

but then you have to convert every string to a MyString before you use
it. That way leads to madness.

Better to just use a function. Don't worry, you're allowed to mix
functional and OO code :)


As for the function, I think we can re-write it to be a bit more
Pythonic. We start with a helper function. (Since it's a one-liner,
that's not strictly necessary.)


def bracketed_by(s, open, close):
"""Return True if string s is bracketed by open and close pairs."""
# I'm too lazy to put proper error handling here...
return s.startswith(open) and s.endswith(close)


# UNTESTED
def strip_pairs(s, open='([{\'"', close=')]}\'"'):
if len(open) != len(close):
raise ValueError(
"'open' and 'close' arguments must be equal length strings.")
if not isinstance(s, basestring):
# FIX ME: this wrongly fails on UserString arguments.
raise TypeError('argument must be a string')
pairs = zip(open, close)
while any(bracketed_by(s, a, b) for (a,b) in pairs):
s = s[1:-1]
return s



The above is written for simplicity rather than efficiency -- for small
strings, say, under a couple of megabytes, it is likely to be faster than
a more "clever" algorithm. If you are routinely dealing with strings
which are tens of megabytes in size, bracketed by dozens or hundreds of
pairs of brackets, then the above may be a little slow. But for small
strings, it will probably be faster than a routine which tries to be
smarter at avoiding copying strings.
 
B

Bruno Desthuilliers

r0g a écrit :
(snip)
> I've never tried it but I think it is possible to inject new methods
into existing classes, see...

Doesn't work for most builtin types - for both performances and sanity
reasons.
 
T

Terry Reedy

Steven said:
Not exactly. You can subclass string and add such a method:

class MyString(str):
def strip_pairs(self, ...):
...

but then you have to convert every string to a MyString before you use
it. That way leads to madness.

Better to just use a function. Don't worry, you're allowed to mix
functional and OO code :)

Unlike certain other languages, Python is not designed around a fetish
for calling all functions as methods. s.func(arg) is immediately
translated to cls.func(s,arg) where cls is either the class of s or some
superclass thereof. Directly writing mod.func(s,arg), where mod is some
module, is just as good. Methods have three purposes: bundle several
related functions in a class-specific namespace; inheritance; mapping of
operations, like '+', to class-specific (special) methods. Modules are
an alernative namespace bundle.

Terry Jan Reedy
 
D

Dr. Phillip M. Feldman

Bruno- You've made some excellent suggestions, and I'm always grateful for
the opportunity to learn. My revised code appears below. Philllip

def strip_pairs(s, open='([{\'"', close=')]}\'"'):
"""
OVERVIEW

This function strips matching pairs of characters from the beginning and
end of the input string `s`. If `s` begins with a character in `open`
and
ends with the corresponding character in `close` (see below), both are
removed from the string. This process continues until no further matching
pairs can be removed.

INPUTS

`open` and `close`: These arguments, which must be equal-length strings,
specify matching start-of-scope and end-of-scope characters. The same
character may appear in both `open` and `close`; single and double quotes
conventionally match themselves. By default, `open` contains a left
parenthesis, left square bracket, left curly bracket, single quote, and
double quote), and `close` contains the corresponding characters."""

if not isinstance(s,(str,unicode)):
raise TypeError, '`s` must be a string (str or unicode).'

if not isinstance(open,(str,unicode)) or not
isinstance(close,(str,unicode)):
raise TypeError, '`open` and `close` must be strings (str or
unicode).'

if len(open) != len(close): raise ValueError, \
'\'open\' and \'close\' arguments must be equal-length strings.'

while len(s) >= 2:

# Check whether first character of `s` is in `open`:
i= open.find(s[0])

# If `s` does not begin with a character from `open`, there are no
more
# pairs to be stripped:
if i == -1: break

# If `s` does not begin and end with matching characters, there are no
# more pairs to be stripped:
if s[-1] != close: break

# Strip the first and last character from `s`:
s= s[1:-1]

return s
 
B

Bruno Desthuilliers

Dr. Phillip M. Feldman a écrit :
Bruno- You've made some excellent suggestions, and I'm always grateful for
the opportunity to learn.

Glad to know I've been of any help !-)
My revised code appears below. Philllip

def strip_pairs(s, open='([{\'"', close=')]}\'"'):
"""
OVERVIEW

This function strips matching pairs of characters from the beginning and
end of the input string `s`. If `s` begins with a character in `open`
and
ends with the corresponding character in `close` (see below), both are
removed from the string. This process continues until no further matching
pairs can be removed.

INPUTS

`open` and `close`: These arguments, which must be equal-length strings,
specify matching start-of-scope and end-of-scope characters. The same
character may appear in both `open` and `close`; single and double quotes
conventionally match themselves. By default, `open` contains a left
parenthesis, left square bracket, left curly bracket, single quote, and
double quote), and `close` contains the corresponding characters."""

if not isinstance(s,(str,unicode)):
raise TypeError, '`s` must be a string (str or unicode).'

Might be a bit more helpful (for the programmer using your function) to
specify what 's' actually is. Also, the recommanded way to raise
exceptions is to use the 'call' syntax, ie:


if not isinstance(s,(str,unicode)):
raise TypeError("'s' must be a str or unicode, got '%s'" % type(s))

if not isinstance(open,(str,unicode)) or not
isinstance(close,(str,unicode)):
raise TypeError, '`open` and `close` must be strings (str or
unicode).'

Mmmm.... I still wonder why you wouldn't accept a tuple or list of chars
here.
if len(open) != len(close): raise ValueError, \
'\'open\' and \'close\' arguments must be equal-length strings.'

while len(s) >= 2:

# Check whether first character of `s` is in `open`:
i= open.find(s[0])

# If `s` does not begin with a character from `open`, there are no
more
# pairs to be stripped:
if i == -1: break

wrt/ readability, it might be better to put the break statement on it's
own line - but you can probably count this one as more of a personnal
preference than a guideline !-)
# If `s` does not begin and end with matching characters, there are no
# more pairs to be stripped:
if s[-1] != close: break

# Strip the first and last character from `s`:
s= s[1:-1]
return s





Steven (D'Aprano) posted a possibly interesting implementation. Might be
worth timeit'ing both.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,007
Latest member
obedient dusk

Latest Threads

Top