Làm thế nào để tôi vượt qua một biến bằng cách tham khảo?


2628

Tài liệu Python dường như không rõ ràng về việc các tham số được truyền bởi tham chiếu hoặc giá trị và mã sau đây tạo ra giá trị không thay đổi 'Bản gốc'

class PassByReference:
    def __init__(self):
        self.variable = 'Original'
        self.change(self.variable)
        print(self.variable)

    def change(self, var):
        var = 'Changed'

Có điều gì tôi có thể làm để vượt qua biến bằng tham chiếu thực tế không?


23
Để được giải thích ngắn gọn / làm rõ, hãy xem câu trả lời đầu tiên cho câu hỏi stackoverflow này . Vì các chuỗi là bất biến, chúng sẽ không bị thay đổi và một biến mới sẽ được tạo, do đó biến "bên ngoài" vẫn có cùng giá trị.
PhilS

10
Mã trong câu trả lời của BlairConrad là tốt, nhưng lời giải thích được cung cấp bởi DavidCournapeau và DarenThomas là chính xác.
Ethan Furman

70
Trước khi đọc câu trả lời đã chọn, vui lòng xem xét việc đọc văn bản ngắn này Các ngôn ngữ khác có "biến", Python có "tên" . Hãy suy nghĩ về "tên" và "đối tượng" thay vì "biến" và "tham chiếu" và bạn nên tránh rất nhiều vấn đề tương tự.
lqc

24
Tại sao điều này không cần thiết sử dụng một lớp? Đây không phải là Java: P
Peter R

2
một cách giải quyết khác là tạo một trình bao bọc 'tham chiếu' như thế này: ref = type ('', (), {'n': 1}) stackoverflow.com/a/1123054
409638

Câu trả lời:


2831

Đối số được thông qua bởi sự phân công . Lý do đằng sau này là gấp đôi:

  1. tham số được truyền vào thực sự là một tham chiếu đến một đối tượng (nhưng tham chiếu được truyền theo giá trị)
  2. một số loại dữ liệu có thể thay đổi, nhưng một số khác thì không

Vì thế:

  • Nếu bạn truyền một đối tượng có thể thay đổi vào một phương thức, phương thức đó sẽ tham chiếu đến cùng một đối tượng đó và bạn có thể biến đổi nó thành niềm vui của trái tim bạn, nhưng nếu bạn bật lại tham chiếu trong phương thức đó, phạm vi bên ngoài sẽ không biết gì về nó và sau đó bạn đã hoàn thành, tham chiếu bên ngoài vẫn sẽ trỏ vào đối tượng ban đầu.

  • Nếu bạn truyền một đối tượng bất biến cho một phương thức, bạn vẫn không thể bật lại tham chiếu bên ngoài và thậm chí bạn không thể thay đổi đối tượng.

Để làm cho nó rõ ràng hơn, hãy có một số ví dụ.

Danh sách - một loại đột biến

Hãy thử sửa đổi danh sách đã được truyền cho một phương thức:

def try_to_change_list_contents(the_list):
    print('got', the_list)
    the_list.append('four')
    print('changed to', the_list)

outer_list = ['one', 'two', 'three']

print('before, outer_list =', outer_list)
try_to_change_list_contents(outer_list)
print('after, outer_list =', outer_list)

Đầu ra:

before, outer_list = ['one', 'two', 'three']
got ['one', 'two', 'three']
changed to ['one', 'two', 'three', 'four']
after, outer_list = ['one', 'two', 'three', 'four']

Vì tham số được truyền vào là tham chiếu đến outer_list, không phải là bản sao của tham số, chúng ta có thể sử dụng các phương thức danh sách đột biến để thay đổi tham số và có các thay đổi được phản ánh trong phạm vi bên ngoài.

Bây giờ hãy xem điều gì xảy ra khi chúng ta cố gắng thay đổi tham chiếu được truyền vào dưới dạng tham số:

def try_to_change_list_reference(the_list):
    print('got', the_list)
    the_list = ['and', 'we', 'can', 'not', 'lie']
    print('set to', the_list)

outer_list = ['we', 'like', 'proper', 'English']

print('before, outer_list =', outer_list)
try_to_change_list_reference(outer_list)
print('after, outer_list =', outer_list)

Đầu ra:

before, outer_list = ['we', 'like', 'proper', 'English']
got ['we', 'like', 'proper', 'English']
set to ['and', 'we', 'can', 'not', 'lie']
after, outer_list = ['we', 'like', 'proper', 'English']

Do the_listtham số được truyền theo giá trị, việc gán một danh sách mới cho nó không có tác dụng mà mã bên ngoài phương thức có thể nhìn thấy. Đây the_listlà một bản sao của outer_listtài liệu tham khảo và chúng tôi đã the_listchỉ ra một danh sách mới, nhưng không có cách nào để thay đổi nơi outer_listchỉ.

Chuỗi - một loại bất biến

Điều đó là bất biến, vì vậy chúng tôi không thể làm gì để thay đổi nội dung của chuỗi

Bây giờ, hãy thử thay đổi tham chiếu

def try_to_change_string_reference(the_string):
    print('got', the_string)
    the_string = 'In a kingdom by the sea'
    print('set to', the_string)

outer_string = 'It was many and many a year ago'

print('before, outer_string =', outer_string)
try_to_change_string_reference(outer_string)
print('after, outer_string =', outer_string)

Đầu ra:

before, outer_string = It was many and many a year ago
got It was many and many a year ago
set to In a kingdom by the sea
after, outer_string = It was many and many a year ago

Một lần nữa, vì the_stringtham số được truyền theo giá trị, việc gán một chuỗi mới cho nó không có tác dụng mà mã bên ngoài phương thức có thể nhìn thấy. Đây the_stringlà một bản sao của outer_stringtài liệu tham khảo và chúng tôi đã the_stringchỉ ra một chuỗi mới, nhưng không có cách nào để thay đổi nơi outer_stringchỉ.

Tôi hy vọng điều này sẽ làm sáng tỏ mọi thứ một chút.

EDIT: Lưu ý rằng điều này không trả lời câu hỏi mà ban đầu @David đã hỏi, "Có điều gì tôi có thể làm để vượt qua biến bằng tham chiếu thực tế không?". Hãy làm việc đó.

Làm thế nào để chúng ta có được xung quanh này?

Như câu trả lời của @ Andrea cho thấy, bạn có thể trả về giá trị mới. Điều này không thay đổi cách mọi thứ được truyền vào, nhưng không cho phép bạn lấy lại thông tin bạn muốn:

def return_a_whole_new_string(the_string):
    new_string = something_to_do_with_the_old_string(the_string)
    return new_string

# then you could call it like
my_string = return_a_whole_new_string(my_string)

Nếu bạn thực sự muốn tránh sử dụng giá trị trả về, bạn có thể tạo một lớp để giữ giá trị của mình và chuyển nó vào hàm hoặc sử dụng một lớp hiện có, như một danh sách:

def use_a_wrapper_to_simulate_pass_by_reference(stuff_to_change):
    new_string = something_to_do_with_the_old_string(stuff_to_change[0])
    stuff_to_change[0] = new_string

# then you could call it like
wrapper = [my_string]
use_a_wrapper_to_simulate_pass_by_reference(wrapper)

do_something_with(wrapper[0])

Mặc dù điều này có vẻ hơi cồng kềnh.


