Neden bir işlev arayan tarafından algılanan bazı argümanları değiştirebilir, ancak diğerleri değiştiremez?


182

Python'un değişken kapsama yaklaşımını anlamaya çalışıyorum. Bu örnekte, içinde algılanan f()değeri neden değiştirebilir , fakat değerini değiştiremezsiniz ?xmain()n

def f(n, x):
    n = 2
    x.append(4)
    print('In f():', n, x)

def main():
    n = 1
    x = [0,1,2,3]
    print('Before:', n, x)
    f(n, x)
    print('After: ', n, x)

main()

Çıktı:

Before: 1 [0, 1, 2, 3]
In f(): 2 [0, 1, 2, 3, 4]
After:  1 [0, 1, 2, 3, 4]

Yanıtlar:


212

Bazı cevaplar bir işlev çağrısı bağlamında "kopyala" sözcüğünü içerir. Kafa karıştırıcı buluyorum.

Python kopyalamaz nesneleri bir işlev çağrısı sırasında geçmek hiç .

Fonksiyon parametreleri isimlerdir . Bir işlevi çağırdığınızda Python bu parametreleri geçtiğiniz nesnelere bağlar (arayan kapsamındaki isimler aracılığıyla).

Nesneler değiştirilebilir (listeler gibi) veya değiştirilemez (tamsayılar, Python'daki dizeler gibi) olabilir. Değiştirilebilir değişken nesne. Bir adı değiştiremezsiniz, sadece başka bir nesneye bağlayabilirsiniz.

Sizin örnek ilgili değil kapsamları veya ad alanları hakkında olduğunu, adlandırma ve bağlayıcı ve bir nesnenin mutability Python.

def f(n, x): # these `n`, `x` have nothing to do with `n` and `x` from main()
    n = 2    # put `n` label on `2` balloon
    x.append(4) # call `append` method of whatever object `x` is referring to.
    print('In f():', n, x)
    x = []   # put `x` label on `[]` ballon
    # x = [] has no effect on the original list that is passed into the function

İşte diğer dillerdeki değişkenler ve Python'daki isimler arasındaki fark hakkında güzel resimler .


3
Bu makale sorunu daha iyi anlamama yardımcı oldu ve bir geçici çözüm ve bazı gelişmiş kullanımlar önerdi: Python'daki Varsayılan Parametre Değerleri
Gfy

@Gfy, daha önce benzer örnekler gördüm ama bana göre gerçek dünyadaki durumu tanımlamıyor. İçinden geçen bir şeyi değiştiriyorsanız, varsayılan olarak vermek mantıklı değildir.
Mark Ransom

@MarkRansom, sana olduğu gibi isteğe bağlı çıkış hedefini sağlamak istiyorsanız bu mantıklı mı düşünüyorum: def foo(x, l=None): l=l or []; l.append(x**2); return l[-1].
Janusz Lenar

Sebastian'ın kodunun son satırı için "# yukarıdakilerin orijinal listede hiçbir etkisi yoktur" yazıyordu. Ama bence, sadece "n" üzerinde bir etkisi yok, ama main () fonksiyonunda "x" değişti. Doğrumuyum?
user17670

1
@ user17670: x = []in f(), xana işlevdeki liste üzerinde hiçbir etkisi yoktur . Daha spesifik hale getirmek için yorumu güncelledim.
jfs

15

Zaten birkaç cevabınız var ve JF Sebastian'a genel olarak katılıyorum, ancak bunu bir kısayol olarak yararlı bulabilirsiniz:

Her gördüğünüzde varname =, işlevin kapsamı içinde yeni bir ad bağlayıcısı oluşturursunuz . Daha varnameönce bağlanmış olan değer bu kapsamda kaybolur .

Ne zaman varname.foo()bir yöntem çağırdığınızı gördüğünüzde varname. Yöntem varname (örneğin list.append) değiştirebilir . varname(veya daha ziyade, varnameadlandıran nesne ) birden fazla kapsamda mevcut olabilir ve aynı nesne olduğundan, tüm değişiklikler tüm kapsamlarda görünür olacaktır.

[ globalanahtar kelimenin ilk durum için bir istisna oluşturduğunu unutmayın ]


13

faslında değerini değiştirmez x(her zaman bir liste örneğiyle aynı referanstır). Aksine, bu listenin içeriğini değiştirir .

Her iki durumda da, referansın bir kopyası işleve iletilir. Fonksiyonun içinde,

  • nyeni bir değer atandı. Sadece fonksiyonun içindeki referans değiştirilir, onun dışındaki referans değiştirilmez.
  • xyeni bir değer atanmaz: ne işlev içinde ne de dışında referans değiştirilmez. Bunun yerine, x'nin değeri değiştirilir.

Hem xişlevin içi hem de dışı aynı değere başvurduğundan, ikisi de değişikliğe bakın. Aksine, nfonksiyonun içi ve dışı fonksiyonun içine yeniden atandıktan sonra farklı değerleri ifade eder n.


8
"kopya" yanıltıcı. Python'un C gibi değişkenleri yoktur. Python'daki tüm isimler referanstır. Adı değiştiremezsiniz, sadece başka bir nesneye bağlayabilirsiniz, hepsi bu. Sadece isimler değil Python'daki değişebilir ve değişmez nesne hakkında konuşmak mantıklı .
jfs

1
@JF Sebastian: İfadeniz en iyi ihtimalle yanıltıcı. Sayıları referans olarak düşünmek yararlı değildir.
Pitarou

9
@ disfunctor: sayılar, değiştirilemeyen nesnelere referanstır. Onları başka bir şekilde düşünmeyi tercih ederseniz, açıklamak için bir sürü garip özel vakanız var. Onları değişmez olarak düşünüyorsanız, özel durumlar yoktur.
S.Lott

@ S.Lott: Kaputun altında neler olup bittiğine bakılmaksızın, Guido van Rossum Python'u tasarlamak için çok çaba harcadı, böylece programcı sayıları sadece ... sayılar olarak yapabiliyor.
Pitarou

1
@JF, başvuru kopyalanır.
habnabit

7

Karışıklığı azaltmak için değişkenleri yeniden adlandıracağım. n -> nf veya nmain . x -> xf veya xmain :

def f(nf, xf):
    nf = 2
    xf.append(4)
    print 'In f():', nf, xf

def main():
    nmain = 1
    xmain = [0,1,2,3]
    print 'Before:', nmain, xmain
    f(nmain, xmain)
    print 'After: ', nmain, xmain

main()

Eğer fonksiyon çağırdığınızda f , Python çalışma zamanı bir kopyasını oluşturur xmain ve atar onu xf ve benzer bir kopyasını atar nmain için nf .

N durumunda , kopyalanan değer 1'dir.

X durumunda kopyalanan değer değişmez liste değildir [0, 1, 2, 3] . Bu listeye bir göndermedir . xf ve xmain böylece değiştirdiğinizde, aynı listede baktığından xf da değiştirdiğiniz xmain .

Ancak, şöyle bir şey yazacak olsaydınız:

    xf = ["foo", "bar"]
    xf.append(4)

Bunu bulur xmain değişmedi. Bunun nedeni, xf = ["foo", "bar"] satırında yeni bir listeyi göstermek için xf'yi değiştirmiş olmanızdır . Bu yeni listede yaptığınız değişikliklerin xmain'in hala işaret ettiği listede hiçbir etkisi olmayacaktır .

Umarım yardımcı olur. :-)


