Câu hỏi Hiểu về Python super () với __init __ () phương thức [duplicate]


Câu hỏi này đã có câu trả lời ở đây:

Tôi đang cố gắng hiểu việc sử dụng super(). Từ vẻ ngoài của nó, cả hai lớp con đều có thể được tạo ra, tốt thôi.

Tôi tò mò muốn biết về sự khác biệt thực sự giữa 2 lớp học trẻ em sau đây.

class Base(object):
    def __init__(self):
        print "Base created"

class ChildA(Base):
    def __init__(self):
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        super(ChildB, self).__init__()

ChildA() 
ChildB()

1982
2018-02-23 00:30


gốc




Các câu trả lời:


super() cho phép bạn tránh tham chiếu đến lớp cơ sở một cách rõ ràng, điều này có thể tốt đẹp. Nhưng lợi thế chính đi kèm với nhiều thừa kế, nơi tất cả các loại công cụ thú vị có thể xảy ra. Xem tài liệu chuẩn trên siêu nếu bạn chưa có.

Lưu ý rằng cú pháp thay đổi trong Python 3.0: bạn có thể nói super().__init__() thay vì super(ChildB, self).__init__() IMO nào đẹp hơn một chút.


1427
2018-02-23 00:37



Bạn có thể cung cấp một ví dụ về super() được sử dụng với các đối số? - Steven Vascellaro
Bạn có thể giải thích dùm không super(ChildB, self).__init__() cái này, cái gì ChildB và self phải làm gì với siêu - rimiro


tôi đang cố để hiểu super()

Lý do chúng tôi sử dụng super để các lớp con có thể sử dụng hợp tác đa thừa kế sẽ gọi đúng hàm lớp cha kế tiếp theo trong thứ tự phương thức phân giải (MRO).

Trong Python 3, chúng ta có thể gọi nó như sau:

class ChildB(Base):
    def __init__(self):
        super().__init__() 

Trong Python 2, chúng ta bắt buộc phải sử dụng nó như sau:

        super(ChildB, self).__init__()

Nếu không có siêu, bạn bị giới hạn trong khả năng sử dụng đa thừa kế của mình:

        Base.__init__(self) # Avoid this.

Tôi giải thích thêm bên dưới.

"Có gì khác biệt trong mã này ?:"

class ChildA(Base):
    def __init__(self):
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        super(ChildB, self).__init__()
        # super().__init__() # you can call super like this in Python 3!

Sự khác biệt chính trong mã này là bạn nhận được một lớp hướng dẫn trong __init__ với super, sử dụng lớp hiện tại để xác định lớp tiếp theo __init__ để tra cứu trong MRO.

Tôi minh họa sự khác biệt này trong câu trả lời ở câu hỏi kinh điển, Làm thế nào để sử dụng 'siêu' trong Python?, minh họa tiêm phụ thuộc và hợp tác đa thừa kế.

Nếu Python không có super

Đây là mã thực sự tương đương với super (cách nó được triển khai trong C, trừ đi một số hành vi kiểm tra và dự phòng, và được dịch sang Python):

class ChildB(Base):
    def __init__(self):
        mro = type(self).mro()             # Get the Method Resolution Order.
        check_next = mro.index(ChildB) + 1 # Start looking after *this* class.
        while check_next < len(mro):
            next_class = mro[check_next]
            if '__init__' in next_class.__dict__:
                next_class.__init__(self)
                break
            check_next += 1

Viết một chút giống như Python bản địa:

class ChildB(Base):
    def __init__(self):
        mro = type(self).mro()
        for next_class in mro[mro.index(ChildB) + 1:]: # slice to end
            if hasattr(next_class, '__init__'):
                next_class.__init__(self)
                break

Nếu chúng ta không có super đối tượng, chúng ta sẽ phải viết mã thủ công này ở mọi nơi (hoặc tạo lại nó!) để đảm bảo rằng chúng ta gọi phương thức tiếp theo thích hợp trong Lệnh phân giải phương thức!

Làm thế nào siêu làm điều này trong Python 3 mà không được nói một cách rõ ràng mà lớp và dụ từ phương pháp nó được gọi là từ?

Nó nhận được khung stack gọi và tìm lớp (được lưu trữ ngầm định dưới dạng biến cục bộ miễn phí, __class__, làm cho chức năng gọi là một đóng cửa trên lớp) và đối số đầu tiên cho hàm đó, cần phải là cá thể hoặc lớp mà thông báo cho nó thứ tự phân giải phương thức (MRO) nào để sử dụng.

Vì nó đòi hỏi lập luận đầu tiên cho MRO, sử dụng super với phương pháp tĩnh là không thể.

Chỉ trích các câu trả lời khác:

