Làm thế nào để super () của Python hoạt động với nhiều kế thừa?


888

Tôi khá mới trong lập trình hướng đối tượng Python và tôi gặp khó khăn trong việc hiểu super()chức năng (các lớp kiểu mới) đặc biệt là khi nói đến nhiều kế thừa.

Ví dụ: nếu bạn có một cái gì đó như:

class First(object):
    def __init__(self):
        print "first"

class Second(object):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()
        print "that's it"

Điều tôi không nhận được là: Third()lớp có kế thừa cả hai phương thức constructor không? Nếu có, thì cái nào sẽ được chạy với super () và tại sao?

Và nếu bạn muốn chạy cái khác thì sao? Tôi biết nó có liên quan đến thứ tự phân giải phương thức Python ( MRO ).


Trong thực tế, nhiều kế thừa là trường hợp duy nhất super()được sử dụng. Tôi không khuyên bạn nên sử dụng nó với các lớp sử dụng kế thừa tuyến tính, trong đó nó chỉ là vô dụng.
Bachsau

9
@Bachsau đúng về mặt kỹ thuật ở chỗ nó là một chi phí nhỏ nhưng super () là pythonic hơn và cho phép bao thanh toán lại và thay đổi mã theo thời gian. Sử dụng super () trừ khi bạn thực sự cần một phương thức cụ thể của lớp được đặt tên.
Paul Whipp

2
Một vấn đề khác super()là, nó buộc mọi lớp con cũng phải sử dụng nó, trong khi khi không sử dụng super(), mọi người đều có thể tự quyết định. Nếu một nhà phát triển sử dụng nó không biết super()hoặc không biết nó đã được sử dụng, các vấn đề với mro có thể phát sinh rất khó theo dõi.
Bachsau

Tôi đã tìm thấy hầu như mỗi câu trả lời ở đây khó hiểu theo cách này hay cách khác. Bạn thực sự sẽ tham khảo ở đây để thay thế.
matanster

Câu trả lời:


709

Đây là chi tiết với số lượng chi tiết hợp lý của chính Guido trong bài đăng trên blog Phương thức giải quyết Phương thức (bao gồm hai lần thử trước đó).

Trong ví dụ của bạn, Third()sẽ gọi First.__init__. Python tìm kiếm từng thuộc tính trong lớp cha mẹ của lớp khi chúng được liệt kê từ trái sang phải. Trong trường hợp này, chúng tôi đang tìm kiếm __init__. Vì vậy, nếu bạn xác định

class Third(First, Second):
    ...

Python sẽ bắt đầu bằng cách nhìn vào First, và, nếu Firstkhông có thuộc tính, thì nó sẽ xem xét Second.

Tình huống này trở nên phức tạp hơn khi kế thừa bắt đầu băng qua các đường dẫn (ví dụ nếu Firstđược kế thừa từ Second). Đọc liên kết ở trên để biết thêm chi tiết, nhưng, tóm lại, Python sẽ cố gắng duy trì thứ tự mỗi lớp xuất hiện trong danh sách thừa kế, bắt đầu với chính lớp con.

Vì vậy, ví dụ, nếu bạn có:

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First):
    def __init__(self):
        print "third"

class Fourth(Second, Third):
    def __init__(self):
        super(Fourth, self).__init__()
        print "that's it"

MRO sẽ là [Fourth, Second, Third, First].

Nhân tiện: nếu Python không thể tìm thấy thứ tự phân giải phương thức mạch lạc, nó sẽ đưa ra một ngoại lệ, thay vì quay trở lại hành vi có thể gây ngạc nhiên cho người dùng.

Đã chỉnh sửa để thêm một ví dụ về MRO mơ hồ:

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        print "third"

Có nên Third's MRO có [First, Second]hay [Second, First]? Không có kỳ vọng rõ ràng và Python sẽ phát sinh lỗi:

TypeError: Error when calling the metaclass bases
    Cannot create a consistent method resolution order (MRO) for bases Second, First

Chỉnh sửa: Tôi thấy một số người lập luận rằng các ví dụ ở trên thiếu super()các cuộc gọi, vì vậy hãy để tôi giải thích: Điểm của các ví dụ là chỉ ra cách MRO được xây dựng. Họ không có ý định in "đầu tiên \ nsecond \ third" hoặc bất cứ điều gì. Tất nhiên, bạn có thể - và nên chơi xung quanh với ví dụ, thêm super()các cuộc gọi, xem điều gì xảy ra và hiểu sâu hơn về mô hình thừa kế của Python. Nhưng mục tiêu của tôi ở đây là giữ cho nó đơn giản và chỉ ra cách MRO được xây dựng. Và nó được xây dựng như tôi đã giải thích:

>>> Fourth.__mro__
(<class '__main__.Fourth'>,
 <class '__main__.Second'>, <class '__main__.Third'>,
 <class '__main__.First'>,
 <type 'object'>)

