Nạp chồng toán tử (operator overloading) trong Python

Đây là bài 37/54 bài của series môn học Ngôn ngữ lập trình Python

1. Nạp chồng toán tử (operator overloading) là gì?

Python xây dựng sẵn nhiều loại toán tử để thao tác với các kiểu dữ liệu được hỗ trợ trong Python. Một toán tử sẽ thực hiện các hành động khác nhau trên từng kiểu dữ liệu khác nhau. Ví dụ, sử dụng toán tử + trên 2 số thì đó là phép cộng 2 số. Sử dụng toán tử + trên 2 chuỗi (string) thì đó là phép nối (concatenate) 2 chuỗi.

Python cho phép cùng một toán tử nhưng có thể thực thi các hành động khác nhau với từng loại kiểu dữ liệu, được gọi là nạp chồng toán tử (operator overloading).

Một class được tạo ra cũng có thể xem là một kiểu dữ liệu. Vậy điều gì sẽ xảy ra khi chúng ta thực hiện các toán tử trên class mới được tạo ra?

class Point():
  def __init__(self, x=0, y=0):
    self.x = x
    self.y = y

p1 = Point(1, 2)
p2 = Point(2, 3)
print(p1+p2)
Kết quả
Traceback (most recent call last):
  File "c:\python-examples\main.py", line 8, in <module>
    print(p1+p2)
TypeError: unsupported operand type(s) for +: 'Point' and 'Point'

Trong ví dụ trên, chúng ta tạo ra một lớp Point với các đối tượng p1, p2. Sau đó, chúng ta sử dụng toán tử + trên 2 đối tượng này và kết quả ra xảy ra lỗi. Lỗi này là do chúng ta chưa định nghĩa toán tử + cho lớp Point để thực thi toán tử + trên các đối tượng của lớp Point.

Để biết cách nạp chồng toán tử, tức là định nghĩa các toán tử nhằm thực hiện được trên các kiểu dữ liệu mới thì chúng ta cần tìm hiểu về special function.

2. Các hàm đặc biệt (special function) trong Python

Những hàm của một class trong Python được bắt đầu với dấu gạch dưới __ thì được gọi là hàm đặc biệt (special function). Chúng ta đã từng tìm hiểu về một hàm đặc biệt, đó là hàm khởi tạo __init__().

Ngoài ra, một class trong Python còn rất nhiều hàm đặc biệt khác. Các hàm đặc biệt này có thể giúp chúng ta định nghĩa lại một số method, operator được định nghĩa sẵn trong Python. Ví dụ, chúng ta sử dụng hàm print() của Python để in một đối tượng của lớp Point thì sẽ được như sau:

class Point():
  def __init__(self, x=0, y=0):
    self.x = x
    self.y = y

p1 = Point(1, 2)
print(p1)
Kết quả
<__main__.Point object at 0x000002A6793D7D60>

Chúng ta có thể định nghĩa lại hàm đặc biệt __str__() để thay đổi cách hàm print() in ra một đối tượng của lớp Point.

class Point():
  def __init__(self, x=0, y=0):
    self.x = x
    self.y = y
  def __str__(self):
    return "({0}, {1})".format(self.x, self.y)

p1 = Point(1, 2)
print(p1)
Kết quả
(1, 2)

Những toán tử trong Python sẽ thay đổi, bổ sung chức năng khi các hàm đặc biệt này được định nghĩa lại. Do đó, chúng ta sẽ có thể nạp chồng các toán tử (operator overloading).

3. Nạp chồng toán tử số học (arithmetic operator)

Toán tử + là một trong những toán tử số học được sử dụng phổ biến nhất. Chúng ta sẽ thử nạp chồng toán tử + trong Python. Để làm việc này, chúng ta cần định nghĩa lại hàm đặc biệt __add__() trong lớp của những đối tượng mà ta muốn toán tử + có thể thực thi trên chúng.

class Point():
  def __init__(self, x=0, y=0):
    self.x = x
    self.y = y

  # define special function __str__() 
  def __str__(self):
    return "({0},{1})".format(self.x, self.y)

  # define special function __add__ 
  def __add__(self, apoint):
    x = self.x + apoint.x
    y = self.y + apoint.y
    return Point(x, y)

# create objects of Point
p1 = Point(1, 2)
p2 = Point(2, 3)
p3 = p1 + p2
print(p3)
Kết quả
(3,5)

Điều gì xảy ra khi chúng ta thực hiện toán tử + với p1p2 qua câu lệnh p3 = p1 + p2? Khi đó, Python sẽ gọi hàm p1.__add__(p2) mà hàm này sẽ trả về một đối tượng Point(x, y). Đối tượng này sẽ được gán cho p3 và in ra bởi hàm print() được định nghĩa lại với hàm đặc biệt __str__().

