Kế thừa và ghi đè __init__ trong python


129

Tôi đã đọc 'Đi sâu vào Python' và trong chương về các lớp học, nó đưa ra ví dụ này:

class FileInfo(UserDict):
    "store file metadata"
    def __init__(self, filename=None):
        UserDict.__init__(self)
        self["name"] = filename

Sau đó, tác giả nói rằng nếu bạn muốn ghi đè __init__phương thức, bạn phải gọi rõ ràng cha mẹ __init__với các tham số chính xác.

  1. Điều gì nếu FileInfolớp đó có nhiều hơn một lớp tổ tiên?
    • Tôi có phải gọi một cách rõ ràng tất cả các __init__phương thức của lớp tổ tiên không?
  2. Ngoài ra, tôi có phải làm điều này với bất kỳ phương pháp nào khác mà tôi muốn ghi đè không?

3
Lưu ý rằng Quá tải là một khái niệm riêng biệt với Ghi đè.
Dana the Sane

Câu trả lời:


158

Cuốn sách này có một chút thời gian liên quan đến cách gọi siêu lớp con. Nó cũng có một chút ngày đối với các lớp dựng sẵn.

Có vẻ như ngày nay:

class FileInfo(dict):
    """store file metadata"""
    def __init__(self, filename=None):
        super(FileInfo, self).__init__()
        self["name"] = filename

Lưu ý những điều dưới đây:

  1. Chúng ta có thể trực tiếp phân lớp được xây dựng trong lớp học, như dict, list, tuplevv

  2. Các superxử lý chức năng theo dõi xuống superclasses của lớp này và gọi chức năng trong chúng một cách thích hợp.


5
Tôi có nên tìm một cuốn sách / hướng dẫn tốt hơn?
liewl

2
Vì vậy, trong trường hợp thừa kế nhiều lần, super () có theo dõi tất cả chúng thay mặt bạn không?
Dana

dict .__ init __ (tự), thực ra, nhưng không có gì sai với nó - cuộc gọi super (...) chỉ cung cấp một cú pháp phù hợp hơn. (Tôi không chắc nó hoạt động như thế nào đối với nhiều kế thừa. Tôi nghĩ rằng nó chỉ có thể tìm thấy một init siêu lớp )
David Z

4
Ý định của super () là nó xử lý nhiều kế thừa. Nhược điểm là, trong thực tế, nhiều di sản vẫn bị phá vỡ rất dễ dàng (xem < fuhm.net/super-harmful ).
sth

2
Có, trong trường hợp có nhiều lớp kế thừa và lớp cơ sở lấy các đối số của hàm tạo, bạn thường thấy mình gọi các hàm tạo theo cách thủ công.
Torsten Marek

18

Trong mỗi lớp mà bạn cần kế thừa từ đó, bạn có thể chạy một vòng lặp của mỗi lớp cần bắt đầu khi bắt đầu lớp con ... một ví dụ có thể được sao chép có thể được hiểu rõ hơn ...

class Female_Grandparent:
    def __init__(self):
        self.grandma_name = 'Grandma'

class Male_Grandparent:
    def __init__(self):
        self.grandpa_name = 'Grandpa'

class Parent(Female_Grandparent, Male_Grandparent):
    def __init__(self):
        Female_Grandparent.__init__(self)
        Male_Grandparent.__init__(self)

        self.parent_name = 'Parent Class'

class Child(Parent):
    def __init__(self):
        Parent.__init__(self)
#---------------------------------------------------------------------------------------#
        for cls in Parent.__bases__: # This block grabs the classes of the child
             cls.__init__(self)      # class (which is named 'Parent' in this case), 
                                     # and iterates through them, initiating each one.
                                     # The result is that each parent, of each child,
                                     # is automatically handled upon initiation of the 
                                     # dependent class. WOOT WOOT! :D
#---------------------------------------------------------------------------------------#



g = Female_Grandparent()
print g.grandma_name

p = Parent()
print p.grandma_name

child = Child()

print child.grandma_name

2
Có vẻ như vòng lặp for Child.__init__là cần thiết. Khi tôi xóa nó khỏi ví dụ, con tôi vẫn in "Bà". Không phải là ông bà init được xử lý bởi Parentlớp học?
Adam

4
Tôi nghĩ rằng init của granparents đã được xử lý bởi init của Parent, phải không?
johk95

15

Bạn không thực sự phải gọi các __init__phương thức của (các) lớp cơ sở, nhưng bạn thường muốn làm điều đó bởi vì các lớp cơ sở sẽ thực hiện một số khởi tạo quan trọng cần thiết để các phương thức lớp còn lại hoạt động.

Đối với các phương pháp khác, nó phụ thuộc vào ý định của bạn. Nếu bạn chỉ muốn thêm một cái gì đó vào hành vi của lớp cơ sở, bạn sẽ muốn gọi phương thức lớp cơ sở thêm vào mã của riêng bạn. Nếu bạn muốn thay đổi cơ bản hành vi, bạn có thể không gọi phương thức của lớp cơ sở và thực hiện tất cả các chức năng trực tiếp trong lớp dẫn xuất.


4
Để hoàn thiện kỹ thuật, một số lớp, như luồng. Điều này, sẽ đưa ra các lỗi khổng lồ nếu bạn từng cố gắng tránh gọi init của cha mẹ .
David Berger

5
Tôi thấy tất cả điều này "bạn không cần phải gọi nhà xây dựng của cơ sở" nói chuyện vô cùng khó chịu. Bạn không cần phải gọi nó bằng bất kỳ ngôn ngữ nào tôi biết. Tất cả trong số họ sẽ làm hỏng (hoặc không) theo cùng một cách, bằng cách không khởi tạo thành viên. Đề nghị không khởi tạo các lớp cơ sở chỉ là sai theo nhiều cách. Nếu một lớp không cần khởi tạo bây giờ thì nó sẽ cần nó trong tương lai. Hàm tạo là một phần của giao diện của cấu trúc ngôn ngữ / cấu trúc lớp và nên được sử dụng đúng cách. Việc sử dụng đúng là gọi nó đôi khi trong hàm tạo của bạn. Vậy làm thế đi.
AndreasT

2
"Đề nghị không khởi tạo các lớp cơ sở chỉ là sai theo nhiều cách." Không ai đề nghị không khởi tạo lớp cơ sở. Đọc câu trả lời cẩn thận. Đó là tất cả về ý định. 1) Nếu bạn muốn để nguyên logic init của lớp cơ sở, bạn không ghi đè phương thức init trong lớp dẫn xuất của mình. 2) Nếu bạn muốn mở rộng logic init từ lớp cơ sở, bạn xác định phương thức init của riêng bạn và sau đó gọi phương thức init của lớp cơ sở từ nó. 3) Nếu bạn muốn thay thế logic init của lớp cơ sở, bạn xác định phương thức init của riêng bạn trong khi không gọi phương thức từ lớp cơ sở.
wombatonfire

4

Nếu lớp FileInfo có nhiều hơn một lớp tổ tiên thì bạn chắc chắn nên gọi tất cả các hàm __init __ () của chúng. Bạn cũng nên làm tương tự cho hàm __del __ (), là hàm hủy.


2

Có, bạn phải gọi __init__cho mỗi lớp cha. Điều tương tự cũng xảy ra với các hàm, nếu bạn đang ghi đè một hàm tồn tại trong cả hai cha mẹ.

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.