设置每个实例的__call__方法的方法是什么?

5 投票
3 回答
734 浏览
提问于 2025-04-17 14:04

我想做的事情大概是这样的:

class Foo(object):
    def __init__(self):
        pass
    def f(self):
        print "f"
    def g(self):
        print "g"


# programatically set the "default" operation
fer=Foo()
fer.__call__=fer.f

# a different instance does something else as its
# default operation
ger=Foo()
ger.__call__=ger.g

fer()  # invoke different functions on different
ger()  # objects depending on how they were set up.

但是在我现在使用的2.7版本中,我无法做到这一点,尝试使用fer()时会出现错误。

有没有办法实际上为每个实例设置一个__call__方法呢?

3 个回答

1

可能的解决方案:

class Foo(object):
    def __init__(self):
        self._callable = lambda s: None
    def f(self):
        print "f"
    def set_callable(self, func):
        self._callable = func
    def g(self):
        print "g"
    def __call__(self):
        return self._callable()
d = Foo()
d.set_callable(d.g)
2

你可能在解决一个错误的问题。

因为Python允许以过程的方式创建类,所以你可以写出这样的代码:

>>> def create_class(cb):
...     class Foo(object):
...         __call__ = cb
...     return Foo
... 
>>> Foo1 = create_class(lambda self: 42)
>>> foo1 = Foo1()
>>> foo1()
>>> Foo2 = create_class(lambda self: self.__class__.__name__)
>>> foo2 = Foo2()
>>> foo2()

不过请注意,在这种情况下,Foo1和Foo2没有共同的基类。所以 isinstanceissubclass 这两个函数是无法使用的。如果你希望它们有一个共同的基类,我建议使用以下代码:

>>> class Foo(object):
...     @classmethod
...     def create_subclass(cls, cb):
...          class SubFoo(cls):
...               __call__ = cb
...          return SubFoo
... 
>>> Foo1 = Foo.create_subclass(lambda self: 42)
>>> foo1 = Foo1()
>>> foo1()
>>> Foo2 = Foo.create_subclass(lambda self: self.__class__.__name__)
>>> foo1 = Foo2()
>>> foo2()
'Foo'
>>> issubclass(Foo1, Foo)
True
>>> issubclass(Foo2, Foo)
True

我个人非常喜欢第二种方法,因为它提供了一个清晰的类层次结构,看起来也很整洁。

3

使用 types.MethodType 的常规方法在这里不太管用,因为 __call__ 是一个特殊的方法。

根据数据模型:

类的实例只有在类中有 __call__() 方法时才能被调用;也就是说,调用 x(arguments) 实际上是 x.__call__(arguments) 的简写。

这有点模糊,具体调用了什么不太明确,但很清楚的是,你的类 必须 有一个 __call__ 方法。

你需要想办法搞个小技巧:

class Foo(object):
    def __init__(self):
        pass
    def f(self):
        print "f"
    def g(self):
        print "g"

    def __call__(self):
        return self.__call__()

f = Foo()
f.__call__ = f.f
f()

g = Foo()
g.__call__ = g.g
g()

不过要小心,如果你在尝试调用实例之前没有给它设置 __call__,就会导致无限递归。

值得注意的是,我并不推荐直接调用你重新绑定的魔法属性 __call__。这里的重点是演示 Python 是如何将 f() 转换为 f.__class__.__call__(f) 的,因此你无法在每个实例上单独改变这一点。无论你做什么,类的 __call__ 都会被调用——你只需要做一些事情来改变类的 __call__ 在每个实例上的行为,这很简单。


你可以使用类似 setter 的方法来真正创建类中的方法(而不是简单的函数)——当然,这也可以变成一个属性:

import types
class Foo(object):
    def __init__(self):
        pass
    def f(self):
        print "f"
    def g(self):
        print "g"

    def set_func(self,f):
        self.func = types.MethodType(f,self)

    def __call__(self,*args,**kwargs):
        self.func(*args,**kwargs)

f = Foo()
f.set_func(Foo.f)
f()

def another_func(self,*args):
    print args

f.set_func(another_func)
f(1,2,3,"bar")

撰写回答