Câu hỏi Điểm kế thừa trong Python là gì?


Giả sử bạn có tình huống sau

#include <iostream>

class Animal {
public:
    virtual void speak() = 0;
};

class Dog : public Animal {
    void speak() { std::cout << "woff!" <<std::endl; }
};

class Cat : public Animal {
    void speak() { std::cout << "meow!" <<std::endl; }
};

void makeSpeak(Animal &a) {
    a.speak();
}

int main() {
    Dog d;
    Cat c;
    makeSpeak(d);
    makeSpeak(c);
}

Như bạn có thể thấy, makeSpeak là một thường trình chấp nhận một đối tượng Animal chung. Trong trường hợp này, Animal khá giống với giao diện Java, vì nó chỉ chứa một phương thức ảo thuần túy. makeSpeak không biết bản chất của Animal mà nó được truyền đi. Nó chỉ gửi cho nó tín hiệu “nói” và rời khỏi ràng buộc trễ để chăm sóc phương thức gọi: Cat :: speak () hoặc Dog :: speak (). Điều này có nghĩa rằng, như xa như makeSpeak là có liên quan, kiến ​​thức về lớp con thực sự được thông qua là không thích hợp.

Nhưng còn Python thì sao? Hãy xem mã cho cùng một trường hợp trong Python. Xin lưu ý rằng tôi cố gắng càng giống với trường hợp C ++ trong giây lát:

class Animal(object):
    def speak(self):
        raise NotImplementedError()

class Dog(Animal):
    def speak(self):
        print "woff!"

class Cat(Animal):
    def speak(self):
        print "meow"

def makeSpeak(a):
    a.speak()

d=Dog()
c=Cat()
makeSpeak(d)
makeSpeak(c)

Bây giờ, trong ví dụ này, bạn sẽ thấy cùng một chiến lược. Bạn sử dụng thừa kế để tận dụng khái niệm phân cấp của cả Chó và Mèo là Động vật. Nhưng trong Python, không cần phân cấp này. Điều này hoạt động tốt như nhau

class Dog:
    def speak(self):
        print "woff!"

class Cat:
    def speak(self):
        print "meow"

def makeSpeak(a):
    a.speak()

d=Dog()
c=Cat()
makeSpeak(d)
makeSpeak(c)

Trong Python bạn có thể gửi tín hiệu “nói” tới bất kỳ đối tượng nào bạn muốn. Nếu đối tượng có thể xử lý nó, nó sẽ được thực thi, nếu không nó sẽ làm tăng một ngoại lệ. Giả sử bạn thêm lớp Máy bay vào cả hai mã và gửi đối tượng Máy bay để tạo raSpeak. Trong trường hợp C ++, nó sẽ không biên dịch, vì Máy bay không phải là một loại động vật có nguồn gốc. Trong trường hợp Python, nó sẽ tăng một ngoại lệ trong thời gian chạy, mà thậm chí có thể là một hành vi mong đợi.

Ở phía bên kia, giả sử bạn thêm một lớp MouthOfTruth với một phương thức speak (). Trong trường hợp C ++, bạn sẽ phải cấu trúc lại hệ thống phân cấp của bạn, hoặc bạn sẽ phải định nghĩa một phương thức makeSpeak khác để chấp nhận các đối tượng MouthOfTruth hoặc trong java, bạn có thể trích xuất hành vi đó thành một CanSpeakIface và triển khai giao diện cho mỗi đối tượng. Có rất nhiều giải pháp ...

Điều tôi muốn chỉ ra là tôi chưa tìm thấy một lý do nào để sử dụng thừa kế trong Python (ngoài khung công tác và cây ngoại lệ, nhưng tôi đoán rằng các chiến lược thay thế tồn tại). bạn không cần triển khai hệ thống phân cấp có nguồn gốc cơ bản để thực hiện đa hình. Nếu bạn muốn sử dụng thừa kế để sử dụng lại, bạn có thể thực hiện tương tự thông qua ngăn chặn và ủy nhiệm, với lợi ích bổ sung mà bạn có thể thay đổi nó trong thời gian chạy, và bạn xác định rõ giao diện chứa, không gây rủi ro cho các tác dụng phụ không mong muốn.

Vì vậy, cuối cùng, câu hỏi là viết tắt: điểm của thừa kế trong Python là gì?