165
Tương tự như vậy là ở C, khi bạn vượt qua "bằng tham chiếu", bạn thực sự vượt qua giá trị của tham chiếu ... Xác định "bằng tham chiếu": P
Andrea Ambu

104
Tôi không chắc tôi hiểu các điều khoản của bạn. Tôi đã rời khỏi trò chơi C được một thời gian, nhưng khi tôi ở trong đó, không có "thông qua tham chiếu" - bạn có thể vượt qua mọi thứ và nó luôn luôn vượt qua giá trị, vì vậy bất cứ điều gì đều có trong danh sách tham số đã được sao chép. Nhưng đôi khi thứ đó là một con trỏ, mà con người có thể đi theo mảnh bộ nhớ (nguyên thủy, mảng, cấu trúc, bất cứ thứ gì), nhưng bạn không thể thay đổi con trỏ được sao chép từ phạm vi bên ngoài - khi bạn đã hoàn thành chức năng , con trỏ ban đầu vẫn trỏ đến cùng một địa chỉ. C ++ giới thiệu tài liệu tham khảo, mà hành xử khác nhau.
Blair Conrad

34
@Zac Bowling Tôi thực sự không hiểu những gì bạn nói có liên quan, theo nghĩa thực tế, cho câu trả lời này. Nếu một người mới sử dụng Python muốn biết về việc chuyển bằng ref / val, thì việc lấy từ câu trả lời này là: 1- Bạn có thể sử dụng tham chiếu mà hàm nhận làm đối số của nó, để sửa đổi giá trị 'bên ngoài' của một biến, miễn là khi bạn không gán lại tham số để tham chiếu đến một đối tượng mới. 2- Gán vào một loại không thay đổi sẽ luôn tạo ra một đối tượng mới, phá vỡ tham chiếu mà bạn có cho biến bên ngoài.
Cam Jackson

11
@CamJackson, bạn cần một ví dụ tốt hơn - số cũng là đối tượng bất biến trong Python. Bên cạnh đó, sẽ không đúng khi nói rằng bất kỳ nhiệm vụ nào mà không đăng ký ở bên trái của đẳng thức sẽ gán lại tên cho một đối tượng mới cho dù đó là bất biến hay không? def Foo(alist): alist = [1,2,3]sẽ không sửa đổi nội dung của danh sách từ góc độ người gọi.
Đánh dấu tiền chuộc

59
-1. Các mã hiển thị là tốt, giải thích như thế nào là hoàn toàn sai. Xem câu trả lời của DavidCournapeau hoặc DarenThomas để được giải thích chính xác về lý do tại sao.
Ethan Furman

685

Vấn đề xuất phát từ sự hiểu lầm về các biến trong Python. Nếu bạn đã quen với hầu hết các ngôn ngữ truyền thống, bạn có một mô hình tinh thần về những gì xảy ra theo trình tự sau:

a = 1
a = 2

Bạn tin rằng đó alà một vị trí bộ nhớ lưu trữ giá trị 1, sau đó được cập nhật để lưu trữ giá trị 2. Đó không phải là cách mọi thứ hoạt động trong Python. Thay vào đó, abắt đầu như một tham chiếu đến một đối tượng có giá trị 1, sau đó được gán lại làm tham chiếu đến một đối tượng có giá trị 2. Hai đối tượng đó có thể tiếp tục cùng tồn tại mặc dù akhông đề cập đến đối tượng đầu tiên nữa; trong thực tế, chúng có thể được chia sẻ bởi bất kỳ số lượng tài liệu tham khảo nào khác trong chương trình.

Khi bạn gọi một hàm với một tham số, một tham chiếu mới được tạo tham chiếu đến đối tượng được truyền vào. Điều này tách biệt với tham chiếu được sử dụng trong lệnh gọi hàm, vì vậy không có cách nào để cập nhật tham chiếu đó và làm cho nó tham chiếu đến đối tượng mới. Trong ví dụ của bạn:

def __init__(self):
    self.variable = 'Original'
    self.Change(self.variable)

def Change(self, var):
    var = 'Changed'

self.variablelà một tham chiếu đến đối tượng chuỗi 'Original'. Khi bạn gọi Changebạn tạo một tham chiếu thứ hai varcho đối tượng. Bên trong hàm bạn gán lại tham chiếu varcho một đối tượng chuỗi khác 'Changed', nhưng tham chiếu self.variablelà riêng biệt và không thay đổi.

Cách duy nhất để vượt qua điều này là vượt qua một đối tượng có thể thay đổi. Bởi vì cả hai tham chiếu đều đề cập đến cùng một đối tượng, bất kỳ thay đổi nào đối với đối tượng đều được phản ánh ở cả hai vị trí.

def __init__(self):         
    self.variable = ['Original']
    self.Change(self.variable)

def Change(self, var):
    var[0] = 'Changed'

106
Giải thích ngắn gọn súc tích. Đoạn văn của bạn "Khi bạn gọi một hàm ..." là một trong những giải thích tốt nhất mà tôi đã nghe về cụm từ khá khó hiểu rằng 'Tham số hàm Python là tham chiếu, được truyền theo giá trị.' Tôi nghĩ rằng nếu bạn hiểu đoạn đó một mình, mọi thứ khác chỉ có ý nghĩa và trôi chảy như một kết luận hợp lý từ đó. Sau đó, bạn chỉ cần lưu ý khi bạn tạo một đối tượng mới và khi bạn sửa đổi một đối tượng hiện có.
Cam Jackson

3
Nhưng làm thế nào bạn có thể chỉ định lại tài liệu tham khảo? Tôi nghĩ rằng bạn không thể thay đổi địa chỉ của 'var' nhưng chuỗi "Đã thay đổi" của bạn hiện sẽ được lưu trữ trong địa chỉ bộ nhớ 'var'. Mô tả của bạn làm cho nó có vẻ như "Đã thay đổi" và "Bản gốc" thuộc về các vị trí khác nhau trong bộ nhớ và bạn chỉ cần chuyển 'var' sang một địa chỉ khác. Đúng không?
Glassjawed

9
@Glassjawed, tôi nghĩ bạn đang nhận được nó. "Đã thay đổi" và "Bản gốc" là hai đối tượng chuỗi khác nhau tại các địa chỉ bộ nhớ khác nhau và 'var' thay đổi từ chỉ sang một để chỉ đến khác.
Đánh dấu tiền chuộc

sử dụng hàm id () giúp làm rõ các vấn đề, bởi vì nó làm rõ khi Python tạo một đối tượng mới (vì vậy tôi nghĩ, dù sao đi nữa).
Tim Richardson

1
@MinhTran theo thuật ngữ đơn giản nhất, một tham chiếu là một cái gì đó "đề cập" đến một đối tượng. Biểu diễn vật lý của nó rất có thể là một con trỏ, nhưng đó đơn giản chỉ là một chi tiết triển khai. Nó thực sự là một khái niệm trừu tượng ở trái tim.
Đánh dấu tiền chuộc

329

Tôi thấy các câu trả lời khác khá dài và phức tạp, vì vậy tôi đã tạo ra sơ đồ đơn giản này để giải thích cách Python xử lý các biến và tham số. nhập mô tả hình ảnh ở đây


2
đáng yêu, làm cho nó dễ dàng nhận ra sự khác biệt tinh tế rằng có một nhiệm vụ trung gian, không rõ ràng đối với một người xem bình thường. +1
dùng22866

