2009-03-03 20 views
46

Tôi đang chạy Python 2.5, vì vậy câu hỏi này có thể không áp dụng cho Python 3. Khi bạn thực hiện phân cấp lớp kim cương bằng cách sử dụng đa thừa kế và tạo đối tượng của lớp được thừa hưởng nhất, Python thực hiện Điều Đúng (TM). Nó gọi hàm tạo cho lớp có xuất phát nhiều nhất, sau đó các lớp cha của nó được liệt kê từ trái sang phải, sau đó là ông bà. Tôi đã quen với Python MRO; đó không phải là câu hỏi của tôi. Tôi tò mò làm thế nào đối tượng trở về từ siêu thực sự quản lý để giao tiếp với các cuộc gọi của siêu trong các lớp học cha mẹ đúng thứ tự. Hãy xem xét mã ví dụ này:Làm thế nào để "siêu" của Python làm điều đúng?

#!/usr/bin/python 

class A(object): 
    def __init__(self): print "A init" 

class B(A): 
    def __init__(self): 
     print "B init" 
     super(B, self).__init__() 

class C(A): 
    def __init__(self): 
     print "C init" 
     super(C, self).__init__() 

class D(B, C): 
    def __init__(self): 
     print "D init" 
     super(D, self).__init__() 

x = D() 

Mã này không điều trực quan, nó in:

D init 
B init 
C init 
A init 

Tuy nhiên, nếu bạn nhận xét ra các cuộc gọi đến siêu trong hàm init B, không phải A hoặc hàm init C được gọi là. Điều này có nghĩa là lời gọi của B đến siêu là bằng cách nào đó nhận thức được sự tồn tại của C trong hệ thống phân cấp lớp tổng thể. Tôi biết rằng siêu trả về một đối tượng proxy với toán tử get overloaded, nhưng đối tượng được trả về bởi siêu trong định nghĩa init của D truyền đạt sự tồn tại của C tới đối tượng được trả về bởi siêu trong định nghĩa init của B? Có phải thông tin mà các cuộc gọi siêu sử dụng tiếp theo được lưu trữ trên chính đối tượng đó không? Nếu vậy, tại sao không phải là siêu thay vì self.super?

Chỉnh sửa: Jekke hoàn toàn đúng chỉ ra rằng nó không phải self.super vì siêu là một thuộc tính của lớp, không phải là một thể hiện của lớp. Khái niệm này có ý nghĩa, nhưng trong thực tế siêu không phải là một thuộc tính của lớp học! Bạn có thể kiểm tra điều này trong trình thông dịch bằng cách tạo hai lớp A và B, trong đó B kế thừa từ A và gọi dir(B). Nó không có thuộc tính super hoặc __super__.

+0

Siêu không phải là self.super vì siêu là thuộc tính của lớp, không phải là cá thể. (Tôi thực sự không hiểu phần còn lại của câu hỏi.) –

+0

Không liên quan, có thể; nhưng tôi đã được khuyên bởi nhiều người không sử dụng Python 2.5 nữa, vì Python 3 giới thiệu rất nhiều tính năng mới/sửa lỗi rất nhiều lỗi. – adam

+0

Chỉnh sửa thành 'đối tượng được trả về bởi siêu định nghĩa init của D truyền đạt sự tồn tại của C tới đối tượng được trả về bởi siêu trong định nghĩa init của B'? Tôi tin rằng đó là những gì bạn đang yêu cầu - nó có thể có ý nghĩa hơn :) –

Trả lời

14