Chỉnh sửa: cảm ơn vì những câu trả lời rất thú vị. Thật vậy, bạn có thể sử dụng nó để tái sử dụng mã, nhưng tôi luôn cẩn thận khi sử dụng lại việc thực hiện. Nói chung, tôi có xu hướng làm cây thừa kế rất nông hoặc không có cây nào cả, và nếu một chức năng là phổ biến, tôi tái cấu trúc nó ra như là một thường trình mô-đun chung và sau đó gọi nó từ mỗi đối tượng. Tôi thấy lợi thế của việc có một điểm thay đổi (ví dụ: thay vì thêm vào Dog, Cat, Moose và vân vân, tôi chỉ thêm vào Animal, đó là lợi thế cơ bản của thừa kế), nhưng bạn có thể đạt được điều tương tự một chuỗi đại biểu (ví dụ: a la JavaScript). Tôi không tuyên bố nó tốt hơn, chỉ là một cách khác.

Tôi cũng tìm thấy một bài đăng tương tự về vấn đề này.


76
2018-06-19 23:28


gốc


-1: "bạn có thể đạt được cùng với một chuỗi đoàn". Đúng, nhưng đau đớn hơn nhiều so với thừa kế. Bạn có thể đạt được điều tương tự mà không sử dụng bất kỳ định nghĩa lớp nào cả, chỉ có rất nhiều hàm thuần túy phức tạp. Bạn có thể đạt được cùng một điều một cách, tất cả các cách đơn giản hơn so với thừa kế. - S.Lott
thực sự tôi đã nói "Tôi không tuyên bố nó tốt hơn;)" - Stefano Borini
"Tôi đã không tìm thấy một lý do duy nhất nào để sử dụng thừa kế trong python" ... chắc chắn âm thanh như "giải pháp của tôi là tốt hơn". - S.Lott
Xin lỗi nếu nó cho bạn ấn tượng này. Bài viết của tôi nhằm mục đích nhận được phản hồi tích cực cho những câu chuyện có thật về việc sử dụng thừa kế trong python, như hôm nay, tôi không thể tìm thấy (chủ yếu vì trong tất cả các chương trình python của tôi, tôi phải đối mặt với trường hợp này không cần thiết, và khi nào Tôi đã làm, đó là tình huống tôi đã giải thích ở trên). - Stefano Borini
Phân loại thực tế hiếm khi là cơ sở cho các ví dụ về định hướng đối tượng. - Apalala


Các câu trả lời:


Bạn đang đề cập đến thời gian chạy kiểu gõ vịt là "thừa kế" thừa kế, tuy nhiên tôi tin rằng thừa kế có giá trị riêng của nó như là một cách tiếp cận thiết kế và thực hiện, là một phần không thể thiếu của thiết kế hướng đối tượng. Theo quan điểm khiêm tốn của tôi, câu hỏi liệu bạn có thể đạt được điều gì đó không liên quan, bởi vì thực sự bạn có thể viết mã Python mà không cần lớp, chức năng và nhiều hơn nữa, nhưng câu hỏi là mã của bạn được thiết kế tốt, mạnh mẽ và dễ đọc.

Tôi có thể đưa ra hai ví dụ cho nơi thừa kế là cách tiếp cận đúng trong quan điểm của tôi, tôi chắc chắn có nhiều hơn nữa.

Đầu tiên, nếu bạn mã hóa một cách khôn ngoan, hàm makeSpeak của bạn có thể muốn xác thực rằng đầu vào của nó thực sự là một Động vật, và không chỉ là "nó có thể nói", trong trường hợp đó, phương thức thanh lịch nhất sẽ là sử dụng thừa kế. Một lần nữa, bạn có thể làm điều đó theo những cách khác, nhưng đó là vẻ đẹp của thiết kế hướng đối tượng với thừa kế - mã của bạn sẽ "thực sự" kiểm tra xem đầu vào có phải là "động vật" hay không.

Thứ hai, và rõ ràng hơn đơn giản, là Encapsulation - một phần không thể thiếu của thiết kế hướng đối tượng. Điều này trở nên có liên quan khi tổ tiên có các thành viên dữ liệu và / hoặc các phương thức không trừu tượng. Lấy ví dụ ngớ ngẩn sau đây, trong đó tổ tiên có một hàm (speak_twice) gọi hàm trừu tượng sau đó:

class Animal(object):
    def speak(self):
        raise NotImplementedError()

    def speak_twice(self):
        self.speak()
        self.speak()

class Dog(Animal):
    def speak(self):
        print "woff!"

class Cat(Animal):
    def speak(self):
        print "meow"

Giả định "speak_twice" là một tính năng quan trọng, bạn không muốn mã hóa nó trong cả Chó và Mèo, và tôi chắc chắn bạn có thể ngoại suy ví dụ này. Chắc chắn, bạn có thể thực hiện một hàm độc lập Python để chấp nhận một đối tượng gõ vịt, kiểm tra xem nó có chức năng nói và gọi nó hai lần, nhưng đó là cả số không thanh lịch và điểm số 1 (xác nhận đó là một Động vật). Thậm chí tệ hơn, và để tăng cường ví dụ Encapsulation, nếu một hàm thành viên trong lớp hậu duệ muốn sử dụng "speak_twice"?

