[Tutor] class decorator question

Steven D'Aprano steve at pearwood.info
Sat Oct 5 15:14:42 CEST 2013


On Sat, Oct 05, 2013 at 05:33:46AM -0700, Albert-Jan Roskam wrote:
> Hi,
> 
> On http://lucumr.pocoo.org/2013/5/21/porting-to-python-3-redux/ I saw 
> a very cool and useful example of a class decorator. It (re)implements 
> __str__ and __unicode__ in case Python 2 is used. For Python 3, the 
> decorator does nothing. I wanted to generalize this decorator so the 
> __str__ method under Python 2 encodes the string to an arbitrary 
> encoding. This is what I've created: http://pastebin.com/vghD1bVJ.
> 
> It works, but the code is not very easy to understand, I am affraid. 

It's easy to understand, it's just doing it the wrong way. It creates 
and subclass of your class, which it shouldn't do. Here's a better 
approach: inject the appropriate methods into the class directly. Here's 
a version for Python 3:


def decorate(cls):
    if '__str__' not in cls.__dict__:
        # inject __str__ method
        def __str__(self):
            ...
        cls.__str__ = __str__

    if '__bytes__' not in cls.__dict__:
        # like above

    return cls


This avoids overwriting __str__ if it is already defined, and likewise 
for __bytes__.


> Or is it? And I have no idea how to call the class Klass. Maybe 
> reimplements_Test? Is this a good approach, or can this be done in an 
> easier way? I would *really* like keep statements "if 
> sys.version_info[0] == 3..." separate from the "main" code. Also, 
> learning about class decorators is cool ;-). So the code below... 
> mehhh  no sir, I don't like it.
> 
> 
> def __str__(self):
> 
>     if sys.version_info[0] == 3:
>         blah
>     else:
>         bleh
>   
> if sys.version_info[0] == 2:
>     def __unicode__(self):
>         blooooh


That performs the version check every time the __str__ method is called. 
Better would be something like this:

if sys.version_info[0] == 2:
    def __str__(self):
        ...

    def __unicode__(self):
        ...

else:
    def __bytes__(self):
        ...

    def __str__(self):
        ...



If you don't like repeating the code twice, once for version 2 and once 
for version 3, you may be able to define the methods once, then rename 
them, something like this:


# Assume version 3
def __str__(self):
    ...

def __bytes__(self):
    ...

if sys.version_info[0] == 2:
    __str__.__name__ = '__unicode__'
    __bytes.__name__ = '__str__'
    # Inject into the class, as above.
    cls.__unicode__ = __str__
    cls.__str__ = __bytes__

else:
    cls.__str__ = __str__
    cls.__bytes__ = __bytes__


Combining this with the decorator is left for you :-)



-- 
Steven


More information about the Tutor mailing list