Tôi đã cung cấp một loạt các liên kết bên dưới, trả lời câu hỏi của bạn chi tiết hơn và chính xác hơn tôi có thể hy vọng. Tuy nhiên, tôi sẽ đưa ra câu trả lời cho câu hỏi của bạn theo cách của riêng tôi, để giúp bạn tiết kiệm thời gian. Tôi sẽ đặt nó vào các điểm -

  1. siêu là hàm dựng sẵn chứ không phải thuộc tính.
  2. Mỗi loại (lớp) bằng Python có thuộc tính __mro__, lưu trữ thứ tự phân giải phương thức của cá thể cụ thể đó.
  3. Mỗi cuộc gọi đến siêu có dạng siêu (loại [, đối tượng hoặc loại]). Giả sử thuộc tính thứ hai là một đối tượng cho thời điểm này.
  4. Tại điểm bắt đầu của các cuộc gọi siêu, đối tượng thuộc loại của lớp Có nguồn gốc (nói DC).
  5. siêu tìm kiếm các phương thức phù hợp (trong trường hợp của bạn __init__) trong các lớp trong MRO, sau khi lớp được chỉ định làm đối số đầu tiên (trong trường hợp này là các lớp sau DC).
  6. Khi phương thức đối sánh được tìm thấy (trong lớp BC1), nó được gọi.
    (Phương pháp này nên sử dụng siêu, vì vậy tôi giả định nó - Xem siêu của Python là tiện lợi nhưng không thể được sử dụng - liên kết bên dưới) Phương pháp đó sau đó sẽ tìm kiếm trong lớp của đối tượng 'MRO cho phương pháp tiếp theo, bên phải của BC1.
  7. Rửa sạch lặp lại cho đến khi tất cả các phương pháp được tìm thấy và gọi.

Giải thích ví dụ của bạn

MRO: D,B,C,A,object 
  1. super(D, self).__init__() được gọi. isinstance (tự, D) => Đúng
  2. Tìm kiếm phương pháp tiếp theo trong MRO trong lớp học ở bên phải của D.

    B.__init__ tìm thấy và gọi

  3. B.__init__ cuộc gọi super(B, self).__init__().

    isinstance (tự, B) => False
    isinstance (tự, D) => Đúng

  4. Như vậy, MRO là như nhau, nhưng việc tìm kiếm vẫn tiếp tục ở bên phải của B tức là C, A , đối tượng được tìm kiếm từng cái một. Đã tìm thấy số __init__ tiếp theo được gọi.

  5. Và cứ tiếp tục như vậy.

Giải thích về siêu
http://www.python.org/download/releases/2.2.3/descrintro/#cooperation
Những điều cần xem cho khi sử dụng siêu
http://fuhm.net/super-harmful/
Trăn MRO Thuật toán:
http://www.python.org/download/releases/2.3/mro/
docs siêu nhân:
http://docs.python.org/library/functions.html
Đáy của trang này có một phần tốt đẹp trên siêu:
http://docstore.mik.ua/orelly/other/python/0596001886_pythonian-chp-5-sect-2.html

Tôi hy vọng điều này giúp làm sạch nó lên.

+0

trong lời giải thích của bạn, tôi không hiểu điểm thứ ba "isinstance (self, B) => False", vậy tại sao tìm kiếm tiếp tục? – Alcott

6

chỉ đoán:

self trong tất cả bốn phương pháp tham khảo cùng một đối tượng, đó là, của lớp D. như vậy, trong B.__init__(), gọi tới super(B,self) biết toàn bộ tổ tiên kim cương của self và nó phải lấy phương thức từ 'sau' B. trong trường hợp này, đó là lớp C.

+0

' self' nên tham chiếu đến thể hiện của lớp có chứa, và không phải là một lớp khác xuống dòng, phải không? –

+1

không, nếu bạn gọi một 'self.method()' nó sẽ là thực hiện cụ thể nhất, không có vấn đề làm thế nào bạn đang có. đó là bản chất của đa hình – Javier

34

Thay đổi mã của bạn để này và tôi nghĩ rằng nó sẽ giải thích mọi thứ (có lẽ super được nhìn vào nơi, nói, B là trong __mro__?):

class A(object): 
    def __init__(self): 
     print "A init" 
     print self.__class__.__mro__ 

class B(A): 
    def __init__(self): 
     print "B init" 
     print self.__class__.__mro__ 
     super(B, self).__init__() 

class C(A): 
    def __init__(self): 
     print "C init" 
     print self.__class__.__mro__ 
     super(C, self).__init__() 

class D(B, C): 
    def __init__(self): 
     print "D init" 
     print self.__class__.__mro__ 
     super(D, self).__init__() 

x = D() 

Nếu bạn chạy nó, bạn sẽ thấy :

D init 
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>) 
B init 
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>) 
C init 
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>) 
A init 
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>) 

Ngoài ra, hãy kiểm tra số Python's Super is nifty, but you can't use it.

+2

giống như tôi đoán: tự là lớp D mọi lúc. – Javier

+0