Nó thậm chí còn rõ ràng hơn nếu lớp tổ tiên có một thành viên dữ liệu, ví dụ "number_of_legs" được sử dụng bởi các phương pháp không trừu tượng trong tổ tiên như "print_number_of_legs", nhưng được bắt đầu trong constructor của lớp con cháu (ví dụ: Dog sẽ khởi tạo nó với 4 trong khi Snake sẽ khởi tạo nó bằng 0).

Một lần nữa, tôi chắc chắn có nhiều ví dụ vô tận, nhưng về cơ bản mọi phần mềm (đủ lớn) dựa trên thiết kế hướng đối tượng vững chắc sẽ yêu cầu thừa kế.


79
2018-06-19 23:35



Đối với trường hợp đầu tiên, nó có nghĩa là bạn đang kiểm tra các loại thay vì hành vi, đó là loại unpythonic. Đối với trường hợp thứ hai, tôi đồng ý, và về cơ bản bạn đang làm cách tiếp cận "khung". Bạn đang tái chế việc thực hiện của speak_twice, không chỉ giao diện, nhưng để ghi đè, bạn có thể sống mà không thừa hưởng khi bạn xem xét python. - Stefano Borini
Bạn có thể sống mà không có nhiều thứ, như lớp học và chức năng, nhưng câu hỏi là điều làm cho mã trở nên tuyệt vời. Tôi nghĩ rằng thừa kế. - Roee Adler
@Stefano Borini - Có vẻ như bạn đang sử dụng phương pháp "dựa trên quy tắc". Các cliche cũ là đúng mặc dù: họ đã được thực hiện để được chia. :-) - Jason Baker
upvoted cho eloquence của đoạn đầu tiên - Shawn
Tôi không tìm thấy ví dụ này để rõ ràng - động vật, xe hơi và hình dạng ví dụ thực sự hút cho những dicussions :) Điều duy nhất mà vấn đề IMHO là cho dù bạn muốn kế thừa thực hiện hay không. Nếu vậy, các quy tắc trong python thực sự tương tự như java / C ++; sự khác biệt chủ yếu là để thừa kế cho các giao diện. Trong trường hợp đó, gõ vịt thường là giải pháp - nhiều hơn thế so với thừa kế. - David Cournapeau


Thừa kế trong Python là tất cả về tái sử dụng mã. Đưa tính năng phổ biến vào một lớp cơ sở và thực hiện các chức năng khác nhau trong các lớp dẫn xuất.


12
2018-06-20 04:51





Thừa kế trong Python là thuận tiện hơn bất cứ thứ gì khác. Tôi thấy rằng nó được sử dụng tốt nhất để cung cấp một lớp học với "hành vi mặc định".

Thật vậy, có một cộng đồng đáng kể các nhà phát triển Python, người tranh luận chống lại việc sử dụng thừa kế. Dù bạn làm gì, đừng chỉ đừng lạm dụng nó. Có một hệ thống phân cấp lớp quá phức tạp là một cách chắc chắn để được gắn nhãn là "lập trình Java", và bạn không thể có được điều đó. :-)


9
2018-06-20 03:44





Tôi nghĩ rằng điểm thừa kế trong Python không phải là để biên dịch mã, nó là vì lý do thực sự của thừa kế đang mở rộng lớp vào một lớp con khác, và để ghi đè lên logic trong lớp cơ sở. Tuy nhiên việc gõ vịt trong Python làm cho khái niệm "giao diện" vô dụng, bởi vì bạn chỉ có thể kiểm tra xem phương thức tồn tại trước khi invokation không cần sử dụng một giao diện để giới hạn cấu trúc lớp.


8
2018-06-19 23:34



Ghi đè có chọn lọc là lý do thừa kế. Nếu bạn định ghi đè mọi thứ, đó là một trường hợp đặc biệt kỳ lạ. - S.Lott
Ai sẽ ghi đè mọi thứ? bạn có thể nghĩ python giống như tất cả các phương thức công khai và ảo - bashmohandes
@ bashmohandes: Tôi sẽ không bao giờ ghi đè mọi thứ. Nhưng câu hỏi cho thấy một trường hợp thoái hóa nơi mọi thứ bị ghi đè; trường hợp đặc biệt kỳ lạ này là cơ sở cho câu hỏi. Vì nó không bao giờ xảy ra trong thiết kế OO bình thường, câu hỏi là vô nghĩa. - S.Lott


Tôi nghĩ rằng rất khó để đưa ra một câu trả lời có ý nghĩa, cụ thể với những ví dụ trừu tượng như vậy ...