2
"N durumunda, kopyalanan değer ..." - Bu yanlış, burada hiçbir kopyalama yapılmaz (referansları saymazsanız). Bunun yerine, python gerçek nesnelere işaret eden 'isimleri' kullanır. nf ve xf nf = 2, adın nfişaret edildiği şekilde değiştirildiği zamana kadar nmain ve xmain'i gösterir 2. Sayılar değiştirilemez, listeler değiştirilebilir.
Casey Kuball

2

Bunun nedeni bir listenin değiştirilebilir bir nesne olmasıdır. X'i [0,1,2,3] değerine ayarlamıyorsunuz, nesneye bir etiket tanımlıyorsunuz [0,1,2,3].

F () işlevinizi şu şekilde beyan etmelisiniz:

def f(n, x=None):
    if x is None:
        x = []
    ...

3
Değişebilirlik ile ilgisi yoktur. x = x + [4]Bunun yerine bunu yaparsanız x.append(4), bir liste değiştirilebilir olmasına rağmen arayanda hiçbir değişiklik görmezsiniz. O ilgisi var eğer gerçekten mutasyona uğrar.
glglgl

1
Otoh, bunu yaparsanız x += [4]o zaman xsadece ile ne gibi mutasyona uğrar x.append(4)arayan değiştiğini görürsünüz böylece.
PM 2Ring

2

n bir int (değişmez) ve fonksiyona bir kopya aktarılır, böylece fonksiyonda kopyayı değiştirirsiniz.

X bir listedir (değiştirilebilir) ve işaretçinin bir kopyası geçirilir, böylece x.append (4) listenin içeriğini değiştirir. Ancak, işlevinizde x = [0,1,2,3,4] dediniz, main () içindeki x içeriğini değiştirmezsiniz.


3
"İşaretçinin kopyası" ifadesini izleyin. Her iki yer de nesnelere referans alır. n değişmez bir nesneye referanstır; x değişebilir bir nesneye referanstır.
S.Lott

2

Fonksiyonlar tamamen farklı değişkenlerle yeniden yazılırsa ve bu değişkenlere id adını verirsek , noktayı iyi gösterir. İlk başta bunu alamadım ve jfs'ın gönderisini büyük bir açıklama ile okudum , bu yüzden kendimi anlamaya / ikna etmeye çalıştım:

def f(y, z):
    y = 2
    z.append(4)
    print ('In f():             ', id(y), id(z))

