Tại sao Python chỉ tạo một bản sao của phần tử riêng lẻ khi lặp lại một danh sách?


31

Tôi chỉ nhận ra rằng trong Python, nếu một người viết

for i in a:
    i += 1

Các phần tử của danh sách ban đầu athực sự sẽ không bị ảnh hưởng, vì biến ihóa ra chỉ là một bản sao của phần tử gốc a.

Để sửa đổi phần tử gốc,

for index, i in enumerate(a):
    a[index] += 1

sẽ là cần thiết

Tôi thực sự bất ngờ trước hành vi này. Điều này dường như rất phản trực giác, dường như khác với các ngôn ngữ khác và đã dẫn đến lỗi trong mã của tôi mà tôi đã phải gỡ lỗi trong một thời gian dài ngày hôm nay.

Tôi đã đọc Hướng dẫn Python trước đây. Để chắc chắn, tôi đã kiểm tra cuốn sách một lần nữa và thậm chí nó không đề cập đến hành vi này.

Lý do đằng sau thiết kế này là gì? Nó có được dự kiến ​​là một thông lệ tiêu chuẩn trong nhiều ngôn ngữ để hướng dẫn tin rằng người đọc nên hiểu nó một cách tự nhiên không? Trong những ngôn ngữ khác là hành vi tương tự trên lặp đi lặp lại hiện tại, mà tôi nên chú ý trong tương lai?


19
Điều đó chỉ đúng nếu ikhông thay đổi hoặc bạn đang thực hiện một hoạt động không đột biến. Với một danh sách lồng nhau for i in a: a.append(1)sẽ có hành vi khác nhau; Python không sao chép các danh sách lồng nhau. Tuy nhiên, số nguyên là bất biến và phép cộng trả về một đối tượng mới, nó không thay đổi đối tượng cũ.
jonrsharpe

10
Nó không đáng ngạc nhiên chút nào. Tôi không thể nghĩ về một ngôn ngữ không hoàn toàn giống nhau cho một mảng các loại cơ bản như số nguyên. Ví dụ: thử trong javascript a=[1,2,3];a.forEach(i => i+=1);alert(a). Tương tự trong C #
edc65

7
Bạn có muốn i = i + 1ảnh hưởng a?
deltab

7
Lưu ý rằng hành vi này không khác nhau trong các ngôn ngữ khác. C, Javascript, Java, vv hành xử theo cách này.
slebetman

1
@jonrsharpe cho danh sách "+ =" thay đổi danh sách cũ, trong khi "+" tạo danh sách mới
Vasily Alexeev

Câu trả lời:


68

Gần đây tôi đã trả lời một câu hỏi tương tự và rất quan trọng để nhận ra rằng +=có thể có ý nghĩa khác nhau:

  • Nếu kiểu dữ liệu thực hiện bổ sung tại chỗ (nghĩa là có __iadd__chức năng hoạt động chính xác ) thì dữ liệu itham chiếu được cập nhật (không quan trọng nếu nó nằm trong danh sách hoặc ở nơi nào khác).

  • Nếu kiểu dữ liệu không thực hiện một __iadd__phương thức, i += xcâu lệnh chỉ là đường cú pháp i = i + x, do đó, một giá trị mới được tạo và gán cho tên biến i.

  • Nếu kiểu dữ liệu thực hiện __iadd__nhưng nó làm một cái gì đó kỳ lạ. Có thể nó được cập nhật ... hoặc không - điều đó phụ thuộc vào những gì được thực hiện ở đó.

Số nguyên, số float, chuỗi không thực hiện được __iadd__vì vậy những thứ này sẽ không được cập nhật tại chỗ. Tuy nhiên, các loại dữ liệu khác như numpy.arrayhoặc lists thực hiện nó và sẽ hoạt động như bạn mong đợi. Vì vậy, đó không phải là vấn đề sao chép hoặc không sao chép khi lặp (thông thường nó không tạo ra các bản sao cho lists và tuples - nhưng điều đó cũng phụ thuộc vào việc triển khai các thùng chứa __iter____getitem__phương thức!) - đó là vấn đề của kiểu dữ liệu bạn đã lưu trữ trong của bạn a.


2
Đây là lời giải thích chính xác cho hành vi được mô tả trong câu hỏi.
pabouk

19

Làm rõ - thuật ngữ

