× Giới thiệu Lịch khai giảng Tin tức Sản phẩm học viên

Lập trình hướng đối tượng (OOP) trong Python 3

20/03/2023 01:29

Bạn đã biết về lập trình hướng đối tượng chưa? Trong hướng dẫn này, bạn sẽ tìm hiểu kiến ​​thức cơ bản về lập trình hướng đối tượng trong Python.

Lập trình hướng đối tượng (OOP) là một phương pháp cấu trúc chương trình bằng cách gộp các thuộc tính và hành vi có liên quan vào các đối tượng riêng lẻ . Trong hướng dẫn này, bạn sẽ tìm hiểu kiến ​​thức cơ bản về lập trình hướng đối tượng trong Python.

Về mặt khái niệm, các đối tượng giống như các thành phần của một hệ thống. Hãy nghĩ về một chương trình như một loại dây chuyền lắp ráp của nhà máy. Tại mỗi bước của dây chuyền lắp ráp, một thành phần hệ thống xử lý một số vật liệu, cuối cùng chuyển đổi nguyên liệu thô thành sản phẩm hoàn chỉnh.

Một đối tượng chứa dữ liệu, chẳng hạn như nguyên liệu thô hoặc vật liệu đã được xử lý trước ở mỗi bước trên dây chuyền lắp ráp và hành vi, chẳng hạn như hành động mà mỗi thành phần dây chuyền lắp ráp thực hiện.

Lập trình hướng đối tượng trong Python là gì?

Lập trình hướng đối tượng là một mô hình lập trình cung cấp phương tiện cấu trúc chương trình sao cho các thuộc tính và hành vi được gói gọn trong các đối tượng riêng lẻ .

Chẳng hạn, một đối tượng có thể đại diện cho một người có các thuộc tính như tên, tuổi, địa chỉ và các hành vi như đi bộ, nói chuyện, thở và chạy. Hoặc nó có thể đại diện cho một email có các thuộc tính như danh sách người nhận, chủ đề và nội dung cũng như các hành vi như thêm tệp đính kèm và gửi.

Nói cách khác, lập trình hướng đối tượng là một cách tiếp cận để mô hình hóa những sự vật cụ thể, trong thế giới thực, như ô tô, cũng như mối quan hệ giữa các sự vật, như công ty và nhân viên, học sinh và giáo viên, v.v. OOP mô hình hóa các thực thể trong thế giới thực dưới dạng các đối tượng phần mềm có một số dữ liệu được liên kết với chúng và có thể thực hiện các chức năng nhất định.

Một mô hình lập trình phổ biến khác là lập trình thủ tục , cấu trúc chương trình giống như một công thức trong đó nó cung cấp một tập hợp các bước, dưới dạng các hàm và khối mã, chạy tuần tự để hoàn thành một tác vụ.

Điểm mấu chốt là các đối tượng nằm ở trung tâm của lập trình hướng đối tượng trong Python, không chỉ biểu diễn dữ liệu, như trong lập trình thủ tục, mà còn trong cấu trúc tổng thể của chương trình.

Xác định một lớp trong Python

Các cấu trúc dữ liệu nguyên thủy —như số, chuỗi và danh sách—được thiết kế để biểu diễn các mẩu thông tin đơn giản, chẳng hạn như giá của một quả táo, tên một bài thơ hoặc màu sắc yêu thích của bạn, tương ứng. Nếu bạn muốn đại diện cho một cái gì đó phức tạp hơn thì sao?

Ví dụ: giả sử bạn muốn theo dõi nhân viên trong một tổ chức. Bạn cần lưu trữ một số thông tin cơ bản về từng nhân viên như tên, tuổi, chức vụ, năm bắt đầu làm việc.

Một cách để làm điều này là đại diện cho mỗi nhân viên như một danh sách :

kirk = ["James Kirk", 34, "Captain", 2265]
spock = ["Spock", 35, "Science Officer", 2254]
mccoy = ["Leonard McCoy", "Chief Medical Officer", 2266]

Có một số vấn đề với cách tiếp cận này.

Đầu tiên, nó có thể làm cho các tệp mã lớn khó quản lý hơn. Nếu bạn tham chiếu kirk[0]cách nơi kirkkhai báo danh sách vài dòng, bạn có nhớ phần tử có chỉ số 0là tên nhân viên không?

Thứ hai, nó có thể gây ra lỗi nếu không phải mọi nhân viên đều có cùng số lượng phần tử trong danh sách. Trong mccoydanh sách trên, tuổi bị thiếu, vì vậy mccoy[1]sẽ trở lại "Chief Medical Officer"thay vì tuổi của Tiến sĩ McCoy.

Một cách tuyệt vời để làm cho loại mã này dễ quản lý hơn và dễ bảo trì hơn là sử dụng các lớp .

Các lớp so với các trường hợp