Thú vị, tôi sẽ đoán rằng nếu __mro__ xuất hiện khi thực hiện dir (lớp), nhưng nó không. Nhưng nếu bạn làm dir (class .__ class__) thì nó có thể nhìn thấy! Bất kỳ ý tưởng tại sao sự khác biệt? class .__ mro__ và class .__ class __.__ mro__ cả hai công việc –

+3

Lý do là __mro__ được xác định bởi metaclass, không phải lớp, vì vậy nó không hiển thị trong thư mục (xem http://mail.python.org/pipermail/ python-dev/2008-March/077604.html). –

3

super() biết số đầy đủ phân cấp lớp.Đây là những gì xảy ra bên trong init B:

>>> super(B, self) 
<super: <class 'B'>, <D object>> 

này giải quyết các vấn đề trung tâm,

cách nào để đối tượng được trả về bởi siêu trong định nghĩa init D's trao đổi sự tồn tại của C cho các đối tượng được trả về bởi siêu trong Định nghĩa init của B?

Cụ thể, trong định nghĩa init B, self là một thể hiện của D, và do đó truyền đạt sự tồn tại của C. Ví dụ: C có thể được tìm thấy trong type(self).__mro__.

1

Câu trả lời của Jacob cho thấy cách hiểu vấn đề, trong khi batbrat cho thấy chi tiết và giờ đi thẳng đến điểm.

Một điều họ không bao gồm (ít nhất là không explicity) từ câu hỏi của bạn là điểm này:

Tuy nhiên, nếu bạn nhận xét ra các cuộc gọi đến siêu trong hàm init B, không phải A hoặc hàm init C được gọi là.

Để hiểu rằng, thay đổi mã của Gia-cốp để in stack trên init A, như sau:

import traceback 

class A(object): 
    def __init__(self): 
     print "A init" 
     print self.__class__.__mro__ 
     traceback.print_stack() 

class B(A): 
    def __init__(self): 
     print "B init" 
     print self.__class__.__mro__ 
     super(B, self).__init__() 

class C(A): 
    def __init__(self): 
     print "C init" 
     print self.__class__.__mro__ 
     super(C, self).__init__() 

class D(B, C): 
    def __init__(self): 
     print "D init" 
     print self.__class__.__mro__ 
     super(D, self).__init__() 

x = D() 

Nó là một chút ngạc nhiên khi thấy rằng B 's dòng super(B, self).__init__() là thực sự gọi C.__init__(), như C không phải là baseclass của B.

D init 
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>) 
B init 
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>) 
C init 
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>) 
A init 
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>) 
    File "/tmp/jacobs.py", line 31, in <module> 
    x = D() 
    File "/tmp/jacobs.py", line 29, in __init__ 
    super(D, self).__init__() 
    File "/tmp/jacobs.py", line 17, in __init__ 
    super(B, self).__init__() 
    File "/tmp/jacobs.py", line 23, in __init__ 
    super(C, self).__init__() 
    File "/tmp/jacobs.py", line 11, in __init__ 
    traceback.print_stack() 

Điều này xảy ra bởi vì super (B, self) không phải là 'gọi phiên bản baseclass của B __init__'. Thay vào đó, đó là 'gọi số __init__ ở lớp đầu tiên ở bên phải B hiện diện trên self' s __mro__ và thuộc tính đó có thuộc tính.

Vì vậy, nếu bạn bình luận ra các cuộc gọi đến siêu trong hàm init B, phương pháp ngăn xếp sẽ dừng lại trên B.__init__, và sẽ không bao giờ đạt C hoặc A.

Để tóm tắt:

  • Bất kể lớp được đề cập đến nó, self luôn là một tham chiếu đến dụ, và __mro____class__ của nó không thay đổi
  • siêu() tìm thấy phương pháp tìm kiếm với các lớp học ở bên phải của lớp hiện tại trên __mro__. Khi __mro__ vẫn không đổi, điều xảy ra là nó được tìm kiếm dưới dạng danh sách, không phải là cây hoặc biểu đồ.

Trên điểm cuối cùng đó, lưu ý rằng tên đầy đủ của thuật toán MRO là C3 tuyến tính siêu lớp. Nghĩa là, nó làm phẳng cấu trúc đó thành một danh sách. Khi các cuộc gọi khác nhau super() xảy ra, chúng có hiệu lực khi lặp lại danh sách đó.