Python işlevleri başvuruya göre çağırır


84

Bazı dillerde, ref veya val gibi özel bir ayrılmış sözcük kullanarak bir parametreyi referans veya değere göre geçirebilirsiniz . Bir Python işlevine bir parametre ilettiğinizde, işlevden çıkarken parametrenin değerini asla değiştirmez.Bunu yapmanın tek yolu, genel ayrılmış sözcüğü kullanmaktır (veya şu anda anladığım kadarıyla).

Örnek 1:

k = 2

def foo (n):
     n = n * n     #clarity regarding comment below
     square = n
     return square

j = foo(k)
print j
print k

gösterecekti

>>4
>>2

k'nin değişmediğini gösterme

Bu örnekte, n değişkeni asla değiştirilmez

Örnek 2:

n = 0
def foo():
    global n
    n = n * n
    return n

Bu örnekte, n değişkeni değiştirilmiştir

Bir fonksiyonu çağırmak ve o Python anlatmak için Python herhangi bir yolu var mı parametre ya olan bir değer veya referans parametresi yerine küresel kullanılarak?

İkinci olarak, A seviyesi Cambridge sınavlarında artık bir fonksiyonun tek bir değer döndürdüğünü, bir prosedürün birden fazla değer döndürdüğünü söylüyorlar . 80'lerde bir fonksiyonun dönüş ifadesi olduğu ve prosedürün olmadığı öğretildi. Bu neden şimdi yanlış?


5
Python'un arama modelini anlamak için bulduğum en iyi kaynak effbot'ta şu makaledir: effbot.org/zone/call-by-object.htm
Wilduck

1
Python değişkenleri, değiştirilebilir ve değiştirilemez nesneler hakkında okumalısınız. Ayrıca ilk örneğinizde, neden ndeğiştirilecek, squarehesaplamanızın sonucunu saklamak için yeni bir değişken kullanıyorsunuz .
Facundo Casco

6
Lütfen alakasız iki soruyu bir arada göndermeyin. Fonksiyon ve prosedür tanımı bir tanım meselesidir ve görünüşe göre tanımınız Pascal'a yöneliktir. Python jargonunda prosedür diye bir şey yoktur.
Fred Foo

2
Ayrıca, bu önceki StackOverflow cevabına bir göz atın. stackoverflow.com/a/10262945/173292 Nesne referans modeline göre çağrıyı oldukça sezgisel olarak açıklar.
Wilduck

1
Çok faydalı bir açıklama için şu SO sorusuna da bakın: Bir değişkeni referans alarak nasıl
geçiririm

Yanıtlar:


79

Python'daki bir işlev gibi strveya tupleiçindeki değişmez bir nesneyi değiştiremezsiniz , ancak aşağıdaki gibi şeyler yapabilirsiniz:

def foo(y):
  y[0] = y[0]**2

x = [5]
foo(x)
print x[0]  # prints 25

Bununla birlikte, bir dizideki belirli öğeleri her zaman karelemeniz gerekmedikçe, bu garip bir yoldur.

Python'da, birden fazla değer döndürebileceğinizi ve bu nedenle, referansla geçiş için bazı kullanım durumlarının daha az önemli olduğunu unutmayın:

def foo(x, y):
   return x**2, y**2

a = 2
b = 3
a, b = foo(a, b)  # a == 4; b == 9

Bunun gibi değerler döndürdüğünüzde, bunlar sırayla paketten çıkarılmış bir Tuple olarak döndürülür.

edit: Bunu düşünmenin bir başka yolu da, Python'da değişkenleri referans olarak açık bir şekilde geçiremezseniz de, iletilen nesnelerin özelliklerini değiştirebilirsiniz. Örneğimde (ve diğerlerinde) listenin üyelerini değiştirebilirsiniz geçildi. Ancak, aktarılan değişkeni tamamen yeniden atayamazsınız. Örneğin, aşağıdaki iki kod parçasına bakın, benzer bir şey yapıyorlarmış gibi görünüyor, ancak farklı sonuçlarla sonuçlanıyor:

def clear_a(x):
  x = []

def clear_b(x):
  while x: x.pop()

z = [1,2,3]
clear_a(z) # z will not be changed
clear_b(z) # z will be emptied