Python không phân biệt giữa các khái niệm tham chiếucon trỏ . Họ thường chỉ sử dụng thuật ngữ tham chiếu , nhưng nếu bạn so sánh với các ngôn ngữ như C ++ có sự khác biệt đó - thì nó gần với một con trỏ hơn .

Vì người hỏi rõ ràng xuất phát từ nền tảng C ++ và vì sự khác biệt đó - cần thiết cho lời giải thích - không tồn tại trong Python, nên tôi đã chọn sử dụng thuật ngữ của C ++, đó là:

  • Giá trị : Dữ liệu thực tế nằm trong bộ nhớ. void foo(int x);là chữ ký của hàm nhận số nguyên theo giá trị .
  • Con trỏ : Một địa chỉ bộ nhớ được coi là giá trị. Có thể được hoãn lại để truy cập vào bộ nhớ mà nó trỏ tới. void foo(int* x);là một chữ ký của hàm nhận số nguyên bằng con trỏ .
  • Tham khảo : Đường xung quanh con trỏ. Có một con trỏ phía sau hậu trường, nhưng bạn chỉ có thể truy cập giá trị hoãn lại và không thể thay đổi địa chỉ mà nó trỏ đến. void foo(int& x);là chữ ký của hàm nhận số nguyên theo tham chiếu .

Bạn có ý nghĩa gì "khác với các ngôn ngữ khác"? Hầu hết các ngôn ngữ mà tôi biết rằng hỗ trợ cho mỗi vòng lặp đều sao chép phần tử trừ khi được hướng dẫn cụ thể khác.

Cụ thể cho Python (mặc dù nhiều lý do trong số này có thể áp dụng cho các ngôn ngữ khác có khái niệm kiến ​​trúc hoặc triết học tương tự):

  1. Hành vi này có thể gây ra lỗi cho những người không biết về nó, nhưng hành vi thay thế có thể gây ra lỗi ngay cả đối với những người biết về nó. Khi bạn chỉ định một biến ( i), bạn thường không dừng lại và xem xét tất cả các biến khác sẽ được thay đổi vì nó ( a). Giới hạn phạm vi bạn đang làm việc là một yếu tố chính trong việc ngăn chặn mã spaghetti và do đó, lặp lại bằng bản sao thường là mặc định ngay cả trong các ngôn ngữ hỗ trợ lặp theo tham chiếu.

  2. Các biến Python luôn là một con trỏ duy nhất, do đó, giá rẻ để lặp lại bằng cách sao chép - rẻ hơn so với lặp theo tham chiếu, điều này sẽ yêu cầu hoãn lại thêm mỗi khi bạn truy cập giá trị.

  3. Python không có khái niệm về các biến tham chiếu như - ví dụ - C ++. Đó là, tất cả các biến trong Python thực sự là các tham chiếu, nhưng theo nghĩa là chúng là các con trỏ - không phải là các tham chiếu constat phía sau hậu trường như các type& nameđối số C ++ . Vì khái niệm này không tồn tại trong Python, nên việc thực hiện lặp theo tham chiếu - hãy để nó làm cho nó trở thành mặc định! - sẽ yêu cầu thêm độ phức tạp hơn vào mã byte.

  4. forTuyên bố của Python không chỉ hoạt động trên các mảng, mà còn trên một khái niệm tổng quát hơn về các trình tạo. Đằng sau hậu trường, Python gọi itercác mảng của bạn để lấy một đối tượng - khi bạn gọi nextnó - trả về phần tử tiếp theo hoặc raisesa StopIteration. Có một số cách để triển khai các trình tạo trong Python và việc triển khai chúng để lặp lại theo tham chiếu sẽ khó hơn nhiều.


Cảm ơn câu trả lời. Có vẻ như sự hiểu biết của tôi về các trình vòng lặp vẫn chưa đủ vững chắc sau đó. Theo mặc định, không phải là trình lặp trong tham chiếu C ++? Nếu bạn hủy đăng ký lặp, bạn luôn có thể thay đổi ngay giá trị của phần tử của container ban đầu?
xji

4
Python không lặp bằng cách tham chiếu (tốt, bởi giá trị, nhưng giá trị là một tài liệu tham khảo). Thử điều này với một danh sách các đối tượng có thể thay đổi sẽ nhanh chóng chứng minh rằng không có sự sao chép nào xảy ra.
jonrsharpe

