1. Các toán tử định danh is và is not

Trong python, isis not là các toán tử dùng để so sánh "danh tính" (identity) của hai đối tượng (object), cụ thể là địa chỉ bộ nhớ của chúng. Mọi thứ trong Python đều là một đối tượng và mỗi đối tượng được lưu trữ tại một vị trí bộ nhớ cụ thể nào đó. Trong python, isis not là các toán tử kiểm tra xem hai biến có tham chiếu đến cùng một đối tượng trong bộ nhớ hay không.
Chúng ta có thể sử dụng id() để kiểm tra identity của một đối tượng bất kỳ.


Có một số trường hợp phổ biến trong đó các đối tượng có cùng giá trị sẽ có cùng id theo mặc định. Ví dụ, các số -5 đến 256 được đặt trong CPython. Mỗi số được lưu trữ tại một vị trí duy nhất và cố định trong bộ nhớ, giúp tiết kiệm bộ nhớ cho các số nguyên thường được sử dụng.
Tuy nhiên, khi chúng ta sử dụng toán tử gán (=) để tạo một biến bằng với biến khác, thì chúng ta đã làm cho các biến này trỏ đến cùng một đối tượng trong bộ nhớ. Điều này có thể dẫn đến hành vi không mong muốn cho các đối tượng mutable. Ví dụ trong trường hợp sau:

>>> a = [1, 2, 3]
>>> b = a
>>> a
[1, 2, 3]
>>> b
[1, 2, 3]


Khi ta thay đổi giá trị của biến a sẽ dẫn đến b cũng bị thay đổi theo:

>>> a.append(4)
>>> a
[1, 2, 3, 4]
>>> b
[1, 2, 3, 4]


Lí do dẫn đến điều này là do khi gán biến b theo a thì cả biến ba đều trỏ đến cùng một địa chỉ trong bộ nhớ, hay cả ab cùng tham chiếu đến một đối tượng.

>>> id(a)
2570926056520
>>> id(b)
2570926056520

Nếu chúng ta định nghĩa các biến này một cách độc lập với nhau, thì chúng sẽ được lưu trữ tại các địa chỉ bộ nhớ khác nhau và hoạt động hoàn toàn độc lập:
>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> a is b
False
>>> id(a)
2356388925576
>>> id(b)
2356388952648

Như các bạn đã thấy, vì ab bây giờ tham chiếu đến các đối tượng khác nhau trong bộ nhớ, việc thay đổi một đối tượng này không ảnh hưởng đến đối tượng còn lại. Và đương nhiên, kết quả của phép is trong trường hợp này sẽ là False

2. Các toán tử so sánh (==) và (!=)

Các đối tượng có cùng giá trị thường được lưu trữ tại các địa chỉ bộ nhớ riêng biệt. Sử dụng các toán tử đẳng thức (==)(!=) giúp chúng ta kiểm tra xem hai đối tượng có cùng giá trị hay không, bất kể chúng được lưu trữ ở đâu trong bộ nhớ. Trong đa số các trường hợp, đây là những gì mà chúng ta muốn làm. Hãy cùng xem xét ví dụ dưới đây:

>>> a = [1, 2, 3]
>>> b = a.copy()
>>> a
[1, 2, 3]
>>> b
[1, 2, 3]

>>> id(a)
2570926058312
>>> id(b)
2570926057736

>>> a == b
True
>>> a is b
False

Trong ví dụ ở trên, biến b là một bản sao của a. Ta có thể thấy rằng mặc dù hai biến ab có cùng một giá trị nhưng chúng có địa chỉ trong bộ nhớ hoàn toàn khác nhau. Và trong trường hợp này, toán tử (==) trả về kết quả True còn toán tử is trả về kết quả False. Thực tế là, khi sử dụng toán tử đẳng thức (==), thì ta đã gọi đến phương thức lớp __eq __() của đối tượng nằm bên trái (==). Ta hoàn toàn có thể override lại phương thức này

class SillyString(str):
  
    def __eq__(self, other):
        print(f'comparing {self} to {other}')
        # Trả về True nếu self và object còn lại có cùng độ dài
        return len(self) == len(other) 

Bây giờ, chuỗi 'Hello world' của SillyString bằng chuỗi 'World hello' và thậm chí với bất kỳ đối tượng nào khác có cùng độ dài:

>>> 'hello world' == 'world hello'
False

>>> 'hello world' == SillyString('world hello')
True

>>> SillyString('hello world') == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
True

Đương nhiên sẽ rất ít khi chúng ta viết lại hàm so sánh, nhưng ví dụ trên minh họa những gì xảy ra khi bạn so sánh hai đối tượng sử dụng (==). Đối với toán tử (!=) chúng ta có hàm __ne __() tương ứng.

3. Kết luận

Theo nguyên tắc thông thường, bạn phải luôn luôn sử dụng các toán tử đẳng thức (==)(!=), trừ trường hợp bạn so sánh với None:

  • Sử dụng toán tử (==)(!=) để so sánh giá trị của hai đối tượng. Đây là những gì bạn cần nếu bạn muốn so sánh xem hai đối tượng có cùng nội dung hay không và bạn không quan tâm đến nơi lưu trữ chúng trong bộ nhớ.
  • Sử dụng toán tử isis not khi bạn muốn so sánh indentity của hai đối tượng. Ở đây, bạn có thể so sánh xem hai biến có trỏ đến cùng một đối tượng trong bộ nhớ hay không. Trường hợp sử dụng chính cho các toán tử này là khi bạn so sánh với None. Nó nhanh hơn và an toàn hơn khi so sánh với None theo địa chỉ bộ nhớ so với sử dụng các phương thức lớp.

Tóm lại, các biến có cùng giá trị thường được lưu trữ tại các địa chỉ bộ nhớ riêng biệt. Điều này có nghĩa là bạn nên sử dụng (==) hoặc (!=) để so sánh các giá trị của chúng và sử dụng isis not khi bạn muốn kiểm tra xem hai biến có trỏ đến cùng một địa chỉ bộ nhớ hay không.