2
Programların sonuçlarını görmek faydalı olacaktır. Örneğin, 'x [0] yazdır' 25 döndürür mü? (Evet öyle ama bu gösterilmeli.)
Ray Salemi

11
@alexey'e clear_abir referans verilir zve bu referansı içinde depolar x. Daha sonra hemen xfarklı bir diziye başvurmak için değişir . Orijinal zdizi kapsamında unutuldu clear_a, ancak gerçekte değiştirilmedi. Küresel kapsama dönüldüğünde değişmeden devam eder. O Kontrast clear_bhangi bir referans alır zhiç yeni bir dizi oluşturmak veya başka işaret olmadan, ve sonra doğrudan faaliyet xve farklı bir şey.
dave mankoff

teşekkürler dave! Ben clear_b içinde "yeni bir dizi yaratma" çalıştı y = x, y: y.pop()sonra da denilen clear_b(z)gibi biz bir şey gerekiyor Yani, z hala ... boşaltılmış var y = list(x)(açıkladı listesi (x) x bir kopyasını oluşturmak için buraya )
alexey

run_thread = [True] t= threading.Thread(target=module2.some_method, \ args=(11,1,run_thread)) --do something - and when you want to sop run_thread[0] =False
Alex Punnen

Python'da Java'nın aksine ilkel bir tür yoktur. Tüm türler örnek nesneler.
Marco Sulla

184

Esasen üç tür 'işlev çağrısı' vardır:

  • Değere göre aktar
  • Referansla geç
  • Nesne başvurusuna göre aktar

Python, NESNE BAŞVURUYLA GEÇEN bir programlama dilidir.

İlk olarak, bir değişkenin ve değişkenin (nesnenin) değerinin iki ayrı şey olduğunu anlamak önemlidir. Değişken nesneyi 'işaret eder'. Değişken nesne değildir. Tekrar:

DEĞİŞKEN NESNE DEĞİLDİR

Örnek: aşağıdaki kod satırında:

>>> x = []

[]boş listedir, boş listeyi xgösteren bir değişkendir, ancak xkendisi boş liste değildir.

Değişkeni ( xyukarıdaki durumda) bir kutu olarak ve değişkenin ( []) 'değerini' kutunun içindeki nesne olarak düşünün .

