扩展类(Monkey Patching)如何在Python中工作?

时间:2011-06-08 22:23:39

标签: python oop object monkeypatching

class Foo(object):
  pass

foo = Foo()
def bar(self):
  print 'bar'

Foo.bar = bar
foo.bar() #bar

来自JavaScript,如果“类”原型增加了某个属性。众所周知,该“类”的所有实例在其原型链中都具有该属性,因此不必对其任何实例或“子类”进行任何修改。

从这个意义上讲,像Python这样的基于类的语言如何实现Monkey修补?

2 个答案:

答案 0 :(得分:11)

真正的问题是,怎么能不呢?在Python中,类本身就是一流的对象。通过查找实例上的属性,然后查找类,然后查找父类(按方法解析顺序)来解析对类实例的属性访问。这些查找都是在运行时完成的(就像Python中的所有内容一样)。如果在创建实例后向类添加属性,则实例仍将“看到”新属性,因为没有任何东西阻止它。

换句话说,它起作用是因为Python不缓存属性(除非你的代码有),因为它不使用负缓存或阴影类或任何会阻止它的优化技术(或者,当Python实现时,他们考虑到类可能会改变),因为一切都是运行时。

答案 1 :(得分:10)

我刚刚阅读了大量文档,据我所知,整个故事解决了foo.bar如何解决,如下:

  • 我们可以通过以下流程找到foo.__getattribute__吗?如果是,请使用foo.__getattribute__('bar')的结果。
    • (查找__getattribute__不会导致无限递归,但可能会执行它。)
    • (实际上,我们总是会在新式对象中找到__getattribute__,因为object中提供了默认实现 - 但该实现属于以下过程。;))
    • (如果我们在__getattribute__中定义Foo方法,并且访问foo.__getattribute__,则会调用foo.__getattribute__('__getattribute__') !但这样做暗示无限递归 - 如果你小心;))
  • bar是Python运行时提供的属性的“特殊”名称(例如__dict____class____bases____mro__)?如果是这样,请使用它。 (据我所知,__getattribute__属于这一类,避免了无限递归。)
  • bar dict中的foo.__dict__?如果是,请使用foo.__dict__['bar']
  • foo.__mro__是否存在(即,foo实际上是一个类)?如果是这样,
    • 对于base [1:]中的每个基类foo.__mro__
      • (请注意,第一个将是foo本身,我们已经搜索过了。)
      • bar中有base.__dict__吗?如果是这样:
        • x成为base.__dict__['bar']
        • 我们能找到(再次,递归,但不会导致问题)x.__get__
          • 如果是,请使用x.__get__(foo, foo.__class__)
          • (请注意,函数bar本身就是一个对象,Python编译器会自动为函数提供一个__get__属性,该属性旨在以这种方式使用。)
          • 否则,请使用x
  • 对于base的每个基类foo.__class__.__mro__
    • (请注意,此递归不是问题:这些属性应该始终存在,并且属于“由Python运行时提供”的情况。foo.__class__.__mro__[0]将始终为foo.__class__,即{{1在我们的示例中。)
    • (请注意,即使Foo存在,我们也会这样做。这是因为类也有一个类:它的名称是foo.__mro__,它提供了用于计算的方法等等首先是type个属性。)
    • __mro__中有bar吗?如果是这样:
      • base.__dict__成为x
      • 我们能找到(再次,递归,但不会导致问题)base.__dict__['bar']
        • 如果是,请使用x.__get__
        • (请注意,函数x.__get__(foo, foo.__class__)本身就是一个对象,Python编译器会自动为函数提供一个bar属性,该属性旨在以这种方式使用。)
        • 否则,请使用__get__
  • 如果我们尚未找到要使用的内容:我们可以通过前面的过程找到x吗?如果是,请使用foo.__getattr__
  • 的结果
  • 如果一切失败,foo.__getattr__('bar')

raise AttributeError实际上并不是一个函数 - 它是一个“方法包装器” - 但你可以想象它的实现模糊不清:

bar.__get__

顺便说一下,# Somewhere in the Python internals class __method_wrapper(object): def __init__(self, func): self.func = func def __call__(self, obj, cls): return lambda *args, **kwargs: func(obj, *args, **kwargs) # Except it actually returns a "bound method" object # that uses cls for its __repr__ # and there is a __repr__ for the method_wrapper that I *think* # uses the hashcode of the underlying function, rather than of itself, # but I'm not sure. # Automatically done after compiling bar bar.__get__ = __method_wrapper(bar) 自动附加到__get__(称为描述符)中发生的“绑定”或多或少是您拥有的原因为Python方法显式指定bar参数。在Javascript中,self本身就是神奇的;在Python中,它只是将事物绑定到this神奇的过程。 ;)

是的,可以在您自己的对象上显式设置self方法,并在将类属性设置为对象的实例然后访问它时让它执行特殊操作来自另一个类的实例。 Python 非常反射。 :)但是如果你想学习如何做到这一点,并且对这种情况有一个非常全面的了解,你have a lot阅读to do。 ;)

相关问题