python方法 - python陣列傳遞




如何通過引用傳遞變量? (16)

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

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

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

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


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


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”都可見(正如數據庫更新對於該數據庫的每個連接都是可見的)。

希望能為您澄清問題。


(編輯 - 布萊爾更新了他非常受歡迎的答案,現在它已經準確了)

我認為值得注意的是,目前投票率最高的帖子(布萊爾康拉德)雖然在結果方面是正確的,但卻是誤導性的,並且基於其定義是不正確的。 雖然有許多語言(如C)允許用戶通過引用傳遞或按值傳遞,但Python不是其中之一。

David Cournapeau的回答指出了真正的答案,並解釋了為什麼布萊爾康拉德的帖子中的行為似乎是正確的,而定義則不然。

在Python按值傳遞的範圍內,所有語言都是按值傳遞的,因為必鬚髮送一些數據(無論是“值”還是“引用”)。 但是,這並不意味著Python在C程序員會想到它的意義上是通過值傳遞的。

如果你想要這種行為,布萊爾康拉德的回答很好。 但是如果你想知道為什麼Python既沒有通過值傳遞也沒有通過引用傳遞,那麼請閱讀David Cournapeau的回答。


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

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

參數通過賦值傳遞 。 這背後的理由是雙重的:

  1. 傳入的參數實際上是對象的引用(但引用按值傳遞)
  2. 一些數據類型是可變的,但其他數據類型則不可變

所以:

  • 如果你將一個可變對像傳遞給一個方法,那麼該方法會獲得對同一個對象的引用,你可以將它改變為你心中的喜悅,但是如果你在方法中重新引用引用,那麼外部範圍將對它一無所知,之後你完成後,外部引用仍將指向原始對象。

  • 如果將不可變對像傳遞給方法,則仍然無法重新綁定外部引用,甚至無法改變對象。

為了更清楚,讓我們舉一些例子。

列表 - 可變類型

讓我們嘗試修改傳遞給方法的列表:

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)

輸出:

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

由於傳入的參數是對outer_list的引用,而不是對它的副本,我們可以使用變異列表方法來更改它,並將更改反映在外部作用域中。

現在讓我們看看當我們嘗試更改作為參數傳入的引用時會發生什麼:

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)

輸出:

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

由於the_list參數是按值傳遞的,因此the_list分配新列表不會影響方法外部的代碼。 the_listouter_list引用的副本,我們將the_list指向一個新列表,但是沒有辦法改變outer_list指向的位置。

字符串 - 不可變類型

它是不可變的,所以我們無法改變字符串的內容

現在,讓我們嘗試更改引用

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)

輸出:

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

同樣,由於the_string參數是通過值傳遞的,因此為其分配新字符串不會影響方法外部的代碼。 the_stringouter_string引用的副本,我們讓the_string指向一個新字符串,但是沒有辦法改變outer_string指向的位置。

我希望這可以解決一些問題。

編輯:有人指出,這並沒有回答@David最初提出的問題,“我能做些什麼來通過實際參考傳遞變量?”。 讓我們繼續努力。

我們如何解決這個問題?

正如@ Andrea的回答所示,您可以返回新值。 這不會改變傳遞方式,但可以讓您獲得想要的信息:

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)

如果你真的想避免使用返回值,你可以創建一個類來保存你的值並將它傳遞給函數或使用現有的類,如列表:

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

雖然這看起來有點麻煩。


問題來自對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'

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

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

這是一個重要的引用:

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

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


從技術上講, Python總是使用傳遞引用值 。 我將重複我的其他答復以支持我的發言。

Python始終使用傳遞引用值。 沒有任何例外。 任何變量賦值都意味著複製參考值。 沒有例外。 任何變量都是綁定到引用值的名稱。 總是。

您可以將參考值視為目標對象的地址。 使用時會自動取消引用該地址。 這樣,使用參考值,您似乎直接使用目標對象。 但總會有一個參考,更多的是跳到目標。

下面是證明Python使用引用傳遞的示例:

如果參數是按值傳遞的,則無法修改外部的lst 。 綠色是目標對象(黑色是存儲在內部的值,紅色是對像類型),黃色是內部具有參考值的內存 - 繪製為箭頭。 藍色實線箭頭是傳遞給函數的參考值(通過虛線藍色箭頭路徑)。 醜陋的深黃色是內部字典。 (它實際上也可以畫成綠色橢圓。顏色和形狀只表示它是內部的。)

您可以使用id()內置函數來了解引用值(即目標對象的地址)。

在編譯語言中,變量是能夠捕獲類型值的內存空間。 在Python中,變量是綁定到引用變量的名稱(內部捕獲為字符串),該引用變量保存目標對象的引用值。 變量的名稱是內部字典中的鍵,該字典項的值部分將參考值存儲到目標。

參考值隱藏在Python中。 沒有任何顯式用戶類型用於存儲參考值。 但是,您可以使用list元素(或任何其他合適的容器類型中的元素)作為引用變量,因為所有容器都將元素存儲為對目標對象的引用。 換句話說,元素實際上不包含在容器內 - 只有對元素的引用。


我發現其他答案相當漫長而復雜,所以我創建了這個簡單的圖來解釋Python處理變量和參數的方式。


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

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

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

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


給定python處理值和引用它們的方式,可以引用任意實例屬性的唯一方法是按名稱:

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

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

在實際代碼中,您當然會在dict查找中添加錯誤檢查。


雖然通過引用傳遞並不適合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文檔建議更改字典可能不起作用,但它似乎對我有用。


我使用以下方法將一些Fortran代碼快速轉換為Python。確實,它並沒有像原始問題那樣通過引用傳遞,但在某些情況下是一個簡單的工作。

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

這是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中運行的所有重要解釋之外,我沒有看到針對該問題的簡單建議。當您似乎創建對象和實例時,處理實例變量並更改它們的pythonic方法如下:

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

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

在實例方法中,您通常會引用self訪問實例屬性。__init__在實例方法中設置實例屬性並讀取或更改它們是正常的。這也是你傳遞self第一個參數的原因def Change

另一個解決方案是創建一個這樣的靜態方法:

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






pass-by-reference