[Python] 如何通過引用傳遞變量?


Answers

問題來自於誤解Python中的變量。 如果你已經習慣了大多數傳統語言,那麼你就有了一個心理模型,以下面的順序發生了什麼:

a = 1
a = 2

您認為a是存儲值1的內存位置,然後更新為存儲值2 。 這不是Python在Python中的工作方式。 相反, a作為對值為1的對象的引用開始,然後被重新分配為對值為2的對象的引用。 這兩個對象可能會繼續共存,即使a不再提及第一個對象; 實際上它們可以被程序內的任何其他引用所共享。

使用參數調用函數時,會創建一個引用傳入對象的新引用。它與函數調用中使用的引用分開,因此無法更新該引用並使其引用新對象。 在你的例子中:

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

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

self.variable是對字符串對象'Original'的引用。 當您調用Change將為對象創建第二個參考var 。 在函數內部,您將引用var重新分配給不同的字符串對象'Changed' ,但引用self.variable是分開的,不會更改。

唯一的解決辦法是傳遞一個可變對象。 因為兩個引用都引用同一個對象,所以對這個對象的任何更改都會反映在這兩個地方。

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

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

Python文檔似乎不清楚參數是按引用還是按值傳遞,並且以下代碼會生成未更改的值“原始”

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

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

有什麼我可以做的,通過實際參考傳遞變量?




由於您的示例碰巧是面向對象的,因此您可以進行以下更改以獲得類似的結果:

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'



它既不是按值傳遞也不是傳遞引用 - 它是按對象分類的。 Fredrik Lundh看到這個:

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

這裡有一個重要的報價:

“...變量[名稱] 不是對象;它們不能被其他變量表示或被對象引用。”

在你的例子中,當Change方法被調用時 - 為它創建一個namespace ; var在該名稱空間內成為字符串對象'Original' 。 那個對像在兩個名字空間中有一個名字。 接下來, var = 'Changed'var綁定到一個新的字符串對象,因此該方法的名稱空間會忘記'Original' 。 最後,這個名字空間被遺忘了,字符串'Changed'隨之被'Changed'




我通常使用的簡單技巧就是將它包裝在一個列表中:

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

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

(是的,我知道這可能不方便,但有時候這很簡單。)




There is a little trick to pass an object by reference, even though the language doesn't make it possible. It works in Java too, it's the list with one item. ;-)

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

It's an ugly hack, but it works. ;-P




在這種情況下,方法Change標題為var的變量被賦予一個對self.variable的引用,並且您立即將一個字符串賦值給var 。 它不再指向self.variable 。 以下代碼片段顯示如果修改varself.variable指向的數據結構(在本例中為list),會發生什麼情況:

>>> 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']
>>> 

我相信別人可以進一步澄清這一點。




Here is the simple (I hope) explanation of the concept pass by object used in Python.
Whenever you pass an object to the function, the object itself is passed (object in Python is actually what you'd call a value in other programming languages) not the reference to this object. In other words, when you call:

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

my_list = [0, 1]
change_me(my_list)

The actual object - [0, 1] (which would be called a value in other programming languages) is being passed. So in fact the function change_me will try to do something like:

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

which obviously will not change the object passed to the function. If the function looked like this:

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

Then the call would result in:

[0, 1].append(2)

which obviously will change the object. This answer explains it well.




Python中沒有變量

理解參數傳遞的關鍵是停止思考“變量”。 Python中有名稱和對象,它們一起顯示為變量,但總是區分這三個變量很有用。

  1. Python有名稱和對象。
  2. 賦值將一個名稱綁定到一個對象。
  3. 將參數傳遞給函數還會將名稱(函數的參數名稱)綁定到對象。

這就是它的全部。 可變性與這個問題無關。

例:

a = 1

這將名稱a綁定到保存值為1的integer類型的對象。

b = x

這將名稱b綁定到名稱x當前綁定到的相同對象。 之後,名字b與名字x無關。

請參閱Python 3語言參考中的第3.14.2節。

因此,在問題中顯示的代碼中,語句self.Change(self.variable)將名稱var (在函數Change中的範圍self.Change(self.variable)綁定到包含值'Original'和賦值var = 'Changed' (在函數Change的主體中)再次賦予相同的名稱:指向其他某個對象(恰好也包含一個字符串,但可能完全是其他東西)。




Effbot(又名Fredrik Lundh)將Python的變量傳遞風格描述為逐個對象: http://effbot.org/zone/call-by-object.htm : http://effbot.org/zone/call-by-object.htm

對象分配在堆上,指向它們的指針可以在任何地方傳遞。

  • 當您進行x = 1000等賦值時,會創建一個字典條目,將當前名稱空間中的字符串“x”映射到指向包含一千個整數對象的指針。

  • 當您使用x = 2000更新“x”時,將創建一個新的整數對象,並更新字典以指向新對象。 舊的一千個對像是不變的(並且根據其他事物是否指向該對象,可能還可能不存在)。

  • 當您執行新的賦值(如y = x ,會創建一個新的字典條目“y”,指向與“x”條目相同的對象。

  • 像字符串和整數這樣的對像是不可變的 。 這只是意味著沒有方法可以在創建對像後更改對象。 例如,一旦創建了一千個整數對象,它將永遠不會改變。 數學是通過創建新的整數對象完成的。

  • 像列表這樣的對像是可變的 。 這意味著對象的內容可以通過指向對象的任何東西來改變。 例如, x = []; y = x; x.append(10); print y x = []; y = x; x.append(10); print y x = []; y = x; x.append(10); print y將打印[10] 。 空的列表已創建。 “x”和“y”都指向相同的列表。 append方法改變(更新)列表對象(就像向數據庫添加記錄一樣),並且結果對“x”和“y”都是可見的(正如數據庫更新對於該數據庫的每個連接都可見)。

希望能夠為您解決問題。




While pass by reference is nothing that fits well into python and should be rarely used there are some workarounds that actually can work to get the object currently assigned to a local variable or even reassign a local variable from inside of a called function.

The basic idea is to have a function that can do that access and can be passed as object into other functions or stored in a class.

One way is to use global (for global variables) or nonlocal (for local variables in a function) in a wrapper function.

def change(wrapper):
    wrapper(7)

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

The same idea works for reading and del eting a variable.

For just reading there is even a shorter way of just using lambda: x which returns a callable that when called returns the current value of x. This is somewhat like "call by name" used in languages in the distant past.

Passing 3 wrappers to access a variable is a bit unwieldy so those can be wrapped into a class that has a proxy attribute:

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

Pythons "reflection" support makes it possible to get a object that is capable of reassigning a name/variable in a given scope without defining functions explicitly in that scope:

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)

Here the ByRef class wraps a dictionary access. So attribute access to wrapped is translated to a item access in the passed dictionary. By passing the result of the builtin locals and the name of a local variable this ends up accessing a local variable. The python documentation as of 3.5 advises that changing the dictionary might not work but it seems to work for me.




正如你可以說你需要一個可變對象,但讓我建議你檢查全局變量,因為它們可以幫助你甚至解決這類問題!

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

例:

>>> 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