12
Nó trở nên thú vị hơn (và, có thể nói là khó hiểu hơn) khi bạn bắt đầu gọi super () trong Thứ nhất, Thứ hai và Thứ ba [ pastebin.com/ezTyZ5Wa ].
gatoatigrado

52
Tôi nghĩ rằng việc thiếu các cuộc gọi siêu hạng trong các lớp học đầu tiên là một vấn đề thực sự lớn với câu trả lời này; mà không thảo luận làm thế nào / tại sao đó là sự hiểu biết quan trọng đối với câu hỏi bị mất.
Sam Hartman

3
Câu trả lời này đơn giản là sai. Không có các cuộc gọi siêu () trong cha mẹ, sẽ không có gì xảy ra. @ câu trả lời vô hồn là câu trả lời đúng.
Cerin

8
@Cerin Điểm của ví dụ này là chỉ ra cách MRO được xây dựng. Ví dụ này KHÔNG có ý định in "đầu tiên \ nsecond \ third" hoặc bất cứ điều gì. Và MRO là thực sự đúng: Thứ tư .__ mro__ == (<class ' chính .Fourth'>, <class ' chính .Second'>, <class ' chính .Third'>, <class ' chính .First'>, < gõ 'object'>)
rbp

2
Theo như tôi có thể thấy, câu trả lời này thiếu một trong những câu hỏi của OP, đó là "Và nếu bạn muốn chạy câu hỏi kia thì sao?". Tôi muốn xem câu trả lời cho câu hỏi này. Có phải chúng ta chỉ cần đặt tên rõ ràng cho lớp cơ sở?
Ray

251

Mã của bạn và các câu trả lời khác, tất cả đều có lỗi. Họ đang thiếu các super()cuộc gọi trong hai lớp đầu tiên được yêu cầu để phân lớp hợp tác hoạt động.

Đây là một phiên bản cố định của mã:

class First(object):
    def __init__(self):
        super(First, self).__init__()
        print("first")

class Second(object):
    def __init__(self):
        super(Second, self).__init__()
        print("second")

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()
        print("third")

Cuộc super()gọi tìm phương thức tiếp theo trong MRO ở mỗi bước, đó là lý do tại sao Thứ nhất và Thứ hai cũng phải có nó, nếu không thì việc thực thi dừng lại ở cuối Second.__init__().

Đây là những gì tôi nhận được:

>>> Third()
second
first
third

90
Phải làm gì nếu các lớp này cần các tham số khác nhau để tự khởi tạo?

2
"Phân lớp hợp tác"
Quant Metropolis

6
Theo cách này, các phương thức init của các lớp cơ sở BOTH sẽ được thực thi, trong khi ví dụ ban đầu chỉ gọi init đầu tiên gặp phải trong MRO. Tôi đoán điều đó được ngụ ý bởi thuật ngữ "phân lớp hợp tác", nhưng một sự làm rõ sẽ hữu ích ('Rõ ràng là tốt hơn ngầm định', bạn biết đấy;))
Quant Metropolis

1
Có, nếu bạn đang truyền các tham số khác nhau cho một phương thức được gọi thông qua siêu, tất cả các cài đặt của phương thức đó đi lên MRO đối với đối tượng () cần phải có chữ ký tương thích. Điều này có thể đạt được thông qua các tham số từ khóa: chấp nhận nhiều tham số hơn phương thức sử dụng và bỏ qua các tham số phụ. Nhìn chung, nó được coi là xấu khi thực hiện điều này và trong hầu hết các trường hợp, việc thêm các phương thức mới sẽ tốt hơn, nhưng init là (gần như) duy nhất như một tên phương thức đặc biệt nhưng với các tham số do người dùng xác định.
vô hồn

15
Thiết kế của nhiều kế thừa thực sự rất tệ trong python. Các lớp cơ sở hầu như cần biết ai sẽ lấy nó và bao nhiêu lớp cơ sở khác mà dẫn xuất sẽ xuất phát, và theo thứ tự nào ... nếu không supersẽ không chạy (vì tham số không khớp), hoặc nó sẽ không gọi một vài trong số các cơ sở (vì bạn đã không viết supervào một trong những cơ sở phá vỡ liên kết)!
Nawaz

186

Tôi muốn xây dựng câu trả lời một cách vô hồn một chút bởi vì khi tôi bắt đầu đọc về cách sử dụng super () trong hệ thống phân cấp nhiều kế thừa trong Python, tôi đã không nhận được nó ngay lập tức.

Điều bạn cần hiểu là super(MyClass, self).__init__()cung cấp phương thức tiếp __init__ theo theo thuật toán Thứ tự phân giải phương thức (MRO) được sử dụng trong bối cảnh phân cấp thừa kế hoàn chỉnh .

Phần cuối cùng này là rất quan trọng để hiểu. Hãy xem xét lại ví dụ:

#!/usr/bin/env python2

class First(object):
  def __init__(self):
    print "First(): entering"
    super(First, self).__init__()
    print "First(): exiting"

class Second(object):
  def __init__(self):
    print "Second(): entering"
    super(Second, self).__init__()
    print "Second(): exiting"

