Python 3 - Hướng đối tượng

Python đã là một ngôn ngữ hướng đối tượng kể từ thời điểm nó tồn tại. Do đó, việc tạo và sử dụng các lớp và đối tượng hoàn toàn dễ dàng. Chương này giúp bạn trở thành chuyên gia trong việc sử dụng hỗ trợ lập trình hướng đối tượng của Python.

Nếu bạn chưa có bất kỳ kinh nghiệm nào về lập trình hướng đối tượng (OO) trước đây, bạn có thể muốn tham khảo một khóa học giới thiệu về nó hoặc ít nhất là một hướng dẫn nào đó để bạn nắm được các khái niệm cơ bản.

Tuy nhiên, đây là một giới thiệu nhỏ về Lập trình hướng đối tượng (OOP) để giúp bạn -

Tổng quan về thuật ngữ OOP

  • Class- Một nguyên mẫu do người dùng định nghĩa cho một đối tượng xác định một tập hợp các thuộc tính đặc trưng cho bất kỳ đối tượng nào của lớp. Các thuộc tính là thành viên dữ liệu (biến lớp và biến phiên bản) và phương thức, được truy cập thông qua ký hiệu dấu chấm.

  • Class variable- Một biến được chia sẻ bởi tất cả các trường hợp của một lớp. Các biến lớp được định nghĩa trong một lớp nhưng bên ngoài bất kỳ phương thức nào của lớp. Các biến lớp không được sử dụng thường xuyên như các biến cá thể.

  • Data member - Một biến lớp hoặc biến thể hiện chứa dữ liệu được liên kết với một lớp và các đối tượng của nó.

  • Function overloading- Việc gán nhiều hơn một hành vi cho một chức năng cụ thể. Thao tác được thực hiện khác nhau tùy theo loại đối tượng hoặc đối số có liên quan.

  • Instance variable - Một biến được định nghĩa bên trong một phương thức và chỉ thuộc về thể hiện hiện tại của một lớp.

  • Inheritance - Việc chuyển các đặc tính của một lớp sang các lớp khác có nguồn gốc từ nó.

  • Instance- Một đối tượng riêng lẻ của một giai cấp nhất định. Ví dụ, một đối tượng thuộc về Vòng tròn lớp là một thể hiện của Vòng tròn lớp.

  • Instantiation - Việc tạo một thể hiện của một lớp.

  • Method - Một loại chức năng đặc biệt được định nghĩa trong định nghĩa lớp.

  • Object- Một thể hiện duy nhất của cấu trúc dữ liệu được định nghĩa bởi lớp của nó. Một đối tượng bao gồm cả thành viên dữ liệu (biến lớp và biến cá thể) và phương thức.

  • Operator overloading - Việc gán nhiều hơn một chức năng cho một toán tử cụ thể.

Tạo lớp học

Câu lệnh lớp tạo ra một định nghĩa lớp mới. Tên của lớp ngay sau lớp từ khóa, theo sau là dấu hai chấm như sau:

class ClassName:
   'Optional class documentation string'
   class_suite
  • Lớp có một chuỗi tài liệu, có thể được truy cập qua ClassName.__doc__.

  • Các class_suite bao gồm tất cả các câu lệnh thành phần xác định các thành viên lớp, thuộc tính dữ liệu và chức năng.

Thí dụ

Sau đây là một ví dụ về một lớp Python đơn giản:

class Employee:
   'Common base class for all employees'
   empCount = 0

   def __init__(self, name, salary):
      self.name = name
      self.salary = salary
      Employee.empCount += 1
   
   def displayCount(self):
     print ("Total Employee %d" % Employee.empCount)

   def displayEmployee(self):
      print ("Name : ", self.name,  ", Salary: ", self.salary)
  • Biến empCount là một biến lớp có giá trị được chia sẻ giữa tất cả các trường hợp của a trong lớp này. Điều này có thể được truy cập dưới dạng Employee.empCount từ bên trong lớp hoặc bên ngoài lớp.

  • Phương thức đầu tiên __init __ () là một phương thức đặc biệt, được gọi là phương thức khởi tạo hoặc phương thức khởi tạo lớp mà Python gọi khi bạn tạo một thể hiện mới của lớp này.

  • Bạn khai báo các phương thức lớp khác giống như các hàm bình thường với ngoại lệ là đối số đầu tiên của mỗi phương thức là chính nó . Python thêm tự đối số vào danh sách cho bạn; bạn không cần phải bao gồm nó khi bạn gọi các phương thức.

Tạo đối tượng phiên bản

Để tạo các thể hiện của một lớp, bạn gọi lớp bằng cách sử dụng tên lớp và chuyển vào bất kỳ đối số nào mà phương thức __init__ của nó chấp nhận.

