Sự khác biệt giữa __init__ và __call__ là gì?


554

Tôi muốn biết sự khác biệt giữa __init____call__phương pháp.

Ví dụ:

class test:

  def __init__(self):
    self.a = 10

  def __call__(self): 
    b = 20

Câu trả lời:


754

Đầu tiên được sử dụng để khởi tạo đối tượng mới được tạo và nhận các đối số được sử dụng để làm điều đó:

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

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

Toán tử thứ hai thực hiện chức năng gọi.

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

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

433
Vì vậy, __init__phương thức được sử dụng khi lớp được gọi để khởi tạo thể hiện, trong khi __call__phương thức được gọi khi cá thể được gọi
pratikm

1
Điều đó có vẻ đúng, rõ ràng các biến thể hiện có thể được sửa đổi trong vòng đời của một thể hiện có thể có lợi trong một số trường hợp.
Murphy

5
init được gọi khi khởi tạo lớp: myfoo = Foo (1,4,7.8) cuộc gọi là một khuôn mẫu để gọi lớp đã được khởi tạo để làm một cái gì đó, hãy nói lớp Foo: \ def __call __ (self, zzz) Sau đó, myfoo (12) gọi lớp để làm những gì lớp đó làm.
dùng1270710

1
Chỉ cần lưu ý: Vì các hàm nói chung là các hàm có thể gọi được, chúng luôn hỗ trợ gọi phương thức . Vì vậy, bạn có thể gọi hàm cũng như thế này: myFun .__ gọi __ (arg)
Arindam Roychowdhury

8
Sử dụng thực tế là __call__gì?
mrgloom

262

Việc xác định một __call__()phương thức tùy chỉnh trong lớp meta cho phép cá thể của lớp được gọi là một hàm, không phải lúc nào cũng sửa đổi chính thể hiện đó.

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

In [2]: a = A()
init

In [3]: a()
call

2
__call__không chỉ cho phép một thể hiện được sử dụng như một hàm ... nó xác định thân hàm được thực thi khi một thể hiện được sử dụng như một hàm.
Arthur

85

Trong Python, các hàm là các đối tượng hạng nhất, điều này có nghĩa là: các tham chiếu hàm có thể được truyền vào các đầu vào cho các hàm và / hoặc các phương thức khác và được thực thi từ bên trong chúng.

Các trường hợp của Lớp (còn gọi là Đối tượng), có thể được xử lý như thể chúng là các hàm: chuyển chúng sang các phương thức / hàm khác và gọi chúng. Để đạt được điều này, __call__chức năng lớp phải được chuyên biệt.

def __call__(self, [args ...]) Nó nhận như là một đầu vào một số lượng các đối số. Giả sử xlà một thể hiện của Class X, x.__call__(1, 2)tương tự như việc gọi x(1,2)hoặc chính cá thể đó là một hàm .

Trong Python, __init__()được định nghĩa đúng là Trình xây dựng lớp (cũng như Trình __del__()hủy lớp). Do đó, có một sự phân biệt rõ ràng giữa __init__()__call__(): cái đầu tiên xây dựng một thể hiện của Class up, cái thứ hai làm cho cá thể đó có thể gọi như một hàm sẽ không ảnh hưởng đến vòng đời của chính đối tượng (nghĩa là __call__không ảnh hưởng đến vòng đời xây dựng / phá hủy) nhưng nó có thể sửa đổi trạng thái bên trong của nó (như hiển thị bên dưới).

Thí dụ.

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

2
Tôi hiểu khái niệm này, nhưng không phải là tính năng đặc biệt của việc sửa đổi trạng thái bên trong của nó . Nếu chúng ta trong đoạn mã trên thay thế def __call__đơn giản bằng def update, chúng ta cung cấp cho lớp một updatephương thức thực hiện điều tương tự. Bây giờ nó cũng có thể sửa đổi trạng thái nội bộ, nếu được gọi dưới đây là s.update(7, 8). Vì vậy, __call__chỉ là đường tổng hợp?
Alex Pigs

