__Init __ () có nên gọi lớp cha là __init __ () không?


132

Tôi đã sử dụng nó trong Objective-C Tôi đã có cấu trúc này:

- (void)init {
    if (self = [super init]) {
        // init class
    }
    return self;
}

Python cũng nên gọi triển khai của lớp cha __init__?

class NewClass(SomeOtherClass):
    def __init__(self):
        SomeOtherClass.__init__(self)
        # init class

Đây cũng là đúng / sai cho __new__()__del__()?

Chỉnh sửa: Có một câu hỏi rất giống nhau: Kế thừa và ghi đè __init__trong Python


bạn đã thay đổi mã của bạn một cách đáng kể. Tôi có thể hiểu rằng bản gốc objectlà một lỗi đánh máy. Nhưng bây giờ bạn thậm chí không có supertiêu đề của câu hỏi của bạn đề cập đến.
SilentGhost

Tôi chỉ nghĩ rằng siêu được sử dụng làm tên cho lớp cha. Tôi không nghĩ ai sẽ nghĩ về chức năng này. Tôi xin lỗi vì bất kỳ sự hiểu lầm.
Georg Schölly

A tại sao không tự động siêu câu hỏi cuộc gọi: stackoverflow.com/questions/3782827/iêng
Ciro Santilli 冠状 病 六四

Câu trả lời:


67

Trong Python, gọi siêu hạng ' __init__là tùy chọn. Nếu bạn gọi nó, thì nó cũng là tùy chọn cho dù sử dụng mã superđịnh danh hay là đặt tên rõ ràng cho siêu hạng:

object.__init__(self)

Trong trường hợp của đối tượng, việc gọi siêu phương thức là không thực sự cần thiết, vì siêu phương thức trống. Tương tự cho __del__.

Mặt khác, __new__thực sự, bạn nên gọi siêu phương thức và sử dụng trả về của nó làm đối tượng mới được tạo - trừ khi bạn rõ ràng muốn trả về một cái gì đó khác biệt.


Vì vậy, không có quy ước để chỉ gọi siêu thực hiện?
Georg Schölly

5
Trong các lớp kiểu cũ, bạn chỉ có thể gọi super init nếu siêu lớp thực sự có một init được định nghĩa (mà nó thường không có). Do đó, mọi người thường nghĩ về việc gọi siêu phương thức, thay vì thực hiện theo nguyên tắc.
Martin v. Löwis

1
Nếu cú ​​pháp trong python đơn giản như vậy [super init], nó sẽ phổ biến hơn. Chỉ là một suy nghĩ đầu cơ; cấu trúc siêu trong Python 2.x là một chút khó xử với tôi.
u0b34a0f6ae

Đây dường như là một ví dụ thú vị (và có thể mâu thuẫn): byte.com/topic/python/answers/iêu init
mlvljr

"Tùy chọn" ở chỗ bạn không phải gọi nó, nhưng nếu bạn không gọi nó, nó sẽ không được gọi tự động.
McKay

140

Nếu bạn cần một cái gì đó từ siêu nhân __init__được thực hiện ngoài những gì đang được thực hiện trong lớp hiện tại, __init__,bạn phải tự gọi nó, vì điều đó sẽ không tự động xảy ra. Nhưng nếu bạn không cần bất cứ thứ gì từ siêu thì __init__,không cần phải gọi nó. Thí dụ:

>>> class C(object):
        def __init__(self):
            self.b = 1


>>> class D(C):
        def __init__(self):
            super().__init__() # in Python 2 use super(D, self).__init__()
            self.a = 1


>>> class E(C):
        def __init__(self):
            self.a = 1


>>> d = D()
>>> d.a
1
>>> d.b  # This works because of the call to super's init
1
>>> e = E()
>>> e.a
1
>>> e.b  # This is going to fail since nothing in E initializes b...
Traceback (most recent call last):
  File "<pyshell#70>", line 1, in <module>
    e.b  # This is going to fail since nothing in E initializes b...
AttributeError: 'E' object has no attribute 'b'

__del__là cách tương tự, (nhưng hãy cảnh giác khi dựa vào __del__quyết toán - thay vào đó hãy xem xét thực hiện thông qua câu lệnh with).

Tôi hiếm khi sử dụng __new__. tôi làm tất cả các khởi tạo trong__init__.


3
Định nghĩa của lớp D (C) phải được sửa như thếsuper(D,self).__init__()
eyquem

11
super () .__ init __ () chỉ hoạt động trong Python 3. Trong Python 2, bạn cần super (D, self) .__ init __ ()
Jacinda

"Nếu bạn cần thứ gì đó từ init của ..." - Đây là một tuyên bố rất có vấn đề bởi vì đây không phải là trường hợp bạn / lớp con có cần "cái gì đó" hay không, nhưng liệu lớp cơ sở có cần thứ gì đó để hợp lệ không Ví dụ lớp cơ sở và hoạt động chính xác. Là người triển khai của lớp dẫn xuất, các phần bên trong lớp cơ sở là những thứ mà bạn không thể / không nên biết và ngay cả khi bạn viết vì cả hai hoặc phần bên trong đều được ghi lại, thiết kế của cơ sở có thể thay đổi trong tương lai và bị hỏng do viết kém Lớp có nguồn gốc. Vì vậy, luôn luôn đảm bảo rằng lớp cơ sở được khởi tạo đầy đủ.
Nick