9
Không có vấn đề gì nếu A có thể thay đổi hay không. Nếu bạn gán một cái gì đó khác cho B, A sẽ không thay đổi . Nếu một đối tượng có thể thay đổi, bạn có thể biến đổi nó, chắc chắn. Nhưng điều đó không liên quan gì đến việc gán trực tiếp vào một cái tên ..
Martijn Pieters

1
@Martijn Bạn nói đúng. Tôi đã loại bỏ một phần của câu trả lời đề cập đến tính đột biến. Tôi không nghĩ rằng nó có thể trở nên đơn giản hơn bây giờ.
Zenadix

4
Cảm ơn đã cập nhật, tốt hơn nhiều! Điều gây nhầm lẫn cho hầu hết mọi người là gán cho một thuê bao; ví dụ B[0] = 2, so với phân công trực tiếp , B = 2.
Martijn Pieters

11
"A được gán cho B." Đó không phải là mơ hồ? Tôi nghĩ trong tiếng Anh thông thường có thể có nghĩa là A=Bhoặc B=A.
Hatshepsut

240

Nó không phải là pass-by-value hay pass-by-Reference - nó là call-by-object. Xem cái này, bởi Fredrik Lundh:

http://effbot.org/zone/call-by-object.htm

Đây là một trích dẫn quan trọng:

"... biến [tên] không phải là đối tượng; chúng không thể được biểu thị bằng các biến khác hoặc được gọi bởi các đối tượng."

Trong ví dụ của bạn, khi Changephương thức được gọi - một không gian tên được tạo cho nó; và vartrở thành một tên, trong không gian tên đó, cho đối tượng chuỗi 'Original'. Đối tượng đó sau đó có một tên trong hai không gian tên. Tiếp theo, var = 'Changed'liên kết varvới một đối tượng chuỗi mới và do đó, không gian tên của phương thức quên đi 'Original'. Cuối cùng, không gian tên đó bị lãng quên và chuỗi 'Changed'cùng với nó.


21
Tôi thấy khó mua. Đối với tôi cũng giống như Java, các tham số là các con trỏ tới các đối tượng trong bộ nhớ và các con trỏ đó được truyền qua ngăn xếp hoặc các thanh ghi.
Luciano

10
Điều này không giống như java. Một trong những trường hợp không giống nhau là các đối tượng bất biến. Hãy nghĩ về hàm tầm thường lambda x: x. Áp dụng điều này cho x = [1, 2, 3] và x = (1, 2, 3). Trong trường hợp đầu tiên, giá trị được trả về sẽ là một bản sao của đầu vào và giống hệt trong trường hợp thứ hai.
David Cournapeau

26
Không, nó chính xác như ngữ nghĩa của Java cho các đối tượng. Tôi không chắc ý của bạn là gì trong "Trong trường hợp đầu tiên, giá trị được trả về sẽ là một bản sao của đầu vào và giống hệt trong trường hợp thứ hai." nhưng tuyên bố đó dường như không chính xác
Mike Graham

23
Nó chính xác giống như trong Java. Tham chiếu đối tượng được thông qua bởi giá trị. Bất cứ ai nghĩ khác nên đính kèm mã Python cho một swapchức năng có thể trao đổi hai tham chiếu, như sau: a = [42] ; b = 'Hello'; swap(a, b) # Now a is 'Hello', b is [42]
cayhorstmann

24
Nó hoàn toàn giống với Java khi bạn truyền các đối tượng trong Java. Tuy nhiên, Java cũng có các nguyên hàm, được thông qua bằng cách sao chép giá trị của nguyên thủy. Vì vậy, họ khác nhau trong trường hợp đó.
Claudiu

174

Hãy nghĩ về những thứ được thông qua bằng cách chuyển nhượng thay vì tham chiếu / theo giá trị. Bằng cách đó, nó luôn luôn rõ ràng, những gì đang xảy ra miễn là bạn hiểu những gì xảy ra trong quá trình chuyển nhượng bình thường.

Vì vậy, khi truyền danh sách cho hàm / phương thức, danh sách được gán cho tên tham số. Việc thêm vào danh sách sẽ dẫn đến danh sách bị sửa đổi. Việc gán lại danh sách bên trong chức năng sẽ không thay đổi danh sách ban đầu, vì:

a = [1, 2, 3]
b = a
b.append(4)
b = ['a', 'b']
print a, b      # prints [1, 2, 3, 4] ['a', 'b']

Vì các loại bất biến không thể được sửa đổi, nên chúng có vẻ như được truyền theo giá trị - chuyển một int vào một hàm có nghĩa là gán int cho tham số của hàm. Bạn chỉ có thể xác định lại điều đó, nhưng nó sẽ không thay đổi giá trị biến ban đầu.


3
Thoạt nhìn câu trả lời này dường như bỏ qua câu hỏi ban đầu. Sau một giây đọc tôi đã nhận ra rằng điều này làm cho vấn đề khá rõ ràng. Có thể tìm thấy một khái niệm tốt về khái niệm "gán tên" này tại đây: Code Like a Pythonista: Idiomatic Python
Christian Groleau

65

Effbot (còn gọi là Fredrik Lundh) đã mô tả kiểu chuyển đổi biến của Python là gọi theo đối tượng: http://effbot.org/zone/call-by-object.htmlm

Các đối tượng được phân bổ trên heap và con trỏ tới chúng có thể được truyền xung quanh bất cứ nơi nào.

  • Khi bạn thực hiện một bài tập, chẳng hạn như x = 1000một mục từ điển được tạo để ánh xạ chuỗi "x" trong không gian tên hiện tại thành một con trỏ tới đối tượng số nguyên chứa một nghìn.

  • Khi bạn cập nhật "x" với x = 2000, một đối tượng số nguyên mới được tạo và từ điển được cập nhật để trỏ đến đối tượng mới. Một nghìn đối tượng cũ không thay đổi (và có thể tồn tại hoặc không tồn tại tùy thuộc vào việc có bất cứ điều gì khác đề cập đến đối tượng đó không).

  • Khi bạn thực hiện một nhiệm vụ mới, chẳng hạn như y = xmột mục từ điển mới "y" được tạo ra trỏ đến cùng một đối tượng như mục nhập cho "x".

  • Các đối tượng như chuỗi và số nguyên là bất biến . Điều này đơn giản có nghĩa là không có phương thức nào có thể thay đổi đối tượng sau khi nó được tạo. Ví dụ, một khi đối tượng số nguyên một nghìn được tạo, nó sẽ không bao giờ thay đổi. Toán học được thực hiện bằng cách tạo các đối tượng số nguyên mới.

  • Các đối tượng như danh sách là có thể thay đổi . Điều này có nghĩa là nội dung của đối tượng có thể được thay đổi bởi bất cứ điều gì chỉ vào đối tượng. Ví dụ, x = []; y = x; x.append(10); print ysẽ in [10]. Danh sách trống đã được tạo. Cả "x" và "y" đều trỏ đến cùng một danh sách. Phần phụ đột biến phương pháp (cập nhật) các đối tượng danh sách (như thêm một kỷ lục với một cơ sở dữ liệu) và kết quả là có thể nhìn thấy cả hai "x" và "y" (cũng giống như một bản cập nhật cơ sở dữ liệu sẽ được hiển thị cho tất cả các kết nối đến cơ sở dữ liệu).

Hy vọng rằng làm rõ vấn đề cho bạn.


