Các biến python có phải là con trỏ không? hoặc họ là gì?


81

Các biến trong Python chỉ là con trỏ, theo như tôi biết.

Dựa trên quy tắc này, tôi có thể giả định rằng kết quả cho đoạn mã này:

i = 5
j = i
j = 3 
print(i)

sẽ được 3. Nhưng tôi đã nhận được một kết quả bất ngờ cho tôi, đó là 5.

Hơn nữa, cuốn sách Python của tôi bao gồm ví dụ này:

i = [1,2,3]
j = i
i[0] = 5
print(j)

kết quả sẽ là [5,2,3].

Tôi hiểu sai điều gì?



5
Tôi không thấy như thế nào inên đã bằng3
Tooniis

@Tooniis j bây giờ trỏ đến một khối bộ nhớ khác, không phải khối bộ nhớ mà tôi đang trỏ tới. Bây giờ có lẽ bạn đã hiểu, nhưng đề phòng người khác nhìn thấy điều này
Swaroop Joshi

Câu trả lời:


88

Chúng tôi gọi chúng là tài liệu tham khảo. Họ làm việc như thế này

i = 5     # create int(5) instance, bind it to i
j = i     # bind j to the same int as i
j = 3     # create int(3) instance, bind it to j
print i   # i still bound to the int(5), j bound to the int(3)

Các int nhỏ được thực tập, nhưng điều đó không quan trọng đối với lời giải thích này

i = [1,2,3]   # create the list instance, and bind it to i
j = i         # bind j to the same list as i
i[0] = 5      # change the first item of i
print j       # j is still bound to the same list as i

3
Xin chào John, ý bạn là gì khi nói 'Những người có ý thức nhỏ được thực tập'? Cảm ơn!
yuqli

6
@yuqli Trong python, mọi thứ đều là đối tượng, bao gồm cả số. Vì các số nhỏ (-5,256) được sử dụng rất thường xuyên nên chúng được "thực tập" hoặc lưu trong bộ nhớ cache trong CPython. Do đó, mỗi khi bạn nhập 40, bạn đang tham chiếu đến cùng một đối tượng trong bộ nhớ. Để xem loại a, b = 256 và kiểm tra a is b. Bây giờ hãy thử nó với a, b = 257. Xem: stackoverflow.com/a/1136852/3973834codementor.io/python/tutorial/…
Evan Rosica

3
Theo kinh nghiệm của tôi, gọi chúng là "tên" phổ biến hơn trong số các nhà phát triển Python. Thuật ngữ "tham chiếu" đi kèm với hành lý C không cần thiết và có lẽ đang thiên vị Python ( ngôn ngữ ) quá nhiều đối với CPython ( triển khai ), điều này xảy ra sử dụng tính tham chiếu.
wim

33

Các biến không phải là con trỏ. Khi bạn gán cho một biến, bạn đang ràng buộc tên với một đối tượng. Từ thời điểm đó trở đi, bạn có thể chỉ đối tượng bằng cách sử dụng tên, cho đến khi tên đó được phục hồi.

Trong ví dụ đầu tiên của bạn, tên iđược liên kết với giá trị 5. Việc ràng buộc các giá trị khác nhau với tên jkhông có bất kỳ ảnh hưởng nào i, vì vậy khi bạn in sau đó, giá trị của igiá trị vẫn còn 5.

Trong ví dụ thứ hai, bạn liên kết cả hai ijvới cùng một đối tượng danh sách. Khi bạn sửa đổi nội dung của danh sách, bạn có thể thấy sự thay đổi bất kể bạn sử dụng tên nào để tham chiếu đến danh sách.

Lưu ý rằng sẽ không chính xác nếu bạn nói "cả hai danh sách đã thay đổi". Chỉ có một danh sách nhưng nó có hai tên ( ij) đề cập đến nó.

Tài liệu liên quan


15

Các biến Python là các tên liên kết với các đối tượng

Từ các tài liệu :

Tên chỉ các đối tượng. Tên được giới thiệu bằng các hoạt động ràng buộc tên. Mỗi sự xuất hiện của một tên trong văn bản chương trình đề cập đến sự ràng buộc của tên đó được thiết lập trong khối chức năng trong cùng chứa công dụng.

Khi bạn làm

i = 5
j = i

điều đó cũng giống như làm:

i = 5
j = 5

jkhông trỏ đến i, và sau khi chuyển nhượng, jkhông biết điều đó itồn tại. jchỉ đơn giản là bị ràng buộc với bất cứ điều gì iđược trỏ đến tại thời điểm được chỉ định.

Nếu bạn đã thực hiện các bài tập trên cùng một dòng, nó sẽ giống như sau:

i = j = 5

Và kết quả sẽ hoàn toàn giống nhau.

Như vậy, sau này làm

i = 3

không thay đổi những gì jđang trỏ đến - và bạn có thể hoán đổi nó - j = 3sẽ không thay đổi những gì iđang trỏ tới.