Để đơn giản hóa, có hai loại thừa kế: giao diện và thực hiện. Nếu bạn cần kế thừa việc triển khai, thì python không khác biệt so với các ngôn ngữ OO được gõ tĩnh như C ++.

Thừa kế giao diện là nơi có sự khác biệt lớn, với những hậu quả cơ bản cho việc thiết kế phần mềm của bạn theo kinh nghiệm của tôi. Các ngôn ngữ như Python không ép buộc bạn sử dụng thừa kế trong trường hợp đó, và tránh thừa kế là một điểm tốt trong hầu hết các trường hợp, bởi vì rất khó để sửa một lựa chọn thiết kế sai ở đó sau này. Đó là một điểm nổi tiếng được nêu ra trong bất kỳ cuốn sách OOP nào tốt.

Có những trường hợp sử dụng kế thừa cho giao diện được khuyến khích trong Python, ví dụ cho trình cắm thêm, v.v ... Đối với những trường hợp này, Python 2.5 trở xuống thiếu phương pháp tiếp cận trang nhã và một số khung công tác lớn đã thiết kế các giải pháp riêng của chúng. (zope, trac, twister). Python 2.6 trở lên có Các lớp ABC để giải quyết vấn đề này.


7
2018-06-20 03:31





Trong C ++ / Java / etc, đa hình là do thừa kế. Từ bỏ niềm tin sai lệch đó, và ngôn ngữ năng động mở ra cho bạn.

Về cơ bản, trong Python không có giao diện nào nhiều như "sự hiểu biết rằng một số phương thức có thể gọi được". Khá lượn sóng và học thuật, phải không? Nó có nghĩa là bởi vì bạn gọi "nói" bạn rõ ràng mong rằng đối tượng nên có một phương pháp "nói". Đơn giản, huh? Điều này là rất Liskov-ian trong đó người dùng của một lớp xác định giao diện của nó, một khái niệm thiết kế tốt dẫn bạn vào TDD khỏe mạnh hơn.

Vì vậy, những gì còn lại là, như một poster lịch sự quản lý để tránh nói, một lừa chia sẻ mã. Bạn có thể viết cùng một hành vi vào mỗi lớp "con", nhưng điều đó sẽ là thừa. Dễ dàng hơn để kế thừa hoặc trộn trong chức năng đó là bất biến trên hệ thống phân cấp thừa kế. Nhỏ hơn, DRY-er code là tốt hơn nói chung.


5
2018-06-20 01:01





Không phải thừa kế rằng việc gõ vịt làm cho vô nghĩa, đó là các giao diện - giống như giao diện bạn đã chọn trong việc tạo ra một lớp động vật trừu tượng.

Nếu bạn đã sử dụng một lớp động vật giới thiệu một số hành vi thực sự cho con cháu của nó để sử dụng, sau đó chó và mèo lớp học giới thiệu một số hành vi bổ sung sẽ có một lý do cho cả hai lớp. Chỉ trong trường hợp lớp tổ tiên không có mã thực sự cho các lớp con cháu mà đối số của bạn là chính xác.

Bởi vì Python có thể trực tiếp biết khả năng của bất kỳ đối tượng nào, và bởi vì những khả năng đó có thể thay đổi được ngoài định nghĩa lớp, ý tưởng sử dụng giao diện trừu tượng thuần túy để "nói" chương trình những phương thức nào có thể được gọi là hơi vô nghĩa. Nhưng đó không phải là duy nhất, hoặc thậm chí chính, điểm thừa kế.


5
2017-10-15 22:28





Bạn có thể nhận được sự thừa kế trong Python và khá nhiều ngôn ngữ khác. Đó là tất cả về tái sử dụng mã và đơn giản hóa mã mặc dù.

Chỉ cần một thủ thuật ngữ nghĩa, nhưng sau khi xây dựng các lớp học và các lớp cơ sở của bạn, bạn thậm chí không cần phải biết những gì có thể với đối tượng của bạn để xem nếu bạn có thể làm điều đó.

Giả sử bạn có d là một Con chó được phân loại là Động vật.

command = raw_input("What do you want the dog to do?")
if command in dir(d): getattr(d,command)()

Nếu bất cứ điều gì người dùng nhập vào có sẵn, mã sẽ chạy phương thức thích hợp.

Sử dụng điều này, bạn có thể tạo ra bất kỳ sự kết hợp nào của quái vật lai Mammal / Reptile / Bird mà bạn muốn, và bây giờ bạn có thể làm cho nó nói 'Bark!' trong khi bay và thò lưỡi ra và nó sẽ xử lý nó đúng cách! Hãy vui vẻ với nó!


1
2018-06-24 23:40