Các lớp được sử dụng để tạo cấu trúc dữ liệu do người dùng định nghĩa. Các lớp định nghĩa các hàm được gọi là các phương thức , xác định các hành vi và hành động mà một đối tượng được tạo từ lớp có thể thực hiện với dữ liệu của nó.

Trong hướng dẫn này, bạn sẽ tạo một Doglớp lưu trữ một số thông tin về các đặc điểm và hành vi mà một con chó có thể có.

Một lớp là một kế hoạch chi tiết về cách định nghĩa một thứ gì đó. Nó không thực sự chứa bất kỳ dữ liệu. Lớp Dogchỉ định rằng tên và tuổi là cần thiết để xác định một con chó, nhưng nó không chứa tên hoặc tuổi của bất kỳ con chó cụ thể nào.

Trong khi lớp là bản thiết kế, thì một thể hiện là một đối tượng được xây dựng từ một lớp và chứa dữ liệu thực. Một thể hiện của Doglớp không còn là một kế hoạch chi tiết nữa. Đó là một con chó thực sự có tên, như Miles, bốn tuổi.

Nói cách khác, lớp học giống như một biểu mẫu hoặc bảng câu hỏi. Một ví dụ giống như một biểu mẫu đã được điền thông tin. Giống như nhiều người có thể điền vào cùng một biểu mẫu với thông tin duy nhất của riêng họ, nhiều phiên bản có thể được tạo từ một lớp duy nhất.

Cách xác định một lớp

Tất cả các định nghĩa lớp bắt đầu bằng classtừ khóa, theo sau là tên của lớp và dấu hai chấm. Bất kỳ mã nào được thụt vào bên dưới định nghĩa lớp được coi là một phần của nội dung lớp.

Đây là một ví dụ về một Doglớp học:

class Dog:
    pass

Phần thân của Doglớp bao gồm một câu lệnh duy nhất: passtừ khóa. passthường được sử dụng như một trình giữ chỗ cho biết mã cuối cùng sẽ đi đến đâu. Nó cho phép bạn chạy mã này mà Python không báo lỗi.

Lớp này Doghiện không thú vị lắm, vì vậy hãy cải thiện nó một chút bằng cách xác định một số thuộc tính mà tất cả Dogcác đối tượng nên có. Có một số thuộc tính mà chúng ta có thể chọn, bao gồm tên, tuổi, màu lông và giống. Để giữ cho mọi thứ đơn giản, chúng tôi sẽ chỉ sử dụng tên và tuổi.

Các thuộc tính mà tất cả Dogcác đối tượng phải có được định nghĩa trong một phương thức gọi là .__init__(). Mỗi khi một Dogđối tượng mới được tạo, hãy đặt trạng thái.__init__() ban đầu của đối tượng bằng cách gán các giá trị thuộc tính của đối tượng. Nghĩa là, khởi tạo từng thể hiện mới của lớp..__init__()

Bạn có thể đưa ra .__init__()bất kỳ số lượng tham số nào, nhưng tham số đầu tiên sẽ luôn là một biến có tên self. Khi một thể hiện của lớp mới được tạo, thể hiện đó sẽ tự động được chuyển đến selftham số trong .__init__()để các thuộc tính mới có thể được xác định trên đối tượng.

Hãy cập nhật Doglớp với một .__init__()phương thức tạo .namevà .agethuộc tính:

class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age

Lưu ý rằng .__init__()chữ ký của phương thức được thụt vào bốn khoảng trắng. Phần thân của phương thức được thụt vào bởi tám khoảng trắng. Sự thụt đầu dòng này là cực kỳ quan trọng. Nó nói với Python rằng .__init__()phương thức này thuộc về Doglớp.

Trong phần thân của .__init__(), có hai câu lệnh sử dụng selfbiến:

  1. self.name = nametạo một thuộc tính được gọi namevà gán cho nó giá trị của nametham số.
  2. self.age = agetạo một thuộc tính được gọi agevà gán cho nó giá trị của agetham số.

Các thuộc tính được tạo trong .__init__()được gọi là các thuộc tính thể hiện . Giá trị của thuộc tính thể hiện là dành riêng cho một thể hiện cụ thể của lớp. Tất cả Dogcác đối tượng đều có tên và tuổi, nhưng các giá trị cho thuộc tính namevà agesẽ thay đổi tùy theo Dogtrường hợp.

Mặt khác, thuộc tính lớp là thuộc tính có cùng giá trị cho tất cả các thể hiện của lớp. Bạn có thể xác định thuộc tính lớp bằng cách gán giá trị cho tên biến bên ngoài .__init__().

Ví dụ: Doglớp sau có thuộc tính lớp được gọi speciesvới giá trị "Canis familiaris":

class Dog:
    # Class attribute
    species = "Canis familiaris"

    def __init__(self, name, age):
        self.name = name
        self.age = age

