Nạp chồng toán tử trong Python

<p style="text-align: justify;">Trong b&agrave;i về đa h&igrave;nh, ch&uacute;ng ta đ&atilde; nhắc đến một v&iacute; dụ về t&iacute;nh đa h&igrave;nh của ph&eacute;p cộng trong Python. Trong một số trường hợp dữ liệu như chuỗi k&yacute; tự, danh s&aacute;ch,...n&oacute; được hiểu như ph&aacute;p nối c&aacute;c phần tử, trong khi đối với c&aacute;c kiểu dữ liệu số như số nguy&ecirc;n, số thực, số phức th&igrave; n&oacute; lại được hiểu như một ph&eacute;p t&iacute;nh số học th&ocirc;ng thường. Điều n&agrave;y gọi l&agrave; nạp chồng to&aacute;n tử. Trong b&agrave;i viết n&agrave;y, ch&uacute;ng ta sẽ c&ugrave;ng t&igrave;m hiểu về kh&aacute;i niệm nạp chồng to&aacute;n tử n&agrave;y trong Python.</p> <h2 style="text-align: justify;">Nạp chồng to&aacute;n tử trong Python l&agrave; g&igrave;?</h2> <p style="text-align: justify;">X&eacute;t v&iacute; dụ dưới đ&acirc;y:</p> <pre class="language-python"><code>class vi_du: def __init__(self, a, b): self.x = a self.y = b t1 = vi_du(5, 6) t2 = vi_du(9, 8) print(t1+t2)</code></pre> <p style="text-align: justify;">Ch&uacute;ng ta biết rằng đoạn m&atilde; b&ecirc;n tr&ecirc;n sẽ đưa ra lỗi, v&igrave; Python kh&ocirc;ng biết c&aacute;ch cộng hai đối tượng vi_du trong Python với nhau. Tuy nhi&ecirc;n, ch&uacute;ng ta c&oacute; thể l&agrave;m được điều n&agrave;y trong Python th&ocirc;ng qua việc nạp chồng to&aacute;n tử.</p> <p style="text-align: justify;">C&aacute;c to&aacute;n tử trong Python hoạt động cho c&aacute;c lớp được t&iacute;ch hợp sẵn. Nhưng c&ugrave;ng một to&aacute;n tử c&oacute; thể thực hiện c&aacute;c thao t&aacute;c kh&aacute;c nhau với c&aacute;c kiểu dữ liệu kh&aacute;c nhau. V&iacute; dụ, to&aacute;n tử + sẽ thực hiện ph&eacute;p cộng cho dữ liệu kiểu số học tr&ecirc;n hai số hạng, thực hiện ph&eacute;p nối cho hai danh s&aacute;ch hoặc nối hai chuỗi k&yacute; tự.</p> <p style="text-align: justify;">T&iacute;nh năng n&agrave;y trong Python cho ph&eacute;p c&ugrave;ng một to&aacute;n tử c&oacute; thể thực hiện c&aacute;c thao t&aacute;c kh&aacute;c nhau t&ugrave;y theo ngữ cảnh được gọi l&agrave; nạp chồng to&aacute;n tử.</p> <p style="text-align: justify;">Để thực hiện việc nạp chồng to&aacute;n tử, ch&uacute;ng ta cần phải định nghĩa c&aacute;c phương thức c&oacute; t&ecirc;n đặc biệt cho ph&eacute;p thay thế c&aacute;c to&aacute;n tử tương ứng. C&aacute;c phương thức n&agrave;y được bắt đầu bằng c&aacute;c dấu gạch dưới, giống như phương thức __init__() đ&atilde; được học.</p> <p style="text-align: justify;">V&iacute; dụ để nạp chồng to&aacute;n tử dấu cộng +, ch&uacute;ng ta sẽ cần triển khai h&agrave;m __add__() trong lớp. Ch&uacute;ng ta c&oacute; thể l&agrave;m bất cứ điều g&igrave; ch&uacute;ng ta muốn b&ecirc;n trong h&agrave;m n&agrave;y.</p> <pre class="language-python"><code>class vi_du: def __init__(self, a, b): self.a = a self.b = b def __add__(self, other): a = self.a + other.a b = self.b + other.b return a,b t1 = vi_du(100, 102) t2 = vi_du(104, 108) print(t1 + t2)</code></pre> <p style="text-align: justify;">Kết quả:</p> <pre class="language-markup"><code>(204, 210)</code></pre> <p style="text-align: justify;">Điều thực sự xảy ra l&agrave;, khi ta sử dụng t1 + t2, Python sẽ gọi p1 .__ add __ (p2), tương đương với Point .__ add __ (p1, p2). Sau đ&oacute;, ph&eacute;p to&aacute;n cộng được thực hiện theo c&aacute;ch ch&uacute;ng ta đ&atilde; chỉ định. Tương tự, ch&uacute;ng ta cũng c&oacute; thể nạp chồng c&aacute;c to&aacute;n tử kh&aacute;c.</p> <p style="text-align: justify;">Một số c&aacute;c h&agrave;m đặc biệt ch&uacute;ng ta cần triển khai việc nạp chồng to&aacute;n tử được đưa ra trong bảng b&ecirc;n dưới.</p> <table style="border-collapse: collapse; margin-left: auto; margin-right: auto;"> <tbody> <tr> <td style="text-align: center;">To&aacute;n tử</td> <td style="text-align: center;">Biểu thức</td> <td style="text-align: center;">H&agrave;m để định nghĩa nạp chồng cho lớp</td> </tr> <tr> <td style="text-align: center;">Ph&eacute;p cộng</td> <td style="text-align: center;">a + b</td> <td style="text-align: center;">a.__add__(b)</td> </tr> <tr> <td style="text-align: center;">Ph&eacute;p trừ</td> <td style="text-align: center;">a &ndash; b</td> <td style="text-align: center;">a.__sub__(b)</td> </tr> <tr> <td style="text-align: center;">Ph&eacute;p nh&acirc;n</td> <td style="text-align: center;">a * b</td> <td style="text-align: center;">a.__mul__(b)</td> </tr> <tr> <td style="text-align: center;">Ph&eacute;p số mũ</td> <td style="text-align: center;">a ** b</td> <td style="text-align: center;">a.__pow__(b)</td> </tr> <tr> <td style="text-align: center;">Ph&eacute;p chia</td> <td style="text-align: center;">a / b</td> <td style="text-align: center;">a.__truediv__(b)</td> </tr> <tr> <td style="text-align: center;">Ph&eacute;p chia l&agrave;m tr&ograve;n</td> <td style="text-align: center;">a // b</td> <td style="text-align: center;">a.__floordiv__(b)</td> </tr> <tr> <td style="text-align: center;">Ph&eacute;p chia lấy dư</td> <td style="text-align: center;">a % b</td> <td style="text-align: center;">a.__mod__(b)</td> </tr> <tr> <td style="text-align: center;">Dịch bit sang tr&aacute;i</td> <td style="text-align: center;">a &lt;&lt; b</td> <td style="text-align: center;">a.__lshift__(b)</td> </tr> <tr> <td style="text-align: center;">Dịch bit sang phải</td> <td style="text-align: center;">a &gt;&gt; b</td> <td style="text-align: center;">a.__rshift__(b)</td> </tr> <tr> <td style="text-align: center;">Ph&eacute;p AND</td> <td style="text-align: center;">a &amp; b</td> <td style="text-align: center;">a.__and__(b)</td> </tr> <tr> <td style="text-align: center;">Ph&eacute;p OR</td> <td style="text-align: center;">a | b</td> <td style="text-align: center;">a.__or__(b)</td> </tr> <tr> <td style="text-align: center;">Ph&eacute;p XOR</td> <td style="text-align: center;">a ^ b</td> <td style="text-align: center;">a.__xor__(b)</td> </tr> <tr> <td style="text-align: center;">Ph&eacute;p NOT</td> <td style="text-align: center;">~a</td> <td style="text-align: center;">a.__invert__()</td> </tr> </tbody> </table> <p style="text-align: justify;">Nh&igrave;n v&agrave;o bảng tr&ecirc;n, ta thấy rằng, để nạp chồng ph&eacute;p trừ (-) ch&uacute;ng ta cần định nghĩa một phương thức c&oacute; t&ecirc;n l&agrave; __sub__() trong lớp, để nạp chồng ph&eacute;p nh&acirc;n (*), ch&uacute;ng ta cần phải định nghĩa một phương thức c&oacute; t&ecirc;n l&agrave; __mul__() trong lớp,...C&aacute;c phương thức n&agrave;y bắt buộc phải c&oacute; t&ecirc;n tương ứng như vậy để Python hiểu rằng ch&uacute;ng ta đang cố gắng nạp chồng to&aacute;n từ n&agrave;o.</p> <p style="text-align: justify;">Python kh&ocirc;ng giới hạn việc nạp chồng to&aacute;n tử chỉ với c&aacute;c to&aacute;n tử số học. Ch&uacute;ng ta cũng c&oacute; thể nạp chồng c&aacute;c to&aacute;n tử so s&aacute;nh. Giả sử ch&uacute;ng ta muốn triển khai to&aacute;n tử so s&aacute;nh bằng = trong lớp m&agrave; ta đ&atilde; định nghĩa.</p> <p style="text-align: justify;">V&iacute; dụ:</p> <pre class="language-python"><code>class vi_du: def __init__(self, a, b): self.a = a self.b = b def __str__(self): return "({0},{1})".format(self.a, self.b) def __eq__(self, other): c = abs(self.a) + abs(self.b) d = abs(other.a) + abs(other.b) return c == d t1 = vi_du(100,3) t2 = vi_du(-100,-3) t3 = vi_du(200,5) print(t1==t2) print(t2==t3) print(t1==t3)</code></pre> <p style="text-align: justify;">Kết quả:</p> <pre class="language-markup"><code>True False False</code></pre> <p style="text-align: justify;">Tương tự như vậy, c&aacute;c h&agrave;m đặc biệt m&agrave; ch&uacute;ng ta cần triển khai để nạp chồng c&aacute;c to&aacute;n tử so s&aacute;nh kh&aacute;c được liệt k&ecirc; trong bảng b&ecirc;n dưới đ&acirc;y.</p> <table style="border-collapse: collapse; width: 28.9069%; height: 176px; margin-left: auto; margin-right: auto;"> <tbody> <tr style="height: 44px;"> <td style="text-align: center; width: 39.5527%; height: 44px;">To&aacute;n tử</td> <td style="text-align: center; width: 18.5224%; height: 44px;">Biểu thức</td> <td style="text-align: center; width: 30.1734%; height: 44px;">H&agrave;m b&ecirc;n trong</td> </tr> <tr style="height: 22px;"> <td style="text-align: center; width: 39.5527%; height: 22px;">Nhỏ hơn</td> <td style="text-align: center; width: 18.5224%; height: 22px;">a &lt; b</td> <td style="text-align: center; width: 30.1734%; height: 22px;">a.__lt__(b)</td> </tr> <tr style="height: 22px;"> <td style="text-align: center; width: 39.5527%; height: 22px;">Nhỏ hơn hoặc bằng</td> <td style="text-align: center; width: 18.5224%; height: 22px;">a &lt;= b</td> <td style="text-align: center; width: 30.1734%; height: 22px;">a.__le__(b)</td> </tr> <tr style="height: 22px;"> <td style="text-align: center; width: 39.5527%; height: 22px;">Bằng</td> <td style="text-align: center; width: 18.5224%; height: 22px;">a == b</td> <td style="text-align: center; width: 30.1734%; height: 22px;">a.__eq__(b)</td> </tr> <tr style="height: 22px;"> <td style="text-align: center; width: 39.5527%; height: 22px;">Kh&ocirc;ng bằng/ Kh&aacute;c</td> <td style="text-align: center; width: 18.5224%; height: 22px;">a != b</td> <td style="text-align: center; width: 30.1734%; height: 22px;">a.__ne__(b)</td> </tr> <tr style="height: 22px;"> <td style="text-align: center; width: 39.5527%; height: 22px;">Lớn hơn</td> <td style="text-align: center; width: 18.5224%; height: 22px;">a &gt; b</td> <td style="text-align: center; width: 30.1734%; height: 22px;">a.__gt__(b)</td> </tr> <tr style="height: 22px;"> <td style="text-align: center; width: 39.5527%; height: 22px;">Lớn hơn hoặc bằng</td> <td style="text-align: center; width: 18.5224%; height: 22px;">a &gt;= b</td> <td style="text-align: center; width: 30.1734%; height: 22px;">a.__ge__(b)</td> </tr> </tbody> </table> <p style="text-align: justify;">T&oacute;m lại:</p> <ul style="text-align: justify;"> <li>Ch&uacute;ng ta c&oacute; thể định nghĩa bất cứ một to&aacute;n tử n&agrave;o trong một lớp để thay thế cho c&aacute;c ph&eacute;p to&aacute;n th&ocirc;ng thường mặc định của Python để n&oacute; c&oacute; thể sử dụng để l&agrave;m việc như c&aacute;c ph&eacute;p to&aacute;n n&agrave;y</li> <li>Việc nạp chồng to&aacute;n tử c&oacute; mục ti&ecirc;u l&agrave; l&agrave;m cho chương tr&igrave;nh viết dễ hiểu v&agrave; r&otilde; r&agrave;ng hơn do sử dụng c&aacute;c to&aacute;n tử quen thuộc h&agrave;ng ng&agrave;y</li> <li>Ch&uacute;ng ta buộc phải nhớ c&aacute;c t&ecirc;n h&agrave;m đặc biệt cần sử dụng để thực hiện việc nạp chồng một to&aacute;n tử tương ứng v&iacute; dụ __add__(), __mul__(), __sub__(),...c&aacute;c t&ecirc;n n&agrave;y buộc phải đặt đ&uacute;ng th&igrave; việc nạp chồng to&aacute;n tử mới diễn ra.</li> </ul> <p style="text-align: justify;">Ch&uacute;ng ta h&atilde;y c&ugrave;ng xem x&eacute;t một v&iacute; dụ ho&agrave;n chỉnh về việc nạp chồng to&aacute;n tử n&agrave;y th&ocirc;ng qua b&agrave;i to&aacute;n dưới đ&acirc;y.</p> <h2 style="text-align: justify;">V&iacute; dụ về nạp chồng to&aacute;n tử</h2> <p style="text-align: justify;">B&agrave;i to&aacute;n: X&acirc;y dựng lớp vector v&agrave; c&aacute;c ph&eacute;p to&aacute;n tr&ecirc;n n&oacute; sử dụng nạp chồng to&aacute;n tử.</p> <p style="text-align: justify;">Ph&acirc;n t&iacute;ch:</p> <p style="text-align: justify;">Một vector n chiều l&agrave; một bộ số thực gồm n phần tử, do đ&oacute;, ch&uacute;ng ta khai b&aacute;o một h&agrave;m tạo trong lớp Vector như sau:</p> <pre class="language-python"><code>class Vector: def __init__(self,v): self.v=v</code></pre> <p style="text-align: justify;">Tr&ecirc;n kh&ocirc;ng giản vector hỗ trợ một số ph&eacute;p t&iacute;nh như sau:</p> <ul style="text-align: justify;"> <li>Ph&eacute;p cộng hai vector l&agrave; một vector m&agrave; mỗi th&agrave;nh phần của n&oacute; l&agrave; tổng của mỗi th&agrave;nh phần tương ứng của hai vector đầu v&agrave;o với nhau</li> <li>T&iacute;ch v&ocirc; hướng của 2 vector l&agrave; tổng của t&iacute;ch c&aacute;c th&agrave;nh phần tương ứng với nhau.</li> </ul> <p style="text-align: justify;">Do đ&oacute;, ch&uacute;ng ta định nghĩa nạp chồng 3 to&aacute;n tử cộng (+), v&agrave; to&aacute;n tử nh&acirc;n (*) như sau:</p> <pre class="language-python"><code> def __add__(self,other): if len(self.v)!=len(other.v): return None else: a=[] for i in range(len(self.v)): a.append(self.v[i]+other.v[i]) return a def __mul__(self,other): if len(self.v)!=len(other.v): return None else: tich=0 for i in range(len(self.v)): tich+=self.v[i]*other.v[i] return tich</code></pre> <p style="text-align: justify;">Cuối c&ugrave;ng, ch&uacute;ng ta thử nghiệm việc nạp chồng bằng c&aacute;ch khai b&aacute;o c&aacute;c vector mẫu để thực hiện việc t&iacute;nh to&aacute;n:</p> <pre class="language-python"><code>a=[1,2,3] b=[3,4,5] v1=Vector(a) v2=Vector(b) print(v1+v2) print(v1*v2)</code></pre> <p style="text-align: justify;">Kết quả:</p> <pre class="language-markup"><code>[4, 6, 8] 26</code></pre> <p style="text-align: justify;">Như vậy, việc nạp chồng diễn ra đ&uacute;ng như mong đợi.</p> <p style="text-align: justify;">Bạn c&oacute; thể định nghĩa việc nạp chồng qua c&aacute;c ph&eacute;p to&aacute;n kh&aacute;c. Đ&acirc;y sẽ l&agrave; một b&agrave;i tập để bạn tự luyện tập về vấn đề n&agrave;y.</p> <p style="text-align: justify;">Đến đ&acirc;y ch&uacute;ng ta đ&atilde; ho&agrave;n th&agrave;nh chủ đề về lập tr&igrave;nh hướng đối tượng cơ bản trong Python. Đ&acirc;y l&agrave; một chủ đề kh&aacute; kh&oacute; v&agrave; những g&igrave; được cung cấp trong kh&oacute;a học n&agrave;y mới chỉ dừng ở mức cơ bản. C&ograve;n rất nhiều khối lượng kiến thức kh&aacute; kh&oacute; nhằn cho người mới bắt đầu như đa kế thừa, h&agrave;m hủy, phương thức của đối tượng v&agrave; phương thức của lớp,...chưa được tr&igrave;nh b&agrave;y ở đ&acirc;y. Nếu bạn muốn t&igrave;m hiểu s&acirc;u hơn về c&aacute;c kh&aacute;i niệm n&acirc;ng cao n&agrave;y, bạn c&oacute; thể tiếp tục với kh&oacute;a Lập tr&igrave;nh Python n&acirc;ng cao - l&agrave; một kh&oacute;a tiếp theo của kh&oacute;a học n&agrave;y tr&ecirc;n TEK.4VN. C&ograve;n ở đ&acirc;y, trong khu&ocirc;n khổ của kh&oacute;a học n&agrave;y, ch&uacute;ng ta sẽ tạm dừng chủ đề về lập tr&igrave;nh hướng đối tượng trong Python tại đ&acirc;y. </p> <p style="text-align: justify;">Ch&uacute;ng ta sẽ tiếp tục luyện tập với một số b&agrave;i tập cơ bản, trước khi chuyển sang chủ đề tiếp theo về Module v&agrave; Package trong Python.</p>