【问题标题】:Is it possible to override a class' __call__ method? [duplicate]是否可以覆盖类的 __call__ 方法? [复制]
【发布时间】:2020-02-04 16:55:07
【问题描述】:

我需要更改给定对象的__call__ 方法的行为。天真的方法是这样的:

class A(object):
    def __call__(self):
        return 1

def new_call():
    return 42


a = A()
a.__call__ = new_call

为什么不是a()42的输出?有没有我可以利用的解决方法来达到相同的效果? (不使用类)


============================= 编辑================== ===============

根据记录,简短的回答是否定的。 Python 直接在类而不是实例上调用像__call_ 这样的“特殊方法”,因此如果您需要更改方法,则需要在类本身上进行更改。

【问题讨论】:

  • 首先你不需要在 new_call 中包含 self 参数吗?为什么不直接继承 A 类

标签: python python-3.x


【解决方案1】:

特殊方法(又名“dunder”方法)是根据对象的查找的,因此要覆盖它,您需要更改类,而不是实例。另请注意,方法都有一个初始参数传递给它们,通常称为self

以下将做你想做的(注意它如何影响类的所有个实例):

class A(object):
    def __call__(self):
        return 1

def new_call(self):
    return 42


a1 = A()
a2 = A()
A.__call__ = new_call

print(a1())  # -> 42
print(a2())  # -> 42

如果您只想更改特定实例,一个相对简单的解决方法是让类的__call_() 方法调用一个“特殊”的方法 - 即通过引入一个级别间接的。

这就是我的意思:

# Workaround

class B(object):
    def __call__(self):
        return self.call_method(self)

    @staticmethod
    def call_method(self):
        return 1

def new_call(self):
    return 42

# Create two instances for testing.
b1 = B()
b2 = B()
b2.call_method = new_call  # Will only affect this instance.

print(b1())  # -> 1
print(b2())  # -> 42

【讨论】:

    【解决方案2】:

    通常,特殊方法名称,例如__call__,是通过对象的类型而不是实例的属性集合来访问的。来自Special Method Names

    如果一个类定义了一个名为__getitem__()的方法,x是这个类的一个实例,那么x[i]大致等价于type(x).__getitem__(x, i)

    这也适用于__call__a() 大致相当于type(a).__call__(a)。更改a__call__ 属性没有效果,因为代码正在寻找A 的__call__ 属性。

    A.__call__ 分配一个新函数应该可以满足您的需求。

    class A(object):
        def __call__(self):
            return 1
    
    def new_call(self):
        return 42
    
    
    a = A()
    A.__call__ = new_call
    print(a())
    

    结果:

    42
    

    【讨论】:

    • 这会更改__call__所有A 实例,而不仅仅是a,我认为这不是OP 想要的。
    • 没错,这种方式我不能改变两个不同的对象是一种不同的方式。不过感谢您的宝贵解释
    • 嗯,很棘手。 AFAICT,即使类没有定义方法,特殊方法查找也不会费心查看实例的属性。所以很难获得每个实例的行为。 Jordan Brière 的方法或类似的方法可能是将__call__ 的行为显式“分派”到每个实例的最简单方法。
    【解决方案3】:

    因为A.__call__a.__call__ 之前解析。如果您想为每个实例绑定__call__,那么您必须通过尝试从那里解决它来更改分辨率。例如

    class A(object):
        def __call__(self):
            try:
                return vars(self)['__call__']()
            except KeyError:
                return 1
    
    def new_call():
        return 42
    
    
    a = A()
    
    print(a())
    a.__call__ = new_call
    print(a())
    

    会打印:

    1
    42
    

    如果你想让它成为一个方法,并且可以访问self,那么你必须绑定它。例如

    from types import MethodType
    
    class A(object):
        def __call__(self):
            try:
                return vars(self)['__call__']()
            except KeyError:
                return 1
    
    def new_call_method(self):
        return self
    
    a = A()
    a.__call__ = MethodType(new_call_method, a)
    also_a = a()
    

    将返回a

    【讨论】:

    • 第二次尝试失败,因为魔术方法直接进入类属性,而不是实例属性。
    • 第二次尝试依赖于第一个A 定义。我没有重新定义它,以避免重复代码。
    • 不,重点是a(...)总是 A.__call__(a, ...),不管a.__call__是否被定义。
    • 这就是为什么A.__call__首先尝试return vars(self)['__call__'](),它将从实例a.__call__解析,或者回退到return 1
    • @JordanBrière 您在这里有两个完全不同的解决方案:第一个有效,因为您概括了A.__call__。第二个没有,因为您不能通过定义 a.__call__ 来“抢占”A.__call__。这根本不是 () 调用语法的工作原理。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多