Các thuộc tính của lớp được xác định trực tiếp bên dưới dòng đầu tiên của tên lớp và được thụt vào trong bốn khoảng trắng. Chúng phải luôn được gán một giá trị ban đầu. Khi một thể hiện của lớp được tạo, các thuộc tính của lớp sẽ tự động được tạo và gán cho các giá trị ban đầu của chúng.

Sử dụng các thuộc tính của lớp để xác định các thuộc tính phải có cùng giá trị cho mọi thể hiện của lớp. Sử dụng các thuộc tính phiên bản cho các thuộc tính thay đổi từ phiên bản này sang phiên bản khác.

Khởi tạo một đối tượng trong Python

Mở cửa sổ tương tác của IDLE và gõ như sau:

>>>
>>> class Dog:
...     pass

Điều này tạo ra một lớp mới Dogkhông có thuộc tính hoặc phương thức.

Tạo một đối tượng mới từ một lớp được gọi là khởi tạo một đối tượng. Bạn có thể khởi tạo một Dogđối tượng mới bằng cách nhập tên của lớp, tiếp theo là mở và đóng dấu ngoặc đơn:

>>>
>>> Dog()
<__main__.Dog object at 0x106702d30>

Bây giờ bạn có một Dogđối tượng mới tại 0x106702d30. Chuỗi ký tự và số trông ngộ nghĩnh này là một địa chỉ bộ nhớ cho biết Dogđối tượng được lưu trữ ở đâu trong bộ nhớ máy tính của bạn. Lưu ý rằng địa chỉ bạn nhìn thấy trên màn hình sẽ khác.

Bây giờ khởi tạo một Dogđối tượng thứ hai:

>>>
>>> Dog()
<__main__.Dog object at 0x0004ccc90>

Phiên bản mới Dogđược đặt tại một địa chỉ bộ nhớ khác. Đó là bởi vì nó là một thể hiện hoàn toàn mới và hoàn toàn độc nhất so với Dogđối tượng đầu tiên mà bạn đã khởi tạo.

Để xem điều này theo cách khác, hãy gõ như sau:

>>>
>>> a = Dog()
>>> b = Dog()
>>> a == b
False

Trong mã này, bạn tạo hai Dogđối tượng mới và gán chúng cho các biến avà b. Khi bạn so sánh avà bsử dụng ==toán tử, kết quả là False. Mặc dù avà bcả hai đều là thể hiện của Doglớp, nhưng chúng đại diện cho hai đối tượng riêng biệt trong bộ nhớ.

Thuộc tính lớp và trường hợp

Bây giờ hãy tạo một lớp mới Dogvới một thuộc tính lớp được gọi .speciesvà hai thuộc tính thể hiện được gọi .namelà và .age:

>>>
>>> class Dog:
...     species = "Canis familiaris"
...     def __init__(self, name, age):
...         self.name = name
...         self.age = age

Để khởi tạo các đối tượng của Doglớp này, bạn cần cung cấp các giá trị cho namevà age. Nếu bạn không, thì Python sẽ tăng một TypeError:

>>>
>>> Dog()
Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    Dog()
TypeError: __init__() missing 2 required positional arguments: 'name' and 'age'

Để truyền đối số cho tham số namevà age, hãy đặt giá trị vào dấu ngoặc đơn sau tên lớp:

>>>
>>> buddy = Dog("Buddy", 9)
>>> miles = Dog("Miles", 4)

Điều này tạo ra hai phiên bản mới Dog—một dành cho chú chó chín tuổi tên Buddy và một dành cho chú chó bốn tuổi tên Miles.

DogPhương thức của lớp có .__init__()ba tham số, vậy tại sao chỉ có hai đối số được truyền cho nó trong ví dụ?

Khi bạn khởi tạo một Dogđối tượng, Python sẽ tạo một thể hiện mới và chuyển nó tới tham số đầu tiên của .__init__(). Điều này về cơ bản sẽ loại bỏ selftham số, vì vậy bạn chỉ cần lo lắng về tham số namevà age.

Sau khi tạo Dogphiên bản, bạn có thể truy cập các thuộc tính phiên bản của chúng bằng cách sử dụng ký hiệu dấu chấm :

>>>
>>> buddy.name
'Buddy'
>>> buddy.age
9

>>> miles.name
'Miles'
>>> miles.age
4

Bạn có thể truy cập các thuộc tính của lớp theo cùng một cách:

>>>
>>> buddy.species
'Canis familiaris'

Một trong những lợi thế lớn nhất của việc sử dụng các lớp để sắp xếp dữ liệu là các phiên bản được đảm bảo có các thuộc tính mà bạn mong đợi. Tất cả Dogcác phiên bản đều có .species.namevà .agecác thuộc tính, vì vậy bạn có thể yên tâm sử dụng các thuộc tính đó khi biết rằng chúng sẽ luôn trả về một giá trị.