Ví dụ của bạn không xóa tham chiếu đến danh sách

Vì vậy, khi bạn làm điều này:

i = [1,2,3]
j = i

Nó cũng giống như làm điều này:

i = j = [1,2,3]

vì vậy ijcả hai đều trỏ đến cùng một danh sách. Sau đó, ví dụ của bạn thay đổi danh sách:

i[0] = 5

Danh sách Python là các đối tượng có thể thay đổi, vì vậy khi bạn thay đổi danh sách từ một tham chiếu và bạn xem nó từ một tham chiếu khác, bạn sẽ thấy cùng một kết quả vì đó là cùng một danh sách.


9

TLDR: Tên Python hoạt động giống như con trỏ với tự động hủy / tham chiếu nhưng không cho phép hoạt động con trỏ rõ ràng. Các mục tiêu khác thể hiện hướng dẫn, hoạt động tương tự như con trỏ.


Việc triển khai CPython sử dụng loại con trỏPyObject* dưới mui xe. Như vậy, có thể dịch ngữ nghĩa tên sang các phép toán con trỏ. Chìa khóa là tách tên khỏi các đối tượng thực tế .

Mã Python ví dụ bao gồm cả tên ( i) và đối tượng ( 5).

i = 5  # name `i` refers to object `5`
j = i  # ???
j = 3  # name `j` refers to object `3`

Điều này có thể được tạm dịch là mã C với các tên và đối tượng riêng biệt .

int three=3, five=5;  // objects
int *i, *j;           // names
i = &five;   // name `i` refers to position of object `5`
j = i;       // name `j` refers to referent of `i`
j = &three;  // name `j` refers to position of object `3`

Phần quan trọng là "name-as-pointers" không lưu trữ các đối tượng! Chúng tôi không xác định *i = five, nhưng i = &five. Tên và đối tượng tồn tại độc lập với nhau.

Tên chỉ trỏ đến các đối tượng hiện có trong bộ nhớ.

Khi gán từ tên sang tên, không có đối tượng nào được trao đổi! Khi chúng tôi định nghĩa j = i, điều này tương đương với j = &five. Không ivà cũng không jđược kết nối với khác.

+- name i -+ -\
               \
                --> + <five> -+
               /    |        5 |
+- name j -+ -/     +----------+

Do đó, việc thay đổi mục tiêu của một tên không ảnh hưởng đến tên kia . Nó chỉ cập nhật những gì mà tên cụ thể trỏ đến.


Python cũng có các loại phần tử giống tên khác : tham chiếu thuộc tính ( i.j), đăng ký ( i[j]) và cắt ( i[:j]). Không giống như tên, chỉ trực tiếp đến các đối tượng, cả ba đều gián tiếp đề cập đến các yếu tố của đối tượng.

Mã ví dụ bao gồm cả tên ( i) và đăng ký ( i[0]).

i = [1,2,3]  # name `i` refers to object `[1, 2, 3]`
j = i        # name `j` refers to referent of `i`
i[0] = 5     # ???

CPython listsử dụng một mảng C các PyObject*con trỏ dưới mui xe. Điều này một lần nữa có thể được tạm dịch là mã C với các tên và đối tượng riêng biệt.

typedef struct{
    int *elements[3];
} list;  // length 3 `list` type

int one = 1, two = 2, three = 3, five = 5;
list values = {&one, &two, &three};  // objects
list *i, *j;                         // names
i = &values;             // name `i` refers to object `[1, 2, 3]`
j = i;                   // name `j` refers to referent of `i`
i->elements[0] = &five;  // leading element of `i` refers to object `5`

Phần quan trọng là chúng tôi đã không thay đổi bất kỳ tên nào! Chúng tôi đã thay đổi i->elements[0], phần tử của một đối tượng mà cả tên của chúng tôi đều trỏ đến.

Giá trị của các đối tượng ghép hiện có có thể được thay đổi.

Khi thay đổi giá trị của một đối tượng thông qua tên, các tên không được thay đổi. Cả hai ijvẫn tham chiếu đến cùng một đối tượng, có giá trị mà chúng ta có thể thay đổi.

+- name i -+ -\
               \
                --> + <values> -+
               /    |  elements | --> [1, 2, 3]
+- name j -+ -/     +-----------+

Đối tượng trung gian hoạt động tương tự như một con trỏ ở chỗ chúng ta có thể thay đổi trực tiếp những gì nó trỏ tới và tham chiếu nó từ nhiều tên.


1
Tôi thực sự thích câu trả lời này, nhưng tôi nghĩ rằng bạn đã đảo ngược các phép gán ijtrong ví dụ của bạn. Bạn bắt đầu với i = 5, j = 3và sau đó đảo ngược chúng trong phần còn lại của bài đăng của bạn. Điều đó nói lại một lần nữa, đây là câu trả lời duy nhất imo thực hiện công lý cho câu hỏi trong OP và thực sự giải thích những gì xảy ra dưới mui xe.
jeremy radcliff