2
Tôi thực sự đánh giá cao việc học về điều này từ một nhà phát triển. Có đúng là id()hàm trả về giá trị của con trỏ (tham chiếu đối tượng), như câu trả lời của pepr không?
thật Abe

4
@HonestAbe Có, trong CPython id () trả về địa chỉ. Nhưng trong các python khác như PyPy và Jython, id () chỉ là một định danh đối tượng duy nhất.
Raymond Hettinger

59

Về mặt kỹ thuật, Python luôn sử dụng các giá trị tham chiếu . Tôi sẽ lặp lại câu trả lời khác của tôi để hỗ trợ tuyên bố của tôi.

Python luôn sử dụng các giá trị tham chiếu qua. Không có ngoại lệ. Bất kỳ gán biến có nghĩa là sao chép giá trị tham chiếu. Không ngoại lệ. Bất kỳ biến là tên ràng buộc với giá trị tham chiếu. Luôn luôn.

Bạn có thể nghĩ về một giá trị tham chiếu là địa chỉ của đối tượng đích. Địa chỉ được tự động hủy đăng ký khi sử dụng. Bằng cách này, làm việc với giá trị tham chiếu, có vẻ như bạn làm việc trực tiếp với đối tượng đích. Nhưng luôn có một tài liệu tham khảo ở giữa, một bước nữa để nhảy đến mục tiêu.

Dưới đây là ví dụ chứng minh rằng Python sử dụng chuyển qua tham chiếu:

Ví dụ minh họa về việc thông qua đối số

Nếu đối số được truyền bằng giá trị, bên ngoài lst không thể sửa đổi . Màu xanh lá cây là các đối tượng đích (màu đen là giá trị được lưu trữ bên trong, màu đỏ là loại đối tượng), màu vàng là bộ nhớ có giá trị tham chiếu bên trong - được vẽ dưới dạng mũi tên. Mũi tên rắn màu xanh là giá trị tham chiếu được truyền cho hàm (thông qua đường dẫn mũi tên màu xanh nét đứt). Màu vàng đậm xấu xí là từ điển nội bộ. (Nó thực sự có thể được vẽ cũng như một hình elip màu xanh lá cây. Màu sắc và hình dạng chỉ nói nó là bên trong.)

Bạn có thể sử dụng hàm id()dựng sẵn để tìm hiểu giá trị tham chiếu là gì (nghĩa là địa chỉ của đối tượng đích).

Trong các ngôn ngữ được biên dịch, một biến là một không gian bộ nhớ có thể nắm bắt giá trị của loại. Trong Python, một biến là một tên (được bắt giữ bên trong dưới dạng một chuỗi) được liên kết với biến tham chiếu giữ giá trị tham chiếu cho đối tượng đích. Tên của biến là khóa trong từ điển nội bộ, phần giá trị của mục từ điển đó lưu trữ giá trị tham chiếu cho mục tiêu.

Giá trị tham chiếu được ẩn trong Python. Không có bất kỳ loại người dùng rõ ràng nào để lưu trữ giá trị tham chiếu. Tuy nhiên, bạn có thể sử dụng một phần tử danh sách (hoặc phần tử trong bất kỳ loại vùng chứa phù hợp nào khác) làm biến tham chiếu, bởi vì tất cả các vùng chứa cũng lưu trữ các phần tử làm tham chiếu đến các đối tượng đích. Nói cách khác, các phần tử thực sự không được chứa bên trong container - chỉ có các tham chiếu đến các phần tử là.


1
Trên thực tế điều này được xác nhận thông qua giá trị tham chiếu của nó. +1 cho câu trả lời này mặc dù ví dụ không tốt.
BugShotGG

30
Phát minh thuật ngữ mới (chẳng hạn như "vượt qua giá trị tham chiếu" hoặc "gọi theo đối tượng" là không hữu ích). "Gọi theo (giá trị | tham chiếu | tên)" là các thuật ngữ tiêu chuẩn. "Tài liệu tham khảo" là một thuật ngữ tiêu chuẩn. Truyền tham chiếu theo giá trị mô tả chính xác hành vi của Python, Java và một loạt các ngôn ngữ khác, sử dụng thuật ngữ chuẩn.
cayhorstmann

4
@cayhorstmann: Vấn đề là biến Python không có nghĩa thuật ngữ giống như trong các ngôn ngữ khác. Bằng cách này, gọi theo tham chiếu không phù hợp ở đây. Ngoài ra, làm thế nào để bạn xác định chính xác các thuật ngữ tham khảo ? Một cách không chính thức, cách Python có thể dễ dàng được mô tả là truyền địa chỉ của đối tượng. Nhưng nó không phù hợp với việc triển khai Python có khả năng phân tán.
pepr

1
Tôi thích câu trả lời này, nhưng bạn có thể xem xét nếu ví dụ này thực sự giúp ích hay làm tổn thương dòng chảy. Ngoài ra, nếu bạn thay thế 'giá trị tham chiếu' bằng 'tham chiếu đối tượng', bạn sẽ sử dụng thuật ngữ mà chúng tôi có thể xem là 'chính thức', như đã thấy ở đây: Xác định hàm
Honest Abe

2
Có một chú thích được chỉ ra ở phần cuối của trích dẫn đó, có nội dung: "Trên thực tế, cuộc gọi theo tham chiếu đối tượng sẽ là một mô tả tốt hơn, vì nếu một đối tượng có thể thay đổi được thông qua, người gọi sẽ thấy bất kỳ thay đổi nào mà callee thực hiện đối với nó ... " Tôi đồng ý với bạn rằng sự nhầm lẫn là do cố gắng phù hợp với thuật ngữ được thiết lập với các ngôn ngữ khác. Về mặt ngữ nghĩa, những điều cần được hiểu là: từ điển / không gian tên, hoạt động ràng buộc tên và mối quan hệ của tên → con trỏ → đối tượng (như bạn đã biết).
thật Abe

56

Không có biến trong Python

Chìa khóa để hiểu thông số truyền là dừng suy nghĩ về "các biến". Có tên và đối tượng trong Python và cùng nhau chúng xuất hiện như các biến, nhưng thật hữu ích khi luôn phân biệt ba.

  1. Python có tên và đối tượng.
  2. Bài tập liên kết một tên với một đối tượng.
  3. Truyền một đối số vào một hàm cũng liên kết một tên (tên tham số của hàm) với một đối tượng.

Đó là tất cả để có nó. Khả năng tương tác là không liên quan đến câu hỏi này.

Thí dụ:

a = 1

Cái này liên kết tên avới một đối tượng có kiểu nguyên chứa giá trị 1.

b = x

Điều này liên kết tên bvới cùng một đối tượng mà tên xhiện đang bị ràng buộc. Sau đó, tên bkhông còn liên quan gì đến tên xnữa.

Xem phần 3.14.2 trong tài liệu tham khảo ngôn ngữ Python 3.

Cách đọc ví dụ trong câu hỏi

Trong mã được hiển thị trong câu hỏi, câu lệnh self.Change(self.variable)liên kết tên var(trong phạm vi hàm Change) với đối tượng giữ giá trị 'Original'và phép gán var = 'Changed'(trong phần thân của hàm Change) gán lại tên đó: cho một số đối tượng khác (điều đó xảy ra để giữ một chuỗi là tốt nhưng có thể là một cái gì đó hoàn toàn khác).

Làm thế nào để vượt qua bằng cách tham khảo

