__init__和__call__之间有什么区别?

774

我想了解__init____call__方法之间的区别。

比如:

class test:

  def __init__(self):
    self.a = 10

  def __call__(self): 
    b = 20

4
这实际上是两个问题:“__init__方法是什么 / 它是做什么的?”和“__call__方法是什么 / 它是做什么的?”我没有先验地认为它们有任何关联,实际上它们没有关联 - 因此没有有意义的描述它们之间的“区别”。 - Karl Knechtel
1
foo和bar有什么区别? - David Heffernan
17个回答

1040

第一个参数被用于初始化新创建的对象,并接收用于此目的的参数:

class Foo:
    def __init__(self, a, b, c):
        # ...

x = Foo(1, 2, 3) # __init__
第二个实现了函数调用运算符。
class Foo:
    def __call__(self, a, b, c):
        # ...

x = Foo()
x(1, 2, 3) # __call__

620
调用类来初始化实例时,__init__方法被使用,而当调用实例时,__call__方法被调用。 - pratikm
2
看起来没问题,实例变量显然可以在实例的生命周期内被修改,在某些情况下这可能是有益的。 - Murphy
7
init 在实例化类时被调用: myfoo = Foo(1,4,7.8) call 是一个用于调用已实例化类执行某些操作的模板。假设有一个类Foo: def call(self, zzz) 那么,myfoo(12) 调用该类执行该类所需的操作。 - user1270710
31
__call__ 的实际用途是什么? - mrgloom
20
下面Dmitriy Sintsov的回答提出了一个非常重要的观点,所以我觉得我应该在这里引起注意:“__call__”可以返回任意值,而“__init__”必须返回None - cantordust
显示剩余6条评论

340

定义一个自定义的__call__()方法允许该类的实例被作为函数调用,而不总是修改实例本身。

In [1]: class A:
   ...:     def __init__(self):
   ...:         print "init"
   ...:         
   ...:     def __call__(self):
   ...:         print "call"
   ...:         
   ...:         

In [2]: a = A()
init

In [3]: a()
call

12
__call__ 不仅允许将一个实例用作函数,还定义了当实例被用作函数时要执行的函数体。 - Arthur

115

在Python中,函数是一等对象,这意味着:函数引用可以作为输入传递给其他函数和/或方法,并且可以从其中执行。

类的实例(也称为对象)可以像函数一样处理:将它们传递给其他方法/函数并调用它们。为了实现这一点,必须专门化__call__类函数。

def __call__(self, [args...]) 它以可变数量的参数作为输入。假设x是类X的实例,则x.__call__(1,2)类似于调用x(1,2)将实例本身作为函数

在Python中,__init__()被正确定义为类构造函数(以及__del__()是类析构函数)。因此,__init__()__call__()之间存在明显区别:前者构建类实例,后者使该实例可调用,就像函数一样,而不影响对象本身的生命周期(即__call__不影响构造/析构生命周期),但它可以修改其内部状态(如下所示)。

例子。

class Stuff(object):

    def __init__(self, x, y, range):
        super(Stuff, self).__init__()
        self.x = x
        self.y = y
        self.range = range

    def __call__(self, x, y):
        self.x = x
        self.y = y
        print '__call__ with (%d,%d)' % (self.x, self.y)

    def __del__(self):
        del self.x
        del self.y
        del self.range

>>> s = Stuff(1, 2, 3)
>>> s.x
1
>>> s(7, 8)
__call__ with (7,8)
>>> s.x
7

7
我理解这个概念,但不了解“修改其内部状态”的特殊功能。如果在上面的代码中将 def __call__ 直接替换为 def update,我们就给类添加了一个执行相同操作的 update 方法。如果像下面这样调用 s.update(7, 8),它现在也可以修改内部状态。因此,__call__ 只是语法糖吗? - Alex Povel
3
是的,基本上就是这样。它只是一个快捷方式,可以在不必指定方法的情况下调用对象上的方法。除此之外,它就像任何其他实例方法一样。有趣的是,如果你用@classmethod修饰它,它既可以作为一个类方法,也可以让实例可调用。但是因为类方法不能使用self参数,所以没有状态可以传递,试图将类作为方法调用会调用__init__,所以幸运的是它不会破坏类的构造。 - Chris Ivan

36

__call__使类的实例成为可调用的。为什么需要呢?

从技术上讲,__init__在对象创建时由__new__调用一次,以便初始化对象。

但是有许多情况下,您可能希望重新定义您的对象,例如您已经完成了当前对象,并且可能需要一个新对象。使用__call__,您可以将同一对象重新定义为新对象。

这只是其中一种情况,还有很多其他情况。


