cách chính xác để sử dụng super (truyền đối số)


99

Vì vậy, tôi đã theo dõi Siêu được coi là có hại của Python và đi kiểm tra các ví dụ của anh ấy.

Tuy nhiên, Ví dụ 1-3 , được cho là hiển thị cách gọi chính xác superkhi xử lý __init__các phương thức mong đợi các đối số khác nhau, tính năng căn hộ không hoạt động.

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

~ $ python example1-3.py 
MRO: ['E', 'C', 'A', 'D', 'B', 'object']
E arg= 10
C arg= 10
A
D arg= 10
B
Traceback (most recent call last):
  File "Download/example1-3.py", line 27, in <module>
    E(arg=10)
  File "Download/example1-3.py", line 24, in __init__
    super(E, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 14, in __init__
    super(C, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 4, in __init__
    super(A, self).__init__(*args, **kwargs)
  File "Download/example1-3.py", line 19, in __init__
    super(D, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 9, in __init__
    super(B, self).__init__(*args, **kwargs)
TypeError: object.__init__() takes no parameters

Có vẻ như objectbản thân nó đã vi phạm một trong những phương pháp hay nhất được đề cập trong tài liệu, đó là các phương pháp sử dụng superphải chấp nhận *args**kwargs.

Bây giờ, rõ ràng ngài Knight đã mong đợi các ví dụ của mình hoạt động, vậy đây có phải là thứ đã được thay đổi trong các phiên bản Python gần đây không? Tôi đã kiểm tra 2.6 và 2.7, và nó không thành công trên cả hai.

Vậy đâu là cách chính xác để giải quyết vấn đề này?


2
Cách ưa thích của tôi là: Phân cấp kế thừa phẳng và đơn giản.
milimoose

19
Bạn cũng nên đọc "siêu Python () coi siêu" để có được một cái nhìn cân bằng :)
Björn Pollex

@ BjörnPollex: Cảm ơn! Liên kết của bạn cung cấp một câu trả lời cho câu hỏi: Bạn có thể viết một "lớp gốc" mà thừa hưởng từ object, và nó làm cho chắc chắn để gọi object__init__chính xác.
cha0site

4
Lưu ý rằng __init__trên objectâm thầm bỏ qua bất kỳ tham số nào trên Python 2.5. Điều này đã thay đổi trong Python 2.6.
Wilfred Hughes

1
@Wilfred: Này, Cảm ơn bạn đã trả lời câu hỏi thực tế! Giờ thì tôi đã biết tại sao bài luận lại lỗi thời!
cha0site

Câu trả lời:


103

Đôi khi hai lớp có thể có một số tên tham số chung. Trong trường hợp đó, bạn không thể tách **kwargshoặc xóa các cặp khóa-giá trị khỏi hoặc xóa chúng khỏi đó *args. Thay vào đó, bạn có thể xác định một Baselớp không giống như object, hấp thụ / bỏ qua các đối số:

class Base(object):
    def __init__(self, *args, **kwargs): pass

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

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

class C(A):
    def __init__(self, arg, *args, **kwargs):
        print "C","arg=",arg
        super(C, self).__init__(arg, *args, **kwargs)

class D(B):
    def __init__(self, arg, *args, **kwargs):
        print "D", "arg=",arg
        super(D, self).__init__(arg, *args, **kwargs)

class E(C,D):
    def __init__(self, arg, *args, **kwargs):
        print "E", "arg=",arg
        super(E, self).__init__(arg, *args, **kwargs)

print "MRO:", [x.__name__ for x in E.__mro__]
E(10)

hoa lợi

MRO: ['E', 'C', 'A', 'D', 'B', 'Base', 'object']
E arg= 10
C arg= 10
A
D arg= 10
B

Lưu ý rằng để điều này hoạt động, Basephải là lớp áp chót trong MRO.


4
Không nên Basegọi super(Base, self).__init__()?
cha0site

4
Để điều này hoạt động, Basephải đến cuối MRO (ngoại trừ object). Gọi object.__init__không ra gì nên không gọi cũng được super(Base, self).__init__(). Trong thực tế, tôi nghĩ rằng có thể rõ ràng hơn là không bao gồm super(Base, self).__init__()việc lái xe về nhà điểm Basecuối hàng.
unutbu

25

Nếu bạn sẽ có nhiều quyền thừa kế (đó là trường hợp ở đây), tôi khuyên bạn nên chuyển tất cả các tham số bằng cách sử dụng **kwargsvà sau đó chuyển popchúng ngay sau khi bạn sử dụng chúng (trừ khi bạn cần chúng ở các lớp cao hơn).

class First(object):
    def __init__(self, *args, **kwargs):
        self.first_arg = kwargs.pop('first_arg')
        super(First, self).__init__(*args, **kwargs)

class Second(First):
    def __init__(self, *args, **kwargs):
        self.second_arg = kwargs.pop('second_arg')
        super(Second, self).__init__(*args, **kwargs)

class Third(Second):
    def __init__(self, *args, **kwargs):
        self.third_arg = kwargs.pop('third_arg')
        super(Third, self).__init__(*args, **kwargs)

Đây là cách đơn giản nhất để giải quyết những vấn đề đó.

third = Third(first_arg=1, second_arg=2, third_arg=3)

6

Như đã giải thích trong super () được coi là super của Python , một cách là để lớp ăn các đối số mà nó yêu cầu và chuyển phần còn lại vào. Do đó, khi chuỗi gọi đạt đến object, tất cả các đối số đã được ăn và object.__init__sẽ được gọi mà không có đối số (như nó mong đợi). Vì vậy, mã của bạn sẽ giống như sau:

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

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

class C(A):
    def __init__(self, arg, *args, **kwargs):
        print "C","arg=",arg
        super(C, self).__init__(*args, **kwargs)

class D(B):
    def __init__(self, arg, *args, **kwargs):
        print "D", "arg=",arg
        super(D, self).__init__(*args, **kwargs)

class E(C,D):
    def __init__(self, arg, *args, **kwargs):
        print "E", "arg=",arg
        super(E, self).__init__(*args, **kwargs)

print "MRO:", [x.__name__ for x in E.__mro__]
E(10, 20, 30)

Vậy thì E(arg = 10)sao? Tôi không thích phương pháp này, tôi cần biết trước về MRO để sử dụng nó. Phương pháp khác mà tôi viết một "lớp gốc" kế thừa từ objectcó vẻ sạch hơn nhiều.
cha0site

Phương thức này hữu ích nếu các hàm được gọi không dùng chung tên đối số. Tôi thừa nhận rằng tôi không có nhiều kinh nghiệm thực tế super, vì vậy cách tiếp cận này thực sự có thể mang tính lý thuyết.
Björn Pollex
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.