105

Trong câu trả lời của Anon:
"Nếu bạn cần một thứ gì đó từ siêu nhân __init__được thực hiện ngoài những gì đang được thực hiện trong lớp hiện tại __init__, bạn phải tự gọi nó, vì điều đó sẽ không tự động xảy ra"

Thật không thể tin được: anh ta diễn đạt chính xác trái với nguyên tắc thừa kế.


Không phải là "một cái gì đó từ siêu __init__ (...) sẽ không tự động xảy ra" , mà là nó sẽ xảy ra tự động, nhưng nó không xảy ra vì lớp cơ sở ' __init__bị ghi đè bởi định nghĩa của clas dẫn xuất__init__

Vì vậy, TẠI SAO xác định một dẫn xuất ' __init__, vì nó ghi đè lên những gì được nhắm đến khi ai đó dùng đến quyền thừa kế ??

Đó là bởi vì người ta cần định nghĩa một cái gì đó KHÔNG được thực hiện trong lớp cơ sở ' __init__và khả năng duy nhất để có được điều đó là đặt sự thực thi của nó trong __init__hàm của lớp dẫn xuất' .
Nói cách khác, người ta cần một cái gì đó trong lớp cơ sở ' __init__ngoài những gì sẽ được thực hiện tự động trong classe cơ sở' __init__nếu cái này không bị quá tải.
KHÔNG ngược lại.


Sau đó, vấn đề là các hướng dẫn mong muốn có trong lớp cơ sở ' __init__không còn được kích hoạt tại thời điểm khởi tạo. Để bù lại sự bất hoạt này, cần phải có một điều đặc biệt: gọi rõ ràng là lớp cơ sở ' __init__, để KEEP , KHÔNG THÊM, việc khởi tạo được thực hiện bởi lớp cơ sở' __init__. Đó chính xác là những gì được nói trong tài liệu chính thức:

Một phương thức ghi đè trong một lớp dẫn xuất trong thực tế có thể muốn mở rộng hơn là chỉ đơn giản thay thế phương thức lớp cơ sở cùng tên. Có một cách đơn giản để gọi phương thức lớp cơ sở trực tiếp: chỉ cần gọi BaseClassName.methodname (self, argument).
http://docs.python.org/tutorial/groupes.html#inherribution

Đó là tất cả câu chuyện:

  • Khi mục đích là KEEP việc khởi tạo được thực hiện bởi lớp cơ sở, đó là sự kế thừa thuần túy, không có gì đặc biệt là cần thiết, người ta chỉ cần tránh để xác định một __init__hàm trong lớp dẫn xuất

  • khi mục đích là thay thế việc khởi tạo được thực hiện bởi lớp cơ sở, __init__phải được định nghĩa trong lớp dẫn xuất

  • khi mục đích là THÊM các quá trình khởi tạo được thực hiện bởi lớp cơ sở, một lớp dẫn xuất ' __init__ phải được xác định, bao gồm một lệnh gọi rõ ràng đến lớp cơ sở__init__


Điều tôi cảm thấy đáng kinh ngạc trong bài đăng của Anon không chỉ là anh ta bày tỏ sự trái ngược với lý thuyết thừa kế, mà còn có 5 chàng trai đi ngang qua mà không được rẽ tóc, và hơn nữa đã không có ai phản ứng trong 2 năm qua một chủ đề mà chủ đề thú vị phải được đọc tương đối thường xuyên.


1
Tôi chắc chắn rằng bài đăng này sẽ được nâng cấp. Tôi sợ rằng tôi sẽ không có nhiều lời giải thích về lý do tại sao. Thật dễ dàng để downvote hơn là phân tích một văn bản dường như không thể hiểu được. Tôi đã có một thời gian dài cố gắng để hiểu bài của Anon trước khi cuối cùng tôi nhận ra rằng nó được viết một cách đặc biệt và không có nhiều thẩm quyền. Có lẽ nó có thể được hiểu là gần đúng cho một người biết về thừa kế; nhưng tôi thấy khó hiểu khi được đọc bởi một người có quan niệm không ổn định về quyền thừa kế, một chủ đề không rõ ràng như nước đá nói chung
Eyquem

2
"Tôi chắc chắn rằng bài đăng này sẽ được nâng cấp ..." Vấn đề chính là bạn đến bữa tiệc muộn một chút và hầu hết mọi người có thể không đọc được ngoài vài câu trả lời đầu tiên. Cách giải thích tuyệt vời bằng cách +1
Gerrat

Câu trả lời tuyệt vời này là.
Trilarion