27

__call__làm cho thể hiện của một lớp có thể gọi được. Tại sao nó sẽ được yêu cầu?

Về mặt kỹ thuật __init__được gọi một lần __new__khi đối tượng được tạo, để nó có thể được khởi tạo.

Nhưng có nhiều kịch bản mà bạn có thể muốn xác định lại đối tượng của mình, giả sử bạn đã hoàn thành với đối tượng của mình và có thể thấy cần một đối tượng mới. Với __call__bạn có thể xác định lại cùng một đối tượng như thể nó là mới.

Đây chỉ là một trường hợp, có thể có nhiều hơn nữa.


9
Đối với trường hợp cụ thể này, chúng ta không nên tạo một thể hiện mới? Đây có phải là hiệu quả trong một số cách để sửa đổi và sử dụng cùng một ví dụ.
Murphy

19
>>> 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 ... 
>>> 

Tôi nghĩ rằng đây nên là câu trả lời được chấp nhận. Nó trả lời chính xác.
Prasad Raghavendra

18

__init__sẽ được coi là Con Contor trong đó __call__các phương thức có thể được gọi với các đối tượng bất kỳ số lần nào. Cả hai __init__và các __call__hàm đều có các đối số mặc định.


1
__init__không phải là một hàm xây dựng nhưng __new__là. __init__được gọi ngay sau__new__
dallonsi

Tôi nghĩ __new__tạo ra cá thể lớp và nhận một lớp làm đối số, trong khi đó __init__là hàm tạo cá thể, đó là lý do tại sao nó nhận được self. Một cách dễ dàng để thấy điều này là trong lệnh gọi a = Foo(1,2,3)hàm sẽ nhận được các đối số của hàm tạo __init__.
Cà phê_fan

13

Tôi sẽ cố gắng giải thích điều này bằng một ví dụ, giả sử bạn muốn in một số thuật ngữ cố định từ sê-ri. Hãy nhớ rằng 2 thuật ngữ đầu tiên của loạt trò chơi là 1s. Ví dụ: 1, 1, 2, 3, 5, 8, 13 ....

Bạn muốn danh sách chứa các số Wikipedia chỉ được khởi tạo một lần và sau đó nó sẽ cập nhật. Bây giờ chúng ta có thể sử dụng các __call__chức năng. Đọc câu trả lời của @mudit verma. Giống như bạn muốn đối tượng có thể gọi được dưới dạng hàm nhưng không được khởi tạo lại mỗi khi bạn gọi nó.

Ví dụ:

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)

Đầu ra là:

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__

Nếu bạn quan sát đầu ra chỉ __init__được gọi một lần duy nhất khi lớp được khởi tạo lần đầu tiên, thì sau đó đối tượng sẽ được gọi mà không khởi tạo lại.


7

Bạn cũng có thể sử dụng __call__phương pháp có lợi cho việc thực hiện trang trí .

Ví dụ này được lấy từ Python 3 Mẫu, Bí quyết và Thành ngữ

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")

Đầu ra :

nhập mô tả hình ảnh ở đây


4
Bạn có thể vui lòng sao chép đầu ra dưới dạng văn bản?
Solomon Ucko

Điểm của phương pháp này là gì? Bạn có thể đối chiếu nó với một cách tiếp cận khác?
nealmcb

7

Vì vậy, __init__được gọi khi bạn đang tạo một thể hiện của bất kỳ lớp nào và cũng khởi tạo biến thể hiện.

Thí dụ:

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__được gọi khi bạn gọi đối tượng như bất kỳ chức năng nào khác.

Thí dụ:

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

1
@Redowan Delowar cảm ơn broh
Ayemun Hossain Ashik

4

__init__là một phương thức đặc biệt trong các lớp Python, nó là phương thức constructor cho một lớp. Nó được gọi bất cứ khi nào một đối tượng của lớp được xây dựng hoặc chúng ta có thể nói nó khởi tạo một đối tượng mới. Thí dụ:

    In [4]: class A:
   ...:     def __init__(self, a):
   ...:         print(a)
   ...:
   ...: a = A(10) # An argument is necessary
