Giải mã bí ẩn phép toán +=
list và tuple - hai loài khác biệt
Biết list + list trả về 1 list, tuple + tuple trả về 1 tuple, vậy list + tuple thì trả về gì?
Câu trả lời đúng là: không trả về gì cả bởi có exception xảy ra. Dù cho lấy list + tuple hay tuple + list đi chăng nữa:
>>> [1] + (2,)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate list (not "tuple") to list
>>> (2,) + [1]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate tuple (not "list") to tuple
đây là TypeError exception, với nội dung rất rõ ràng: chỉ có thể cộng list với list, tuple với tuple.
a += b có đúng là a = a + b?
Thông thường, phép tính a += b
được hiểu là a = a + b
.
Điều này đúng trong hầu hết các trường hợp... cho đến khi nó sai:
>>> a = 5
>>> b = 4
>>> a += b
>>> print(a)
9
>>> a = 'pymi'
>>> b = '.vn'
>>> a += b
>>> print(a)
pymi.vn
>>> a = [1]
>>> b = (2,)
>>> a += b
>>> print(a)
[1, 2]
>>> # Ồ ồ ồ ồ ồ 😱 vì sao list lại cộng được với tuple 🙄
Phép toán +=
, -=
, *=
, /=
được gọi là augmented
assignment operators (phép toán "augmented assignment").
Augmented /ɔːɡˈmɛntɪd/
Vietsub: làm tăng lên. Engsub: Having been made greater in size or value.
Xem đầy đủ danh sách các phép toán augmented assignment tại trang định nghĩa cú pháp của Python.
Bản chất của các phép toán
Trong Python, bản chất các phép toán đều chỉ là syntactic sugar
(làm cho cú
pháp thêm ngọt - dễ nhìn). Phép cộng a + b
, thực chất được thực hiện bằng
cách gọi method __add__
của object a
với argument là b
.
>>> a = 5
>>> b = 4
>>> a.__add__(b)
9
Tại đây, method __add__
trả về một object mới là số 9
.
Khi viết a = a + b
, ta có
>>> a = 5
>>> id(a)
4309492224
>>> b = 4
>>> a = a.__add__(b)
>>> print(a, id(a))
9 4309492352 # id đã khác
Ở đây sẽ gán cho tên (biến) a
kết quả của phép tính a+b
- một object
mới (số 9).
Phép toán += hoạt động thế nào?
Cách hoạt động của augmented assigment được mô tả tại PEP0203. Đoạn trích từ PEP0203:
So, given an instance object `x', the expression
x += y
tries to call x.__iadd__(y), which is the `in-place' variant of
__add__. If __iadd__ is not present, x.__add__(y) is attempted,
and finally y.__radd__(x) if __add__ is missing too.
khi thực hiện phép toán +=
, Python sẽ thử method __iadd__
trước. Nếu method này không tồn tại, Python lại thử __add__
rồi sau cùng thử gọi __radd__
.
__iadd__
là phiên bản in-place của __add__
,
Khi mà __add__
sẽ trả về một object mới chứa kết quả của phép cộng,
thì __iadd__
lại thay đổi giá trị của chính object gọi nó.
list += tuple
List có method __iadd__
, nên khi dùng phép toán +=
, python
sẽ gọi __iadd__
, method này thay đổi chính list object bằng việc
gọi method
extend
.
extend
chấp nhận đầu vào là
iterable (kiểu dữ liệu chứa nhiều giá trị như list, string, tuple ...)
nên có thể viết list += tuple. Kết quả thu được là list ban đầu với giá trị mới
được extend từ tuple đưa vào.
>>> lst = [1]
>>> id(lst)
4340949768
>>> lst += (2,)
>>> print(lst, id(lst))
[1, 2] 4340949768 # id không đổi
tuple += list
Với kiểu dữ liệu immutable (như tuple, integer, string ... ), chúng không
có method __iadd__
, nên phép toán +=
sẽ gọi
method __add__
. Viết a += b
khi a là tuple, b là list sẽ tương đương
với viết a = a + b
, mà tuple + list thì không thu được gì:
>>> a = (2,)
>>> b = [1]
>>> a += b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate tuple (not "list") to tuple
Đây không phải là bug của Python, nó là một tính năng ™ theo thiết kế.
Bởi nếu cho phép thay đổi một kiểu dữ liệu immutable
thì khi viết 4 + 5
bạn sẽ muốn nhận được 1 object
mới là số 9
, hay bạn muốn sửa số 4
thành số 9
? 🤔
Kết luận
Phép toán +=
khiến cho một list có thể "cộng" được với một tuple đã
không còn là điều bí ẩn một khi bức màn bí mật đã được tụt xuống.
Hãy nhìn các phép toán theo một con mắt khác, chúng chỉ là ký hiệu và che
giấu đi cơ chế hoạt động thực sự ở phía dưới. Đây là lập trình, không phải
toán học.