Mặc dù các thuộc tính được đảm bảo tồn tại nhưng giá trị của chúng có thể thay đổi linh hoạt:

>>>
>>> buddy.age = 10
>>> buddy.age
10

>>> miles.species = "Felis silvestris"
>>> miles.species
'Felis silvestris'

Trong ví dụ này, bạn thay đổi .agethuộc tính của buddyđối tượng thành 10. Sau đó, bạn thay đổi .speciesthuộc tính của milesđối tượng "Felis silvestris"thành loài mèo. Điều đó khiến Miles trở thành một con chó khá kỳ lạ, nhưng nó là Python hợp lệ!

Điểm mấu chốt ở đây là các đối tượng tùy chỉnh có thể thay đổi theo mặc định. Một đối tượng có thể thay đổi nếu nó có thể được thay đổi động. Ví dụ: danh sách và từ điển có thể thay đổi, nhưng chuỗi và bộ dữ liệu là bất biến .

Phương thức sơ thẩm

Các phương thức sơ thẩm là các hàm được định nghĩa bên trong một lớp và chỉ có thể được gọi từ một thể hiện của lớp đó. Giống như .__init__(), tham số đầu tiên của một phương thức thể hiện luôn là self.

Mở một cửa sổ soạn thảo mới trong IDLE và gõ vào Doglớp sau:

class Dog:
    species = "Canis familiaris"

    def __init__(self, name, age):
        self.name = name
        self.age = age

    # Instance method
    def description(self):
        return f"{self.name} is {self.age} years old"

    # Another instance method
    def speak(self, sound):
        return f"{self.name} says {sound}"

Lớp này Dogcó hai phương thức thể hiện:

  1. .description()trả về một chuỗi hiển thị tên và tuổi của con chó.
  2. .speak()có một tham số được gọi soundvà trả về một chuỗi chứa tên của con chó và âm thanh mà con chó tạo ra.

Lưu Doglớp đã sửa đổi vào một tệp có tên dog.pyvà nhấn F5để chạy chương trình. Sau đó, mở cửa sổ tương tác và nhập nội dung sau để xem các phương thức phiên bản của bạn đang hoạt động:

>>>
>>> miles = Dog("Miles", 4)

>>> miles.description()
'Miles is 4 years old'

>>> miles.speak("Woof Woof")
'Miles says Woof Woof'

>>> miles.speak("Bow Wow")
'Miles says Bow Wow'

Trong lớp trên Dog.description()trả về một chuỗi chứa thông tin về Dogthể hiện miles. Khi viết các lớp của riêng bạn, bạn nên có một phương thức trả về một chuỗi chứa thông tin hữu ích về một thể hiện của lớp. Tuy nhiên, không phải là cách Pythonic.description() nhất để làm điều này.

Khi bạn tạo một listđối tượng, bạn có thể sử dụng print()để hiển thị một chuỗi giống như danh sách:

>>>
>>> names = ["Fletcher", "David", "Dan"]
>>> print(names)
['Fletcher', 'David', 'Dan']

Hãy xem điều gì xảy ra khi bạn là print()đối milestượng:

>>>
>>> print(miles)
<__main__.Dog object at 0x00aeff70>

Khi bạn print(miles), bạn nhận được một thông báo bí ẩn cho bạn biết đó mileslà một Dogđối tượng tại địa chỉ bộ nhớ 0x00aeff70. Thông báo này không hữu ích lắm. Bạn có thể thay đổi nội dung được in bằng cách xác định một phương thức thể hiện đặc biệt có tên là .__str__().

Trong cửa sổ soạn thảo, đổi tên phương thức Dogcủa lớp .description()thành .__str__():

class Dog:
    # Leave other parts of Dog class as-is

    # Replace .description() with __str__()
    def __str__(self):
        return f"{self.name} is {self.age} years old"

Lưu tệp và nhấn F5. Bây giờ, khi bạn print(miles), bạn sẽ nhận được kết quả đầu ra thân thiện hơn nhiều:

>>>
>>> miles = Dog("Miles", 4)
>>> print(miles)
'Miles is 4 years old'

Các phương thức như .__init__()và .__str__()được gọi là các phương thức dunder vì chúng bắt đầu và kết thúc bằng hai dấu gạch dưới. Có nhiều phương thức khác mà bạn có thể sử dụng để tùy chỉnh các lớp trong Python. Mặc dù một chủ đề quá nâng cao đối với một cuốn sách Python mới bắt đầu, nhưng việc hiểu các phương thức dunder là một phần quan trọng để thành thạo lập trình hướng đối tượng trong Python.

Trong phần tiếp theo, bạn sẽ thấy cách nâng cao kiến ​​thức của mình thêm một bước nữa và tạo các lớp từ các lớp khác.