class Third(First, Second):
  def __init__(self):
    print "Third(): entering"
    super(Third, self).__init__()
    print "Third(): exiting"

Theo bài viết này về Thứ tự giải quyết phương pháp của Guido van Rossum, thứ tự giải quyết __init__được tính toán (trước Python 2.3) bằng cách sử dụng "giao dịch theo chiều sâu từ trái sang phải trước":

Third --> First --> object --> Second --> object

Sau khi loại bỏ tất cả các bản sao, ngoại trừ bản cuối cùng, chúng tôi nhận được:

Third --> First --> Second --> object

Vì vậy, hãy theo dõi những gì xảy ra khi chúng ta khởi tạo một thể hiện của Thirdlớp, vd x = Third().

  1. Theo MRO Third.__init__thực thi.
    • in Third(): entering
    • sau đó super(Third, self).__init__()thực thi và trả về MRO First.__init__được gọi là.
  2. First.__init__ hành quyết.
    • in First(): entering
    • sau đó super(First, self).__init__()thực thi và trả về MRO Second.__init__được gọi là.
  3. Second.__init__ hành quyết.
    • in Second(): entering
    • sau đó super(Second, self).__init__()thực thi và trả về MRO object.__init__được gọi là.
  4. object.__init__ thực thi (không có câu lệnh in trong mã ở đó)
  5. thực hiện quay trở lại Second.__init__mà sau đó inSecond(): exiting
  6. thực hiện quay trở lại First.__init__mà sau đó inFirst(): exiting
  7. thực hiện trở lại Third.__init__mà sau đó inThird(): exiting

Điều này chi tiết tại sao việc bắt đầu Thứ ba () dẫn đến:

Third(): entering
First(): entering
Second(): entering
Second(): exiting
First(): exiting
Third(): exiting

Thuật toán MRO đã được cải tiến từ Python 2.3 trở đi để hoạt động tốt trong các trường hợp phức tạp, nhưng tôi đoán rằng bằng cách sử dụng "truyền tải từ trái sang phải từ sâu đầu tiên" + "loại bỏ trùng lặp mong đợi cho lần cuối" vẫn hoạt động trong hầu hết các trường hợp (vui lòng bình luận nếu đây không phải là trường hợp). Hãy chắc chắn để đọc bài viết trên blog của Guido!


6
Tôi vẫn không hiểu tại sao: Inside init của First super (First, self) .__ init __ () gọi init của Second, bởi vì đó là những gì MRO ra lệnh!
dùng389955

@ user389955 Đối tượng được tạo là loại Thứ ba có tất cả các phương thức init. Vì vậy, nếu bạn giả sử MRO tạo một danh sách tất cả các hàm init theo một thứ tự cụ thể, với mỗi siêu cuộc gọi, bạn sẽ tiến lên một bước cho đến khi bạn kết thúc.
Saletumar R

15
Tôi nghĩ Bước 3 cần giải thích thêm: Nếu Thirdkhông được thừa kế từ Secondđó, thì super(First, self).__init__sẽ gọi object.__init__và sau khi quay lại, "đầu tiên" sẽ được in. Nhưng bởi vì Thirdkế thừa từ cả hai FirstSecond, thay vì gọi object.__init__sau khi First.__init__MRO ra lệnh rằng chỉ có lệnh gọi cuối cùng object.__init__được giữ nguyên và các câu lệnh in trong FirstSecondkhông được thực hiện cho đến khi object.__init__trả về. Vì Secondlà người cuối cùng gọi object.__init__, nó quay lại bên trong Secondtrước khi quay lại First.
MountainDrew