Các trình vòng lặp trong C ++ thực sự là các đối tượng có thể được hoãn lại để truy cập giá trị trong mảng. Để sửa đổi phần tử gốc, bạn sử dụng *it = ...- nhưng loại cú pháp này đã cho biết bạn đang sửa đổi một cái gì đó ở nơi khác - điều này khiến lý do số 1 ít gặp vấn đề hơn. Lý do # 2 và # 3 cũng không áp dụng, vì trong sao chép C ++ rất tốn kém và tồn tại khái niệm biến tham chiếu. Vì lý do số 4 - khả năng trả về một tham chiếu cho phép thực hiện đơn giản cho tất cả các trường hợp.
Idan Arye

1
@jonrsharpe Có, nó được gọi bằng tham chiếu, nhưng trong bất kỳ ngôn ngữ nào có sự phân biệt giữa con trỏ và tham chiếu, loại lặp này sẽ là một phép lặp theo con trỏ (và vì con trỏ là giá trị - lặp theo giá trị). Tôi sẽ thêm một sự làm rõ.
Idan Arye

20
Đoạn đầu tiên của bạn cho thấy Python, giống như các ngôn ngữ khác, sao chép phần tử trong một vòng lặp for. Nó không. Nó không giới hạn phạm vi thay đổi bạn thực hiện đối với yếu tố đó. OP chỉ thấy hành vi này vì các yếu tố của họ là bất biến; thậm chí không đề cập đến sự phân biệt đó, câu trả lời của bạn ít nhất là không đầy đủ và tồi tệ nhất.
jonrsharpe

11

Không có câu trả lời nào ở đây cung cấp cho bạn bất kỳ mã nào để làm việc để thực sự minh họa tại sao điều này xảy ra ở vùng đất Python. Và điều này thật thú vị khi nhìn vào một cách tiếp cận sâu sắc hơn nên ở đây đi.

Lý do chính khiến điều này không hoạt động như bạn mong đợi là vì trong Python, khi bạn viết:

i += 1

nó không làm những gì bạn nghĩ nó đang làm Số nguyên là bất biến. Điều này có thể được nhìn thấy khi bạn nhìn vào những gì đối tượng thực sự có trong Python:

a = 0
print('ID of the first integer:', id(a))
a += 1
print('ID of the first integer +=1:', id(a))

Hàm id đại diện cho một giá trị duy nhất và không đổi cho một đối tượng trong suốt vòng đời của nó. Về mặt khái niệm, nó ánh xạ lỏng lẻo đến một địa chỉ bộ nhớ trong C / C ++. Chạy đoạn mã trên:

ID of the first integer: 140444342529056
ID of the first integer +=1: 140444342529088

Điều này có nghĩa là cái đầu tiên akhông còn giống với cái thứ hai a, bởi vì id của chúng khác nhau. Có hiệu quả họ đang ở các vị trí khác nhau trong bộ nhớ.

Với một đối tượng, tuy nhiên, mọi thứ hoạt động khác nhau. Tôi đã ghi đè +=toán tử ở đây:

class CustomInt:
  def __iadd__(self, other):
    # Override += 1 for this class
    self.value = self.value + other.value
    return self

  def __init__(self, v):
    self.value = v

ints = []
for i in range(5):
  int = CustomInt(i)
  print('ID={}, value={}'.format(id(int), i))
  ints.append(int)


for i in ints:
  i += CustomInt(i.value)

print("######")
for i in ints:
  print('ID={}, value={}'.format(id(i), i.value))

Chạy kết quả này trong đầu ra sau:

ID=140444284275400, value=0
ID=140444284275120, value=1
ID=140444284275064, value=2
ID=140444284310752, value=3
ID=140444284310864, value=4
######
ID=140444284275400, value=0
ID=140444284275120, value=2
ID=140444284275064, value=4
ID=140444284310752, value=6
ID=140444284310864, value=8

Lưu ý rằng thuộc tính id trong trường hợp này thực sự giống nhau cho cả hai lần lặp, mặc dù giá trị của đối tượng là khác nhau (bạn cũng có thể tìm thấy idgiá trị int mà đối tượng giữ, sẽ thay đổi khi nó đang thay đổi - bởi vì số nguyên là bất biến).

So sánh điều này với khi bạn chạy cùng một bài tập với một đối tượng bất biến:

ints_primitives = []
for i in range(5):
  int = i
  ints_primitives.append(int)
  print('ID={}, value={}'.format(id(int), i))

print("######")
for i in ints_primitives:
  i += 1
  print('ID={}, value={}'.format(id(int), i))


print("######")
for i in ints_primitives:
  print('ID={}, value={}'.format(id(i), i))

Kết quả này:

ID=140023258889248, value=0
ID=140023258889280, value=1
ID=140023258889312, value=2
ID=140023258889344, value=3
ID=140023258889376, value=4
######
ID=140023258889280, value=1
ID=140023258889312, value=2
ID=140023258889344, value=3
ID=140023258889376, value=4
ID=140023258889408, value=5
######
ID=140023258889248, value=0
ID=140023258889280, value=1
ID=140023258889312, value=2
ID=140023258889344, value=3
ID=140023258889376, value=4

Một vài điều ở đây cần chú ý. Đầu tiên, trong vòng lặp với +=, bạn không còn thêm vào đối tượng ban đầu. Trong trường hợp này, vì ints nằm trong số các loại bất biến trong Python , python sử dụng một id khác. Cũng thú vị lưu ý rằng Python sử dụng cùng một cơ sở idcho nhiều biến có cùng giá trị bất biến:

a = 1999
b = 1999
c = 1999

print('id a:', id(a))
print('id b:', id(b))
print('id c:', id(c))

id a: 139846953372048
id b: 139846953372048
id c: 139846953372048

tl; dr - Python có một số loại không thay đổi, gây ra hành vi bạn nhìn thấy. Đối với tất cả các loại đột biến, kỳ vọng của bạn là chính xác.


6

Câu trả lời của @ Idan thực hiện tốt việc giải thích lý do tại sao Python không coi biến vòng lặp là con trỏ theo cách bạn có thể trong C, nhưng đáng để giải thích sâu hơn về cách đoạn mã giải nén, như trong Python rất nhiều bit có vẻ đơn giản mã thực sự sẽ được gọi đến các phương thức được xây dựng . Lấy ví dụ đầu tiên của bạn

for i in a:
    i += 1

Có hai điều cần giải nén: for _ in _:cú pháp và _ += _cú pháp. Để lấy vòng lặp for trước tiên, giống như các ngôn ngữ khác Python có một for-eachvòng lặp về cơ bản là cú pháp đường cho mẫu lặp. Trong Python, iterator là một đối tượng xác định một .__next__(self)phương thức trả về phần tử hiện tại trong chuỗi, tiến tới tiếp theo và sẽ đưa ra một StopIterationkhi không có thêm mục nào trong chuỗi. Một Iterable là một đối tượng xác định một .__iter__(self)phương thức trả về một iterator.

(NB: an Iteratorcũng là một Iterablevà trả về chính nó từ .__iter__(self)phương thức của nó .)

Python thường sẽ có một hàm sẵn có để ủy quyền cho phương thức gạch dưới kép tùy chỉnh. Vì vậy, nó có iter(o)mà giải quyết o.__iter__()next(o)mà giải quyết o.__next__(). Lưu ý các hàm sẵn có này thường sẽ thử một định nghĩa mặc định hợp lý nếu phương thức mà chúng ủy quyền không được xác định. Ví dụ, len(o)thường giải quyết o.__len__()nhưng nếu phương thức đó không được xác định thì nó sẽ thử iter(o).__len__().

Một vòng lặp for cơ bản được xác định theo next(), iter()và nhiều hơn nữa cấu trúc điều khiển cơ bản. Nói chung mã

for i in %EXPR%:
    %LOOP%

sẽ được giải nén đến một cái gì đó như

_a_iter = iter(%EXPR%)
while True:
    try:
        i = next(_a_iter)
    except StopIteration:
        break
    %LOOP%

Vì vậy, trong trường hợp này

for i in a:
    i += 1

được giải nén

_a_iter = iter(a) # = a.__iter__()
while True:
    try: 
        i = next(_a_iter) # = _a_iter.__next__()
    except StopIteration:
        break
    i += 1

Nửa còn lại của điều này là i += 1. Nói chung %ASSIGN% += %EXPR%được giải nén để %ASSIGN% = %ASSIGN%.__iadd__(%EXPR%). Ở đây __iadd__(self, other)không bổ sung tại chỗ và trả lại chính nó.

