gah! I hate the new string syntax

Alex Martelli aleaxit at yahoo.com
Mon Mar 12 11:56:33 EST 2001


"Amit Patel" <amitp at Xenon.Stanford.EDU> wrote in message
news:98iqo2$56o$1 at nntp.Stanford.EDU...
> Alex Martelli <aleaxit at yahoo.com> wrote:
> | "Amit Patel" <amitp at Xenon.Stanford.EDU> wrote in message
> | news:98elrk$li8$1 at nntp.Stanford.EDU...
> |     [snip]
> | > What I mean to say is that I cannot write new methods on strings.  So
> |
> | Perfectly true, just as you cannot on dictionaries, lists, tuples,
> | file objects, and so on.  Only classes and extension-classes
> | are extensible (by inheritance and overriding), not types.
>
> Even if strings were a class, and I could extend it, it is NOT the
> thing to do to add new functionality like capitalize().  Suppose

If you wanted to MODIFY the functionality of the hypothetical string
class, so the new functionality applied to all objects of the class,
then you would indeed have to alter the class-object -- just as you
would have to alter the module-object to alter the functionality of
that module, in an exact parallel.


> The advantage of method functions like string.capitalize is that they
> do not require me to change something I can't change (the string
> type).

A class-object is just as alterable as a module-object -- neither
more, nor less.  (Whether it's _wise_ to do so, of course, is a
different issue -- but the ability is there in either case).

Type-objects are different, but there's no inherent reasons why
it should be any harder to perform
    someobj.capitalize = mycapfun
for someobj being a type-object, than for its being a module --
it can be seen as an accident of current implementation if only
one case out of the two possibilities is allowed.

> I can write capitalize(x) when x is a string.  I can't write
> x.capitalize() when x is a string.

But you would probably be writing string.capitalize in the
former case anyway -- unless you're advocating a "from
string import *"?  So, to add (e.g.) a capitalize function
to the string module, you'd have to set string.capitalize
rather than inject the 'capitalize' name into your global
(or local) namespace in other (dirtier?) ways.


> Defining methods is like running things as root.  Yes, you sometimes
> need to do it, but it's best to limit yourself to only those things
> that need superuser access.  Run as much as you can as non-root.  And
> IMO that goes for methods and functions too -- string type methods are
> "root" (where "root" = "Guido & pals") and have access to the
> internals of a string, whereas functions on strings are user-level and
> go through the abstractions.

Focusing on the polymorphism needs, I see the issue in the mirror
way from you: it's a _weakness_ (of Python and most other languages)
that I can't add methods 'post-facto, from the outside' to existing
objects -- it impedes the most natural, elegant, typeswitch-free way
to add functionality.  If you pursued your "capitalize as a function"
idea, you'd be unable to properly capitalize Unicode (e.g.) without
a typeswitch -- a very unadvisable construct.  Something like Haskell's
typeclasses, on the other hand, would provide the best of both worlds
(multimethods would be an even more powerful alternative) -- let the
compiler/interpreter/runtime do the dispatching, and just code in
the most natural, polymorphic way.

If you have to work with both existing and new objects, some of
which provide a method while others don't, then a wrapper function
(or class, &c) is indeed best -- and inside it you can do the
weakest (safest, least-unadvisable) form of typeswitching, e.g.:

def capitalize(self):
    try: return self.capitalize()
    except AttributeError: pass
    # proceed implementing the default way of
    # capitalization

If you can perform your capitalization-default more effectively
in a mixin class (this is often true, since keeping state can
be quite helpful), then extend this to:

def capitalize(self):
    try: return self.capitalize()
    except AttributeError: pass

    # if it's a class we can add our mixin once & for all:
    try: self.__class__.__bases__.append(MixinCapitalizer)
    except AttributeError: pass
    else: # good, we were able to add it, so init & use it:
        MixinCapitalizer.__init__(self)
        return self.capitalize()

    # proceed implementing the default way of
    # capitalization as a function, sigh


The ability to perform such shehanigans does argue for using
functions _from client-code that needs to be very robust in
dealing with all sorts of weird objects including legacy ones_,
but it does NOT argue against adding suitable methods on the
implementation side -- anything BUT, in fact... where the
method is right on the object we get the fastest path through
the wrapper-function, even in client-code that needs to go
through such wrappers for legacy-support reasons.


Alex






More information about the Python-list mailing list