super () cho phép bạn tránh tham chiếu đến lớp cơ sở một cách rõ ràng, có thể tốt đẹp. . Nhưng lợi thế chính đi kèm với nhiều thừa kế, nơi mà tất cả các loại công cụ thú vị có thể xảy ra. Xem tài liệu chuẩn trên siêu nếu bạn chưa có.

Nó khá vẫy tay và không cho chúng ta biết nhiều, nhưng quan điểm của super không phải là tránh viết lớp cha. Vấn đề là để đảm bảo rằng phương thức tiếp theo phù hợp với thứ tự độ phân giải phương thức (MRO) được gọi. Điều này trở nên quan trọng trong nhiều thừa kế.

Tôi sẽ giải thích ở đây.

class Base(object):
    def __init__(self):
        print("Base init'ed")

class ChildA(Base):
    def __init__(self):
        print("ChildA init'ed")
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        print("ChildB init'ed")
        super(ChildB, self).__init__()

Và chúng ta hãy tạo ra một sự phụ thuộc mà chúng ta muốn được gọi sau Child:

class UserDependency(Base):
    def __init__(self):
        print("UserDependency init'ed")
        super(UserDependency, self).__init__()

Bây giờ hãy nhớ, ChildB sử dụng siêu, ChildA không làm:

class UserA(ChildA, UserDependency):
    def __init__(self):
        print("UserA init'ed")
        super(UserA, self).__init__()

class UserB(ChildB, UserDependency):
    def __init__(self):
        print("UserB init'ed")
        super(UserB, self).__init__()

UserA không gọi phương thức UserDependency:

>>> UserA()
UserA init'ed
ChildA init'ed
Base init'ed
<__main__.UserA object at 0x0000000003403BA8>

Nhưng UserB, bởi vì ChildB sử dụng super, làm!:

>>> UserB()
UserB init'ed
ChildB init'ed
UserDependency init'ed
Base init'ed
<__main__.UserB object at 0x0000000003403438>

Phê bình cho câu trả lời khác

Trong mọi trường hợp, bạn nên làm như sau, một câu trả lời khác gợi ý, vì bạn chắc chắn sẽ gặp lỗi khi bạn phân lớp ChildB:

        super(self.__class__, self).__init__() # Don't do this. Ever.

(Câu trả lời đó không thông minh hoặc đặc biệt thú vị, nhưng bất chấp những lời chỉ trích trực tiếp trong các bình luận và hơn 17 downvotes, người trả lời vẫn kiên trì trong việc đề xuất nó cho đến khi một biên tập viên sửa chữa vấn đề của anh ta.)

Giải thích: Câu trả lời gợi ý gọi siêu như thế này:

super(self.__class__, self).__init__()

Đây là hoàn toàn sai rồi. super cho phép chúng tôi tìm kiếm phụ huynh kế tiếp trong MRO (xem phần đầu tiên của câu trả lời này) cho các lớp con. Nêu bạn noi super chúng ta đang trong phương thức của cá thể con, nó sẽ tra cứu phương thức tiếp theo trong dòng (có lẽ là phương thức này) dẫn đến đệ quy, có thể gây ra lỗi hợp lý (trong ví dụ của người trả lời, nó có) hoặc RuntimeError khi độ sâu đệ quy bị vượt quá.

