【问题标题】:How does extending classes (Monkey Patching) work in Python?扩展类(猴子补丁)如何在 Python 中工作?
【发布时间】:2011-09-11 06:37:18
【问题描述】:
class Foo(object):
  pass

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

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

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

从这个意义上说,像 Python 这样的基于类的语言如何实现猴子补丁?

【问题讨论】:

    标签: python oop object monkeypatching


    【解决方案1】:

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

    换句话说,它之所以有效,是因为 Python 不缓存属性(除非您的代码这样做),因为它不使用负缓存或影子类或任何会抑制它的优化技术(或者,当 Python 实现这样做时) ,他们考虑到类可能会改变)并且因为一切都是运行时的。

    【讨论】:

    • 我认为这是无法做到的,这是与 Ruby 的区别。
    • 那么动态基于类的语言中的方法调度与基于原型的语言的工作方式相同吗?
    • “所以方法调度...以相同的方式工作...?”这取决于你想概括多少。有关专门针对 Python 的“它是如何工作的”的详细信息,请参阅我的答案。其他基于动态类的语言不一定做同样的事情。其他基于原型的语言也不一定完全像 Javascript 那样做,FWIW。
    • 与其说“这一切都在运行时完成”,不如说所有名称解析都是在“访问时间”完成的,而不是“定义时间”或“实例化时间”。跨度>
    【解决方案2】:

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

    • 我们可以通过以下过程找到foo.__getattribute__吗?如果是这样,请使用foo.__getattribute__('bar') 的结果。
      • (查找__getattribute__不会导致无限递归,但实现可能会。)
      • (实际上,我们总是会在新式对象中找到__getattribute__,因为object 中提供了默认实现 - 但该实现属于以下过程。;))
      • (如果我们在Foo中定义一个__getattribute__方法,并访问foo.__getattribute__,foo.__getattribute__('__getattribute__') 将被调用!但这确实不意味着无限递归 - 如果你小心点 ;) )
    • bar 是 Python 运行时提供的属性的“特殊”名称(例如,__dict__、__class__、__bases__、__mro__)?如果是这样,请使用它。 (据我所知,__getattribute__ 属于这一类,可以避免无限递归。)
    • bar 是否在 foo.__dict__ 字典中?如果是这样,请使用foo.__dict__['bar']。
    • foo.__mro__ 是否存在(即foo 实际上是一个类)?如果是这样的话,
      • 对于foo.__mro__[1:] 中的每个基类base:
        • (请注意,第一个将是 foo 本身,我们已经搜索过了。)
        • bar 在base.__dict__ 中吗?如果是这样的话:
          • 让x 成为base.__dict__['bar']。
          • 我们能找到(同样,递归,但不会造成问题)x.__get__?
            • 如果是这样,请使用x.__get__(foo, foo.__class__)。
            • (请注意,函数bar 本身就是一个对象,Python 编译器会自动为函数赋予一个设计为以这种方式使用的__get__ 属性。)
            • 否则,请使用x。
    • 对于foo.__class__.__mro__ 的每个基类base:
      • (请注意,这种递归不是问题:那些属性应该始终存在,并且属于“由 Python 运行时提供”的情况。foo.__class__.__mro__[0] 将始终为foo.__class__,即在我们的示例中为Foo。 )
      • (请注意,即使foo.__mro__ 存在,我们也会这样做。这是因为类也有一个类:它的名称是type,除其他外,它还提供了用于计算__mro__ 属性的方法首先。)
      • bar 在base.__dict__ 中吗?如果是这样的话:
        • 让x 成为base.__dict__['bar']。
        • 我们能否找到(再次,递归,但不会造成问题)x.__get__?
          • 如果是这样,请使用x.__get__(foo, foo.__class__)。
          • (请注意,函数bar 本身就是一个对象,Python 编译器会自动为函数赋予一个设计为以这种方式使用的__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__ 中发生的“绑定”自动附加到bar(称为描述符),或多或少是您必须指定@987654380 的原因@ 参数显式地用于 Python 方法。在 Javascript 中,this 本身就很神奇;在 Python 中,只有将事物绑定到 self 的过程才是神奇的。 ;)

    是的,您可以在您自己的对象上显式设置 __get__ 方法,并在您为对象的实例设置类属性然后从另一个类的实例。 Python 非常具有反射性。 :) 但是,如果您想学习如何做到这一点,并真正全面了解情况,请have a lot 阅读to do。 ;)

    【讨论】:

    • 感谢@karl,这很有帮助,但有点令人困惑:)
    猜你喜欢
    • 2011-04-15
    • 1970-01-01
    • 2012-12-18
    • 1970-01-01
    • 2017-10-05
    • 1970-01-01
    • 2023-03-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多