Reportar esta app

Description

Như bạn đã biết, Python định nghĩa sẵn một số phép toán trên các kiểu dữ liệu cảu mình. Ví dụ, phép cộng đã được định nghĩa sẵn cho các kiểu số, kiểu xâu ký tự , kiểu danh sách

Giả sử bạn xây dựng kiểu dữ liệu riêng (class) Matrix (ma trận). Do ma trận cũng có các phép toán như phép cộng, phép nhân, bạn cũng muốn trực tiếp áp dụng các phép toán này cho object của lớp Matrix.

Khi này bạn sẽ cần sử dụng đến kỹ thuật nạp chồng toán tử.

Nạp chồng toán tử (operator overloading) là kỹ thuật định nghĩa các phép toán sẵn có trên kiểu dữ liệu tự tạo.

Trong Python nạp chồng toán tử hoạt động tương đối khác với ngôn ngữ C++/Java/ C#. Các toán tử trong Python hoạt động dựa trên một số magic method.

Magic method trong Python.

Nạp chồng toán tử hoạt động dựa trên magic method. Vì vậy trước khi học cách nạp chồng toán tử, chúng ta cần hiểu thế nào là magic method trong Python.

Khái niệm magic method được nhắc đến lần đầu khi bạn học về hàm tạo trong Python.

Magic method là một phương thức đặc biệt được Python tự động gọi. Lấy ví dụ, khi bạn thực hiện phép cộng, Python sẽ tự động gọi tới phương thức __add__(). Khi bạn muốn khởi tạo một object, phương thức __new__() được Python tự động gọi để tạo object trước.

Các kiểu dữ liệu xây dựng sẵn của Python có một số magic method. Bạn có thể xem danh sách các phương thức này bằng lệnh dir(<tên kiểu>) như sau:


1
2
>>> dir(int) # danh sách magic method của kiểu int
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']

Một ví dụ nhỏ khác:


1
2
3
4
5
>>> a = 10
>>> a.__add__(10) # tương đương với gọi a + 10
20
>>> a + 10
20

Qua ví dụ này bạn có thể thấy sự tương đương giữa phép toán + và lời gọi hàm __add__(). Khi bạn sử dụng phép toán +, Python tự động thay bạn gọi hàm __add__(). Bạn cũng có thể tự mình gọi tới magic method đứng sau ghép +.

Tham khảo thêm ngay  Decorator ( hàm trang trí) trong Python

tóm lại, magic method là những phương thức đứng sau các phép toán thường gặp. Bản thân phép toán chỉ là một dạng cú pháp tiện lợi hơn để gọi tới các phương thức này.

Khi hiểu điều này bạn hẳn có thể nhận ra ngay, thực hiện nạp chồng toán tử trong Python thực chất là định nghĩa lại magic method của phép toán trên class mới. Như vậy trong Python, nạp chồng toán tử = ghi đè magic method tương ứng.

Ghi đè phương thức __new__()

Ghi đè phương thức __new__() cho phép bạn nạp chồng phép toán tạo object. Đây chỉ là một ví dụ để bạn hiểu kỹ hơn về class, object và magic method trong Python. Trên thực tế bạn ít khi phải nạp chồng phép toán này.

Hãy xem ví dụ sau đây:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Employee:
    def __new__(cls, *args, **kwargs):
        print('__new__ is being called')
        instance = object.__new__(cls)
        return instance

    def __init__(self, name: str):
        print('__init__ is being called')
        self.name = name

    def print(self):
        print(self.name)

if __name__ == '__main__':
    trump = Employee('Donald Trump')
    trump.print()
    putin = Employee('Vladimir Putin')    
    putin.print()

Hãy để ý đến cách ghi đè phương thức __new__().


1
2
3
4
def __new__(cls, *args, **kwargs):
        print('__new__ is being called')
        instance = object.__new__(cls)
        return instance

Để ghi đè __new__ bạn phải cung cấp một tham số bắt buộc cls. Tham số này có vai trò tương tự như tham số trong class method: nó chính là class mà bạn đang cần tạo object. Python sẽ tự động truyền giá trị cho tham số này.

Tham khảo thêm ngay  Iterator và Iterable trong Python

Hai tham số còn lại *args và **kwargs giúp bạn viết __init__() với tham số tùy ý. Sở dĩ phải có *args và **kwargs ở đây là vì __new__ đòi hỏi cùng tham số với __init__. Do __new__ được gọi tự động và __init__xây dựng sau, chúng ta sử dụng *args và **kwargs cho __new__() để bao phủ hết các khả năng truyền tham số có thể gặp ở __init__().

Trong thân hàm __new__() bạn bắt buộc phải gọi tới phương thức __new__() của lớp cha object. Nhắc lại: object là lớp cha của mọi class trong Python. Lời gọi __new__() của object sẽ sỉnh a object mới và bạn cần trả lại object này.

