Thứ sáu, 09/03/2018 | 00:00 GMT+7

Hiểu kế thừa lớp trong Python 3

Lập trình hướng đối tượng tạo ra các mẫu mã có thể tái sử dụng để giảm bớt sự dư thừa trong các dự án phát triển. Một cách mà lập trình hướng đối tượng đạt được mã có thể tái chế là thông qua kế thừa, khi một lớp con có thể tận dụng mã từ một lớp cơ sở khác.

Hướng dẫn này sẽ đi qua một số khía cạnh chính của kế thừa trong Python, bao gồm cách lớp cha và lớp con hoạt động, cách overrides các phương thức và thuộc tính, cách sử dụng hàm super() và cách sử dụng đa kế thừa.

Thừa kế là gì?

Kế thừa là khi một lớp sử dụng mã được xây dựng trong một lớp khác. Nếu ta nghĩ về sự kế thừa về mặt sinh học, ta có thể nghĩ về một đứa trẻ thừa hưởng những đặc điểm nhất định từ cha mẹ của chúng. Tức là một đứa trẻ có thể thừa hưởng chiều cao hoặc màu mắt của cha mẹ. Trẻ em cũng có thể có cùng họ với cha mẹ của chúng.

Các lớp được gọi là lớp con hoặc lớp con kế thừa các phương thức và biến từ các lớp cha hoặc lớp cơ sở .

Ta có thể nghĩ về một lớp cha có tên là Parentcác biến lớp cho last_name , heighteye_color mà lớp Child sẽ kế thừa từ Parent .

Bởi vì Child lớp con được kế thừa từ các Parent lớp cơ sở, các Child lớp có thể tái sử dụng mã của Parent , cho phép các lập trình viên sử dụng ít dòng mã và giảm dự phòng.

Lớp dành cho phụ huynh

Lớp cha hoặc lớp cơ sở tạo ra một mẫu mà từ đó các lớp con hoặc lớp con có thể dựa trên. Các lớp cha cho phép ta tạo các lớp con thông qua kế thừa mà không cần phải viết lại cùng một đoạn mã mỗi lần. Bất kỳ lớp nào cũng có thể được tạo thành một lớp cha, vì vậy chúng là mỗi lớp đầy đủ chức năng theo đúng nghĩa của chúng, thay vì chỉ là các khuôn mẫu.

Giả sử ta có một lớp cha Bank_account chung có các lớp con Personal_accountBusiness_account . Nhiều phương thức giữa account cá nhân và account doanh nghiệp sẽ giống nhau, chẳng hạn như phương thức rút và gửi tiền, vì vậy những phương thức đó có thể thuộc về lớp mẹ của Bank_account . Lớp con Business_account sẽ có các phương thức dành riêng cho nó, bao gồm cả cách thu thập các biểu mẫu và profile kinh doanh, cũng như biến employee_identification_number

Tương tự như vậy, một Animal lớp có thể đã eating()sleeping() phương pháp, và một Snake lớp con có thể bao gồm cụ thể của riêng nó hissing()slithering() phương pháp.

Hãy tạo một lớp cha Fish mà sau này ta sẽ sử dụng để xây dựng các loại cá làm lớp con của nó. Mỗi loài cá này sẽ có họ và tên ngoài các đặc điểm.

Ta sẽ tạo một file mới có tên là fish.py và bắt đầu với phương thức khởi tạo __init__() , phương thức này ta sẽ điền với các biến lớp first_namelast_name cho mỗi đối tượng hoặc lớp con Fish .

fish.py
class Fish:     def __init__(self, first_name, last_name="Fish"):         self.first_name = first_name         self.last_name = last_name 

Ta đã khởi tạo biến last_name với chuỗi "Fish" vì ta biết rằng hầu hết các loài cá sẽ có tên này là họ của chúng.

Hãy cũng thêm một số phương pháp khác:

