【问题标题】:Why won't dynamically adding a `__call__` method to an instance work?为什么不能动态地将 `__call__` 方法添加到实例中?
【发布时间】:2019-02-07 20:30:21
【问题描述】:

在 Python 2 和 Python 3 中的代码:

class Foo(object):
    pass

f = Foo()
f.__call__ = lambda *args : args

f(1, 2, 3)

返回错误Foo object is not callable。为什么会这样?

PS:对于老式类,它可以按预期工作。

PPS:这种行为是有意的(参见接受的答案)。作为一种解决方法,可以在类级别定义一个 __call__,它只转发给另一个成员,并将这个“普通”成员设置为每个实例的 __call__ 实现。

【问题讨论】:

    标签: python instance


    【解决方案1】:

    双下划线方法总是在类上查找,而不是在实例上。见Special method lookup for new-style classes

    对于新型类,特殊方法的隐式调用只有在对象类型上定义时才能保证正常工作,而不是在对象的实例字典中。

    这是因为 type 可能需要支持相同的操作(在这种情况下,特殊方法会在元类型上查找)。

    例如,类是可调用的(这就是您生成实例的方式),但是如果 Python 在实际对象上查找 __call__ 方法,那么您永远无法在实现的类上这样做__call__ 他们的实例。 ClassObject() 将变为 ClassObject.__call__(),这将失败,因为 self 参数未传递给未绑定方法。所以改为使用type(ClassObject).__call__(ClassObject),调用instance() 转换为type(instance).__call__(instance)

    要解决此问题,您可以向该类添加一个__call__ 方法,该方法检查该类上的__call__ 属性,如果存在,则调用它。

    【讨论】:

    • 要定义每个实例的__call__ 实现,我目前正在使用转发给普通成员的类级别__call__。然而,这似乎是一个丑陋的解决方案......
    • @6502:这是推荐的解决方法。如果您确实需要委托给每个实例的可调用对象,那么最好在类级别 __call__ 中明确地这样做。
    • @MartijnPieters 将所有运算符(__add____rtruediv__ 等)转发到代理内的整数对象存储的解决方法是什么?如果可能,我不想手动实现所有方法。
    • @danijar:您可以使用元类从名称列表中生成方法。
    【解决方案2】:

    在新样式类中(默认为3.x)并从2.x 中的对象继承,属性拦截方法__getattr____getattribute__ are no longer called for built-in operations 在实例的重载方法上,而是从类开始搜索。

    这背后的基本原理涉及元类的存在引入的技术性。

    因为类是元类的实例,并且因为元类可能定义作用于类的某些操作,所以跳过类(在这种情况下可以被视为instance)是有意义的;您需要调用定义为处理类的元类中定义的方法(cls 作为第一个参数)。如果使用实例查找,则查找将使用为该类的实例定义的类方法 (self)。

    另一个(有争议的原因)涉及优化:由于实例上的内置操作通常非常经常调用,因此跳过实例查找-完全起床并直接去上课可以节省我们一些时间。 [来源 Lutz,学习 Python,第 5 版]


    主要区域可能会带来不便,因为在创建代理对象时会重载__getattr____getattribute__,以便将调用转发到嵌入的内部对象。由于内置调用将完全跳过实例,因此它们不会被捕获,并且效果不会被转发到嵌入对象。

    一个简单但乏味的解决方法是实际重载您希望在代理对象中拦截的所有垃圾。然后在这些重载的 dunders 中,您可以根据需要将调用委托给内部对象。


    我能想到的最简单的解决方法是将Foo 类的属性设置为setattr

    setattr(Foo, '__call__', lambda *args: print(args))
    
    f(1, 2, 3)
    (<__main__.Foo object at 0x7f40640faa90>, 1, 2, 3)
    

    【讨论】:

    • 你在这里混淆了问题。 Python 在某些情况下会绕过 __getattribute__ 作为优化。但是在类上查找特殊方法并不是一种优化选择;需要正确支持作用于类型的特殊方法。例如,比较hash(int)hash(1),两者都需要支持。
    • 我补充说这是第二个原因;但是,如果我们有 许多 个给定类的实例,我们会在新模型中跳过__dict__ 查找,因此是否可以考虑优化这一
    • 这种查找成本很低​​,并且当在实例上调用特殊方法时,只有一个实例
    • 有关更多信息,请参阅我在回答中链接到的文档。您提出优化是第一个原因。
    • @Nearoo:您还能如何将类型用作字典键? dispatch = {int: function_to_handle_integers, float: function_to_handle_floats}。如果在实例上查找 __hash__,那么您会得到 int.__hash__() 被调用并失败,因为它没有绑定到实际整数。而是调用type.__hash__(int),而对于像1 这样的实际整数,则调用int.__hash__(1)。换句话说,这很重要,因为类型(类)也是的对象,它们是type的实例。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-01-18
    • 2011-07-14
    • 1970-01-01
    • 2017-10-06
    相关资源
    最近更新 更多