Bug or Feature?

Michele Simionato mis6 at pitt.edu
Mon Nov 24 09:03:53 EST 2003


Stephan Diehl <stephan.diehl at gmx.net> wrote in message news:<bpsik5$ni7$07$1 at news.t-online.com>...
> I was playing around with defining new types and have seen the
> following behaviour:
> 
> Python 2.3.1
> ============
>  
> >>> class Int(int):pass
>  ...
> >>> a = Int(7)
> >>> b = Int(8)
> >>> c = a + b
> >>> type(c)
> <type 'int'>
> 
> Basicly: Int is not closed under it's defined operations. :-(
> 

You want my "innermethods" module. Originally I wrote it as a recipe
for the cookbook, but then I forgot to post it ;)

"""
A method of a class ``C`` is said to be an ``inner method`` of ``C``
if it returns an instance of ``C``. For instance, the ``.replace``
method of the string class is an inner method of ``str``, since
invoked by a string, it returns a string:

>>> s='s'
>>> r=s.replace('s','r')
>>> print r,type(r)
r <type 'str'>

In general, the property of being an inner method is not preserved 
by inheritance. For instance, if I define a subclass of the string class,

>>> class Str(str):
...     pass

then ``Str`` has still a ``.replace`` method (inherited from ``str``),
but it is not an inner method of ``Str``:

>>> s=Str('s')
>>> r=s.replace('s','r')
>>> print r,type(r) # the type of r is 'str', not 'Str'!
r <type 'str'>

In many circumstances, this is not a problem. However, sometimes
it is important to keep the inner methods safe under inheritance.
If we are writing the methods from scratch, this can be done 
by returning ``self.__class__(returnvalue)`` instead of the
simple ``returnvalue``. However, if the methods have been written
by others and we are simply inhering them, we need a more sophisticated
solution.

This module provides a ``protect_inner_methods`` metaclass-like function
which guarantees that the inner methods of the class invoking it are
safe under inheritance. As an exemplication and a commodoty, the
module also provides a ``Str`` class with protected versions of
all the inner methods of the builtin ``str`` class. The only
exception are ``__str__`` and ``__repr__``: it is better than
they stay unprotected, so the string representation of subclasses
of ``str`` is a simple plain string, not an enhanced one. In
this way ``str(x)`` continues to convert ``x`` instances in ``str``
instances. If ``Str`` instances are wanted, use ``Str()``!

Here is an example, showing that ``Str.replace`` is an inner method
of ``Str``:

>>> from innermethods import Str
>>> s=Str('s')
>>> r=s.replace('s','r') # .replace invoked from an instance of 'Str'
>>> print r,type(r) # returns an instance of 'Str', i.e. it is inner method
r <class 'innermethods.Str'>

Let me show that the inner methods of ``Str`` are protected under
inheritance, i.e. they continues to be inner methods of the
subclasses:

>>> class MyStr(Str):
...     pass

>>> s=MyStr('s')
>>> r=s.replace('s','r') # .replace invoked from an instance of 'Str'
>>> print r,type(r) # returns an instance of 'Str', i.e. it is inner method
r <class 'MyStr'>

"""

def protect_inner__method(cls,methname):
    """Given a class and an inner method name, returns a wrapped
    version of the inner method. Raise an error if the method is not
    defined in any ancestors of the class. In the case of duplicated
    names, the first method in the MRO is taken."""
    def _(self,*args,**kw):
        supermethod=getattr(super(cls,self),methname)
        return self.__class__(supermethod(*args,**kw))
    return _

def protect_inner_methods(name,bases,dic):
    """Metaclass-like function. Returns a new class with inner methods
    protected under inheritance. The calling class must provide a
    ``__innermethods__`` attribute containing the list of the
    method names to be protected, otherwise nothing will be done."""
    cls=type(name,bases,dic) # generate the new class
    innermethods=dic.get('__innermethods__',[])
    # look for the list of methods to wrap and wrap them all
    for methname in innermethods:
        setattr(cls,methname,protect_inner__method(cls,methname))
    return cls

class Str(str):
    """A string class with wrapped inner methods."""
    __metaclass__=protect_inner_methods
    __innermethods__="""__add__ __mod__ __mul__ __rmod__ __rmul__ capitalize
    center expandtabs join ljust lower lstrip replace rjust rstrip strip
    swapcase title translate upper zfill""".split()
    def __radd__(self,other): # inner method
        """Guarantees ``'a'+Str('b')`` returns a ``Str`` instance.
        Also ``s='s'; s+=Str('b')`` makes ``s`` a ``Str`` instance."""
        return self.__class__(other.__add__(self))




More information about the Python-list mailing list