(NB Đây là một trường hợp khác trong đó Python sẽ chọn một phương án thay thế nếu phương thức chính không được xác định. Nếu đối tượng không triển khai, __iadd__nó sẽ quay trở lại __add__. Nó thực sự làm điều này trong trường hợp này như intkhông thực hiện __iadd__- điều này hợp lý bởi vì chúng là bất biến và vì vậy không thể sửa đổi tại chỗ.)

Vì vậy, mã của bạn ở đây trông giống như

_a_iter = iter(a)
while True:
    try:
        i = next(_a_iter)
    except StopIteration:
        break
    i = iadd(i,1)

nơi chúng ta có thể định nghĩa

def iadd(o, v):
    try:
        return o.__iadd__(v)
    except AttributeError:
        return o.__add__(v)

Có thêm một chút diễn ra trong đoạn mã thứ hai của bạn. Hai điều mới mà chúng ta cần biết là %ARG%[%KEY%] = %VALUE%được giải nén (%ARG%).__setitem__(%KEY%, %VALUE%)%ARG%[%KEY%]được giải nén (%ARG%).__getitem__(%KEY%). Đặt kiến ​​thức này cùng nhau, chúng ta sẽ a[ix] += 1giải nén được a.__setitem__(ix, a.__getitem__(ix).__add__(1))(một lần nữa: __add__thay __iadd____iadd__không được thực hiện bởi ints). Mã cuối cùng của chúng tôi trông như:

_a_iter = iter(enumerate(a))
while True:
    try:
        index, i = next(_a_iter)
    except StopIteration:
        break
    a.__setitem__(index, iadd(a.__getitem__(index), 1))

Để thực sự trả lời câu hỏi của bạn là tại sao người đầu tiên không thay đổi danh sách trong khi thứ hai không, trong đoạn đầu tiên của chúng tôi, chúng tôi đang nhận được itừ next(_a_iter), mà có nghĩa là isẽ là một int. Vì intkhông thể sửa đổi tại chỗ, i += 1không có gì trong danh sách. Trong trường hợp thứ hai của chúng tôi, chúng tôi một lần nữa không sửa đổi intnhưng đang sửa đổi danh sách bằng cách gọi __setitem__.

Lý do cho toàn bộ bài tập công phu này là vì tôi nghĩ nó dạy bài học sau đây về Python:

  1. Cái giá của khả năng đọc của Python là nó đang gọi các phương thức tính điểm gấp đôi kỳ diệu này mọi lúc.
  2. Do đó, để có cơ hội thực sự hiểu bất kỳ đoạn mã Python nào, bạn phải hiểu những bản dịch này.

Các phương thức gạch dưới kép là một trở ngại khi bắt đầu, nhưng chúng rất cần thiết để hỗ trợ cho danh tiếng "mã giả chạy được" của Python. Một lập trình viên Python giỏi sẽ có sự hiểu biết thấu đáo về các phương thức này và cách chúng được gọi và sẽ định nghĩa chúng bất cứ nơi nào có ý nghĩa tốt để làm như vậy.

Chỉnh sửa : @deltab đã sửa lỗi sử dụng cẩu thả của cụm từ "bộ sưu tập".


2
"Trình lặp cũng là bộ sưu tập" không hoàn toàn đúng: chúng cũng có thể lặp lại, nhưng bộ sưu tập cũng có __len____contains__
deltab

2

+=hoạt động khác nhau dựa trên việc giá trị hiện tại là có thể thay đổi hay bất biến . Đây là lý do chính khiến phải mất một thời gian dài để nó được triển khai trong Python, vì các nhà phát triển Python sợ nó sẽ gây nhầm lẫn.

Nếu ilà một int, thì nó không thể thay đổi vì int là bất biến, và do đó nếu giá trị của các ithay đổi thì nó nhất thiết phải trỏ đến một đối tượng khác:

>>> i=3
>>> id(i)
14336296
>>> i+=1
>>> id(i)
14336272   # Other object

Tuy nhiên, nếu phía bên trái là có thể thay đổi , thì + = thực sự có thể thay đổi nó; Giống như nếu nó là một danh sách:

>>> i=[]
>>> id(i)
140257231883944
>>> i+=[1]
>>> id(i)
140257231883944  # Still the same object!

