reference python方法 python函數 - 如何通過引用傳遞變量?





11 Answers

問題來自對Python中變量的誤解。 如果您習慣了大多數傳統語言,那麼您將擁有以下序列中發生的事情的心理模型:

a = 1
a = 2

您認為a是存儲值1的內存位置,然後更新為存儲值2 。 這不是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'
python傳參考 python函數表 python帶入變數

Python文檔似乎不清楚參數是通過引用還是值傳遞,以下代碼生成未更改的值'Original'

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

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

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




它既不是按值傳遞,也不是按引用傳遞 - 它是逐個調用的。 請見Fredrik Lundh:

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

這是一個重要的引用:

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

在您的示例中,當調用Change方法時 - 為其創建namespace ; 和var成為該命名空間中的字符串對象'Original' 。 然後,該對像在兩個名稱空間中具有名稱。 接下來, var = 'Changed'var綁定到一個新的字符串對象,因此該方法的命名空間忘記了'Original' 。 最後,忘記了該命名空間,並將字符串'Changed'與它一起使用。




Effbot(又名Fredrik Lundh)將Python的變量傳遞樣式描述為逐個調用對象: http://effbot.org/zone/call-by-object.htmhttp://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”都可見(正如數據庫更新對於該數據庫的每個連接都是可見的)。

希望能為您澄清問題。




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

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

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

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




Python中沒有變量

理解參數傳遞的關鍵是停止思考“變量”。 Python中有名稱和對象,它們一起看起來像變量,但總是區分三者是有用的。

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

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

例:

a = 1

這會將名稱a綁定到包含值1的整數類型的對象。

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 )再次指定相同的名稱:對於其他一些對象(碰巧也包含一個字符串,但可能完全是其他東西)。




在這種情況下,方法Change名為var的變量被賦予對self.variable的引用,並立即為var分配一個字符串。 它不再指向self.variable 。 以下代碼片段顯示瞭如果修改varself.variable指向的數據結構會發生什麼,在本例中是一個列表:

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

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




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

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



這是pass by objectPython中使用的概念的簡單(我希望)解釋。
無論何時將對像傳遞給函數,對象本身都會被傳遞(Python中的對象實際上是您在其他編程語言中稱為值的對象),而不是對該對象的引用。換句話說,當你打電話:

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

my_list = [0, 1]
change_me(my_list)

正在傳遞實際對象 - [0,1](在其他編程語言中稱為值)。所以實際上函數change_me會嘗試做類似的事情:

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

這顯然不會改變傳遞給函數的對象。如果函數看起來像這樣:

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

然後調用將導致:

[0, 1].append(2)

這顯然會改變對象。這個答案解釋得很好。




通過引用傳遞對像有一個小技巧,即使語言無法實現。它也適用於Java,它是帶有一個項目的列表。 ;-)

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

這是一個醜陋的黑客,但它的工作原理。;-P




雖然通過引用傳遞並不適合python並且應該很少使用,但是有一些解決方法實際上可以使得當前分配給局部變量的對像或甚至從被調用函數內部重新分配局部變量。

基本思想是擁有一個可以進行訪問的函數,可以作為對像傳遞給其他函數或存儲在類中。

一種方法是在包裝函數中使用global(對於全局變量)或nonlocal(對於函數中的局部變量)。

def change(wrapper):
    wrapper(7)

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

同樣的想法適用於閱讀和del輸入變量。

對於只是閱讀,甚至有一種更簡單的使用方式,lambda: x它返回一個可調用的,當被調用時返回x的當前值。這有點像在遙遠的過去用於語言的“按姓名呼叫”。

傳遞3個包裝器來訪問變量有點笨拙,因此可以將它們包裝到具有代理屬性的類中:

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“反射”支持使得可以獲得一個能夠在給定範圍內重新分配名稱/變量而無需在該範圍內顯式定義函數的對象:

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)

這裡的ByRef類包裝了一個字典訪問。因此,屬性訪問權限wrapped被轉換為傳遞的字典中的項目訪問權限。通過傳遞內置結果locals和局部變量的名稱,最終會訪問局部變量。從3.5開始的python文檔建議更改字典可能不起作用,但它似乎對我有用。




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

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'





Related