Sử dụng super với một phương thức lớp


82

Tôi đang cố gắng học hàm super () trong Python.

Tôi nghĩ rằng tôi đã nắm được nó cho đến khi tôi xem qua ví dụ này (2.6) và thấy mình bị mắc kẹt.

http://www.cafepy.com/article/python_attributes_and_methods/python_attributes_and_methods.html#super-with-classmethod-example

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "test.py", line 9, in do_something
    do_something = classmethod(do_something)
TypeError: unbound method do_something() must be called with B instance as first argument (got nothing instead)
>>>

Đó không phải là điều tôi mong đợi khi đọc dòng này ngay trước ví dụ:

Nếu chúng ta đang sử dụng một phương thức lớp, chúng ta không có một thể hiện để gọi super với. May mắn thay cho chúng ta, siêu hoạt động ngay cả với một kiểu là đối số thứ hai. --- Loại có thể chuyển thẳng lên super như hình bên dưới.

Đó là chính xác những gì Python nói với tôi là không thể bằng cách nói rằng do_something () nên được gọi với một phiên bản B.


Câu trả lời:


98

Đôi khi văn bản phải được đọc nhiều hơn để tìm ra hương vị của ý tưởng hơn là để biết chi tiết. Đây là một trong những trường hợp đó.

Trong trang được liên kết , các Ví dụ 2.5, 2.6 và 2.7 đều phải sử dụng một phương pháp , do_your_stuff. (Đó là, do_somethingnên được đổi thành do_your_stuff.)

Ngoài ra, như Ned Deily đã chỉ ra , A.do_your_stuffphải là một phương thức lớp.

class A(object):
    @classmethod
    def do_your_stuff(cls):
        print 'This is A'

class B(A):
    @classmethod
    def do_your_stuff(cls):
        super(B, cls).do_your_stuff()

B.do_your_stuff()

super(B, cls).do_your_stuff trả về một phương thức liên kết (xem chú thích 2 ). Vì clsđã được truyền làm đối số thứ hai super(), nó clsđược liên kết với phương thức được trả về. Nói cách khác, clsđược chuyển làm đối số đầu tiên cho phương thức do_your_stuff()của lớp A.

Để nhắc lại: phương thức của super(B, cls).do_your_stuff()nguyên nhân được gọi với truyền là đối số đầu tiên. Để điều đó hoạt động, 's phải là một phương thức lớp. Trang được liên kết không đề cập đến điều đó, nhưng đó chắc chắn là trường hợp.Ado_your_stuffclsAdo_your_stuff

Tái bút. do_something = classmethod(do_something)là cách cũ để thực hiện một phương pháp phân loại. Cách mới (er) là sử dụng trình trang trí @classmethod.


Lưu ý rằng super(B, cls)không thể thay thế bằng super(cls, cls). Làm như vậy có thể dẫn đến vòng lặp vô hạn. Ví dụ,

class A(object):
    @classmethod
    def do_your_stuff(cls):
        print('This is A')

class B(A):
    @classmethod
    def do_your_stuff(cls):
        print('This is B')
        # super(B, cls).do_your_stuff()  # CORRECT
        super(cls, cls).do_your_stuff()  # WRONG

class C(B):
    @classmethod
    def do_your_stuff(cls):
        print('This is C')
        # super(C, cls).do_your_stuff()  # CORRECT
        super(cls, cls).do_your_stuff()  # WRONG

C.do_your_stuff()

sẽ nâng cao RuntimeError: maximum recursion depth exceeded while calling a Python object.

Nếu clsC, hãy super(cls, cls)tìm kiếm C.mro()lớp đứng sau C.

In [161]: C.mro()
Out[161]: [__main__.C, __main__.B, __main__.A, object]

Kể từ khi lớp học đó là B, khi clsC, super(cls, cls).do_your_stuff() luôn luôn kêu gọi B.do_your_stuff. Vì super(cls, cls).do_your_stuff()được gọi bên trong B.do_your_stuff, bạn sẽ gọi B.do_your_stufftrong một vòng lặp vô hạn.

Trong Python3, dạng đối số 0 củasuper đã được thêm vào để super(B, cls)có thể được thay thế bằng super()và Python3 sẽ tìm ra từ ngữ cảnh mà super()trong định nghĩa của class Bphải tương đương với super(B, cls).

