Cộng đồng chia sẻ tri thức Lib24.vn

Sự khác nhau về toán tử của Hashable object và Unhashable object trong Python

Gửi bởi: Phạm Thọ Thái Dương 20 tháng 11 2020 lúc 14:57:18


Mục lục
* * * * *

Giới thiệu cơ bản về hàm id

Cú pháp:

id(<giá trị>)

Như Kteam đã từng đề cập ở các bài trước đây, mọi thứ trong Python xoay quanh các đối tượng, và các giá trị ở đây chính là một đối tượng. Tuy vậy vẫn để là <giá trị> để tránh gây khó hiểu.

Công dụng: Theo định nghĩa về hàm id trong tài liệu của Python thì hàm này sẽ trả về một số nguyên (int hoặc longint).

  • Giá trị này là một giá trị duy nhất và là hằng số không thay đổi suốt chương trình.
  • Trong chi tiết bổ sung của CPython có nói giá trị trả về của hàm id là địa chỉ của giá trị (đối tượng) đó trong bộ nhớ.

Cao siêu là thế, nhưng bạn hoàn toàn có thể nghĩ đơn giản, con số trả về đó như cái số nhà của bạn. Bạn ở đâu, thì số nhà của bạn cũng sẽ tương ứng.

>>> n = 69
>>> s = 'How KTeam'
>>> lst = [1, 2]
>>> tup = (3, 4)
>>> id(n)
1446271792
>>> id(s)
53865712
>>> id(lst)
53838352
>>> id(tup)
53865768
>>>
>>> id(123)
1446272656
>>> id('Free Education')
53865832
123456789101112131415161718

Kteam sẽ tiếp tục giới thiệu hàm id khi nói tới các toán tử so sánh trong Python ở một bài khác.

Toán tử là một phương thức

Lặp lại thêm một lần nữa, mọi thứ xoay quanh Python toàn là hướng đối tượng. Cả các toán tử cũng thế!

>>> n = 69
>>> n + 1
70
>>> n
69
>>> n.__add__(1) # tương tự khi bạn n + 1
70
>>> n
69	
>>> n.__sub__(9) # tương tự n - 9
60
>>> n.__mul__(2) # tương tự n * 2
138
>>> n.__radd__(1) # tương tự 1 + n
70
>>> n.__rsub__(9) # tương tự 9 - n
-60
>>> n.__neg__() # tương tự -n
-69
1234567891011121314151617181920

Mỗi toán tử của mỗi đối tượng sẽ có toán tử đi kèm.

Khác biệt về toán tử Hash Object và Unhash Object

Vấn đề chính của bài này, là chỉ ra sự khác biệt giữa toán tử ở hash object và unhash object. Kteam sẽ lấy ví dụ so sánh đơn giản đó chính là sự khác biệt giữa việc s = s + i với lại s += i

Hãy xem xét đoạn code dưới đây, Kteam sẽ xét một hash object là chuỗi:

>>> s_1 = 'HowKteam'
>>> s_2 = 'Free Education'
>>> id(s_1)
53866032
>>> id(s_2)
53865712
>>> s_1 = s_1 + ' Python'
>>> s_2 += ' Python'
>>> id(s_1) # đã có sự thay đổi
53866152
>>> id(s_2) # cũng có sự thay đổi
23088304
>>> s_1
'HowKteam Python'
>>> s_2
'Free Education Python'
1234567891011121314151617

Ta cũng thấy, 2 toán tử = + cũng không có gì khác biệt lắm so với +=.

Giờ ta xét tới một unhash object

>>> lst_1 = [1, 2]
>>> lst_2 = [3, 4]
>>> id(lst_1)
53839752
>>> id(lst_2)
53864048
>>> lst_1 = lst_1 + [0]
>>> lst_2 += [0]
>>> id(lst_1) # có sự thay đổi
53864088
>>> id(lst_2) # không hề có sự thay đổi
53864048
>>> lst_1
[1, 2, 0]
>>> lst_2
[3, 4, 0]
1234567891011121314151617