NESNE REFERANSINA GÖRE GEÇME (Python'daki durum):

Burada, "Nesne başvuruları değere göre aktarılır."

def append_one(li):
    li.append(1)
x = [0]
append_one(x)
print x

Burada ifade , nesneyi işaret x = [0]eden bir değişken x(kutu) oluşturur [0].

Çağrılan işlevde yeni bir kutu lioluşturulur. İçeriği li, kutunun içeriği ile aynıdır x. Her iki kutu da aynı nesneyi içerir. Yani, her iki değişken de bellekteki aynı nesneyi işaret eder. Bu nedenle, ile gösterilen nesnedeki herhangi bir değişiklik, işaret edilen nesne tarafından lida yansıtılacaktır x.

Sonuç olarak, yukarıdaki programın çıktısı şöyle olacaktır:

[0, 1]

Not:

Değişken liişlevde yeniden atanırsa li, bellekteki ayrı bir nesneyi işaret eder. xancak bellekte daha önce işaret ettiği nesneyi göstermeye devam edecek.

Misal:

def append_one(li):
    li = [0, 1]
x = [0]
append_one(x)
print x

Programın çıktısı:

[0]

REFERANS TARAFINDAN GEÇER:

Çağıran işlevin kutusu çağrılan işleve aktarılır. Örtük olarak, kutunun içeriği (değişkenin değeri) çağrılan işleve aktarılır. Bu nedenle, çağrılan işlevdeki kutunun içeriğindeki herhangi bir değişiklik, çağıran işleve yansıtılacaktır.

DEĞERE GÖRE GEÇİŞ:

Çağrılan işlevde yeni bir kutu oluşturulur ve arama işlevinden gelen kutunun içeriğinin kopyaları yeni kutularda saklanır.

Bu yardımcı olur umarım.


4
"Nesne referansıyla geçiş" için yaptığınız gibi "referansla geçiş" ve "değerle geçiş" örnekleri verebilir misiniz?
TJain

@TJain Başvuruya göre geçiş ve değere göre geçiş C ++ 'da görülebilir. Bu cevap iyi bir açıklama veriyor: stackoverflow.com/a/430958/5076583
Shobhit Verma

3
Hala bunun referansla geçişten nasıl farklı olduğunu anlamıyorum. Bu yanıt, umutsuzca, özdeş sözde kodun 3 paradigmanın tümü için nasıl farklı çıktılar üreteceğine dair bir örneğe ihtiyaç duyar. Yalnızca referansa göre geçirme ve değerle aktarım arasındaki farkı açıklayan farklı bir yanıta bağlanmak yardımcı olmaz.
Jess Riedel

"Python'daki tüm değişkenler işaretçilerdir " ve "bir işlev çağrısındaki parametrelere atanan tüm değişkenler yeni işaretleyicilere atanır " demek daha basit değil mi? Bunu anlarsanız, Python'un Java'ya benzediğini göreceksiniz: işaretçileri her zaman değere göre iletir . Kirk Strauser'in cevabını
Marco Sulla

27

Tamam, buna bir bıçak atacağım. Python, normalde "referansa göre" veya "değere göre" düşündüğünüzden farklı olan nesne referansına göre geçer. Bu örneği ele alalım:

def foo(x):
    print x

bar = 'some value'
foo(bar)

Dolayısıyla, 'bir değer' değerine sahip bir dize nesnesi oluşturuyorsunuz ve onu adlı bir değişkene "bağlamıyorsunuz" bar. C'de, bu, bar'bir değeri' gösteren bir işaretçi olmaya benzer .

Aradığında foo(bar), barkendi başına geçmiyorsun . İçeri geçiyoruz bar'ın değeri: bir işaretçi için 'bazı değer'. Bu noktada, aynı dize nesnesine iki "işaretçi" vardır.

Şimdi bunu şununla karşılaştırın:

def foo(x):
    x = 'another value'
    print x

bar = 'some value'
foo(bar)

İşte fark burada yatıyor. Çizgide:

x = 'another value'

aslında içeriğini değiştirmiyorsunuz x. Aslında bu mümkün bile değil. Bunun yerine, 'başka bir değer' değerine sahip yeni bir dize nesnesi oluşturuyorsunuz. Bu atama operatörü? " xYeni değerin işaret ettiği şeyin üzerine yaz" demiyor . " xBunun yerine yeni nesneyi gösterecek şekilde güncelle " diyor. Bu satırdan sonra, iki dizi nesnesi vardır: 'bir değer' ( baronu işaret eden) ve 'başka bir değer' ( xonu işaret eden).

Bu beceriksiz değil. Nasıl çalıştığını anladığınızda, güzel, zarif ve verimli bir sistemdir.


3
Peki ya işlevin değişkenimin içeriğini değiştirmesini istersem ? Bunu yapmanın bir yolu olmadığını anlıyorum?
aquirdturtle

1
Adın işaret ettiği nesneyi , örneğin bir nesne değiştirilebilirse, bir listeye ekleyerek değiştirebilirsiniz. "Beni çağıran kapsamın ad alanını değiştir, böylece argüman listemdeki ad şimdi beni çağırdığından farklı bir şeye işaret ediyor" demek için Python semantiği yoktur.
Kirk Strauser

2
Etkili bir şekilde sorduğum şey, python'u aynı nesneye yeni bir işaretçi oluşturmanın değil, bunun yerine orijinal işaretçiyi kullanmanın basit bir yolu olup olmadığıdır. Bunu yapmanın bir yolu olmadığını anlıyorum. Diğer cevaplardan alıyorum ki, referansla geçmenin yaptıklarına ulaşmak için normal çözüm, sadece daha fazla parametre döndürmektir. durum buysa, a, b, c, d, e = foo (a, b, c, d, e) 'nin foo (a, b, c, d, e).
aquirdturtle

2
@aquirdturtle Bu, yeni bir işaretçi yaratıldığı için yapılacak bir şey değil. Yeni bir nesne döndürmelisiniz çünkü strings, tuples ve diğer nesneler değişmezdir . A gibi değiştirilebilir bir nesneyi iletirseniz, listonu geri döndürmeye gerek kalmadan işlevin içinde değiştirebilirsiniz, çünkü tüm işlev parametreleri ilettiğiniz aynı nesnelere işaret eder.
Marco Sulla

24

Umarım aşağıdaki açıklama bunu iyi özetler:

Burada dikkate alınması gereken iki şey vardır - değişkenler ve nesneler.

  1. Bir değişkeni iletiyorsanız, o zaman değere göre aktarılır, bu, işlev içinde değişkene yapılan değişikliklerin o işlev için yerel olduğu ve dolayısıyla genel olarak yansıtılmayacağı anlamına gelir. Bu daha çok 'C' benzeri bir davranış.

Misal:

def changeval( myvar ):
   myvar = 20; 
   print "values inside the function: ", myvar
   return

myvar = 10;
changeval( myvar );
print "values outside the function: ", myvar

O / P:

values inside the function:  20 
values outside the function:  10
  1. Değişkenleri bir liste gibi değiştirilebilir bir nesne içinde geçiriyorsanız, nesne yeniden atanmadığı sürece nesnede yapılan değişiklikler genel olarak yansıtılır.

Misal:

def changelist( mylist ):
   mylist2=['a'];
   mylist.append(mylist2);
   print "values inside the function: ", mylist
   return

mylist = [1,2,3];
changelist( mylist );
print "values outside the function: ", mylist

O / P:

values inside the function:  [1, 2, 3, ['a']]
values outside the function:  [1, 2, 3, ['a']]
  1. Şimdi nesnenin yeniden atandığı durumu düşünün. Bu durumda, nesne, bunun meydana geldiği işleve yerel olan ve dolayısıyla genel olarak yansıtılmayan yeni bir bellek konumuna başvurur.

Misal:

def changelist( mylist ):
   mylist=['a'];
   print "values inside the function: ", mylist
   return

mylist = [1,2,3];
changelist( mylist );
print "values outside the function: ", mylist

O / P:

values inside the function:  ['a']
values outside the function:  [1, 2, 3]

2
Bu en iyi özet! ^^ ^^ BURADA EN İYİ ÖZETİ ^^ ^ herkes bu yanıtı oku! 1. basit değişkenleri
iletin

4
Bu cevap çok karışık. Python farklı türde değerleri farklı şekilde iletmez: Python'daki tüm işlev bağımsız değişkenleri değerlerini atama yoluyla alır ve atamanın dilin herhangi bir yerinde tam olarak aynı şekilde davranır. ("Yine de Hollandalı değilseniz bu yol ilk bakışta açık olmayabilir.")
Daniel Pryden

9

Python ne değere göre geçirme ne de başvuruya göre geçirme değildir. Burada açıklandığı gibi daha çok "nesne referansları değere göre aktarılır" :

  1. Değere göre geçiş yapmamasının nedeni işte burada. Çünkü

    def append(list):
        list.append(1)
    
    list = [0]
    reassign(list)
    append(list)
    

değeri geçirerek bir işlevin üst kapsamı değiştirmesine izin vermediği için bir tür başvurunun açıkça iletildiğini gösteren [0,1] döndürür .

Referans geçiş gibi görünüyor, ha? Hayır!

  1. İşte bu yüzden referansla geçiş değil. Çünkü

    def reassign(list):
      list = [0, 1]
    
    list = [0]
    reassign(list)
    print list
    

liste yeniden atandığında orijinal referansın yok edildiğini gösteren [0] döndürür. referansla geçiş [0,1] döndürürdü .

Daha fazla bilgi için buraya bakın :

İşlevinizin dış kapsamı değiştirmemesini istiyorsanız, yeni bir nesne oluşturan girdi parametrelerinin bir kopyasını oluşturmanız gerekir.

from copy import copy

def append(list):
  list2 = copy(list)
  list2.append(1)
  print list2

list = [0]
append(list)
print list

4

Teknik olarak python, argümanları değere göre iletmez: tümü başvuruya göre. Ancak ... python iki tür nesneye sahip olduğundan: değişmez ve değiştirilebilir, işte olan şey:

  • Değişmez argümanlar, değere göre etkin bir şekilde iletilir : dize, tamsayı, tuple, hepsi değişmez nesne türleridir. Teknik olarak "referans olarak aktarılırlar" (tüm parametreler gibi) olsalar da, bunları işlev içinde yerinde değiştiremediğiniz için, değer ile aktarılmış gibi görünür / davranır.

  • Değişken argümanlar referansla etkin bir şekilde aktarılır : listeler veya sözlükler, işaretçileri tarafından iletilir. İşlevin içindeki (ekleme veya silme) gibi herhangi bir yerinde değişiklik, orijinal nesneyi etkileyecektir.

Python böyle tasarlanır: kopya yoktur ve hepsi referans olarak aktarılır. Açıkça bir kopyasını iletebilirsiniz.

def sort(array):
    # do sort
    return array

data = [1, 2, 3]
sort(data[:]) # here you passed a copy

Son olarak, hangi fonksiyonun kendi kapsamı olduğunu belirtmek isterim.

def do_any_stuff_to_these_objects(a, b): 
    a = a * 2 
    del b['last_name']

number = 1 # immutable
hashmap = {'first_name' : 'john', 'last_name': 'legend'} # mutable
do_any_stuff_to_these_objects(number, hashmap) 
print(number) # 1 , oh  it should be 2 ! no a is changed inisde the function scope
print(hashmap) # {'first_name': 'john'}

1
Bu cümle kendinden çelişkiyi vardır: "Immutable argümanlar etkili bir değer geçirilir dize, tamsayı, tuple tüm başvuruya göre iletilir"
eral

@eral, bağlamı tam olarak anlarsanız, kendisiyle çelişmez. Bunu daha net hale getirmek için düzenledim.
Watki02

2

Bu biraz ince bir nokta çünkü Python değişkenleri yalnızca değere göre geçirirken, Python'daki her değişken bir referanstır. Değerlerinizi bir işlev çağrısı ile değiştirebilmek istiyorsanız, ihtiyacınız olan şey değiştirilebilir bir nesnedir. Örneğin:

l = [0]

def set_3(x):
    x[0] = 3

set_3(l)
print(l[0])

Yukarıdaki kodda, işlev bir List nesnesinin (değiştirilebilir olan) içeriğini değiştirir ve bu nedenle çıktı 0 yerine 3'tür.

Bu cevabı yalnızca Python'da 'değer olarak' ne anlama geldiğini göstermek için yazıyorum. Yukarıdaki kod kötü stildir ve değerlerinizi gerçekten değiştirmek istiyorsanız, MPX'in önerdiği gibi bir sınıf yazmalı ve bu sınıf içinde yöntemleri çağırmalısınız.


bu, sorunun yarısını yanıtlar, öyleyse aynı set_3 (x) işlevini l'nin değerini değiştirmeyecek ancak değiştirilmiş yeni bir liste döndürecek şekilde nasıl yazacağım?
Timothy Lawman

Sadece yeni bir liste yapın ve değiştirin:y = [a for a in x]; y[0] = 3; return y
Isaac

her şey, bazı şekillerde yanıt veren listeler ve kopyalama listeleri içerir, ancak bunu bir int'i bir listeye dönüştürmek zorunda kalmadan nasıl yapabilirim yoksa elimizdeki en iyisi bu mu?
Timothy Lawman

İnt gibi ilkel bir argümanı değiştiremezsiniz. Bir çeşit konteynere koymalısınız. Örneğim onu ​​bir listeye koydu. Bunu bir sınıfa veya sözlüğe de koyabilirsiniz. Gerçekten de, onu bir işlevin dışında gibi bir şeyle yeniden atamalısınız x = foo(x).
Isaac

0

Verilen cevap

def set_4(x):
   y = []
   for i in x:
       y.append(i)
   y[0] = 4
   return y

ve

l = [0]

def set_3(x):
     x[0] = 3

set_3(l)
print(l[0])

soruda söylediği şeyi yaptığı sürece en iyi cevap budur. Bununla birlikte, VB veya Pascal'a kıyasla çok beceriksiz görünüyor, elimizdeki en iyi yöntem mi?

Sadece beceriksiz olmakla kalmaz, aynı zamanda orijinal parametrenin bir şekilde manuel olarak değiştirilmesini de içerir, örneğin, orijinal parametreyi bir listeye değiştirerek: veya sadece "bu parametreyi bir değer olarak kullan" veya "bunu kullan" demek yerine başka bir listeye kopyalayarak referans olarak ". Basit cevap, bunun için ayrılmış bir kelime olmadığı, ancak bunlar harika bir çözüm olabilir mi?


0

Değişkenin bir kutu olduğunu ve işaret ettiği değerin kutunun içindeki "şey" olduğunu düşünün:

1. Referansla aktar: işlev aynı kutuyu ve dolayısıyla içindeki şeyi de paylaşır.

2. Değere göre aktar: işlev yeni bir kutu oluşturur, eski kutunun bir kopyasını ve içindeki her şeyin bir kopyasını içerir. Örneğin. Java - işlevler, kutunun ve içindeki şeyin bir kopyasını oluşturur ve bunlar şunlar olabilir: ilkel / bir nesneye referans. (yeni kutudaki kopyalanan referansın ve orijinalin her ikisinin de aynı nesneyi gösterdiğine dikkat edin, burada referans işaret ettiği nesne değil kutunun içindeki şeydir)

3. Nesne başvurusu ile geçiş : işlev bir kutu oluşturur, ancak ilk kutunun içerdiği şeyi içine alır. Yani Python'da :

a) söz konusu kutudaki şey değiştirilebilir ise , yapılan değişiklikler orijinal kutuya (örneğin listeler) geri yansır.