This would create first object of Employee class
emp1 = Employee("Zara", 2000)
This would create second object of Employee class
emp2 = Employee("Manni", 5000)

Truy cập các thuộc tính

Bạn truy cập các thuộc tính của đối tượng bằng toán tử dấu chấm với đối tượng. Biến lớp sẽ được truy cập bằng tên lớp như sau:

emp1.displayEmployee()
emp2.displayEmployee()
print ("Total Employee %d" % Employee.empCount)

Bây giờ, đặt tất cả các khái niệm lại với nhau -

#!/usr/bin/python3

class Employee:
   'Common base class for all employees'
   empCount = 0

   def __init__(self, name, salary):
      self.name = name
      self.salary = salary
      Employee.empCount += 1
   
   def displayCount(self):
     print ("Total Employee %d" % Employee.empCount)

   def displayEmployee(self):
      print ("Name : ", self.name,  ", Salary: ", self.salary)


#This would create first object of Employee class"
emp1 = Employee("Zara", 2000)
#This would create second object of Employee class"
emp2 = Employee("Manni", 5000)
emp1.displayEmployee()
emp2.displayEmployee()
print ("Total Employee %d" % Employee.empCount)

Khi đoạn mã trên được thực thi, nó tạo ra kết quả sau:

Name :  Zara ,Salary:  2000
Name :  Manni ,Salary:  5000
Total Employee 2

Bạn có thể thêm, xóa hoặc sửa đổi các thuộc tính của các lớp và đối tượng bất kỳ lúc nào -

emp1.salary = 7000  # Add an 'salary' attribute.
emp1.name = 'xyz'  # Modify 'age' attribute.
del emp1.salary  # Delete 'age' attribute.

Thay vì sử dụng các câu lệnh bình thường để truy cập các thuộc tính, bạn có thể sử dụng các hàm sau:

  • Các getattr(obj, name[, default]) - để truy cập thuộc tính của đối tượng.

  • Các hasattr(obj,name) - để kiểm tra xem một thuộc tính có tồn tại hay không.

  • Các setattr(obj,name,value)- để thiết lập một thuộc tính. Nếu thuộc tính không tồn tại, thì nó sẽ được tạo.

  • Các delattr(obj, name) - để xóa một thuộc tính.

hasattr(emp1, 'salary')    # Returns true if 'salary' attribute exists
getattr(emp1, 'salary')    # Returns value of 'salary' attribute
setattr(emp1, 'salary', 7000) # Set attribute 'salary' at 7000
delattr(emp1, 'salary')    # Delete attribute 'salary'

Thuộc tính lớp tích hợp

Mọi lớp Python đều tuân theo các thuộc tính tích hợp sẵn và chúng có thể được truy cập bằng toán tử dot giống như bất kỳ thuộc tính nào khác -

  • __dict__ - Từ điển chứa không gian tên của lớp.

  • __doc__ - Chuỗi tài liệu lớp hoặc không có, nếu không xác định.

  • __name__ - Tên lớp.

  • __module__- Tên mô-đun mà lớp được định nghĩa. Thuộc tính này là "__main__" ở chế độ tương tác.

  • __bases__ - Một bộ giá trị có thể trống chứa các lớp cơ sở, theo thứ tự xuất hiện của chúng trong danh sách lớp cơ sở.

Đối với lớp trên, chúng ta hãy thử truy cập vào tất cả các thuộc tính này -

#!/usr/bin/python3

class Employee:
   'Common base class for all employees'
   empCount = 0

   def __init__(self, name, salary):
      self.name = name
      self.salary = salary
      Employee.empCount += 1
   
   def displayCount(self):
     print ("Total Employee %d" % Employee.empCount)

   def displayEmployee(self):
      print ("Name : ", self.name,  ", Salary: ", self.salary)

emp1 = Employee("Zara", 2000)
emp2 = Employee("Manni", 5000)
print ("Employee.__doc__:", Employee.__doc__)
print ("Employee.__name__:", Employee.__name__)
print ("Employee.__module__:", Employee.__module__)
print ("Employee.__bases__:", Employee.__bases__)
print ("Employee.__dict__:", Employee.__dict__ )

Khi đoạn mã trên được thực thi, nó tạo ra kết quả sau:

Employee.__doc__: Common base class for all employees
Employee.__name__: Employee
Employee.__module__: __main__
Employee.__bases__: (<class 'object'>,)
Employee.__dict__: {
   'displayCount': <function Employee.displayCount at 0x0160D2B8>, 
   '__module__': '__main__', '__doc__': 'Common base class for all employees', 
   'empCount': 2, '__init__': 
   <function Employee.__init__ at 0x0124F810>, 'displayEmployee': 
   <function Employee.displayEmployee at 0x0160D300>,
   '__weakref__': 
   <attribute '__weakref__' of 'Employee' objects>, '__dict__': 
   <attribute '__dict__' of 'Employee' objects>
}