fish.py
class Fish:     def __init__(self, first_name, last_name="Fish"):         self.first_name = first_name         self.last_name = last_name      def swim(self):         print("The fish is swimming.")      def swim_backwards(self):         print("The fish can swim backwards.") 

Ta đã thêm các phương pháp swim()swim_backwards() cho Fish lớp, để mỗi lớp con cũng sẽ có thể tận dụng các phương pháp này.

Vì hầu hết các loài cá ta sẽ tạo ra được coi là cá xương (vì chúng có bộ xương làm từ xương) chứ không phải cá sụn (vì chúng có bộ xương làm từ sụn), ta có thể thêm một số các thuộc tính khác cho phương thức __init__() :

fish.py
class Fish:     def __init__(self, first_name, last_name="Fish",                  skeleton="bone", eyelids=False):         self.first_name = first_name         self.last_name = last_name         self.skeleton = skeleton         self.eyelids = eyelids      def swim(self):         print("The fish is swimming.")      def swim_backwards(self):         print("The fish can swim backwards.") 

Việc xây dựng một lớp cha tuân theo cùng một phương pháp như xây dựng bất kỳ lớp nào khác, ngoại trừ việc ta đang suy nghĩ về những phương thức mà các lớp con sẽ có thể sử dụng khi ta tạo chúng.

Lớp trẻ em

Các lớp con hoặc lớp con là các lớp sẽ kế thừa từ lớp cha. Điều đó nghĩa là mỗi lớp con sẽ có thể sử dụng các phương thức và biến của lớp cha.

Ví dụ, một lớp con Goldfish phân lớp con Fish sẽ có thể sử dụng phương thức swim() khai báo trong Fish mà không cần khai báo nó.

Ta có thể coi mỗi lớp con là một lớp của lớp cha. Đó là, nếu ta có một lớp con gọi là Rhombus và một lớp cha mẹ gọi Parallelogram , ta có thể nói rằng một Rhombus là một Parallelogram , cũng giống như một Goldfish là một Fish .

Dòng đầu tiên của lớp con trông hơi khác so với các lớp không phải con vì bạn phải chuyển lớp cha vào lớp con dưới dạng tham số:

class Trout(Fish): 

Lớp Trout là con của lớp Fish . Ta biết điều này vì có từ Fish trong ngoặc đơn.

Với các lớp con, ta có thể chọn thêm nhiều phương thức hơn, overrides các phương thức mẹ hiện có hoặc chỉ cần chấp nhận các phương thức mẹ mặc định với từ khóa pass , điều mà ta sẽ thực hiện trong trường hợp này:

fish.py
... class Trout(Fish):     pass 

Bây giờ ta có thể tạo một đối tượng Trout mà không cần phải xác định bất kỳ phương thức bổ sung nào.

fish.py
... class Trout(Fish):     pass  terry = Trout("Terry") print(terry.first_name + " " + terry.last_name) print(terry.skeleton) print(terry.eyelids) terry.swim() terry.swim_backwards() 

Ta đã tạo một đối tượng Trout terry sử dụng từng phương thức của lớp Fish mặc dù ta không định nghĩa những phương thức đó trong lớp con Trout . Ta chỉ cần chuyển giá trị của "Terry" cho biến first_name vì tất cả các biến khác đã được khởi tạo.

Khi ta chạy chương trình, ta sẽ nhận được kết quả sau:

Output
Terry Fish bone False The fish is swimming. The fish can swim backwards.

Tiếp theo, hãy tạo một lớp con khác bao gồm phương thức riêng của nó. Ta sẽ gọi lớp này là Clownfish và phương pháp đặc biệt của nó sẽ cho phép nó sống chung với hải quỳ:

fish.py
... class Clownfish(Fish):      def live_with_anemone(self):         print("The clownfish is coexisting with sea anemone.") 

Tiếp theo, hãy tạo một đối tượng Clownfish để xem nó hoạt động như thế nào:

fish.py
... casey = Clownfish("Casey") print(casey.first_name + " " + casey.last_name) casey.swim() casey.live_with_anemone() 

