Я конвертирую старый код Python и заменяю некоторые классы новыми классами стилей. Проблема в том, что это нарушило поведение замены __str__
, и я понятия не имею, почему.
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
Я ожидал
original
modified
original
modified
но я получил
original
modified
original
original
То есть, __str__
не был правильно заменен в новом стиле. Печать new.__str__
возвращает новую лямбду правильно, но str(new)
все еще не вызывает ее. Я подумал, что это проблема кэширования поиска метода, но это происходит, даже если объект никогда не печатался раньше.
Почему это происходит? Я никогда не слышал об этой поведенческой разнице, и это происходит только с __str__
, другие методы заменены штрафом.
Это описано в модели данных python под имена специальных методов. В частности:
Например, если класс определяет метод с именем
__getitem__
, а x является экземпляром этого класса, тоx[i]
примерно эквивалентенx.__getitem__(i)
для классов старого стиля иtype(x).__getitem__(x, i)
для нового стиля классы.
Я считаю, что это позволяет классам нового стиля быть немного более эффективными с точки зрения этих операций, потому что со старыми классами стилей python был вынужден искать атрибут, а затем вызывать атрибут с классами нового стиля, python может просто ссылаться на него как часть C-структуры где-то эффективно, вызывая поиск и вызовы в собственный C-код. Например:
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()')
Для меня (python2.7.3, OS-X 10.5.8) класс нового стиля почти в 4 раза быстрее!
2.27801704407
0.602614879608
Это работает:
NewStyle.__str__ = lambda self: 'modified'
Кажется, что мы видим, что метод __str__
привязан к типу класса сейчас, а не к экземпляру. Я не знаю, почему это так. Вы можете явно вызвать new.__str__()
и получить замененный метод, но str(new)
всегда вызывает NewStyle.__str__(new)
.
NewStyle
.
NewStyle.__str__ = lambda self: 'modified'
работает как положено. Я думаю, что порядок разрешения методов различен для классов старого и нового стиля.__str__
только в этом единственном экземпляре. Я думаю, что могу реализовать это по-другому, но мне стало любопытно.