覆盖实例上的特殊方法

Jam*_*lls 21 python metaprogramming

我希望有人可以回答这个对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>'
>>>
Run Code Online (Sandbox Code Playgroud)

注意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'
>>>
Run Code Online (Sandbox Code Playgroud)

kin*_*all 24

Python不会调用特殊方法,那些名称__在实例上包围的方法,但仅限于类,显然是为了提高性能.因此,没有办法__repr__()直接覆盖实例并使其工作.相反,你需要做这样的事情:

class A(object):
    def __repr__(self):
        return self._repr()
    def _repr(self):
        return object.__repr__(self)
Run Code Online (Sandbox Code Playgroud)

现在你可以__repr__()通过替换来覆盖实例_repr().

  • 它主要不是为了提高性能.正如文档所说,"这种行为背后的基本原理在于许多特殊方法,例如`__hash __()和`__repr __()`,它们由所有对象实现,包括类型对象.如果使用这些方法的隐式查找传统的查找过程,它们在类型对象本身上调用时会失败......"(但是,`__getattribute__` _also_的基本原理是可跳过_is_性能.) (8认同)
  • 此外,"Python不会调用特殊方法......"是不正确的.Python没有_guarantee_来称呼它们.CPython有一个特定的案例列表,它跳过正常查找,但该列表是特定于实现的,没有记录,所以你不应该依赖它.可能会或可能不会在实例上查找特殊方法,因此请确保您的代码以任何方式工作. (5认同)
  • 这是“与性能无关”。在类型上查找特殊方法,以确保实例和类型都可以支持特殊操作。例如,“类对象”是可哈希的。他们有一个__hash__方法。如果类实现了使实例可哈希化的__hash__方法,则如果hash(ClassObject)直接在类上查找方法(该对象本身是元类型的实例),则该方法将发生冲突。 (3认同)

aba*_*ert 11

如特殊方法查找中所述:

对于自定义类,只保证在对象的类型上定义特殊方法的隐式调用才能正常工作,而不是在对象的实例字典中定义...除了为了正确性而绕过任何实例属性之外,隐式特殊方法查找通常也会绕过__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)
Run Code Online (Sandbox Code Playgroud)

hacky解决方案是动态构建一个新的子类并重新对对象进行分类.我怀疑任何真正有这种情况的人会知道如何从那句话中实现它,任何不知道该怎么做的人都不应该尝试,所以我会留下它.