Khi ta chạy chương trình, ta sẽ nhận được kết quả sau:

Output
Casey Fish The fish is swimming. The clownfish is coexisting with sea anemone.

Những buổi biểu diễn kết quả rằng Clownfish đối tượng casey có khả năng sử dụng Fish phương pháp __init__()swim() cũng như phương pháp lớp con của live_with_anemone() .

Nếu ta cố gắng sử dụng phương thức live_with_anemone() trong đối tượng Trout , ta sẽ nhận được lỗi:

Output
terry.live_with_anemone() AttributeError: 'Trout' object has no attribute 'live_with_anemone'

Điều này là do phương thức live_with_anemone() chỉ thuộc về lớp con Clownfish chứ không phải lớp cha Fish .

Các lớp con kế thừa các phương thức của lớp cha mà nó thuộc về, vì vậy mỗi lớp con có thể sử dụng các phương thức đó trong các chương trình.

Ghi đè các phương thức root

Lúc này, ta đã xem xét lớp con Trout đã sử dụng từ khóa pass để kế thừa tất cả các hành vi của lớp cha Fish và một lớp con khác là Clownfish kế thừa tất cả các hành vi của lớp cha và cũng tạo ra phương thức duy nhất của riêng nó là cụ thể cho lớp trẻ. Tuy nhiên, đôi khi ta sẽ muốn sử dụng một số hành vi của lớp cha nhưng không phải tất cả chúng. Khi ta thay đổi các phương thức của lớp cha, ta sẽ overrides chúng.

Khi xây dựng các lớp cha và con, điều quan trọng là phải ghi nhớ thiết kế chương trình để việc overrides không tạo ra mã không cần thiết hoặc thừa.

Ta sẽ tạo một lớp con Shark của lớp cha Fish . Bởi vì ta đã tạo ra lớp Fish với ý tưởng rằng ta sẽ tạo ra chủ yếu là cá có xương, ta sẽ phải thực hiện các điều chỉnh cho lớp Shark thay vào đó là cá sụn. Về thiết kế chương trình, nếu ta có nhiều hơn một loài cá không xương, ta rất có thể cần tạo các lớp riêng biệt cho từng loại trong hai loại cá này.

Cá mập, không giống như cá có xương, có bộ xương bằng sụn thay vì xương. Chúng cũng có mí mắt và không có khả năng bơi ngược. Tuy nhiên, cá mập có thể tự di chuyển về phía sau bằng cách chìm xuống.

Về vấn đề này, ta sẽ overrides phương thức khởi tạo __init__() và phương thức swim_backwards() . Ta không cần phải sửa đổi phương thức swim() vì cá mập là loài cá có thể bơi. Hãy cùng nhìn lại lớp học nhí này:

fish.py
... class Shark(Fish):     def __init__(self, first_name, last_name="Shark",                  skeleton="cartilage", eyelids=True):         self.first_name = first_name         self.last_name = last_name         self.skeleton = skeleton         self.eyelids = eyelids      def swim_backwards(self):         print("The shark cannot swim backwards, but can sink backwards.") 

Ta đã overrides các tham số được khởi tạo trong phương thức __init__() , do đó biến last_name hiện được đặt bằng chuỗi "Shark" , biến skeleton hiện được đặt bằng chuỗi "cartilage" và biến eyelids hiện được đặt thành giá trị Boolean True . Mỗi cá thể của lớp cũng có thể overrides các tham số này.

Phương thức swim_backwards() bây giờ in ra một chuỗi khác với chuỗi trong lớp cha Fish vì cá mập không thể bơi ngược theo cách mà cá xương có thể.

Bây giờ ta có thể tạo một thể hiện của lớp con Shark , lớp này vẫn sẽ sử dụng phương thức swim() của lớp cha Fish :

fish.py
... sammy = Shark("Sammy") print(sammy.first_name + " " + sammy.last_name) sammy.swim() sammy.swim_backwards() print(sammy.eyelids) print(sammy.skeleton) 