3
Bạn đã bỏ lỡ những từ quan trọng "ngoài ra" trong câu bạn trích dẫn từ Aaron. Tuyên bố của Aaron là hoàn toàn chính xác, và phù hợp với những gì bạn kết thúc.
GreenAsJade

1
Đây là lời giải thích đầu tiên khiến cho sự lựa chọn thiết kế của python có ý nghĩa.
Joseph Garvin

20

Chỉnh sửa : (sau khi thay đổi mã)
Không có cách nào để chúng tôi cho bạn biết bạn có cần hay không gọi cho bố mẹ bạn __init__(hoặc bất kỳ chức năng nào khác). Kế thừa rõ ràng sẽ làm việc mà không có cuộc gọi như vậy. Tất cả phụ thuộc vào logic của mã của bạn: ví dụ: nếu tất cả của bạn __init__được thực hiện trong lớp cha, bạn có thể bỏ qua lớp con __init__hoàn toàn.

xem xét ví dụ sau:

>>> class A:
    def __init__(self, val):
        self.a = val


>>> class B(A):
    pass

>>> class C(A):
    def __init__(self, val):
        A.__init__(self, val)
        self.a += val


>>> A(4).a
4
>>> B(5).a
5
>>> C(6).a
12

Tôi đã loại bỏ siêu cuộc gọi khỏi ví dụ của mình, tôi có muốn biết liệu người ta có nên gọi việc thực hiện init của lớp cha hay không.
Georg Schölly

bạn có thể muốn chỉnh sửa tiêu đề sau đó. nhưng câu trả lời của tôi vẫn đứng vững.
SilentGhost

5

Không có quy tắc cứng và nhanh. Tài liệu cho một lớp sẽ cho biết liệu các lớp con có nên gọi phương thức siêu lớp hay không. Đôi khi bạn muốn thay thế hoàn toàn hành vi của siêu lớp và đôi khi làm tăng nó - tức là gọi mã của riêng bạn trước và / hoặc sau một cuộc gọi siêu lớp.

Cập nhật: Logic cơ bản tương tự áp dụng cho bất kỳ cuộc gọi phương thức nào. Các nhà xây dựng đôi khi cần xem xét đặc biệt (vì họ thường thiết lập trạng thái xác định hành vi) và các hàm hủy vì chúng là các hàm tạo song song (ví dụ như trong việc phân bổ tài nguyên, ví dụ: kết nối cơ sở dữ liệu). Nhưng điều tương tự có thể áp dụng cho render()phương pháp của một widget.

Cập nhật thêm: OPP là gì? Ý bạn là OOP? Không - một lớp con thường cần biết điều gì đó về thiết kế của siêu lớp. Không phải các chi tiết thực hiện nội bộ - mà là hợp đồng cơ bản mà siêu lớp có với các khách hàng của nó (sử dụng các lớp). Điều này không vi phạm các nguyên tắc OOP dưới bất kỳ hình thức nào. Đó là lý do tại sao protectedmột khái niệm hợp lệ trong OOP nói chung (mặc dù không, tất nhiên, trong Python).


Bạn nói rằng đôi khi người ta muốn gọi mã riêng trước cuộc gọi siêu lớp. Để làm điều này, người ta cần có kiến ​​thức về việc triển khai lớp cha, điều này sẽ vi phạm OPP.
Georg Schölly

4

IMO, bạn nên gọi nó. Nếu siêu lớp của bạn là object, bạn không nên, nhưng trong các trường hợp khác tôi nghĩ rằng thật tuyệt vời khi không gọi nó. Như đã được trả lời bởi những người khác, sẽ rất thuận tiện nếu lớp của bạn thậm chí không phải ghi đè __init__chính nó, ví dụ như khi nó không có trạng thái nội bộ (bổ sung) để khởi tạo.


2

Có, bạn nên luôn luôn gọi lớp cơ sở __init__một cách rõ ràng là một thực hành mã hóa tốt. Quên làm điều này có thể gây ra các vấn đề tinh tế hoặc chạy lỗi thời gian. Điều này đúng ngay cả khi __init__không lấy bất kỳ tham số nào. Điều này không giống như các ngôn ngữ khác nơi trình biên dịch sẽ gọi hàm xây dựng lớp cơ sở cho bạn. Python không làm điều đó!

Lý do chính để luôn gọi lớp cơ sở _init__là lớp cơ sở thường có thể tạo biến thành viên và khởi tạo chúng thành mặc định. Vì vậy, nếu bạn không gọi init lớp cơ sở, không có mã nào trong số đó sẽ được thực thi và bạn sẽ kết thúc với lớp cơ sở không có biến thành viên.

Ví dụ :

class Base:
  def __init__(self):
    print('base init')

class Derived1(Base):
  def __init__(self):
    print('derived1 init')

class Derived2(Base):
  def __init__(self):
    super(Derived2, self).__init__()
    print('derived2 init')

print('Creating Derived1...')
d1 = Derived1()
print('Creating Derived2...')
d2 = Derived2()

Bản in này ..

Creating Derived1...
derived1 init
Creating Derived2...
base init
derived2 init

Chạy mã này .

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.