Vì vậy, nếu điều bạn muốn thay đổi là một đối tượng có thể thay đổi, thì không có vấn đề gì, vì mọi thứ đều được thông qua một cách hiệu quả bằng cách tham chiếu.

Nếu nó là một đối tượng bất biến (ví dụ: bool, number, string), cách để đi là bọc nó trong một đối tượng có thể thay đổi.
Giải pháp nhanh và bẩn cho việc này là danh sách một yếu tố (thay vì self.variable, vượt qua [self.variable]và trong chức năng sửa đổi var[0]).
Cách tiếp cận pythonic hơn sẽ là giới thiệu một lớp thuộc tính tầm thường, một thuộc tính. Hàm nhận một thể hiện của lớp và thao tác thuộc tính.


20
"Python không có biến" là một khẩu hiệu ngớ ngẩn và khó hiểu, và tôi thực sự mong mọi người sẽ ngừng nói nó ... :( Phần còn lại của câu trả lời này là tốt!
Ned Batchelder

9
Nó có thể gây sốc, nhưng nó không ngớ ngẩn. Và tôi cũng không nghĩ nó khó hiểu: Hy vọng sẽ mở ra suy nghĩ của người nhận về lời giải thích đang đến và đưa cô ấy vào một thái độ "Tôi tự hỏi họ có gì thay vì biến". (Có, số dặm của bạn có thể thay đổi.)
Lutz Prechelt

13
Bạn cũng sẽ nói rằng Javascript không có biến? Chúng hoạt động giống như của Python. Ngoài ra, Java, Ruby, PHP, .... Tôi nghĩ rằng một kỹ thuật giảng dạy tốt hơn là "Các biến của Python hoạt động khác với C".
Ned Batchelder

9
Vâng, Java có các biến. Python cũng vậy, và JavaScript, Ruby, PHP, v.v. Bạn sẽ không nói trong Java intkhai báo một biến, nhưng Integerkhông. Cả hai đều khai báo các biến. Các Integerbiến là một đối tượng, các intbiến là một nguyên thủy. Ví dụ, bạn đã chứng minh cách các biến của bạn hoạt động bằng cách hiển thị a = 1; b = a; a++ # doesn't modify b. Điều đó cũng chính xác trong Python (sử dụng += 1vì không có ++Python)!
Ned Batchelder

1
Khái niệm "biến" rất phức tạp và thường mơ hồ: Biến là một thùng chứa cho một giá trị, được xác định bằng tên. Trong Python, các giá trị là các đối tượng, các thùng chứa là các đối tượng (xem vấn đề?) Và các tên thực sự là những thứ riêng biệt. Tôi tin rằng sẽ khó khăn hơn nhiều để có được sự hiểu biết chính xác về các biến theo cách này. Việc giải thích tên và đối tượng có vẻ khó khăn hơn, nhưng thực sự đơn giản hơn.
Lutz Prechelt

43

Một mẹo đơn giản tôi thường sử dụng là chỉ gói nó trong một danh sách:

def Change(self, var):
    var[0] = 'Changed'

variable = ['Original']
self.Change(variable)      
print variable[0]

(Vâng tôi biết điều này có thể bất tiện, nhưng đôi khi nó đủ đơn giản để làm điều này.)


7
+1 cho số lượng nhỏ văn bản đưa ra cách giải quyết cần thiết cho vấn đề Python không có tham chiếu qua. (Như một bình luận / câu hỏi tiếp theo phù hợp ở đây cũng như mọi nơi trên trang này: Tôi không rõ tại sao python không thể cung cấp từ khóa "ref" như C #, chỉ đơn giản là bao bọc đối số của người gọi trong danh sách như này và coi các tham chiếu đến đối số trong hàm là phần tử thứ 0 của danh sách.)
M Katz

5
Đẹp. Để vượt qua ref, bọc trong [] 's.
Justas

38

(chỉnh sửa - Blair đã cập nhật câu trả lời cực kỳ phổ biến của mình để bây giờ chính xác)

Tôi nghĩ điều quan trọng cần lưu ý là bài đăng hiện tại có nhiều phiếu bầu nhất (bởi Blair Conrad), trong khi chính xác với kết quả của nó, là sai lệch và sai giới hạn dựa trên định nghĩa của nó. Mặc dù có nhiều ngôn ngữ (như C) cho phép người dùng chuyển qua tham chiếu hoặc chuyển theo giá trị, Python không phải là một trong số đó.

Câu trả lời của David Cournapeau chỉ ra câu trả lời thực sự và giải thích lý do tại sao hành vi trong bài đăng của Blair Conrad có vẻ đúng trong khi các định nghĩa thì không.

Trong phạm vi Python được truyền theo giá trị, tất cả các ngôn ngữ đều được truyền theo giá trị do một số dữ liệu (có thể là "giá trị" hoặc "tham chiếu") phải được gửi. Tuy nhiên, điều đó không có nghĩa là Python vượt qua giá trị theo nghĩa là một lập trình viên C sẽ nghĩ về nó.

Nếu bạn muốn hành vi, câu trả lời của Blair Conrad là tốt. Nhưng nếu bạn muốn biết các loại hạt và bu lông tại sao Python không vượt qua giá trị hoặc vượt qua tham chiếu, hãy đọc câu trả lời của David Cournapeau.


4
Nó chỉ đơn giản là không đúng sự thật rằng tất cả các ngôn ngữ được gọi theo giá trị. Trong C ++ hoặc Pascal (và chắc chắn nhiều người khác mà tôi không biết), bạn đã gọi bằng cách tham khảo. Ví dụ, trong C ++, void swap(int& x, int& y) { int temp = x; x = y; y = temp; }sẽ trao đổi các biến được truyền cho nó. Trong Pascal, bạn sử dụng varthay vì &.
cayhorstmann

2
Tôi nghĩ rằng tôi đã trả lời điều này từ lâu nhưng tôi không thấy nó. Để hoàn thiện - cayhorstmann đã hiểu nhầm câu trả lời của tôi. Tôi đã không nói rằng mọi thứ đều được gọi theo giá trị theo thuật ngữ mà hầu hết mọi người lần đầu tiên tìm hiểu về C / C ++ . Đơn giản là một số giá trị được thông qua (giá trị, tên, con trỏ, v.v.) và các thuật ngữ được sử dụng trong câu trả lời ban đầu của Blair là không chính xác.
KobeJohn

24

Bạn có một số câu trả lời thực sự tốt ở đây.

x = [ 2, 4, 4, 5, 5 ]
print x  # 2, 4, 4, 5, 5

def go( li ) :
  li = [ 5, 6, 7, 8 ]  # re-assigning what li POINTS TO, does not
  # change the value of the ORIGINAL variable x

go( x ) 
print x  # 2, 4, 4, 5, 5  [ STILL! ]


raw_input( 'press any key to continue' )

2
tuy nhiên, nếu bạn làm x = [2, 4, 4, 5, 5], y = x, X [0] = 1, in x # [1, 4, 4, 5, 5] in y # [1 , 4, 4, 5, 5]
giáo dân

19

Trong trường hợp này, biến có tiêu đề vartrong phương thức Changeđược gán tham chiếu self.variablevà bạn ngay lập tức gán một chuỗi cho var. Nó không còn chỉ vào self.variable. Đoạn mã sau đây cho thấy điều gì sẽ xảy ra nếu bạn sửa đổi cấu trúc dữ liệu được trỏ đến varself.variable, trong trường hợp này là một danh sách:

>>> class PassByReference:
...     def __init__(self):
...         self.variable = ['Original']
...         self.change(self.variable)
...         print self.variable
...         
...     def change(self, var):
...         var.append('Changed')
... 
>>> q = PassByReference()
['Original', 'Changed']
>>> 

Tôi chắc rằng người khác có thể làm rõ điều này hơn nữa.


18

Lược đồ chuyển qua của Python không hoàn toàn giống với tùy chọn tham số tham chiếu của C ++, nhưng hóa ra nó rất giống với mô hình truyền đối số của ngôn ngữ C (và các ngôn ngữ khác) trong thực tế:

  • Đối số bất biến được truyền đạt một cách hiệu quả bởi giá trị . Các đối tượng như số nguyên và chuỗi được truyền bằng tham chiếu đối tượng thay vì sao chép, nhưng vì dù sao bạn cũng không thể thay đổi các đối tượng bất biến, nên hiệu ứng giống như tạo một bản sao.
  • Các đối số có thể thay đổi được truyền qua một cách hiệu quả bởi con trỏ . Các đối tượng như danh sách và từ điển cũng được truyền bằng tham chiếu đối tượng, tương tự như cách C vượt qua các mảng vì con trỏ Các đối tượng có thể thay đổi có thể được thay đổi tại chỗ trong hàm, giống như mảng C.

16

Như bạn có thể nói rằng bạn cần phải có một đối tượng có thể thay đổi, nhưng hãy để tôi đề nghị bạn kiểm tra các biến toàn cục vì chúng có thể giúp bạn hoặc thậm chí giải quyết loại vấn đề này!

http://docs.python.org/3/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python

thí dụ:

>>> def x(y):
...     global z
...     z = y
...

>>> x
<function x at 0x00000000020E1730>
>>> y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'y' is not defined
>>> z
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'z' is not defined

>>> x(2)
>>> x
<function x at 0x00000000020E1730>
>>> y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'y' is not defined
>>> z
2

5
Tôi đã cố gắng gửi một phản hồi tương tự - người hỏi ban đầu có thể không biết rằng thực tế anh ta muốn sử dụng một biến toàn cục, được chia sẻ giữa các chức năng. Đây là liên kết mà tôi đã chia sẻ: stackoverflow.com/questions/423379/, Trả lời @Tim, Stack Overflow không chỉ là một trang web câu hỏi và trả lời, đó là một kho kiến ​​thức tham khảo rộng lớn chỉ mạnh hơn và nhiều sắc thái hơn giống như một wiki hoạt động - với nhiều đầu vào hơn.
Max P Magee

13

Rất nhiều cái nhìn sâu sắc trong câu trả lời ở đây, nhưng tôi nghĩ rằng một điểm bổ sung không được đề cập rõ ràng ở đây. Trích dẫn từ tài liệu python https://docs.python.org/2/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python

"Trong Python, các biến chỉ được tham chiếu bên trong hàm là toàn cục. Nếu một biến được gán một giá trị mới ở bất kỳ vị trí nào trong thân hàm, thì nó được coi là cục bộ. Nếu một biến được gán một giá trị mới bên trong hàm, biến này hoàn toàn cục bộ và bạn cần khai báo rõ ràng là 'toàn cầu'. Mặc dù lúc đầu hơi ngạc nhiên, sự cân nhắc trong một khoảnh khắc giải thích điều này. Một mặt, yêu cầu toàn cầu cho các biến được gán cung cấp một thanh chống lại các tác dụng phụ ngoài ý muốn. mặt khác, nếu toàn cầu là bắt buộc đối với tất cả các tham chiếu toàn cầu, bạn sẽ sử dụng toàn cầu mọi lúc. Bạn phải khai báo là toàn cầu mọi tham chiếu đến chức năng tích hợp hoặc một thành phần của mô-đun nhập khẩu. sẽ đánh bại tính hữu ích của tuyên bố toàn cầu để xác định tác dụng phụ. "

Ngay cả khi chuyển một đối tượng có thể thay đổi đến một chức năng, điều này vẫn được áp dụng. Và với tôi giải thích rõ ràng lý do cho sự khác biệt trong hành vi giữa việc gán cho đối tượng và hoạt động trên đối tượng trong hàm.

def test(l):
    print "Received", l , id(l)
    l = [0, 0, 0]
    print "Changed to", l, id(l)  # New local object created, breaking link to global l

l= [1,2,3]
print "Original", l, id(l)
test(l)
print "After", l, id(l)

cho:

Original [1, 2, 3] 4454645632
Received [1, 2, 3] 4454645632
Changed to [0, 0, 0] 4474591928
After [1, 2, 3] 4454645632

Việc gán cho một biến toàn cục không được khai báo toàn cục do đó tạo ra một đối tượng cục bộ mới và phá vỡ liên kết đến đối tượng ban đầu.


10

Dưới đây là lời giải thích đơn giản (tôi hy vọng) về khái niệm pass by objectđược sử dụng trong Python.
Bất cứ khi nào bạn truyền một đối tượng cho hàm, chính đối tượng đó được truyền (đối tượng trong Python thực sự là thứ bạn gọi là một giá trị trong các ngôn ngữ lập trình khác) không phải là tham chiếu đến đối tượng này. Nói cách khác, khi bạn gọi:

def change_me(list):
   list = [1, 2, 3]

my_list = [0, 1]
change_me(my_list)

Đối tượng thực tế - [0, 1] (sẽ được gọi là giá trị trong các ngôn ngữ lập trình khác) đang được thông qua. Vì vậy, trong thực tế chức năng change_mesẽ cố gắng làm một cái gì đó như:

[0, 1] = [1, 2, 3]

mà rõ ràng sẽ không thay đổi đối tượng được truyền cho hàm. Nếu chức năng trông như thế này:

def change_me(list):
   list.append(2)

Sau đó, cuộc gọi sẽ dẫn đến:

[0, 1].append(2)

mà rõ ràng sẽ thay đổi đối tượng. Câu trả lời này giải thích nó tốt.


2
Vấn đề là sự phân công làm một cái gì đó khác hơn bạn mong đợi. Các list = [1, 2, 3]nguyên nhân sử dụng lại listtên cho một cái gì đó khác và quên đối tượng được thông qua ban đầu. Tuy nhiên, bạn có thể thử list[:] = [1, 2, 3](bằng cách này listlà sai tên cho một biến Nghĩ về. [0, 1] = [1, 2, 3]Là một vô nghĩa hoàn chỉnh Dù sao, điều gì làm bạn nghĩ rằng các phương tiện. Đối tượng chính nó được chuyển gì được sao chép vào các chức năng theo ý kiến của bạn?
PEPR

Đối tượng @pepr không theo nghĩa đen. Họ là những đối tượng. Cách duy nhất để nói về họ là cho họ một số tên. Đó là lý do tại sao nó đơn giản một khi bạn nắm bắt nó, nhưng vô cùng phức tạp để giải thích. :-)
Veky

@Veky: Tôi nhận thức được điều đó. Dù sao, danh sách bằng chữ được chuyển đổi thành đối tượng danh sách. Trên thực tế, bất kỳ đối tượng nào trong Python đều có thể tồn tại mà không có tên và nó có thể được sử dụng ngay cả khi không được đặt bất kỳ tên nào. Và bạn có thể nghĩ về chúng như về các đối tượng ẩn danh. Hãy suy nghĩ về các đối tượng là các yếu tố của một danh sách. Họ không cần một cái tên. Bạn có thể truy cập chúng thông qua lập chỉ mục hoặc lặp qua danh sách. Dù sao, tôi nhấn mạnh [0, 1] = [1, 2, 3]chỉ đơn giản là một ví dụ xấu. Không có gì giống như vậy trong Python.
pepr

@pepr: Tôi không nhất thiết có nghĩa là tên định nghĩa Python, chỉ là tên thông thường. Tất nhiên alist[2]được tính là tên của một yếu tố thứ ba của alist. Nhưng tôi nghĩ rằng tôi đã hiểu nhầm vấn đề của bạn là gì. :-)
Veky

Argh. Tiếng Anh của tôi rõ ràng kém hơn Python của tôi rất nhiều. :-) Tôi sẽ thử một lần nữa. Tôi chỉ nói rằng bạn phải đặt cho đối tượng một số tên chỉ để nói về họ. Bởi "tên" tôi không có nghĩa là "tên theo định nghĩa của Python". Tôi biết cơ chế Python, đừng lo lắng.
Veky