Khi ta chạy mã này, ta sẽ nhận được kết quả sau:

Output
Sammy Shark The fish is swimming. The shark cannot swim backwards, but can sink backwards. True cartilage

Lớp con Shark đã swim_backwards() thành công các phương thức __init__()swim_backwards() của lớp cha Fish , đồng thời kế thừa phương thức swim() của lớp cha.

Khi có một số giới hạn các lớp con duy nhất hơn những lớp khác, việc overrides các phương thức của lớp cha có thể tỏ ra hữu ích.

Hàm super()

Với hàm super() , bạn có thể truy cập vào các phương thức kế thừa đã bị overrides trong một đối tượng lớp.

Khi ta sử dụng hàm super() , ta đang gọi một phương thức cha thành một phương thức con để sử dụng nó. Ví dụ: ta có thể cần overrides một khía cạnh của phương thức mẹ bằng một số chức năng nhất định, nhưng sau đó gọi phần còn lại của phương thức mẹ ban đầu để kết thúc phương thức.

Trong một chương trình phân loại học sinh, ta có thể cần có một lớp con cho Weighted_grade kế thừa từ Grade cha của Lớp. Trong lớp con Weighted_grade , ta có thể cần overrides phương thức calculate_grade() của lớp cha để bao gồm chức năng tính toán lớp có trọng số, nhưng vẫn giữ phần còn lại của chức năng của lớp root . Bằng cách gọi hàm super() ta có thể đạt được điều này.

Hàm super() được sử dụng phổ biến nhất trong phương thức __init__() vì đó là nơi bạn rất có thể cần thêm một số tính duy nhất vào lớp con và sau đó hoàn tất khởi tạo từ lớp cha.

Để xem cách này hoạt động như thế nào, hãy sửa đổi lớp con Trout của ta . Vì cá hồi thường là cá nước ngọt, hãy thêm một biến water vào phương thức __init__() và đặt nó bằng chuỗi "freshwater" , nhưng sau đó duy trì phần còn lại của các biến và tham số của lớp cha:

fish.py
... class Trout(Fish):     def __init__(self, water = "freshwater"):         self.water = water         super().__init__(self) ... 

Ta đã overrides phương thức __init__() trong lớp con Trout , cung cấp một cách triển khai khác của __init__() đã được xác định bởi lớp cha của nó là Fish . Trong phương thức __init__() của lớp Trout ta đã gọi rõ ràng phương thức __init__() của lớp Fish .

Bởi vì ta đã overrides phương thức, ta không cần phải chuyển first_name vào làm tham số cho Trout và nếu ta đã chuyển một tham số, ta sẽ đặt lại freshwater thay thế. Do đó, ta sẽ khởi tạo first_name bằng cách gọi biến trong cá thể đối tượng của ta .

Bây giờ ta có thể gọi các biến đã khởi tạo của lớp cha và cũng có thể sử dụng biến con duy nhất. Hãy sử dụng điều này trong một ví dụ của Trout :

fish.py
... terry = Trout()  # Initialize first name terry.first_name = "Terry"  # Use parent __init__() through super() print(terry.first_name + " " + terry.last_name) print(terry.eyelids)  # Use child __init__() override print(terry.water)  # Use parent swim() method terry.swim() 
Output
Terry Fish False freshwater The fish is swimming.

Kết quả kết quả cho thấy rằng đối tượng terry của lớp con Trout có thể sử dụng cả biến water __init__() dành riêng cho con trong khi cũng có thể gọi các biến Fish mẹ __init__() của first_name , last_nameeyelids .

Hàm super() hợp trong Python cho phép ta sử dụng các phương thức của lớp cha ngay cả khi overrides các khía cạnh nhất định của các phương thức đó trong các lớp con của ta .

Nhiều người thừa kế

