[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