Tại sao ma thuật siêu () của Python 3.x?


159

Trong Python 3.x, super()có thể được gọi mà không cần đối số:

class A(object):
    def x(self):
         print("Hey now")

class B(A):
    def x(self):
        super().x()
>>> B().x()
Hey now

Để thực hiện công việc này, một số phép thuật thời gian biên dịch được thực hiện, một hậu quả của nó là đoạn mã sau (được bật superlại super_) không thành công:

super_ = super

class A(object):
    def x(self):
        print("No flipping")

class B(A):
    def x(self):
        super_().x()
>>> B().x()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in x
RuntimeError: super(): __class__ cell not found

Tại sao lại là super() không thể giải quyết siêu lớp trong thời gian chạy mà không có sự trợ giúp từ trình biên dịch? Có những tình huống thực tế trong đó hành vi này, hoặc lý do cơ bản của nó, có thể cắn một lập trình viên không thận trọng?

... Và, như một câu hỏi phụ: có bất kỳ ví dụ nào khác trong Python về các hàm, phương thức, v.v. có thể bị phá vỡ bằng cách đặt lại chúng sang một tên khác không?


6
Tôi sẽ cho Armin làm giải thích về vấn đề này một . Đây cũng là một bài viết
Trò chơi Brainiac

Câu trả lời:


216

super()Hành vi ma thuật mới đã được thêm vào để tránh vi phạm nguyên tắc DRY (Đừng lặp lại chính mình), xem PEP 3135 . Phải đặt tên một cách rõ ràng cho lớp bằng cách gọi nó là toàn cầu cũng dễ xảy ra các vấn đề phản hồi giống như bạn đã phát hiện ra với super()chính nó:

class Foo(Bar):
    def baz(self):
        return super(Foo, self).baz() + 42

Spam = Foo
Foo = something_else()

Spam().baz()  # liable to blow up

Điều tương tự cũng áp dụng cho việc sử dụng các trình trang trí lớp trong đó trình trang trí trả về một đối tượng mới, trong đó đặt lại tên lớp:

@class_decorator_returning_new_class
class Foo(Bar):
    def baz(self):
        # Now `Foo` is a *different class*
        return super(Foo, self).baz() + 42

super() __class__Tế bào ma thuật vượt qua những vấn đề này một cách độc đáo bằng cách cho phép bạn truy cập vào đối tượng lớp ban đầu.

PEP đã được khởi động bởi Guido, người ban đầu hình dung supertrở thành một từ khóa và ý tưởng sử dụng một tế bào để tra cứu lớp hiện tại cũng là của anh ấy . Chắc chắn, ý tưởng biến nó thành một từ khóa là một phần của dự thảo đầu tiên của PEP .

Tuy nhiên, trên thực tế, chính Guido, người sau đó đã từ bỏ ý tưởng từ khóa là 'quá kỳ diệu' , đề xuất việc triển khai hiện tại thay thế. Ông dự đoán rằng việc sử dụng một tên khác super()có thể là một vấn đề :

Bản vá của tôi sử dụng một giải pháp trung gian: nó giả sử bạn cần __class__ bất cứ khi nào bạn sử dụng một biến có tên 'super'. Do đó, nếu bạn (toàn cầu) đổi tên superthành suppervà sử dụng suppernhưng không super, nó sẽ không hoạt động mà không có đối số (nhưng nó vẫn hoạt động nếu bạn vượt qua nó __class__hoặc đối tượng lớp thực tế); nếu bạn có một biến không liên quan được đặt tên super, mọi thứ sẽ hoạt động nhưng phương thức sẽ sử dụng đường dẫn cuộc gọi chậm hơn một chút được sử dụng cho các biến di động.

Vì vậy, cuối cùng, chính Guido đã tuyên bố rằng sử dụng supertừ khóa không cảm thấy đúng và việc cung cấp một __class__tế bào ma thuật là một sự thỏa hiệp chấp nhận được.

Tôi đồng ý rằng sự kỳ diệu, hành vi ngầm của việc thực hiện có phần đáng ngạc nhiên, nhưng super()là một trong những chức năng được áp dụng sai nhất trong ngôn ngữ. Chỉ cần nhìn vào tất cả các ứng dụng sai super(type(self), self)hoặc super(self.__class__, self) tìm thấy trên Internet; nếu bất kỳ mã nào được gọi từ một lớp dẫn xuất, bạn sẽ kết thúc bằng một ngoại lệ đệ quy vô hạn . Ít nhất là super()cuộc gọi được đơn giản hóa , không có đối số, sẽ tránh được vấn đề đó .

Đối với việc đổi tên super_; chỉ cần tham khảo __class__trong phương pháp của bạn cũng và nó sẽ hoạt động trở lại. Ô được tạo nếu bạn tham chiếu tên super hoặc __class__ tên trong phương thức của mình:

>>> super_ = super
>>> class A(object):
...     def x(self):
...         print("No flipping")
... 
>>> class B(A):
...     def x(self):
...         __class__  # just referencing it is enough
...         super_().x()
... 
>>> B().x()
No flipping

1
Tốt viết lên. Nó vẫn rõ ràng như bùn. Bạn đang nói rằng super () tương đương với một hàm tự động khởi tạo giống như def super(of_class=magic __class__)kiểu như self.super(); def super(self): return self.__class__?
Charles Merriam

17
@CharlesMerriam: Bài đăng này không phải là về cách thức super()hoạt động mà không có đối số; nó chủ yếu đề cập đến lý do tại sao nó tồn tại. super(), trong một phương thức lớp, tương đương với super(ReferenceToClassMethodIsBeingDefinedIn, self), ReferenceToClassMethodIsBeingDefinedInđược xác định tại thời điểm biên dịch, được đính kèm với phương thức dưới dạng một bao đóng có tên __class__super()sẽ tra cứu cả hai từ khung gọi trong thời gian chạy. Nhưng bạn không thực sự cần phải biết tất cả điều này.
Martijn Pieters

1
@CharlesMerriam: nhưng super()không ở đâu gần với chức năng tự động khởi tạo , không.
Martijn Pieters

1
@ chris.leonard: câu quan trọng là Ô được tạo nếu bạn sử dụng super () hoặc sử dụng __class__trong phương thức của bạn . Bạn đã sử dụng tên supertrong chức năng của bạn. Trình biên dịch thấy điều đó và thêm phần __class__đóng cửa.
Martijn Pieters

4
@Alexey: vẫn chưa đủ. type(self)đưa ra kiểu hiện tại , không giống với kiểu mà phương thức được định nghĩa trên. Vì vậy, một lớp học Foovới phương pháp baznhu cầu super(Foo, self).baz(), bởi vì nó có thể được subclassed như class Ham(Foo):, tại thời điểm đó type(self)Hamsuper(type(self), self).baz()sẽ cung cấp cho bạn một vòng lặp vô hạn. Xem bài đăng tôi liên kết đến trong câu trả lời của mình: Khi gọi super () trong một lớp dẫn xuất, tôi có thể tự vượt qua .__ class__ không?
Martijn Pieters
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.