Kế tiếp, Python sẽ gọi __init__ và tự động truyền object mới sang phương thức __init__().

Khi chạy chương trình bạn có thể thấy rõ, khi gặp lệnh tạo object, __new__() đượ gọi tự động trước, sau đó mới đến __init__().

Ghi đè phương thức __str__() và __repr__()

Phương thức __str__() được gọi tự động khi bạn phát lệnh in object print(). Phương thức này sẽ chuyển object về một chuỗi ký tự phù hợp cho việc in.

Phương thức __repr__() được gọi tự động khi bạn muốn xuất biến ở chế độ tương tác.

Khi xây dựng một class, nếu muốn tiện lợi cho việc in object của class, bạn nên định nghĩa phương thức __str__() và __repr__()

Hãy xem ví dụ sau:


1
2
3
4
5
6
7
8
9
class Vector:
    """A class for vector"""
    def __init__(self, x:float, y:float):
        self.x = x
        self.y = y
    def __str__(self):
        return f'({self.x}, {self.y})'
    def __repr__(self):
        return f'({self.x}, {self.y})'

Đây là ví dụ về một class Vector hai chiều đơn giản.

Thông thường, vector trong toán học hay được in ra ở dạng (x,y,z,…) chúng ta cũng muốn rằng khi dùng hàm print() trên object của Vector, kết quả in ra cũng có dạng (x, y).

Để làm việc này, chúng ta cần định nghĩa phương thức magic__str__() trong class Vector:


1
2
def __str__(self):
    return f'({self.x}, {self.y})'

Phương thức này cần trả về một chuỗi ký tự.

Với class Vector như trên, bạn có thể sử dụng như sau:


1
2
3
4
5
>>> v1 = Vector(, 1); v2 = Vector(2, 3)
>>> print(v1)
(, 1)
>>> print(v2)
(2, 3)

Tuy nhiên, nếu chỉ có __str__() khi bạn nhập lệnh:


1
2
3
>>> v1 = Vector(1, 2)
>>> v1
&lt;__main__.Vector object at 0x00000183425A2DC0>

Để giao diện tương tác cũng in ra kết quả như khi dùng hàm print(), bạn cần định nghĩa lại phương thức __repr__().


1
2
def __repr__(self):
    return f'({self.x}, {self.y})'

Phần thân __repr__() và __str__() cơ bản là giống nhau.

Tham khảo thêm ngay  Các loại phương thức (method) trong Python class

Nạp chồng các phép toán số học.

Các phép toán số học +.-.*./ tương ứng với các magic method __add__(), __sub__(), __mul__(), __div__().

Phép toán + – tương ứng với __pos__() và __neg__().

Để nạp chồng các phép toán này, bạn cần định nghĩa/ ghi đè magic method tương ứng.

Hãy xem ví dụ sau đây:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Vector:
    """A class for vector"""

    def __init__(self, x:float, y:float):
        self.x = x
        self.y = y

    def __str__(self):
        return f'({self.x}, {self.y})'

    def __repr__(self):
        return f'({self.x}, {self.y})'

    def __add__(self, v):
        x = self.x + v.x
        y = self.y + v.y
        return Vector(x, y)

    def __sub__(self, v):
        x = self.x - v.x
        y = self.y - v.y
        return Vector(x, y)

    def __mul__(self, n):
        x = self.x * n
        y = self.y * n
        return Vector(x, y)

    def __neg__(self):
        return Vector(self.x * -1, self.y * -1)


Với class Vector như trên bạn có thể thực hiện các phép toán cơ bản như sau:


1
2
3
4
5
6
7
8
9
10
>>> v1 = Vector(1, 2)
>>> v2 = Vector(3, 4)
>>> v1 + v2 # phép cộng
(4, 6)
>>> v1 - v2 # phép trừ
(-2, -2)
>>> v1 * 2 # phép nhân vô hướng
(2, 4)
>>> -v1 # nghịch đảo
(-1, -2)

Dưới đây là danh sách các hàm toán học cơ bản và magic method tương ứng của chúng:

__add__(self,other)phép cộng +
__sub__(self,other)phép trù –
__mul__(self, other)phép nhân *
__floordiv__(self,other)phép chia lấy phần nguyên //
__div__(self,other)phép chia/
__mod__(self,other)phép chia lấy phần dư %
__pow__(self,other[, modulo])phép tính lũy thừa **
__It__(self,other)Phép so sánh nhỏ hơn <
__le__(self,other)phép so sánh nhỏ hơn hoặc bằng <=
__eq__(self,other)phép so sánh bằng ==
__ne__(self,other)phép so sánh khác !=
__ge__(self, other) phép so sánh lớn hoặc bằng >=

Leave a Reply

Your email address will not be published. Required fields are marked *