4

I'm converting old Python code and replaced some classes with new style classes. The problem is that this broke the behavior of replacing __str__ and I have no idea why.

class OldStyle():
    def __str__(self):
        return 'original'

old = OldStyle()
print old
old.__str__ = lambda: 'modified'
print old


class NewStyle(object):
    def __str__(self):
        return 'original'

new = NewStyle()
print new
new.__str__ = lambda: 'modified'
print new

I expected

original
modified
original
modified

but I got

original
modified
original
original

That is, __str__ was not correctly replaced in the new style. Printing new.__str__ returns the new lambda correctly, but str(new) still doesn't call it. I thought it was some method lookup caching problem, but this happens even if the object was never printed before.

Why does that happen? I have never heard about this behavior difference and it only happens with __str__, other methods are replaced fine.

BoppreH
  • 8,014
  • 4
  • 34
  • 71
  • Replacing the last modification with `NewStyle.__str__ = lambda self: 'modified'` works as expected. I think the method resolution order is different between old- and new-style classes. –  Apr 17 '13 at 14:11
  • Interesting. The problem is that I need to replace `__str__` in only that single instance. I think I can implement it another way, but I got curious. – BoppreH Apr 17 '13 at 14:15
  • 1
    Out of curiosity, why do you have code replacing `__str__` on instances in the first place? Sounds... ugly. – Wooble Apr 17 '13 at 14:15
  • 1
    @BoppreH: consider subclassing then, and have the subclass implement to-string however you like. – John Zwinck Apr 17 '13 at 14:16
  • @Wooble It's a tree, I use `str(child)` to print children and I want a specific child to be printed with surrounding HTML tags for highlighting. I agree, it's ugly, and it has already been reworked because of that. – BoppreH Apr 17 '13 at 14:19

2 Answers2

3

This works:

NewStyle.__str__ = lambda self: 'modified'

It seems like what we're seeing is that the __str__ method is tied to the class type now, rather than the instance. I'm not sure why that is. You can call new.__str__() explicitly and get the replaced method, but str(new) always calls NewStyle.__str__(new).

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
3

This is documented in the python Data model under special method names. Specifically:

For instance, if a class defines a method named __getitem__, and x is an instance of this class, then x[i] is roughly equivalent to x.__getitem__(i) for old-style classes and type(x).__getitem__(x, i) for new-style classes.

I believe that this allows new-style classes to be a little more efficient in terms of these operations because with the old style classes, python was forced to look up the attribute and then call the attribute, with new-style classes, python can just reference it as part of a C struct somewhere effectively pushing the lookups and calls into native C code. For example:

class Foo:
    def __add__(self,other):
        return 4 + other

class Bar(object):
    def __add__(self,other):
        return 4 + other

import timeit
print timeit.timeit('f + 5','from __main__ import Foo; f = Foo()')
print timeit.timeit('b + 5','from __main__ import Bar; b = Bar()')

For me (python2.7.3, OS-X 10.5.8), the new-style class is almost 4 times faster!

2.27801704407
0.602614879608
mgilson
  • 300,191
  • 65
  • 633
  • 696