def main():
    n = 1
    x = [0,1,2,3]
    print ('Before in main:', n, x,id(n),id(x))
    f(n, x)
    print ('After in main:', n, x,id(n),id(x))

main()
Before in main: 1 [0, 1, 2, 3]   94635800628352 139808499830024
In f():                          94635800628384 139808499830024
After in main: 1 [0, 1, 2, 3, 4] 94635800628352 139808499830024

z ve x aynı kimliğe sahiptir. Makalenin altında yatan aynı yapı için sadece farklı etiketler.


0

Python, doğru şekilde düşünürseniz saf bir değerdir. Bir python değişkeni bir nesnenin konumunu hafızada saklar. Python değişkeni nesnenin kendisini saklamaz. Bir değişkeni bir işleve ilettiğinizde, değişken tarafından işaret edilen nesnenin adresinin bir kopyasını geçirirsiniz .

Bu iki işlevin aksine

def foo(x):
    x[0] = 5

def goo(x):
    x = []

Şimdi, kabuğa yazdığınızda

>>> cow = [3,4,5]
>>> foo(cow)
>>> cow
[5,4,5]

Bunu goo ile karşılaştırın.

>>> cow = [3,4,5]
>>> goo(cow)
>>> goo
[3,4,5]

İlk durumda, ineğin adresini foo'ya geçiririz ve foo, orada bulunan nesnenin durumunu değiştirir. Nesne değiştirilir.

İkinci durumda inek adresinin bir kopyasını goo'ya geçirirsiniz. Ardından goo bu kopyayı değiştirmeye devam eder. Etkisi: yok.

Ben buna pembe ev prensibi diyorum . Adresinizin bir kopyasını yapar ve bir ressamın evi bu adrese pembe boyamasını söylerseniz, pembe bir evle sarılırsınız. Ressama adresinizin bir kopyasını verir ve ona yeni bir adresle değiştirmesini söylerseniz, evinizin adresi değişmez.

Açıklama çok fazla karışıklığı ortadan kaldırıyor. Python adres değişkenlerini mağaza değerine göre geçirir.


İşaretçi değerine göre saf bir geçiş, doğru şekilde düşünürseniz referansla yapılan bir geçişten çok farklı değildir ...
galinette

Goo'ya bak. Referans olarak saf geçiş olsaydınız, argümanını değiştirirdi. Hayır, Python saf bir referans dili değildir. Referansları değere göre geçer.
ncmathsadist

0

Python referans değerine göre kopyalanır. Bir nesne bellekteki bir alanı kaplar ve bir referans bu nesne ile ilişkilendirilir, ancak kendisi bellekteki bir alanı kaplar. Ve isim / değer bir referansla ilişkilendirilir. Python işlevinde, her zaman başvurunun değerini kopyalar, bu nedenle kodunuzda n yeni bir ad olarak kopyalanır, bunu atadığınızda, arayan yığınında yeni bir alana sahiptir. Ancak liste için, ad da kopyalandı, ancak aynı belleğe başvuruyor (listeye asla yeni bir değer atamadığınız için). Bu pitonda bir sihir!


0

Genel anlayışım, herhangi bir nesne değişkeninin (bir liste veya bir dikte gibi, diğerleri arasında) işlevleri aracılığıyla değiştirilebileceğidir. Yapamayacağına inandığım, parametreyi yeniden atamak - yani, çağrılabilir bir işlev içinde referans olarak atamak.

Bu, diğer birçok dille tutarlıdır.

Nasıl çalıştığını görmek için aşağıdaki kısa komut dosyasını çalıştırın:

def func1(x, l1):
    x = 5
    l1.append("nonsense")

y = 10
list1 = ["meaning"]
func1(y, list1)
print(y)
print(list1)

-3

Cevabımı tonlarca değiştirdim ve hiçbir şey söylemek zorunda olmadığımı fark ettim, python zaten kendini açıklamıştı.

a = 'string'
a.replace('t', '_')
print(a)
>>> 'string'

a = a.replace('t', '_')
print(a)
>>> 's_ring'

b = 100
b + 1
print(b)
>>> 100

b = b + 1
print(b)
>>> 101

def test_id(arg):
    c = id(arg)
    arg = 123
    d = id(arg)
    return

a = 'test ids'
b = id(a)
test_id(a)
e = id(a)

# b = c  = e != d
# this function do change original value
del change_like_mutable(arg):
    arg.append(1)
    arg.insert(0, 9)
    arg.remove(2)
    return

test_1 = [1, 2, 3]
change_like_mutable(test_1)



# this function doesn't 
def wont_change_like_str(arg):
     arg = [1, 2, 3]
     return


test_2 = [1, 1, 1]
wont_change_like_str(test_2)
print("Doesn't change like a imutable", test_2)

Bu şeytan referans / değer / değişebilir veya değil / örnek, ad alanı veya değişken / liste veya str değil, IT SYNTAX, EQUAL SIGN.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.