8

Ngoài tất cả những lời giải thích tuyệt vời về cách thức hoạt động của công cụ này trong Python, tôi không thấy một gợi ý đơn giản nào cho vấn đề này. Khi bạn dường như tạo các đối tượng và các thể hiện, cách xử lý các biến đối tượng và thay đổi chúng theo kiểu pythonic như sau:

class PassByReference:
    def __init__(self):
        self.variable = 'Original'
        self.Change()
        print self.variable

    def Change(self):
        self.variable = 'Changed'

Trong các phương thức cá thể, bạn thường tham khảo selfcác thuộc tính truy cập. Việc đặt các thuộc tính thể hiện trong __init__và đọc hoặc thay đổi chúng trong các phương thức thể hiện là bình thường . Đó cũng là lý do tại sao bạn vượt qua selfđối số đầu tiên def Change.

Một giải pháp khác là tạo ra một phương thức tĩnh như thế này:

class PassByReference:
    def __init__(self):
        self.variable = 'Original'
        self.variable = PassByReference.Change(self.variable)
        print self.variable

    @staticmethod
    def Change(var):
        var = 'Changed'
        return var

6

Có một mẹo nhỏ để vượt qua một đối tượng bằng cách tham chiếu, mặc dù ngôn ngữ không thể thực hiện được. Nó cũng hoạt động trong Java, đó là danh sách với một mục. ;-)