Nhưng không có trường hợp nào super(cls, cls)(hoặc vì những lý do tương tự, super(type(self), self)) luôn đúng.


Có cạm bẫy khi sử dụng super (cls, cls) không?
George Moutsopoulos

2
@GeorgeMoutsopoulos: super(cls, cls)gần như chắc chắn là một sai lầm. Tôi đã chỉnh sửa bài đăng ở trên để giải thích tại sao.
unutbu

37

Trong Python 3, bạn có thể bỏ qua việc chỉ định đối số cho super,

class A:
    @classmethod
    def f(cls):
        return "A's f was called."

class B(A):
    @classmethod
    def f(cls):
        return super().f()

assert B.f() == "A's f was called."

Câu trả lời này có thể được chuyển sang một câu hỏi khác nếu câu hỏi cụ thể này liên quan cụ thể đến Python 2 (thật khó để nói vì trang web được liên kết, CafePy, không còn nữa).
Tankobot

Nó không hiệu quả với tôi trong một dự án của tôi. Nó đang choRuntimeError: super(): no arguments
madtyn

4

Tôi đã cập nhật bài viết để làm cho nó rõ ràng hơn một chút: Thuộc tính và phương thức Python # Super

Ví dụ của bạn bằng cách sử dụng classmethod ở trên cho thấy phương thức lớp là gì - nó truyền chính lớp đó thay vì cá thể làm tham số đầu tiên. Nhưng bạn thậm chí không cần một thể hiện để gọi phương thức, ví dụ:

>>> class A(object):
...     @classmethod
...     def foo(cls):
...         print cls
... 
>>> A.foo() # note this is called directly on the class
<class '__main__.A'>

2

Ví dụ từ trang web dường như hoạt động như đã xuất bản. Bạn cũng đã tạo một do_somethingphương thức cho lớp cha nhưng không biến nó thành một phương thức phân lớp? Một cái gì đó như thế này sẽ cung cấp cho bạn lỗi đó:

>>> class A(object):
...     def do_something(cls):
...         print cls
... #   do_something = classmethod(do_something)
... 
>>> class B(A):
...     def do_something(cls):
...         super(B, cls).do_something()
...     do_something = classmethod(do_something)
... 
>>> B().do_something()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in do_something
TypeError: unbound method do_something() must be called with B instance as first argument (got nothing instead)

Không, lớp A của tôi trông như thế này: class A (object): def do_your_stuff (self): print "this is A" Có cần thiết phải có "class A" như bạn đã đăng không? (với do_something = classmethod (do_something))? Tôi cảm thấy như tài liệu không nói bất cứ điều gì về điều này ..
dza

1
Toàn bộ điểm của lệnh supergọi trong phương thức B do_something là gọi một phương thức có tên đó trong một trong các lớp cha của nó. Nếu không có một trong A (hoặc trong Đối tượng), thì lệnh gọi B (). Do_something () không thành công với super object has no attribute do_something. ~ unutbu chỉ ra đúng rằng ví dụ trong tài liệu bị lỗi.
Ned Deily

0

Tôi nghĩ bây giờ tôi đã hiểu được vấn đề nhờ vào trang web tuyệt đẹp và cộng đồng đáng yêu này.

Nếu bạn không phiền, vui lòng sửa cho tôi nếu tôi sai trên classmethods (hiện tôi đang cố gắng hiểu đầy đủ):


# EXAMPLE #1
>>> class A(object):
...     def foo(cls):
...             print cls
...     foo = classmethod(foo)
... 
>>> a = A()
>>> a.foo()
# THIS IS THE CLASS ITSELF (__class__)
class '__main__.A'

# EXAMPLE #2
# SAME AS ABOVE (With new @decorator)
>>> class A(object):
...     @classmethod
...     def foo(cls):
...             print cls
... 
>>> a = A()
>>> a.foo()
class '__main__.A'

# EXAMPLE #3
>>> class B(object):
...     def foo(self):
...             print self
... 
>>> b = B()
>>> b.foo()
# THIS IS THE INSTANCE WITH ADDRESS (self)
__main__.B object at 0xb747a8ec
>>>

Tôi hy vọng minh họa này cho thấy ..


Để giải thích về classmethods, có lẽ bạn sẽ tìm thấy điều này hữu ích: stackoverflow.com/questions/1669445/...
unutbu
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.