>>> class Polygon(object):
...     def __init__(self, id):
...         self.id = id
...
>>> class Rectangle(Polygon):
...     def __init__(self, id, width, height):
...         super(self.__class__, self).__init__(id)
...         self.shape = (width, height)
...
>>> class Square(Rectangle):
...     pass
...
>>> Square('a', 10, 10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __init__
TypeError: __init__() missing 2 required positional arguments: 'width' and 'height'

413
2017-11-25 19:00



Tôi vẫn cần phải làm việc quanh đầu super() chức năng, tuy nhiên, câu trả lời này rõ ràng là tốt nhất về độ sâu và chi tiết. Tôi cũng đánh giá rất cao những lời chỉ trích bên trong câu trả lời. Nó cũng giúp hiểu rõ hơn khái niệm bằng cách xác định các cạm bẫy trong các câu trả lời khác. Cảm ơn bạn ! - Yohan Obadia
@Aaron Hall, cảm ơn thông tin chi tiết. Tôi nghĩ rằng nên có thêm một tùy chọn có sẵn (ít nhất) để cố vấn để gọi một số câu trả lời là không phù hợp hoặc không đầy đủ nếu họ không cung cấp đủ thông tin chính xác. - hunch
Cảm ơn, điều này rất hữu ích. Những lời chỉ trích của người nghèo / sử dụng không đúng cách rất minh họa tại sao, và cách sử dụng siêu - Xarses
Lời giải thích rất tốt về việc thừa kế Python. Cảm ơn - ioaniatr


Nó được lưu ý rằng trong Python 3.0+ bạn có thể sử dụng

super().__init__() 

để thực hiện cuộc gọi của bạn, ngắn gọn và không yêu cầu bạn tham chiếu tên phụ huynh hoặc tên lớp một cách rõ ràng, điều này có thể hữu ích. Tôi chỉ muốn thêm điều đó cho Python 2.7 trở xuống, có thể nhận được hành vi không có tên này bằng cách viết self.__class__ thay vì tên lớp, tức là

super(self.__class__, self).__init__()

BAO GIỜ, điều này phá vỡ các cuộc gọi đến super cho bất kỳ lớp học nào thừa hưởng từ lớp học của bạn, ở đâu self.__class__ có thể trả lại một lớp con. Ví dụ:

class Polygon(object):
    def __init__(self, id):
        self.id = id

class Rectangle(Polygon):
    def __init__(self, id, width, height):
        super(self.__class__, self).__init__(id)
        self.shape = (width, height)

class Square(Rectangle):
    pass

Ở đây tôi có một lớp Square, là một phân lớp của Rectangle. Nói rằng tôi không muốn viết một hàm tạo riêng cho Square bởi vì hàm tạo cho Rectangle là đủ tốt, nhưng vì lý do gì tôi muốn thực hiện một Square để tôi có thể thực hiện lại một số phương pháp khác.

Khi tôi tạo một Square sử dụng mSquare = Square('a', 10,10), Python gọi hàm khởi tạo cho Rectangle bởi vì tôi đã không cho Square nhà xây dựng riêng của nó. Tuy nhiên, trong hàm tạo cho Rectangle, cuộc gọi super(self.__class__,self) sẽ trả lại siêu lớp mSquare, do đó, nó gọi hàm tạo cho Rectangle lần nữa. Đây là cách vòng lặp vô hạn xảy ra, như đã được đề cập bởi @S_C. Trong trường hợp này, khi tôi chạy super(...).__init__() Tôi đang gọi nhà xây dựng cho Rectangle nhưng kể từ khi tôi cho nó không có đối số, tôi sẽ nhận được một lỗi.


223
2017-10-08 20:08



Câu trả lời này gợi ý gì, super(self.__class__, self).__init__() không hoạt động nếu bạn phân lớp lại mà không cung cấp mới __init__. Thế thì bạn có một đệ quy vô hạn. - glglgl
Câu trả lời này là vô lý. Nếu bạn đang đi để lạm dụng siêu theo cách này, bạn cũng có thể chỉ cần hardcode tên lớp cơ sở. Nó là ít sai lầm hơn này. Toàn bộ điểm của lập luận đầu tiên của siêu là nó không phải nhất thiết là kiểu tự. Xin vui lòng đọc "siêu được coi là siêu" bởi rhettinger (hoặc xem một số video của mình). - Veky
Các phím tắt được trình bày ở đây cho Python 2 có những cạm bẫy đã được đề cập rồi. Không sử dụng điều này, hoặc mã của bạn sẽ phá vỡ theo cách bạn không thể dự đoán. Điều này "phím tắt tiện dụng" phá vỡ siêu, nhưng bạn có thể không nhận ra nó cho đến khi bạn đã chìm rất nhiều thời gian vào gỡ lỗi. Sử dụng Python 3 nếu siêu quá dài dòng. - Ryan Hiebert
Đã chỉnh sửa câu trả lời. Xin lỗi nếu chỉnh sửa đó thay đổi ý nghĩa 180 độ, nhưng bây giờ câu trả lời này sẽ làm cho một số ý nghĩa. - Tino
Điều gì làm cho không có ý nghĩa là để nói với ai đó họ có thể làm một cái gì đó được trivially chứng minh là không chính xác. Bạn có thể bí danh echo đến python. Không ai có thể gợi ý điều đó! - Aaron Hall♦


Siêu không có tác dụng phụ

Base = ChildB

Base()

hoạt động như mong đợi

Base = ChildA

Base()

được vào đệ quy vô hạn.


75
2017-11-27 23:26



Các super gọi chính nó không có tác dụng phụ, nó chỉ tạo ra một đối tượng proxy. Tôi không thực sự chắc chắn câu trả lời này đang cố gắng nói về super so với lớp cha mẹ trực tiếp. - davidism
Tuyên bố, "Siêu không có tác dụng phụ", không có ý nghĩa trong bối cảnh này. Siêu đơn giản đảm bảo chúng ta gọi đúng phương thức của lớp tiếp theo theo thứ tự độ phân giải phương pháp, trong khi cách khác mã hóa cứng phương thức tiếp theo được gọi, điều này khiến cho việc thừa kế nhiều hợp tác trở nên khó khăn hơn. - Aaron Hall♦


Chỉ cần một người đứng đầu ... với Python 2.7, và tôi tin rằng kể từ đó super() đã được giới thiệu trong phiên bản 2.2, bạn chỉ có thể gọi super()nếu một trong các bậc cha mẹ kế thừa từ một lớp mà cuối cùng thừa hưởng object (lớp học kiểu mới).

Cá nhân, như đối với mã python 2.7, tôi sẽ tiếp tục sử dụng BaseClassName.__init__(self, args) cho đến khi tôi thực sự có được lợi thế khi sử dụng super().


68
2018-05-25 17:52



rất tốt. NẾU bạn không đề cập rõ ràng: lớp cơ sở (đối tượng): sau đó bạn sẽ nhận được lỗi như thế: "TypeError: phải là loại, không classobj" - andilabs
@andi tôi nhận được lỗi đó vào ngày khác và cuối cùng tôi đã từ bỏ việc cố gắng tìm ra nó. Tôi đã chỉ rối tung trên iPython. Thật là một cơn ác mộng đáng sợ của một thông báo lỗi xấu nếu đó thực sự là mã tôi phải gỡ lỗi! - Two-Bit Alchemist


Không có, thực sự. super() nhìn vào lớp tiếp theo trong MRO (thứ tự độ phân giải phương thức, được truy cập với cls.__mro__) để gọi các phương thức. Chỉ cần gọi cơ sở __init__ gọi cơ sở __init__. Khi nó xảy ra, MRO có chính xác một mục - cơ sở. Vì vậy, bạn đang thực sự làm điều tương tự, nhưng theo một cách đẹp hơn với super() (đặc biệt nếu bạn nhận được nhiều thừa kế sau này).


46
2018-02-23 00:34



Tôi hiểu rồi. Bạn có thể giải thích một chút về lý do tại sao nó đẹp hơn để sử dụng siêu () với nhiều thừa kế? Với tôi, căn cứ .__ init __ (self) ngắn hơn (sạch hơn). Nếu tôi có hai baseclasses, nó sẽ là hai trong số những dòng đó, hoặc hai dòng siêu (). Hay tôi hiểu lầm ý của bạn là "đẹp hơn"? - Mizipzor
Trên thực tế, nó sẽ là một dòng siêu (). Khi bạn có nhiều thừa kế, MRO vẫn bằng phẳng. Vì vậy, cuộc gọi super () .__ init__ đầu tiên gọi các lớp tiếp theo trong đó, sau đó gọi cuộc gọi tiếp theo, v.v. Bạn thực sự nên kiểm tra một số tài liệu trên đó. - Devin Jeanpierre
Lớp con MRO cũng chứa đối tượng - MRO của lớp được hiển thị trong mro biến lớp. - James Brady
Cũng lưu ý rằng các lớp kinh điển (trước 2.2) không hỗ trợ siêu - bạn phải tham chiếu rõ ràng đến các lớp cơ sở. - James Brady
"Lớp con MRO cũng chứa đối tượng - MRO của lớp được hiển thị trong mro biến lớp. "Đó là một oops lớn. Rất tiếc. - Devin Jeanpierre


Sự khác biệt chính là ChildA.__init__ sẽ gọi vô điều kiện Base.__init__ trong khi ChildB.__init__ sẽ gọi __init__ trong bất cứ lớp nào xảy ra ChildB tổ tiên trong selfdòng của tổ tiên (có thể khác với những gì bạn mong đợi).

Nếu bạn thêm ClassC sử dụng nhiều thừa kế:

class Mixin(Base):
  def __init__(self):
    print "Mixin stuff"
    super(Mixin, self).__init__()

class ChildC(ChildB, Mixin):  # Mixin is now between ChildB and Base
  pass

ChildC()
help(ChildC) # shows that the the Method Resolution Order is ChildC->ChildB->Mixin->Base

sau đó Base không còn là phụ huynh của ChildB cho ChildC trường hợp. Hiện nay super(ChildB, self) sẽ trỏ đến Mixin nếu self là một ChildC ví dụ.

Bạn đã chèn Mixin ở giữa ChildB và Base. Và bạn có thể tận dụng nó với super()

Vì vậy, nếu bạn được thiết kế các lớp học của bạn để chúng có thể được sử dụng trong một kịch bản hợp tác đa thừa kế, bạn sử dụng super bởi vì bạn không thực sự biết ai sẽ là tổ tiên lúc chạy.

Các siêu được coi là siêu bài và pycon 2015 video đi kèm giải thích điều này khá tốt.


22
2017-09-21 06:41



Điều này. Nghĩa của super(ChildB, self) thay đổi tùy thuộc vào MRO của đối tượng được tham chiếu bởi self, mà không thể được biết cho đến khi thời gian chạy. Nói cách khác, tác giả của ChildB không có cách nào để biết super() sẽ giải quyết trong mọi trường hợp trừ khi họ có thể đảm bảo rằng ChildB sẽ không bao giờ được phân lớp. - nispio