1
Điều thú vị là, PyCharm dường như biết tất cả điều này (gợi ý của nó nói về những thông số đi mà các cuộc gọi đến siêu. Nó cũng có một số khái niệm về hiệp phương sai đầu vào, do đó nó nhận ra List[subclass]như một List[superclass]nếu subclasslà một lớp con của superclass( Listxuất phát từ typingmô-đun của PEP 483 iirc).
Reb.Cabin

Bài đăng đẹp nhưng tôi bỏ lỡ thông tin liên quan đến các đối số của các nhà xây dựng, tức là điều gì xảy ra nếu Thứ hai và Đầu tiên mong đợi các đối số khác biệt? Hàm tạo của First sẽ phải xử lý một số đối số và chuyển phần còn lại cho Second. Có đúng không? Nó không đúng với tôi rằng Đầu tiên cần biết về các đối số cần thiết cho Thứ hai.
Christian K.

58

Đây được gọi là Vấn đề kim cương , trang có một mục trên Python, nhưng tóm lại, Python sẽ gọi các phương thức của siêu lớp từ trái sang phải.


Đây không phải là vấn đề kim cương. Vấn đề kim cương liên quan đến bốn lớp và câu hỏi của OP chỉ liên quan đến ba lớp.
Ian Goodfellow

147
objectlà thứ tư
GP89

28

Đây là cách tôi giải quyết vấn đề có nhiều kế thừa với các biến khác nhau để khởi tạo và có nhiều MixIns với cùng một lệnh gọi. Tôi đã phải thêm các biến một cách rõ ràng vào ** kwargs và thêm giao diện MixIn để trở thành điểm cuối cho các cuộc gọi siêu hạng.

Dưới đây Alà một lớp cơ sở mở rộng và BCcác lớp học mixin cả người cung cấp chức năng f. ABcả hai mong đợi tham số vtrong họ __init__Cmong đợi w. Hàm flấy một tham số y. Qkế thừa từ cả ba lớp. MixInFlà giao diện mixin cho BC.


class A(object):
    def __init__(self, v, *args, **kwargs):
        print "A:init:v[{0}]".format(v)
        kwargs['v']=v
        super(A, self).__init__(*args, **kwargs)
        self.v = v


class MixInF(object):
    def __init__(self, *args, **kwargs):
        print "IObject:init"
    def f(self, y):
        print "IObject:y[{0}]".format(y)


class B(MixInF):
    def __init__(self, v, *args, **kwargs):
        print "B:init:v[{0}]".format(v)
        kwargs['v']=v
        super(B, self).__init__(*args, **kwargs)
        self.v = v
    def f(self, y):
        print "B:f:v[{0}]:y[{1}]".format(self.v, y)
        super(B, self).f(y)


class C(MixInF):
    def __init__(self, w, *args, **kwargs):
        print "C:init:w[{0}]".format(w)
        kwargs['w']=w
        super(C, self).__init__(*args, **kwargs)
        self.w = w
    def f(self, y):
        print "C:f:w[{0}]:y[{1}]".format(self.w, y)
        super(C, self).f(y)


class Q(C,B,A):
    def __init__(self, v, w):
        super(Q, self).__init__(v=v, w=w)
    def f(self, y):
        print "Q:f:y[{0}]".format(y)
        super(Q, self).f(y)

Tôi nghĩ rằng đây có lẽ nên là một câu hỏi và câu trả lời riêng biệt, vì MRO là một chủ đề đủ lớn mà không cần phải xử lý các đối số khác nhau giữa các chức năng với tính kế thừa (nhiều kế thừa là trường hợp đặc biệt của điều đó).
vô hồn

8
Về mặt lý thuyết, vâng. Thực tế, kịch bản này đã xuất hiện mỗi khi tôi gặp phải thừa kế Kim cương ở trăn, vì vậy tôi đã thêm nó vào đây. Vì, đây là nơi tôi đến mỗi khi tôi không thể tránh khỏi việc thừa kế kim cương. Dưới đây là một số liên kết bổ sung cho tương lai tôi: rhettinger.wordpress.com/2011/05/26/super-considered-super code.activestate.com/recipes/...
brent.payne

Những gì chúng ta muốn là các chương trình với tên tham số có ý nghĩa ngữ nghĩa. Nhưng trong ví dụ này, hầu hết tất cả các tham số đều được đặt tên ẩn danh, điều này sẽ khiến cho lập trình viên ban đầu gặp khó khăn hơn nhiều trong việc ghi lại mã và cho một lập trình viên khác đọc mã.
Arthur

Yêu cầu kéo tới repo github với tên mô tả sẽ được đánh giá cao
brent.payne

@ brent.payne Tôi nghĩ rằng @Arthur có nghĩa là toàn bộ cách tiếp cận của bạn phụ thuộc vào việc sử dụng args/ kwargsthay vì các tham số được đặt tên.
tối đa

25

Tôi hiểu điều này không trực tiếp trả lời super()câu hỏi, nhưng tôi cảm thấy nó đủ liên quan để chia sẻ.

Ngoài ra còn có một cách để gọi trực tiếp từng lớp kế thừa:


class First(object):
    def __init__(self):
        print '1'

class Second(object):
    def __init__(self):
        print '2'

class Third(First, Second):
    def __init__(self):
        Second.__init__(self)

Lưu ý chỉ rằng nếu bạn làm theo cách này, bạn sẽ phải gọi nhau bằng tay như tôi khá chắc chắn rằng First's __init__()sẽ không được gọi.


5
Nó sẽ không được gọi bởi vì bạn đã không gọi từng lớp kế thừa. Vấn đề là đúng hơn là nếu FirstSecondđều kế thừa lớp khác và gọi đó là trực tiếp thì đây (điểm bắt đầu của kim cương) lớp chung được gọi là hai lần. siêu là tránh điều này.
Trilarion

@Trilarion Yea, tôi đã tự tin là không. Tuy nhiên, tôi không biết chắc chắn và tôi không muốn nói như thể tôi đã làm mặc dù điều đó rất khó xảy ra. Đó là một điểm tốt về việc objectđược gọi hai lần. Tôi đã không nghĩ về điều đó. Tôi chỉ muốn đưa ra quan điểm rằng bạn gọi các lớp cha trực tiếp.
Seaux

Thật không may, điều này bị phá vỡ nếu init cố gắng truy cập bất kỳ phương thức riêng tư nào :(
Erik Aronesty

21

Nhìn chung

Giả sử mọi thứ xuất phát từ object(bạn chỉ có một mình nếu không), Python sẽ tính toán thứ tự giải quyết phương thức (MRO) dựa trên cây thừa kế lớp của bạn. MRO thỏa mãn 3 tính chất:

  • Con cái của một lớp học đến trước cha mẹ của họ
  • Cha mẹ trái đến trước cha mẹ phải
  • Một lớp chỉ xuất hiện một lần trong MRO

Nếu không có thứ tự như vậy tồn tại, lỗi Python. Các hoạt động bên trong của điều này là một lớp lót C3 của tổ tiên các lớp. Đọc tất cả về nó ở đây: https://www.python.org/doad/release/2.3/mro/

Vì vậy, trong cả hai ví dụ dưới đây, đó là:

  1. Đứa trẻ
  2. Trái
  3. Đúng
  4. Cha mẹ

Khi một phương thức được gọi, lần xuất hiện đầu tiên của phương thức đó trong MRO là phương thức được gọi. Bất kỳ lớp nào không thực hiện phương thức đó đều bị bỏ qua. Bất kỳ cuộc gọi nào supertrong phương thức đó sẽ gọi lần xuất hiện tiếp theo của phương thức đó trong MRO. Do đó, điều quan trọng là cả thứ tự bạn đặt các lớp trong kế thừa và nơi bạn đặt các cuộc gọi đếnsuper trong các phương thức.

Với superđầu tiên trong mỗi phương pháp

class Parent(object):
    def __init__(self):
        super(Parent, self).__init__()
        print "parent"

class Left(Parent):
    def __init__(self):
        super(Left, self).__init__()
        print "left"

class Right(Parent):
    def __init__(self):
        super(Right, self).__init__()
        print "right"

class Child(Left, Right):
    def __init__(self):
        super(Child, self).__init__()
        print "child"

Child() Đầu ra:

parent
right
left
child

Với supercuối cùng trong mỗi phương pháp

class Parent(object):
    def __init__(self):
        print "parent"
        super(Parent, self).__init__()

class Left(Parent):
    def __init__(self):
        print "left"
        super(Left, self).__init__()

class Right(Parent):
    def __init__(self):
        print "right"
        super(Right, self).__init__()

class Child(Left, Right):
    def __init__(self):
        print "child"
        super(Child, self).__init__()

Child() Đầu ra:

child
left
right
parent

Tôi thấy rằng bạn có thể truy cập Leftbằng cách sử dụng super()từ Child. giả sử tôi muốn truy cập Righttừ bên trong Child. Có cách nào để truy cập Righttừ Childviệc sử dụng siêu? Hay tôi nên gọi trực tiếp Righttừ bên trong super?
alpha_989

4
@ alpha_989 Nếu bạn chỉ muốn truy cập phương thức của một lớp cụ thể, bạn nên tham khảo trực tiếp lớp đó thay vì sử dụng super. Super là về việc tuân theo chuỗi kế thừa, không đi đến một phương thức của một lớp cụ thể.
Zags

1
Cảm ơn vì đã đề cập rõ ràng 'Một lớp chỉ xuất hiện một lần trong MRO'. Điều này đã giải quyết vấn đề của tôi. Bây giờ tôi cuối cùng đã hiểu làm thế nào nhiều kế thừa hoạt động. Ai đó cần phải đề cập đến các thuộc tính của MRO!
Tushar Vazirani

18

Về bình luận của @ calfzhou , bạn có thể sử dụng, như thường lệ,**kwargs :

Ví dụ chạy trực tuyến

class A(object):
  def __init__(self, a, *args, **kwargs):
    print("A", a)

class B(A):
  def __init__(self, b, *args, **kwargs):
    super(B, self).__init__(*args, **kwargs)
    print("B", b)

class A1(A):
  def __init__(self, a1, *args, **kwargs):
    super(A1, self).__init__(*args, **kwargs)
    print("A1", a1)

class B1(A1, B):
  def __init__(self, b1, *args, **kwargs):
    super(B1, self).__init__(*args, **kwargs)
    print("B1", b1)


B1(a1=6, b1=5, b="hello", a=None)

Kết quả:

A None
B hello
A1 6
B1 5

Bạn cũng có thể sử dụng chúng theo vị trí:

B1(5, 6, b="hello", a=None)

nhưng bạn phải nhớ MRO, nó thực sự khó hiểu.

Tôi có thể hơi khó chịu, nhưng tôi nhận thấy rằng mọi người quên mỗi lần sử dụng *args**kwargskhi họ ghi đè lên một phương thức, trong khi đó là một trong số ít cách sử dụng thực sự hữu ích và lành mạnh của các 'biến ma thuật' này.


Wow thật là xấu xí. Thật xấu hổ khi bạn không thể nói siêu lớp cụ thể mà bạn muốn gọi. Tuy nhiên, điều này mang lại cho tôi nhiều động lực hơn để sử dụng thành phần và tránh nhiều sự kế thừa như bệnh dịch hạch.
Tom Busby

15

Một điểm khác chưa được bảo hiểm là truyền tham số để khởi tạo các lớp. Kể từ khi đếnsuper phụ thuộc vào lớp con, cách tốt nhất để truyền tham số là đóng gói tất cả chúng lại với nhau. Sau đó, hãy cẩn thận để không có cùng tên tham số với ý nghĩa khác nhau.

Thí dụ:

class A(object):
    def __init__(self, **kwargs):
        print('A.__init__')
        super().__init__()

class B(A):
    def __init__(self, **kwargs):
        print('B.__init__ {}'.format(kwargs['x']))
        super().__init__(**kwargs)


class C(A):
    def __init__(self, **kwargs):
        print('C.__init__ with {}, {}'.format(kwargs['a'], kwargs['b']))
        super().__init__(**kwargs)


class D(B, C): # MRO=D, B, C, A
    def __init__(self):
        print('D.__init__')
        super().__init__(a=1, b=2, x=3)

print(D.mro())
D()

cho:

[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
D.__init__
B.__init__ 3
C.__init__ with 1, 2
A.__init__

Gọi siêu hạng __init__ trực tiếp để gán tham số trực tiếp nhiều hơn là hấp dẫn nhưng không thành công nếu có bất kỳsuper cuộc gọi trong siêu hạng và / hoặc MRO bị thay đổi và lớp A có thể được gọi nhiều lần, tùy thuộc vào việc triển khai.

Để kết luận: kế thừa hợp tác và các tham số siêu và cụ thể để khởi tạo không hoạt động tốt với nhau.


5
class First(object):
  def __init__(self, a):
    print "first", a
    super(First, self).__init__(20)

class Second(object):
  def __init__(self, a):
    print "second", a
    super(Second, self).__init__()

class Third(First, Second):
  def __init__(self):
    super(Third, self).__init__(10)
    print "that's it"

t = Third()

Đầu ra là

first 10
second 20
that's it

Gọi đến Thứ ba () định vị init được xác định trong Thứ ba. Và gọi siêu trong thói quen đó gọi init được xác định trong First. MRO = [Thứ nhất, thứ hai]. Bây giờ gọi tới super in init được xác định trong First sẽ tiếp tục tìm kiếm MRO và tìm init được xác định trong Second và bất kỳ lệnh gọi super nào cũng sẽ nhấn init đối tượng mặc định . Tôi hy vọng ví dụ này làm rõ khái niệm.

Nếu bạn không gọi siêu từ đầu tiên. Chuỗi dừng lại và bạn sẽ nhận được đầu ra sau.

first 10
that's it

1
đó là bởi vì trong lớp Đầu tiên, bạn gọi là 'in' trước rồi 'siêu'.
đá qi

2
đó là để minh họa cho lệnh gọi
Seraj Ahmad

4

Trong learningpythonthehardway, tôi học được một thứ gọi là super () một hàm dựng sẵn nếu không nhầm. Gọi hàm super () có thể giúp thừa kế đi qua cha mẹ và 'anh chị em' và giúp bạn nhìn rõ hơn. Tôi vẫn là người mới bắt đầu nhưng tôi thích chia sẻ kinh nghiệm của mình về việc sử dụng siêu () này trong python2.7.

Nếu bạn đã đọc qua các bình luận trong trang này, bạn sẽ nghe thấy Thứ tự độ phân giải phương thức (MRO), phương thức là chức năng bạn đã viết, MRO sẽ sử dụng lược đồ Depth-First-Left-Right-Right-Right để tìm và chạy. Bạn có thể làm nhiều nghiên cứu hơn về điều đó.

Bằng cách thêm hàm super ()

super(First, self).__init__() #example for class First.

Bạn có thể kết nối nhiều trường hợp và 'gia đình' với super (), bằng cách thêm vào từng người và mọi người trong đó. Và nó sẽ thực thi các phương thức, đi qua chúng và đảm bảo bạn không bỏ lỡ! Tuy nhiên, việc thêm chúng trước hoặc sau sẽ tạo ra sự khác biệt mà bạn sẽ biết nếu bạn đã thực hiện bài tập learningpythonthehardway 44. Hãy để niềm vui bắt đầu !!

Lấy ví dụ dưới đây, bạn có thể sao chép và dán và thử chạy nó:

class First(object):
    def __init__(self):

        print("first")

class Second(First):
    def __init__(self):
        print("second (before)")
        super(Second, self).__init__()
        print("second (after)")

class Third(First):
    def __init__(self):
        print("third (before)")
        super(Third, self).__init__()
        print("third (after)")


class Fourth(First):
    def __init__(self):
        print("fourth (before)")
        super(Fourth, self).__init__()
        print("fourth (after)")


class Fifth(Second, Third, Fourth):
    def __init__(self):
        print("fifth (before)")
        super(Fifth, self).__init__()
        print("fifth (after)")

Fifth()

Làm thế nào để nó chạy? Ví dụ của five () sẽ diễn ra như thế này. Mỗi bước đi từ lớp này sang lớp khác, nơi siêu chức năng được thêm vào.

1.) print("fifth (before)")
2.) super()>[Second, Third, Fourth] (Left to right)
3.) print("second (before)")
4.) super()> First (First is the Parent which inherit from object)