Các đối tượng phá hủy (thu gom rác)

Python tự động xóa các đối tượng không cần thiết (kiểu tích hợp sẵn hoặc phiên bản lớp) để giải phóng không gian bộ nhớ. Quá trình Python lấy lại định kỳ các khối bộ nhớ không còn được sử dụng nữa được gọi là Bộ sưu tập rác.

Bộ thu gom rác của Python chạy trong quá trình thực thi chương trình và được kích hoạt khi số lượng tham chiếu của đối tượng bằng không. Số lượng tham chiếu của một đối tượng thay đổi khi số lượng bí danh trỏ đến nó thay đổi.

Số lượng tham chiếu của một đối tượng tăng lên khi nó được gán một tên mới hoặc được đặt trong một vùng chứa (danh sách, tuple hoặc từ điển). Số lượng tham chiếu của đối tượng giảm khi nó bị xóa bằng del , tham chiếu của nó được gán lại hoặc tham chiếu của nó vượt ra ngoài phạm vi. Khi số lượng tham chiếu của một đối tượng bằng 0, Python sẽ tự động thu thập nó.

a = 40      # Create object <40>
b = a       # Increase ref. count  of <40> 
c = [b]     # Increase ref. count  of <40> 

del a       # Decrease ref. count  of <40>
b = 100     # Decrease ref. count  of <40> 
c[0] = -1   # Decrease ref. count  of <40>

Thông thường, bạn sẽ không nhận thấy khi trình thu gom rác hủy một cá thể mồ côi và lấy lại không gian của nó. Tuy nhiên, một lớp có thể triển khai phương thức đặc biệt __del __ () , được gọi là hàm hủy, được gọi khi cá thể sắp bị hủy. Phương pháp này có thể được sử dụng để dọn dẹp mọi tài nguyên không phải bộ nhớ được sử dụng bởi một phiên bản.

Thí dụ

Hàm hủy __del __ () này in ra tên lớp của một cá thể sắp bị hủy -

#!/usr/bin/python3

class Point:
   def __init__( self, x=0, y=0):
      self.x = x
      self.y = y
   def __del__(self):
      class_name = self.__class__.__name__
      print (class_name, "destroyed")

pt1 = Point()
pt2 = pt1
pt3 = pt1
print (id(pt1), id(pt2), id(pt3))   # prints the ids of the obejcts
del pt1
del pt2
del pt3

Khi đoạn mã trên được thực thi, nó tạo ra kết quả sau:

140338326963984 140338326963984 140338326963984
Point destroyed

Note- Tốt nhất, bạn nên xác định các lớp của mình trong một tệp riêng biệt, sau đó bạn nên nhập chúng vào tệp chương trình chính của mình bằng cách sử dụng câu lệnh nhập .

Trong ví dụ trên, giả sử định nghĩa của một lớp Point được chứa trong point.py và không có mã thực thi nào khác trong đó.

#!/usr/bin/python3
import point

p1 = point.Point()

Kế thừa giai cấp

Thay vì bắt đầu từ đầu, bạn có thể tạo một lớp bằng cách dẫn xuất nó từ một lớp đã có từ trước bằng cách liệt kê lớp cha trong ngoặc đơn sau tên lớp mới.

Lớp con kế thừa các thuộc tính của lớp cha của nó và bạn có thể sử dụng các thuộc tính đó như thể chúng được định nghĩa trong lớp con. Một lớp con cũng có thể ghi đè các thành viên và phương thức dữ liệu từ lớp cha.

Cú pháp

Các lớp có nguồn gốc được khai báo giống như lớp cha của chúng; tuy nhiên, danh sách các lớp cơ sở để kế thừa được đưa ra sau tên lớp:

class SubClassName (ParentClass1[, ParentClass2, ...]):
   'Optional class documentation string'
   class_suite

Thí dụ

#!/usr/bin/python3

class Parent:        # define parent class
   parentAttr = 100
   def __init__(self):
      print ("Calling parent constructor")

   def parentMethod(self):
      print ('Calling parent method')

   def setAttr(self, attr):
      Parent.parentAttr = attr

   def getAttr(self):
      print ("Parent attribute :", Parent.parentAttr)

class Child(Parent): # define child class
   def __init__(self):
      print ("Calling child constructor")

   def childMethod(self):
      print ('Calling child method')

c = Child()          # instance of child
c.childMethod()      # child calls its method
c.parentMethod()     # calls parent's method
c.setAttr(200)       # again call parent's method
c.getAttr()          # again call parent's method