15
针对这个具体情况,我们是否应该创建一个新实例?修改并重复使用同一实例在某些方面是否高效? - Murphy

35
>>> class A:
...     def __init__(self):
...         print "From init ... "
... 
>>> a = A()
From init ... 
>>> a()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: A instance has no __call__ method
>>> 
>>> class B:
...     def __init__(self):
...         print "From init ... "
...     def __call__(self):
...         print "From call ... "
... 
>>> b = B()
From init ... 
>>> b()
From call ... 
>>> 

1
我认为这应该是被接受的答案。它回答得非常精确。 - Prasad Raghavendra

20

__init__ 被视为构造函数,而 __call__ 方法可以被对象调用任意次数。 __init____call__ 函数都可以使用默认参数。


3
__init__ 不是构造函数,但 __new__ 是。__init____new__ 之后立即被调用。 - dallonsi
我认为__new__创建类实例并接收一个类作为参数,而__init__是实例构造函数,因此它接收self。一个简单的方法来理解这一点是在调用a = Foo(1,2,3)时,将接收构造函数参数的函数将是__init__ - luv2learn

18
我将尝试用一个例子来解释,假设您想要从斐波那契数列中打印固定数量的项。请记住,斐波那契数列的前两个项是1。例如:1, 1, 2, 3, 5, 8, 13....
您希望只初始化包含斐波那契数的列表一次,然后进行更新。现在我们可以使用__call__功能。阅读@mudit verma的答案。这就像您希望对象可调用为函数,但不会在每次调用时重新初始化它。
例如:
class Recorder:
    def __init__(self):
        self._weights = []
        for i in range(0, 2):
            self._weights.append(1)
        print self._weights[-1]
        print self._weights[-2]
        print "no. above is from __init__"

    def __call__(self, t):
        self._weights = [self._weights[-1], self._weights[-1] + self._weights[-2]]
        print self._weights[-1]
        print "no. above is from __call__"

weight_recorder = Recorder()
for i in range(0, 10):
    weight_recorder(i)

输出结果为:
1
1
no. above is from __init__
2
no. above is from __call__
3
no. above is from __call__
5
no. above is from __call__
8
no. above is from __call__
13
no. above is from __call__
21
no. above is from __call__
34
no. above is from __call__
55
no. above is from __call__
89
no. above is from __call__
144
no. above is from __call__

如果您观察输出,__init__ 只在类被实例化为第一次时调用,之后对象被调用时不需要重新初始化。


15

__call__ 允许返回任意值,而 __init__ 作为构造函数隐式地返回类的实例。正如其他回答所指出的那样,__init__ 只会被调用一次,而可以在将初始化后的实例赋给中间变量时多次调用 __call__

>>> class Test:
...     def __init__(self):
...         return 'Hello'
... 
>>> Test()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
TypeError: __init__() should return None, not 'str'
>>> class Test2:
...     def __call__(self):
...         return 'Hello'
... 
>>> Test2()()
'Hello'
>>> 
>>> Test2()()
'Hello'
>>> 

这个实际上解释了__call__()的重要性。 - user41855

10

因此,__init__ 在创建任何类的实例并初始化实例变量时被调用。

例如:

class User:

    def __init__(self,first_n,last_n,age):
        self.first_n = first_n
        self.last_n = last_n
        self.age = age

user1 = User("Jhone","Wrick","40")

__call__ 方法在像调用其他函数一样调用对象时被调用。

例如:

class USER:
    def __call__(self,arg):
        "todo here"
         print(f"I am in __call__ with arg : {arg} ")


user1=USER()
user1("One") #calling the object user1 and that's gonna call __call__ dunder functions

9

你也可以使用__call__方法来代替实现装饰器

这个例子摘自Python 3 Patterns, Recipes and Idioms

class decorator_without_arguments(object):
    def __init__(self, f):
        """
        If there are no decorator arguments, the function
        to be decorated is passed to the constructor.
        """
        print("Inside __init__()")
        self.f = f

    def __call__(self, *args):
        """
        The __call__ method is not called until the
        decorated function is called.
        """
        print("Inside __call__()")
        self.f(*args)
        print("After self.f( * args)")


@decorator_without_arguments
def sayHello(a1, a2, a3, a4):
    print('sayHello arguments:', a1, a2, a3, a4)


print("After decoration")
print("Preparing to call sayHello()")
sayHello("say", "hello", "argument", "list")
print("After first sayHello() call")
sayHello("a", "different", "set of", "arguments")
print("After second sayHello() call")

输出:

这里输入图片描述


4
可以,请问您能将输出内容复制为文本吗? - Solomon Ucko
这种方法的重点是什么?你能与另一种方法进行对比吗? - nealmcb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接