b) şey değişmez ise (python dizeleri ve sayısal türler gibi), o zaman işlevin içindeki kutu, siz onun değerini değiştirmeye çalışıncaya kadar aynı şeyi tutacaktır. Bir kez değiştirildiğinde, işlevin kutusundaki şey, orijinaliyle karşılaştırıldığında tamamen yeni bir şeydir. Bu nedenle, bu kutu için id () şimdi içerdiği yeni şeyin kimliğini verecektir .


0
class demoClass:
    x = 4
    y = 3
foo1 = demoClass()
foo1.x = 2
foo2 = demoClass()
foo2.y = 5
def mySquare(myObj):
    myObj.x = myObj.x**2
    myObj.y = myObj.y**2
print('foo1.x =', foo1.x)
print('foo1.y =', foo1.y)
print('foo2.x =', foo2.x)
print('foo2.y =', foo2.y)
mySquare(foo1)
mySquare(foo2)
print('After square:')
print('foo1.x =', foo1.x)
print('foo1.y =', foo1.y)
print('foo2.x =', foo2.x)
print('foo2.y =', foo2.y)

-2

Python'da referans veya değere göre geçiş, geçmekte olduğunuz gerçek nesnelerin ne olduğu ile ilgilidir. Yani, örneğin bir listeyi iletiyorsanız, o zaman aslında bu geçişi referans olarak yaparsınız çünkü liste değiştirilebilir bir nesne. Böylece, işleve bir işaretçi iletiyorsunuz ve işlev gövdesindeki nesneyi (listeyi) değiştirebilirsiniz.