Đa kế thừa là khi một lớp có thể kế thừa các thuộc tính và phương thức từ nhiều hơn một lớp cha. Điều này có thể cho phép các chương trình giảm bớt sự dư thừa, nhưng nó cũng có thể tạo ra một lượng phức tạp cũng như sự mơ hồ nhất định, vì vậy nó nên được thực hiện một cách thận trọng với thiết kế chương trình tổng thể.

Để hiển thị cách hoạt động của đa kế thừa, hãy tạo một lớp con Coral_reef hơn là kế thừa từ lớp Coral và lớp Sea_anemone . Ta có thể tạo một phương thức trong mỗi phương thức và sau đó sử dụng từ khóa pass trong lớp con Coral_reef :

San hô_reef.py
class Coral:      def community(self):         print("Coral lives in a community.")   class Anemone:      def protect_clownfish(self):         print("The anemone is protecting the clownfish.")   class CoralReef(Coral, Anemone):     pass 

Lớp Coral có một phương thức gọi là community() in một dòng và lớp Anemone có một phương thức tên là protect_clownfish() in một dòng khác. Sau đó, ta gọi là cả hai lớp vào thừa kế tuple . Điều này nghĩa là CoralReef đang kế thừa từ hai lớp cha.

Bây giờ hãy khởi tạo một đối tượng CoralReef :

San hô_reef.py
... great_barrier = CoralReef() great_barrier.community() great_barrier.protect_clownfish() 

Đối tượng great_barrier được đặt làm đối tượng CoralReef và có thể sử dụng các phương thức trong cả hai lớp cha. Khi ta chạy chương trình, ta sẽ thấy kết quả sau:

Output
Coral lives in a community. The anemone is protecting the clownfish.

Kết quả cho thấy rằng các phương thức từ cả hai lớp cha đã được sử dụng hiệu quả trong lớp con.

Đa kế thừa cho phép ta sử dụng mã từ nhiều hơn một lớp cha trong một lớp con. Nếu cùng một phương thức được định nghĩa trong nhiều phương thức cha, thì lớp con sẽ sử dụng phương thức của phương thức cha đầu tiên được khai báo trong danh sách bộ của nó.

Mặc dù nó được dùng một cách hiệu quả, đa kế thừa nên được thực hiện cẩn thận để các chương trình của ta không trở nên mơ hồ và khó hiểu đối với các lập trình viên khác.

Kết luận

Hướng dẫn này đã xây dựng các lớp cha và lớp con, overrides các phương thức và thuộc tính cha trong các lớp con, sử dụng hàm super() và cho phép các lớp con kế thừa từ nhiều lớp cha.

Kế thừa trong mã hóa hướng đối tượng có thể cho phép tuân theo nguyên tắc KHÔ (không lặp lại chính mình) trong phát triển phần mềm, cho phép thực hiện nhiều việc hơn với ít mã và lặp lại hơn. Tính kế thừa cũng buộc các lập trình viên phải suy nghĩ về cách họ thiết kế các chương trình mà họ đang tạo ra đảm bảo rằng mã hiệu quả và rõ ràng.


Tags:

Các tin liên quan

Kế thừa lớp trong Python 3
2018-03-09
Cách viết lệnh Slash với Flask và Python 3 trên Ubuntu 16.04
2018-02-06
Cách cài đặt Phân phối Python Anaconda trên Ubuntu 16.04
2017-12-27
Cách cài đặt Python 3 và thiết lập môi trường lập trình cục bộ trên Ubuntu 16.04
2017-12-20
Cách cài đặt Python 3 và thiết lập môi trường lập trình cục bộ trên Ubuntu 16.04
2017-12-20
Hiểu từ điển bằng Python 3
2017-11-21
Cách sử dụng * args và ** kwargs trong Python 3
2017-11-20
Cách làm việc với control panel tương tác Python
2017-06-21
Cách viết câu lệnh có điều kiện trong Python 3
2017-06-16
Cách cài đặt pygame và tạo mẫu để phát triển trò chơi bằng Python 3
2017-06-15