Cha mẹ đã được tìm thấy và nó sẽ tiếp tục đến Thứ ba và Thứ tư !!

5.) print("third (before)")
6.) super()> First (Parent class)
7.) print ("Fourth (before)")
8.) super()> First (Parent class)

Bây giờ tất cả các lớp với super () đã được truy cập! Lớp cha đã được tìm thấy và được thực thi và bây giờ nó tiếp tục bỏ hộp chức năng trong các kế thừa để hoàn thành các mã.

9.) print("first") (Parent)
10.) print ("Fourth (after)") (Class Fourth un-box)
11.) print("third (after)") (Class Third un-box)
12.) print("second (after)") (Class Second un-box)
13.) print("fifth (after)") (Class Fifth un-box)
14.) Fifth() executed

Kết quả của chương trình trên:

fifth (before)
second (before
third (before)
fourth (before)
first
fourth (after)
third (after)
second (after)
fifth (after)

Đối với tôi bằng cách thêm super () cho phép tôi thấy rõ hơn về cách python sẽ thực thi mã hóa của tôi và đảm bảo tính kế thừa có thể truy cập vào phương thức tôi dự định.


Cảm ơn bản demo chi tiết!
Tushar Vazirani

3

Tôi muốn thêm vào những gì @Vutionscaper nói ở đầu:

Third --> First --> object --> Second --> object

Trong trường hợp này, trình thông dịch không lọc ra lớp đối tượng vì nó trùng lặp, thay vì bởi vì Thứ hai xuất hiện ở vị trí đầu và không xuất hiện ở vị trí đuôi trong tập hợp con phân cấp. Trong khi đối tượng chỉ xuất hiện ở vị trí đuôi và không được coi là vị trí mạnh trong thuật toán C3 để xác định mức độ ưu tiên.

Tuyến tính hóa (mro) của một lớp C, L (C), là

  • lớp C
  • cộng với sự hợp nhất của
    • tuyến tính hóa cha mẹ của nó P1, P2, .. = L (P1, P2, ...) và
    • danh sách cha mẹ của nó P1, P2, ..

Hợp nhất tuyến tính được thực hiện bằng cách chọn các lớp phổ biến xuất hiện dưới dạng phần đầu của danh sách chứ không phải phần đuôi vì vấn đề thứ tự (sẽ trở nên rõ ràng bên dưới)

Việc tuyến tính hóa của Thứ ba có thể được tính như sau:

    L(O)  := [O]  // the linearization(mro) of O(object), because O has no parents

    L(First)  :=  [First] + merge(L(O), [O])
               =  [First] + merge([O], [O])
               =  [First, O]

    // Similarly, 
    L(Second)  := [Second, O]

    L(Third)   := [Third] + merge(L(First), L(Second), [First, Second])
                = [Third] + merge([First, O], [Second, O], [First, Second])
// class First is a good candidate for the first merge step, because it only appears as the head of the first and last lists
// class O is not a good candidate for the next merge step, because it also appears in the tails of list 1 and 2, 
                = [Third, First] + merge([O], [Second, O], [Second])
// class Second is a good candidate for the second merge step, because it appears as the head of the list 2 and 3
                = [Third, First, Second] + merge([O], [O])            
                = [Third, First, Second, O]

Do đó, để thực hiện super () trong đoạn mã sau:

class First(object):
  def __init__(self):
    super(First, self).__init__()
    print "first"

class Second(object):
  def __init__(self):
    super(Second, self).__init__()
    print "second"

class Third(First, Second):
  def __init__(self):
    super(Third, self).__init__()
    print "that's it"

rõ ràng là phương pháp này sẽ được giải quyết như thế nào

Third.__init__() ---> First.__init__() ---> Second.__init__() ---> 
Object.__init__() ---> returns ---> Second.__init__() -
prints "second" - returns ---> First.__init__() -
prints "first" - returns ---> Third.__init__() - prints "that's it"

"đúng hơn là bởi vì Thứ hai xuất hiện ở vị trí đầu và không xuất hiện ở vị trí đuôi trong tập hợp phân cấp." Không rõ vị trí đầu hoặc đuôi là gì, cũng không phải là tập hợp con phân cấp là gì hoặc tập hợp con nào bạn đang đề cập đến.
OrangeSherbet

Vị trí đuôi đề cập đến các lớp cao hơn trong hệ thống phân cấp lớp và ngược lại. Lớp 'đối tượng' của lớp cơ sở nằm ở cuối đuôi. Chìa khóa để hiểu thuật toán mro là cách 'Thứ hai' xuất hiện dưới dạng siêu của 'Thứ nhất'. Chúng ta thường cho rằng nó là lớp 'đối tượng'. Đó là sự thật, nhưng, chỉ trong viễn cảnh của lớp 'Đầu tiên'. Tuy nhiên, khi được nhìn từ góc độ lớp 'Thứ ba', thứ tự phân cấp cho 'Đầu tiên' là khác nhau và được tính như hiển thị ở trên. Thuật toán mro cố gắng tạo phối cảnh này (hoặc tập hợp con phân cấp) cho tất cả nhiều lớp được kế thừa
supi

3

Trong python 3.5+ thừa kế có vẻ dễ đoán và rất đẹp đối với tôi. Xin vui lòng xem mã này:

class Base(object):
  def foo(self):
    print("    Base(): entering")
    print("    Base(): exiting")


class First(Base):
  def foo(self):
    print("   First(): entering Will call Second now")
    super().foo()
    print("   First(): exiting")


class Second(Base):
  def foo(self):
    print("  Second(): entering")
    super().foo()
    print("  Second(): exiting")


class Third(First, Second):
  def foo(self):
    print(" Third(): entering")
    super().foo()
    print(" Third(): exiting")


class Fourth(Third):
  def foo(self):
    print("Fourth(): entering")
    super().foo()
    print("Fourth(): exiting")

Fourth().foo()
print(Fourth.__mro__)

Đầu ra:

Fourth(): entering
 Third(): entering
   First(): entering Will call Second now
  Second(): entering
    Base(): entering
    Base(): exiting
  Second(): exiting
   First(): exiting
 Third(): exiting
Fourth(): exiting
(<class '__main__.Fourth'>, <class '__main__.Third'>, <class '__main__.First'>, <class '__main__.Second'>, <class '__main__.Base'>, <class 'object'>)

Như bạn có thể thấy, nó gọi foo chính xác MỘT lần cho mỗi chuỗi được kế thừa theo cùng thứ tự như nó được kế thừa. Bạn có thể nhận được đơn đặt hàng đó bằng cách gọi . mro :

Thứ tư -> Thứ ba -> Thứ nhất -> Thứ hai -> Cơ sở -> đối tượng


2

Có lẽ vẫn còn một cái gì đó có thể được thêm vào, một ví dụ nhỏ với Django rest_framework và trang trí. Điều này cung cấp một câu trả lời cho câu hỏi ngầm: "tại sao tôi lại muốn điều này?"

Như đã nói: chúng tôi với Django rest_framework và chúng tôi đang sử dụng các chế độ xem chung và đối với từng loại đối tượng trong cơ sở dữ liệu của chúng tôi, chúng tôi thấy mình có một lớp xem cung cấp GET và POST cho danh sách các đối tượng và một lớp xem khác cung cấp GET , PUT và XÓA cho các đối tượng riêng lẻ.

Bây giờ, POST, PUT và DELETE chúng tôi muốn trang trí với login_Vquired của Django. Lưu ý cách điều này chạm vào cả hai lớp, nhưng không phải tất cả các phương thức trong cả hai lớp.

Một giải pháp có thể đi qua nhiều kế thừa.

from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required

class LoginToPost:
    @method_decorator(login_required)
    def post(self, arg, *args, **kwargs):
        super().post(arg, *args, **kwargs)

Tương tự như vậy đối với các phương pháp khác.

Trong danh sách thừa kế của các lớp cụ thể của tôi, tôi sẽ thêm LoginToPosttrước ListCreateAPIViewLoginToPutOrDeletetrước RetrieveUpdateDestroyAPIView. Các lớp học cụ thể của tôi getsẽ không được trang trí.


1

Đăng câu trả lời này để giới thiệu trong tương lai của tôi.

Python Nhiều kế thừa nên sử dụng mô hình kim cương và chữ ký hàm không nên thay đổi trong mô hình.

    A
   / \
  B   C
   \ /
    D

Đoạn mã mẫu sẽ là; -

class A:
    def __init__(self, name=None):
        #  this is the head of the diamond, no need to call super() here
        self.name = name

class B(A):
    def __init__(self, param1='hello', **kwargs):
        super().__init__(**kwargs)
        self.param1 = param1

class C(A):
    def __init__(self, param2='bye', **kwargs):
        super().__init__(**kwargs)
        self.param2 = param2

class D(B, C):
    def __init__(self, works='fine', **kwargs):
        super().__init__(**kwargs)
        print(f"{works=}, {self.param1=}, {self.param2=}, {self.name=}")

d = D(name='Testing')

Đây là lớp A object


1
Anên cũng được gọi __init__. Ađã không "phát minh" ra phương thức __init__, vì vậy nó không thể cho rằng một số lớp khác có thể Asớm hơn trong MRO của nó. Lớp duy nhất có __init__phương thức không (và không nên) gọi super().__init__object.
chepner

Vâng. Đó là lý do tại sao tôi đã viết A là objectCó lẽ tôi nghĩ, class A (object) : thay vào đó tôi nên viết
Akhil Nadh PC

Akhông thể objectnếu bạn đang thêm một tham số cho nó __init__.
chepner
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.