Bir dizgeyi ilettiğinizde, bu geçiş değere göre yapılır, bu nedenle yeni bir dize nesnesi yaratılır ve işlev sona erdiğinde yok edilir. Yani bunların hepsi değişken ve değişmez nesnelerle ilgili.


1
Bu tamamen yanlış. Python her zaman "nesne başvurusu" ile geçer (değişken başvurusundan farklıdır).
Kirk Strauser

benzetmeyi kastetmiştim - referans ve değişmez nesneler - değere göre geçiş. Söylediklerimi tam olarak
anlayamadınız

1
Yakaladım ve yanlış. Bir nesnenin değişebilir veya değişmez olmasıyla hiçbir ilgisi yoktur; her zaman aynı şekilde yapılır.
Kirk Strauser

-2

Python zaten ref ile çağırıyor.

hadi örnek alalım:

  def foo(var):
      print(hex(id(var)))


  x = 1 # any value
  print(hex(id(x))) # I think the id() give the ref... 
  foo(x)

Çıktı

  0x50d43700 #with you might give another hex number deppend on your memory 
  0x50d43700

Hayır, bu doğru değildef foo(var): print(id(var)) var = 1234 print(id(var)) x = 123 print(id(x)) # I think the id() give the ref... foo(x) print(id(x))
Zohaib Ijaz
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.