1
@jeremyradcliff Cảm ơn bạn đã quan tâm. Nên sửa ngay. Hãy cho tôi biết nếu tôi bỏ lỡ một số nữa.
MisterMiyagi

7

Chúng không hoàn toàn là con trỏ, chúng là tham chiếu đến các đối tượng. Các đối tượng có thể có thể thay đổi hoặc bất biến. Một đối tượng không thay đổi được sao chép khi nó được sửa đổi. Một đối tượng có thể thay đổi được thay đổi tại chỗ. Một số nguyên là một đối tượng bất biến mà bạn tham chiếu bởi các biến i và j của mình. Danh sách là một đối tượng có thể thay đổi.

Trong ví dụ đầu tiên của bạn

i=5
# The label i now references 5
j=i
# The label j now references what i references
j=3
# The label j now references 3
print i
# i still references 5

Trong ví dụ thứ hai của bạn:

i=[1,2,3]
# i references a list object (a mutable object)
j=i
# j now references the same object as i (they reference the same mutable object)
i[0]=5
# sets first element of references object to 5
print j
# prints the list object that j references. It's the same one as i.

"Một đối tượng không thay đổi được sao chép khi nó được sửa đổi." Đó là một chút tự mâu thuẫn.
PM 2Ring

1

Khi bạn đặt j=3nhãn jkhông còn áp dụng (điểm) cho i, nó bắt đầu trỏ đến số nguyên 3. Tên ivẫn đề cập đến giá trị bạn đặt ban đầu 5,.


1

biến nào ở bên trái dấu '=' được gán với giá trị ở bên phải của '='

i = 5

j = i --- j có 5

j = 3 --- j có 3 (ghi đè giá trị của 5) nhưng không có gì thay đổi về i

print(i)- vậy cái này in 5


1

Phép gán không sửa đổi các đối tượng; tất cả những gì nó làm là thay đổi vị trí các điểm biến. Thay đổi vị trí một điểm biến sẽ không thay đổi vị trí điểm khác.

Có thể bạn đang nghĩ đến thực tế là danh sách và từ điển là những loại có thể thay đổi. Có các toán tử để sửa đổi các đối tượng thực tế tại chỗ và nếu bạn sử dụng một trong các toán tử đó, bạn sẽ thấy sự thay đổi trong tất cả các biến trỏ đến cùng một đối tượng:

x = []
y = x
x.append(1)
# x and y both are now [1]

Nhưng việc gán vẫn chỉ di chuyển con trỏ xung quanh:

x = [2]
# x now points to new list [2]; y still points to old list [1]

Các con số, không giống như từ điển và danh sách, là bất biến. Nếu bạn làm vậy x = 3; x += 2, bạn không biến số 3 thành số 5; thay vào đó, bạn chỉ đặt biến xđiểm thành 5. Giá trị 3 vẫn không thay đổi và bất kỳ biến nào trỏ đến nó sẽ vẫn xem 3 là giá trị của chúng.

(Trong quá trình triển khai thực tế, các số có thể không phải là kiểu tham chiếu; có nhiều khả năng các biến thực sự chứa biểu thị giá trị trực tiếp hơn là trỏ đến nó. Nhưng chi tiết triển khai đó không thay đổi ngữ nghĩa khi có liên quan đến kiểu bất biến .)


1
Đó không phải là ý nghĩa của loại giá trị. Loại giá trị có nghĩa chính xác là những gì bạn đã mô tả trong đoạn cuối cùng (giá trị được truyền / sao chép xung quanh thay vì tham chiếu đến đối tượng) và nó không giống như vậy bên trong (trong CPython và trong trình biên dịch PyPy sans JIT - mọi số nguyên là một đối tượng được cấp phát đống). Chỉ cần bám vào bất biến, đó chính xác là từ bạn cần ở đó.

-1

Trong Python, mọi thứ đều là đối tượng bao gồm bản thân các mảnh bộ nhớ mà bạn được trả về. Điều đó có nghĩa là, khi đoạn bộ nhớ mới được tạo (bất kể bạn đã tạo những gì: int, str, đối tượng tùy chỉnh, v.v.), bạn có một đối tượng bộ nhớ mới. Trong trường hợp của bạn, đây là phép gán cho 3 tạo ra một đối tượng (bộ nhớ) mới và do đó có một địa chỉ mới.

Nếu bạn chạy phần sau, bạn sẽ dễ dàng hiểu được ý tôi.

i = 5
j = i
print("id of j: {}", id(j))
j = 3
print("id of j: {}", id(j))

IMO, bộ nhớ khôn ngoan, đây là sự hiểu biết / khác biệt chính giữa C và Python. Trong C / C ++, bạn được trả về một con trỏ bộ nhớ (tất nhiên là nếu bạn sử dụng cú pháp con trỏ) thay vì một đối tượng bộ nhớ, điều này giúp bạn linh hoạt hơn trong việc thay đổi địa chỉ được giới thiệu.

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.