Đã có khác biệt, khi thử với unhash object. Tại sao lại như vậy?

Đó là vì khi bạn làm như cách dưới đây. Tức có nghĩa bạn vừa mới gán lại giá trị cho biến lst. Nói cách khác, bạn đã đưa lst tới một địa chỉ khác.

>>> lst = [1, 2]
>>> lst = lst + [3]
>>> lst
[1, 2, 3]
12345

Còn khi bạn làm như thế này

>>> lst = [1, 2]
>>> lst += [3]
>>> lst
[1, 2, 3]
12345

Thì không như vậy, bạn đã gián tiếp gọi một phương thức

>>> lst = [1, 2]
>>> id(lst)
53839752
>>> lst.__iadd__([3])
[1, 2, 3]
>>> id(lst)
53839752
>>> lst
[1, 2, 3]
12345678910

Vậy vì sao, các hash object lại không như vậy?

Là bởi vì các hash object không hề có phương thức iadd, hay imul như các unhash object. Thế nên, khi bạn dùng toán tử +=, Python sẽ làm tương tự như bạn dùng cách gán giá trị.

Vì sao các hash object lại không có phương thức iadd, imul?

Khi bạn khởi tạo một giá trị, nó sẽ được lưu trong bộ nhớ máy tính.

  • Với hash object, bạn không thể thay đổi nội dung của nó. Do đó, Python sẽ xin đủ khoảng trống để lưu trữ dữ liệu của bạn, không nhiều hơn và cũng không ít hơn. Giúp không hoang phí bộ nhớ của bạn. Thế nên, khi bạn cộng thêm một thứ gì đó, Python không biết nhét cái thứ bạn muốn cộng vào chỗ nào. Nên nó đành cuốn gói đi ra chỗ đó, tìm chỗ mới thoáng có đủ khoảng trống.
  • Còn với unhash object. Là một đối tượng bạn thay đổi được nội dung, vì thế, Python luôn xin dư bộ nhớ để chừa chỗ cho các giá trị tiếp theo bạn có thể thêm vào. Trong bài trước, Kteam đã đề cập đến việc Tuple chiếm ít dung lượng hơn List vì Tuple là hash object.

Tại sao có List lại còn sinh ra Tuple? Hoặc là sử dụng Tuple thôi, cần gì tới List?

Đáng lẽ, Kteam sẽ nói vấn đề này ở bài trước, nhưng vì muốn bản hiểu hơn về các hash object với unhash object nên đã để tới bài này.

Bạn dễ dàng nhận thấy, việc ta thay đổi giá trị của Tuple, không nhất thiết là phải trực tiếp như List.

>>> lst = [1, 2]
>>> lst.append(3)
>>> lst
[1, 2, 3]
>>> tup = (1, 2)
>>> tup += (3,)
>>> tup
(1, 2, 3)
123456789

Các bạn cũng thấy, nó không khác nhau là mấy. Ta cũng có thể tạo ra các hàm thay đổi nội dung của Tuple bằng cách slicing. Đã thế List lại còn nặng về việc chiếm nhiều dung lương hơn Tuple, truy xuất chậm hơn Tuple. Việc gì khiến nó còn được trọng dụng?

Vì khi bạn thay đổi Tuple như cách trên, Python phải đi vòng vòng trong bộ nhớ của bạn tìm xem chỗ nào trống, phù hợp để chứa cái Tuple của bạn không, trong khi với List thì không. Do đó, bạn phải biết được dữ liệu của bạn là dạng dữ liệu như thế nào, có cần phải thay đổi không. Dựa vào đó, để chọn ra một kiểu dữ liệu phù hợp cho mình, tối ưu hóa dung lượng sử dụng, thời gian truy xuất.


Được cập nhật: 18 tháng 4 lúc 8:21:46 | Lượt xem: 735

Các bài học liên quan