Trong vòng lặp for của bạn, iđề cập đến từng yếu tố alần lượt. Nếu đó là các số nguyên, thì trường hợp đầu tiên được áp dụng và kết quả của nó i += 1phải là nó tham chiếu đến một đối tượng số nguyên khác. Danh sách atất nhiên vẫn có các yếu tố giống như nó luôn có.


Tôi không hiểu sự khác biệt này giữa các đối tượng có thể thay đổi và bất biến: nếu i = 1đặt ithành một đối tượng số nguyên bất biến, thì i = []nên đặt ithành một đối tượng danh sách bất biến. Nói cách khác, tại sao các đối tượng số nguyên là bất biến và liệt kê các đối tượng có thể thay đổi? Tôi không thấy bất kỳ logic đằng sau này.
Giorgio

@Giorgio: các đối tượng đến từ các lớp khác nhau, listthực hiện các phương thức thay đổi nội dung của nó, intkhông. [] một đối tượng danh sách có thể thay đổi và i = []cho phép itham chiếu đến đối tượng đó.
RemcoGerlich

@Giorgio không có thứ gọi là danh sách bất biến trong Python. Danh sách có thể thay đổi. Số nguyên thì không. Nếu bạn muốn một cái gì đó giống như một danh sách nhưng không thay đổi, hãy xem xét một tuple. Về lý do, không rõ bạn thích câu trả lời ở cấp độ nào.
jonrsharpe

@RemcoGerlich: Tôi hiểu rằng các lớp khác nhau hành xử khác nhau, tôi không hiểu tại sao chúng được thực hiện theo cách này, tức là tôi không hiểu logic đằng sau sự lựa chọn này. Tôi đã triển +=khai toán tử / phương thức để hành xử tương tự (nguyên tắc ít gây ngạc nhiên nhất) cho cả hai loại: thay đổi đối tượng ban đầu hoặc trả về một bản sao đã sửa đổi cho cả số nguyên và danh sách.
Giorgio

1
@Giorgio: hoàn toàn đúng += đang ngạc nhiên bằng Python, nhưng nó đã cảm thấy rằng các tùy chọn khác mà bạn đề cập đến sẽ cũng đã ngạc nhiên, hoặc ít nhất là ít practicial (thay đổi các đối tượng ban đầu không thể được thực hiện với các loại phổ biến nhất của giá trị bạn sử dụng + = with, ints. Và sao chép toàn bộ danh sách tốn kém hơn nhiều so với việc thay đổi nó, Python không sao chép những thứ như danh sách và từ điển trừ khi được nói rõ ràng với). Đó là một cuộc tranh luận lớn sau đó.
RemcoGerlich

1

Vòng lặp ở đây là loại không liên quan. Giống như các tham số hàm hoặc đối số, thiết lập một vòng lặp for như thế về cơ bản chỉ là phép gán trông lạ mắt.

Số nguyên là bất biến. Cách duy nhất để sửa đổi chúng là bằng cách tạo một số nguyên mới và gán nó cho cùng tên với bản gốc.

Ngữ nghĩa của Python cho ánh xạ gán trực tiếp vào C (không gây ngạc nhiên khi đưa ra con trỏ PyObject * của CPython), với điều lưu ý duy nhất là mọi thứ đều là con trỏ và bạn không được phép có con trỏ kép. Hãy xem xét các mã sau đây:

a = 1
b = a
b += 1
print(a)

Chuyện gì xảy ra Nó in 1. Tại sao? Nó thực sự gần tương đương với mã C sau:

i64* a = malloc(sizeof(i64));
*a = 1;
i64* b = a;
i64* tmp = malloc(sizeof(i64));
tmp = *b + 1;
b = tmp;
printf("%d\n", *a);

Trong mã C, rõ ràng giá trị của ahoàn toàn không bị ảnh hưởng.

Về lý do tại sao các danh sách dường như hoạt động, câu trả lời về cơ bản chỉ là bạn đang gán cho cùng một tên. Danh sách có thể thay đổi. Danh tính của đối tượng được đặt tên a[0]sẽ thay đổi, nhưng a[0]vẫn là một tên hợp lệ. Bạn có thể kiểm tra điều này với mã sau đây:

x = 1
a = [x]
print(a[0] is x)
a[0] += 1
print(a[0] is x)

Nhưng, điều này không đặc biệt cho danh sách. Thay thế a[0]mã đó bằngy và bạn nhận được kết quả chính xác như nhau.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.