class PassByReference:
    def __init__(self, name):
        self.name = name

def changeRef(ref):
    ref[0] = PassByReference('Michael')

obj = PassByReference('Peter')
print obj.name

p = [obj] # A pointer to obj! ;-)
changeRef(p)

print p[0].name # p->name

Đó là một hack xấu xí, nhưng nó hoạt động. ;-P


plà tham chiếu đến một đối tượng danh sách có thể thay đổi, lần lượt lưu trữ đối tượng obj. Tham chiếu 'p', được truyền vào changeRef. Bên trong changeRef, một tham chiếu mới được tạo (tham chiếu mới được gọi ref) trỏ đến cùng một đối tượng danh sách ptrỏ đến. Nhưng vì danh sách có thể thay đổi, nên cả hai tham chiếu đều có thể thay đổi danh sách . Trong trường hợp này, bạn đã sử dụng reftham chiếu để thay đổi đối tượng ở chỉ số 0 để sau đó nó lưu trữ PassByReference('Michael')đối tượng. Thay đổi đối tượng danh sách đã được thực hiện bằng cách sử dụng refnhưng thay đổi này được hiển thị p.
Minh Trần

Vì vậy, bây giờ, các tham chiếu preftrỏ đến một đối tượng danh sách lưu trữ đối tượng duy nhất , PassByReference('Michael'). Vì vậy, nó theo sau mà p[0].nametrở lại Michael. Tất nhiên, refbây giờ đã vượt ra khỏi phạm vi và có thể là rác được thu thập nhưng tất cả đều giống nhau.
Minh Trần

Mặc dù vậy, bạn không thay đổi biến cá thể riêng namecủa PassByReferenceđối tượng ban đầu được liên kết với tham chiếu obj. Trong thực tế, obj.namesẽ trở lại Peter. Các ý kiến ​​nói trên giả định định nghĩa Mark Ransomđã đưa ra.
Minh Trần

Vấn đề là, tôi không đồng ý rằng đó là một vụ hack (mà tôi muốn nói là đề cập đến một cái gì đó hoạt động nhưng vì lý do không xác định, chưa được kiểm tra hoặc ngoài ý muốn của người thực hiện). Bạn chỉ cần thay thế một PassByReferenceđối tượng bằng một PassByReferenceđối tượng khác trong danh sách của bạn và tham chiếu đến đối tượng sau của hai đối tượng.
Minh Trần

5

Tôi đã sử dụng phương pháp sau để nhanh chóng chuyển đổi một vài mã Fortran sang Python. Đúng, nó không vượt qua được tham chiếu như câu hỏi ban đầu đã được đặt ra, nhưng là một công việc đơn giản xung quanh trong một số trường hợp.

a=0
b=0
c=0
def myfunc(a,b,c):
    a=1
    b=2
    c=3
    return a,b,c

a,b,c = myfunc(a,b,c)
print a,b,c

Có, điều này cũng giải quyết 'vượt qua tham chiếu' trong trường hợp sử dụng của tôi. Tôi có một hàm về cơ bản dọn sạch các giá trị trong một dictvà sau đó trả về dict. Tuy nhiên, trong khi dọn dẹp, nó có thể trở nên rõ ràng việc xây dựng lại một phần của hệ thống là bắt buộc. Do đó, chức năng không chỉ phải trả lại đã được làm sạch dictmà còn có thể báo hiệu việc xây dựng lại. Tôi đã cố gắng vượt qua boolbằng cách tham khảo, nhưng nó không hoạt động. Tìm ra cách giải quyết vấn đề này, tôi thấy giải pháp của bạn (về cơ bản trả lại một tuple) để hoạt động tốt nhất trong khi cũng không phải là một hack / giải pháp thay thế (IMHO).
kas Elli

3

Mặc dù truyền qua tham chiếu không có gì phù hợp với python và hiếm khi được sử dụng, có một số cách giải quyết thực sự có thể làm việc để đưa đối tượng hiện được gán cho một biến cục bộ hoặc thậm chí gán lại một biến cục bộ từ bên trong hàm được gọi.

Ý tưởng cơ bản là có một hàm có thể thực hiện truy cập đó và có thể được chuyển dưới dạng đối tượng vào các hàm khác hoặc được lưu trữ trong một lớp.

Một cách là sử dụng global(cho các biến toàn cục) hoặc nonlocal(cho các biến cục bộ trong hàm) trong hàm bao bọc.

def change(wrapper):
    wrapper(7)

x = 5
def setter(val):
    global x
    x = val
print(x)

Ý tưởng tương tự làm việc để đọc và khắc delmột biến.

Đối với việc chỉ đọc thậm chí còn có một cách ngắn hơn là chỉ sử dụng lambda: xtrả về một cuộc gọi mà khi được gọi sẽ trả về giá trị hiện tại của x. Điều này hơi giống như "gọi theo tên" được sử dụng trong các ngôn ngữ trong quá khứ xa xôi.