10

Nếu chúng ta sử dụng A (), nó sẽ báo lỗi TypeError: __init__() missing 1 required positional argument: 'a'vì nó yêu cầu 1 đối số a__init__ .

........

__call__ khi được triển khai trong Class giúp chúng ta gọi cá thể Class như một lời gọi hàm.

Thí dụ:

In [6]: class B:
   ...:     def __call__(self,b):
   ...:         print(b)
   ...:
   ...: b = B() # Note we didn't pass any arguments here
   ...: b(20)   # Argument passed when the object is called
   ...:
20

Ở đây nếu chúng ta sử dụng B (), nó sẽ chạy tốt vì nó không có __init__chức năng ở đây.


truyền một đối tượng đến một đối tượng lớp khởi tạo. Vì vậy, một đối tượng có thể gọi được?
Ciasto piekarz

3

__call__cho phép trả về các giá trị tùy ý, trong khi __init__là một hàm tạo trả về thể hiện của lớp ngầm. Như các câu trả lời khác đã chỉ ra một cách chính xác, __init__được gọi chỉ một lần, trong khi có thể gọi __call__nhiều lần, trong trường hợp cá thể khởi tạo được gán cho biến trung gian.

>>> 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'
>>> 

3

Câu trả lời ngắn và ngọt ngào đã được cung cấp ở trên. Tôi muốn cung cấp một số triển khai thực tế so với Java.

 class test(object):
        def __init__(self, a, b, c):
            self.a = a
            self.b = b
            self.c = c
        def __call__(self, a, b, c):
            self.a = a
            self.b = b
            self.c = c


    instance1 = test(1, 2, 3)
    print(instance1.a) #prints 1

    #scenario 1
    #creating new instance instance1
    #instance1 = test(13, 3, 4)
    #print(instance1.a) #prints 13


    #scenario 2
    #modifying the already created instance **instance1**
    instance1(13,3,4)
    print(instance1.a)#prints 13

Lưu ý : kịch bản 1 và kịch bản 2 có vẻ giống nhau về kết quả đầu ra. Nhưng trong kịch bản1, chúng ta lại tạo một thể hiện mới khác1 . Trong kịch bản 2, chúng tôi chỉ cần sửa đổi thể hiện đã tạo1 . __call__có lợi ở đây vì hệ thống không cần tạo phiên bản mới.

Tương đương trong Java

public class Test {

    public static void main(String[] args) {
        Test.TestInnerClass testInnerClass = new Test(). new TestInnerClass(1, 2, 3);
        System.out.println(testInnerClass.a);

        //creating new instance **testInnerClass**
        testInnerClass = new Test().new TestInnerClass(13, 3, 4);
        System.out.println(testInnerClass.a);

        //modifying already created instance **testInnerClass**
        testInnerClass.a = 5;
        testInnerClass.b = 14;
        testInnerClass.c = 23;

        //in python, above three lines is done by testInnerClass(5, 14, 23). For this, we must define __call__ method

    }

    class TestInnerClass /* non-static inner class */{

        private int a, b,c;

        TestInnerClass(int a, int b, int c) {
            this.a = a;
            this.b = b;
            this.c = c;
        }
    }
}

3
So sánh với Java là hoàn toàn nằm ngoài phạm vi của câu hỏi. Trong ví dụ của bạn, bạn không thấy bất kỳ sự khác biệt nào vì nó được chọn kém, các con số đều giống nhau.
Aquiles Carattino

2

Chúng ta có thể sử dụng phương thức gọi để sử dụng các phương thức lớp khác làm phương thức tĩnh.

class _Callable:
    def __init__(self, anycallable):
        self.__call__ = anycallable

class Model:

    def get_instance(conn, table_name):

        """ do something"""

    get_instance = _Callable(get_instance)

provs_fac = Model.get_instance(connection, "users")  
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.