Khi đoạn mã trên được thực thi, nó tạo ra kết quả sau:

Calling child constructor
Calling child method
Calling parent method
Parent attribute : 200

Theo cách tương tự, bạn có thể điều khiển một lớp từ nhiều lớp cha như sau:

class A:        # define your class A
.....

class B:         # define your calss B
.....

class C(A, B):   # subclass of A and B
.....

Bạn có thể sử dụng các hàm Issubclass () hoặc isinstance () để kiểm tra mối quan hệ của hai lớp và cá thể.

  • Các issubclass(sub, sup) hàm boolean trả về True, nếu lớp con đã cho sub thực sự là một lớp con của lớp cha sup.

  • Các isinstance(obj, Class)hàm boolean trả về True, nếu obj là một thể hiện của lớp Class hoặc là một thể hiện của một lớp con của Class

Phương pháp ghi đè

Bạn luôn có thể ghi đè các phương thức lớp cha của mình. Một lý do để ghi đè các phương thức của cha là bạn có thể muốn có chức năng đặc biệt hoặc khác trong lớp con của mình.

Thí dụ

#!/usr/bin/python3

class Parent:        # define parent class
   def myMethod(self):
      print ('Calling parent method')

class Child(Parent): # define child class
   def myMethod(self):
      print ('Calling child method')

c = Child()          # instance of child
c.myMethod()         # child calls overridden method

Khi đoạn mã trên được thực thi, nó tạo ra kết quả sau:

Calling child method

Phương pháp quá tải cơ sở

Bảng sau liệt kê một số chức năng chung mà bạn có thể ghi đè trong các lớp của riêng mình -

Sr.No. Phương thức, Mô tả & Cuộc gọi Mẫu
1

__init__ ( self [,args...] )

Hàm tạo (với bất kỳ đối số tùy chọn nào)

Cuộc gọi mẫu: obj = className (args)

2

__del__( self )

Trình hủy, xóa một đối tượng

Cuộc gọi mẫu: del obj

3

__repr__( self )

Biểu diễn chuỗi có thể đánh giá

Cuộc gọi mẫu: repr (obj)

4

__str__( self )

Biểu diễn chuỗi có thể in

Cuộc gọi mẫu: str (obj)

5

__cmp__ ( self, x )

Đối tượng so sánh

Cuộc gọi mẫu: cmp (obj, x)

Người vận hành quá tải

Giả sử bạn đã tạo một lớp Vector để biểu diễn các vectơ hai chiều. Điều gì xảy ra khi bạn sử dụng toán tử dấu cộng để thêm chúng? Nhiều khả năng Python sẽ hét vào mặt bạn.

Tuy nhiên, bạn có thể xác định phương thức __add__ trong lớp của mình để thực hiện phép cộng vectơ và sau đó toán tử cộng sẽ hoạt động như mong đợi -

Thí dụ

#!/usr/bin/python3

class Vector:
   def __init__(self, a, b):
      self.a = a
      self.b = b

   def __str__(self):
      return 'Vector (%d, %d)' % (self.a, self.b)
   
   def __add__(self,other):
      return Vector(self.a + other.a, self.b + other.b)

v1 = Vector(2,10)
v2 = Vector(5,-2)
print (v1 + v2)

Khi đoạn mã trên được thực thi, nó tạo ra kết quả sau:

Vector(7,8)

Ẩn dữ liệu

Các thuộc tính của một đối tượng có thể có hoặc có thể không hiển thị bên ngoài định nghĩa lớp. Bạn cần đặt tên cho các thuộc tính bằng tiền tố gạch dưới kép và những thuộc tính đó sau đó sẽ không hiển thị trực tiếp với người ngoài.

Thí dụ

#!/usr/bin/python3

class JustCounter:
   __secretCount = 0
  
   def count(self):
      self.__secretCount += 1
      print (self.__secretCount)

counter = JustCounter()
counter.count()
counter.count()
print (counter.__secretCount)

Khi đoạn mã trên được thực thi, nó tạo ra kết quả sau:

1
2
Traceback (most recent call last):
   File "test.py", line 12, in <module>
      print counter.__secretCount
AttributeError: JustCounter instance has no attribute '__secretCount'

Python bảo vệ các thành viên đó bằng cách thay đổi nội bộ tên để bao gồm tên lớp. Bạn có thể truy cập các thuộc tính như object._className__attrName . Nếu bạn thay thế dòng cuối cùng của mình như sau, thì nó phù hợp với bạn -

.........................
print (counter._JustCounter__secretCount)

Khi đoạn mã trên được thực thi, nó tạo ra kết quả sau:

1
2
2