Việc truyền 3 hàm bao để truy cập vào một biến là hơi khó sử dụng để chúng có thể được bọc trong một lớp có thuộc tính proxy:

class ByRef:
    def __init__(self, r, w, d):
        self._read = r
        self._write = w
        self._delete = d
    def set(self, val):
        self._write(val)
    def get(self):
        return self._read()
    def remove(self):
        self._delete()
    wrapped = property(get, set, remove)

# left as an exercise for the reader: define set, get, remove as local functions using global / nonlocal
r = ByRef(get, set, remove)
r.wrapped = 15

Hỗ trợ "phản chiếu" của Pythons cho phép có được một đối tượng có khả năng gán lại tên / biến trong một phạm vi nhất định mà không xác định rõ ràng các chức năng trong phạm vi đó:

class ByRef:
    def __init__(self, locs, name):
        self._locs = locs
        self._name = name
    def set(self, val):
        self._locs[self._name] = val
    def get(self):
        return self._locs[self._name]
    def remove(self):
        del self._locs[self._name]
    wrapped = property(get, set, remove)

def change(x):
    x.wrapped = 7

def test_me():
    x = 6
    print(x)
    change(ByRef(locals(), "x"))
    print(x)

Ở đây ByReflớp kết thúc một truy cập từ điển. Vì vậy, quyền truy cập thuộc tính wrappedđược dịch sang quyền truy cập mục trong từ điển đã qua. Bằng cách chuyển kết quả của nội dung localsvà tên của một biến cục bộ, kết quả này sẽ truy cập vào một biến cục bộ. Tài liệu python kể từ 3.5 khuyên rằng việc thay đổi từ điển có thể không hoạt động nhưng dường như nó hiệu quả với tôi.


3

đưa ra cách python xử lý các giá trị và tham chiếu đến chúng, cách duy nhất bạn có thể tham chiếu một thuộc tính cá thể tùy ý là theo tên:

class PassByReferenceIsh:
    def __init__(self):
        self.variable = 'Original'
        self.change('variable')
        print self.variable

    def change(self, var):
        self.__dict__[var] = 'Changed'

trong mã thực, bạn sẽ, tất nhiên, thêm kiểm tra lỗi trên tra cứu dict.


3

Vì từ điển được truyền bằng tham chiếu, bạn có thể sử dụng biến dict để lưu trữ bất kỳ giá trị được tham chiếu nào bên trong nó.

# returns the result of adding numbers `a` and `b`
def AddNumbers(a, b, ref): # using a dict for reference
    result = a + b
    ref['multi'] = a * b # reference the multi. ref['multi'] is number
    ref['msg'] = "The result: " + str(result) + " was nice!" # reference any string (errors, e.t.c). ref['msg'] is string
    return result # return the sum

number1 = 5
number2 = 10
ref = {} # init a dict like that so it can save all the referenced values. this is because all dictionaries are passed by reference, while strings and numbers do not.

sum = AddNumbers(number1, number2, ref)
print("sum: ", sum)             # the return value
print("multi: ", ref['multi'])  # a referenced value
print("msg: ", ref['msg'])      # a referenced value

3

Pass-By-Reference trong Python khá khác biệt với khái niệm pass by Reference trong C ++ / Java.

  • Java & C #: kiểu nguyên thủy (bao gồm chuỗi) truyền theo giá trị (bản sao), Kiểu tham chiếu được truyền bằng tham chiếu (bản sao địa chỉ) để tất cả các thay đổi được thực hiện trong tham số trong hàm được gọi sẽ hiển thị cho người gọi.
  • C ++: Cho phép cả tham chiếu qua hoặc tham chiếu qua giá trị. Nếu một tham số được truyền bằng tham chiếu, bạn có thể sửa đổi nó hoặc không tùy thuộc vào việc tham số đó có được truyền dưới dạng const hay không. Tuy nhiên, dù có hay không, tham số vẫn duy trì tham chiếu đến đối tượng và tham chiếu không thể được chỉ định để trỏ đến một đối tượng khác trong hàm được gọi.
  • Python: Python là tham chiếu qua từng đối tượng, trong đó người ta thường nói: Các tham chiếu đối tượng được truyền theo giá trị. [[Đọc tại đây] 1. Cả người gọi và hàm đều tham chiếu đến cùng một đối tượng nhưng tham số trong hàm là một biến mới chỉ giữ một bản sao của đối tượng trong trình gọi. Giống như C ++, một tham số có thể được sửa đổi hoặc không hoạt động - Điều này phụ thuộc vào loại đối tượng được truyền. ví dụ; Một loại đối tượng bất biến không thể được sửa đổi trong hàm được gọi trong khi một đối tượng có thể thay đổi có thể được cập nhật hoặc khởi tạo lại. Một sự khác biệt quan trọng giữa việc cập nhật hoặc gán lại / khởi tạo lại biến có thể thay đổi là giá trị cập nhật được phản ánh lại trong hàm được gọi trong khi giá trị được khởi tạo lại thì không. Phạm vi của bất kỳ sự gán đối tượng mới nào cho một biến có thể thay đổi là cục bộ đối với hàm trong python. Các ví dụ được cung cấp bởi @ blair-conrad rất tuyệt để hiểu điều này.

1
Cũ nhưng tôi cảm thấy bắt buộc phải sửa nó. Các chuỗi được truyền bằng tham chiếu trong cả Java và C #, KHÔNG theo giá trị
John

2

Vì ví dụ của bạn là hướng đối tượng, bạn có thể thực hiện thay đổi sau để đạt được kết quả tương tự:

class PassByReference:
    def __init__(self):
        self.variable = 'Original'
        self.change('variable')
        print(self.variable)

    def change(self, var):
        setattr(self, var, 'Changed')

# o.variable will equal 'Changed'
o = PassByReference()
assert o.variable == 'Changed'

1
Mặc dù điều này hoạt động. Nó không được thông qua tham khảo. Đó là 'vượt qua tham chiếu đối tượng'.
Bishwas Mishra

1

Bạn chỉ có thể sử dụng một lớp trống làm ví dụ để lưu trữ các đối tượng tham chiếu vì các thuộc tính đối tượng bên trong được lưu trữ trong một từ điển thể hiện. Xem ví dụ.

class RefsObj(object):
    "A class which helps to create references to variables."
    pass

...

# an example of usage
def change_ref_var(ref_obj):
    ref_obj.val = 24

ref_obj = RefsObj()
ref_obj.val = 1
print(ref_obj.val) # or print ref_obj.val for python2
change_ref_var(ref_obj)
print(ref_obj.val)

1

Vì dường như không có nơi nào đề cập đến một cách tiếp cận để mô phỏng các tham chiếu như được biết đến từ ví dụ C ++ là sử dụng hàm "update" và chuyển nó thay vì biến thực tế (hay đúng hơn là "name"):

def need_to_modify(update):
    update(42) # set new value 42
    # other code

def call_it():
    value = 21
    def update_value(new_value):
        nonlocal value
        value = new_value
    need_to_modify(update_value)
    print(value) # prints 42

Điều này chủ yếu hữu ích cho "tham chiếu ngoài chỉ" hoặc trong tình huống có nhiều luồng / quy trình (bằng cách làm cho luồng chức năng cập nhật / đa xử lý an toàn).

Rõ ràng ở trên không cho phép đọc giá trị, chỉ cập nhật nó.

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.