重写实例上的特殊方法

我希望有人可以回答这个对Python有很好的理解:)

考虑下面的代码:

>>> class A(object): ... pass ... >>> def __repr__(self): ... return "A" ... >>> from types import MethodType >>> a = A() >>> a <__main__.A object at 0x00AC6990> >>> repr(a) '<__main__.A object at 0x00AC6990>' >>> setattr(a, "__repr__", MethodType(__repr__, a, a.__class__)) >>> a <__main__.A object at 0x00AC6990> >>> repr(a) '<__main__.A object at 0x00AC6990>' >>> 

请注意repr(a)不会产生“A”的预期结果吗? 我想知道为什么是这样的情况,如果有办法做到这一点…

相比之下,下面的例子可以工作( 也许是因为我们不想重载一个特殊的方法? ):

 >>> class A(object): ... def foo(self): ... return "foo" ... >>> def bar(self): ... return "bar" ... >>> from types import MethodType >>> a = A() >>> a.foo() 'foo' >>> setattr(a, "foo", MethodType(bar, a, a.__class__)) >>> a.foo() 'bar' >>> 

Python不会调用特殊的方法,那些名称被__包围在实例上,但只在类上的方法,显然是为了提高性能。 因此,无法直接在实例上覆盖__repr__()并使其正常工作。 相反,你需要这样做:

 class A(object): def __repr__(self): return self._repr() def _repr(self): return object.__repr__(self) 

现在,您可以通过替换_repr()来覆盖实例上的__repr__() _repr()

如特殊方法查询中所述 :

对于自定义类,特殊方法的隐式调用只有在定义在对象的类型上,而不是在对象的实例字典中才能保证正常工作。除了为了正确性而绕过任何实例属性外,隐式特殊方法查找通常也绕过__getattribute__()方法甚至对象的元类

(如果你对此感兴趣,我已经详细介绍了这个部分的基本原理。)

Python没有准确地记录一个实现应该或不应该在类型上查找方法; 它的所有文档实际上是实现可能会或可能不会查看特殊的方法查找的实例,所以你不应该指望。

从您的测试结果中可以猜出,在CPython实现中, __repr__是查找类型的函数之一。


2.x中的情况稍有不同,主要是因为经典类的存在,但只要你创建新的类,你可以把它们看作是一样的。


人们想要做这件事的最普遍的原因是猴子修补一个对象的不同实例来做不同的事情。 你不能用特殊的方法来做,所以……你能做什么? 有一个干净的解决方案,和一个hacky的解决方案。

干净的解决方案是在实例上调用常规方法的类上实现一个特殊的方法。 然后你可以在每个实例上修改那个常规方法。 例如:

 class C(object): def __repr__(self): return getattr(self, '_repr')() def _repr(self): return 'Boring: {}'.format(object.__repr__(self)) c = C() def c_repr(self): return "It's-a me, c_repr: {}".format(object.__repr__(self)) c._repr = c_repr.__get__(c) 

hacky的解决方案是即时创建一个新的子类并重新对类进行分类。 我怀疑任何一个真正有这个好主意的情况的人都会知道如何从这个句子中实现它,任何不知道如何这样做的人都不应该尝试,所以我会放弃这个。

原因是特殊的方法( __x__() )是为类定义的,而不是实例。

当你考虑__new__()时,这是有道理的 – 在实例上调用它是不可能的,因为实例在被调用时不存在。

所以如果你想要,你可以在课堂上做到这一点:

 >>> A.__repr__ = __repr__ >>> a A 

或者在个别情况下,如kindall的回答 。 (请注意,这里有很多相似之处,但是我认为我的例子也足够了,可以发布这个)。

对于新的样式类,Python使用绕过实例的特殊方法查找。 这里摘录来源 :

  1164 /* Internal routines to do a method lookup in the type 1165 without looking in the instance dictionary 1166 (so we can't use PyObject_GetAttr) but still binding 1167 it to the instance. The arguments are the object, 1168 the method name as a C string, and the address of a 1169 static variable used to cache the interned Python string. 1170 1171 Two variants: 1172 1173 - lookup_maybe() returns NULL without raising an exception 1174 when the _PyType_Lookup() call fails; 1175 1176 - lookup_method() always raises an exception upon errors. 1177 1178 - _PyObject_LookupSpecial() exported for the benefit of other places. 1179 */ 

您可以更改为旧式类(不从对象继承),也可以将调度程序方法添加到类(将查找转发回实例的方法)。 有关实例调度程序方法的示例,请参阅http://code.activestate.com/recipes/578091上的配方;