Tương tự, chúng ta cũng có thể nạp chồng các toán tử số học khác với các hàm đặc biệt.

Toán tửBiểu thứcHàm đặc biệt
+p1 + p2p1.__add__(p2)
p1 – p2p1.__sub__(p2)
*p1 * p2p1.__mul__(p2)
**p1 ** p2p1.__pow__(p2)
/p1 / p2p1.__truediv__(p2)
//p1 // p2p1.__floordiv__(p2)
%p1 % p2p1.__mod__(p2)

Các bạn có thể xem lại cách sử dụng các toán tử số học (arithmetic operator) ở bài Các loại toán tử (operator) được hỗ trợ trong Python.

4. Nạp chồng toán tử so sánh (comparison operator)

Toán tử < là một trong những toán tử so sánh được sử dụng phổ biến nhất. Chúng ta sẽ thử nạp chồng toán tử < trong Python. Để làm việc này, chúng ta cần định nghĩa lại hàm đặc biệt __lt__() trong lớp của những đối tượng mà ta muốn toán tử < có thể thực thi trên chúng.

class Point():
  def __init__(self, x=0, y=0):
    self.x = x
    self.y = y

  # define special function __str__() 
  def __str__(self):
    return "({0},{1})".format(self.x, self.y)

  # define special function __lt__ 
  def __lt__(self, apoint):
    self_mag = (self.x ** 2) + (self.y ** 2)
    apoint_mag = (apoint.x ** 2) + (apoint.y ** 2)
    return self_mag < apoint_mag

# create objects of Point
p1 = Point(1, 2)
p2 = Point(2, 3)
p3 = Point(-1, 1)
print("p1<p2?", p1<p2)
print("p1<p3?", p1<p3)
print("p2<p3?", p2<p3)
Kết quả
p1<p2? True
p1<p3? False
p2<p3? False

Trong ví dụ trên, để xem xét điểm nào nhỏ hơn điểm nào thì chúng ta so sánh khoảng cách giữa 2 điểm so với tọa độ gốc O(0, 0).

Tương tự, chúng ta cũng có thể nạp chồng các toán tử so sánh khác với các hàm đặc biệt.

Toán tửBiểu thứcHàm đặc biệt
p1 < p2p1.__lt__(p2)
<=p1 <= p2p1.__le__(p2)
==p1 == p2p1.__eq__(p2)
!=p1 != p2p1.__ne__(p2)
p1 > p2p1.__gt__(p2)
>=p1 >= p2p1.__ge__(p2)

Các bạn có thể xem lại cách sử dụng các toán tử so sánh (comparison operator) ở bài Các loại toán tử (operator) được hỗ trợ trong Python.

5. Nạp chồng toán tử trên bit (bitwise operator)

Chúng ta cũng có thể nạp chồng các toán tử trên bit với các hàm đặc biệt.

Toán tửBiểu thứcHàm đặc biệt
<< p1 << p2p1.__lshift__(p2)
>> p1 >> p2p1.__rshift__(p2)
&p1 & p2p1.__and__(p2)
|p1 | p2p1.__or__(p2)
^p1 ^ p2p1.__xor__(p2)
~~p1p1.__invert__()

Các bạn có thể xem lại cách sử dụng các toán tử trên bit (bitwise operator) ở bài Các loại toán tử (operator) được hỗ trợ trong Python.

6. Nạp chồng toán tử logic (logical operator)

Các toán tử logic trong Python hoạt động trên các giá trị boolean. Theo mặc định, giá trị boolean của đối tượng là True. Nếu đối tượng là None hoặc False thì giá trị boolean của đối tượng là False. Python không hỗ trợ nạp chồng các toán tử logic (and, or và not). Nhưng Python có hỗ trợ một hàm đặc biệt __bool__() để thay đổi giá trị boolean mặc định của một object.

class Data:
  def __init__(self, i):
    self.id = i

  def __bool__(self):
    return self.id % 2 == 0
 
d1 = Data(6)
d2 = Data(4)
# False
print(bool(Data(3)) and bool(Data(4)))

Lưu ý: Python không hỗ trợ nạp chồng phương thức (function overloading).

5/5 - (1 bình chọn)
Bài trước và bài sau trong môn học<< Tính đóng gói (encapsulation) và đa hình (polymorphism) trong PythonUser-Defined Exception trong Python >>
Chia sẻ trên mạng xã hội:

Trả lời

Lưu ý:

1) Vui lòng bình luận bằng tiếng Việt có dấu.

2) Khuyến khích sử dụng tên thật và địa chỉ email chính xác.

3) Mọi bình luận trái quy định sẽ bị xóa bỏ.