Python関数は参照によって呼び出されます


82

一部の言語では、refvalなどの特別な予約語を使用して、参照または値でパラメーターを渡すことができます。Python関数にパラメーターを渡すと、関数を離れるときにパラメーターの値が変更されることはありません。これを行う唯一の方法は、グローバル予約語を使用することです(または現在理解しているとおりです)。

例1:

k = 2

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

j = foo(k)
print j
print k

表示されます

>>4
>>2

kが変更されていないことを示す

この例では、変数nは変更されません。

例2:

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

この例では、変数nが変更されています

Pythonで関数を呼び出し、パラメーターがグローバルを使用する代わりにまたは参照パラメーターのいずれかであることをPythonに通知する方法はありますか?

次に、Aレベルのケンブリッジ試験では、関数は単一の値を返すのに対し、プロシージャは複数の値を返すと言われています。80年代に、関数にはreturnステートメントがあり、プロシージャにはないことを教えられました。なぜこれが正しくないのですか?


5
Pythonの呼び出しモデルを理解するために私が見つけた最高のリソースは、effbotに関する次の記事です。effbot.org
zone

1
Python変数、可変および不変オブジェクトについて読む必要があります。また、最初の例では、なぜn変更されるのか、新しい変数squareを使用して計算結果を格納しています。
ファクンドカスコ2012年

6
関係のない2つの質問を1つに投稿しないでください。関数と手順の定義は定義の問題であり、あなたの定義はPascalに偏っているようです。Pythonの専門用語にはプロシージャのようなものはありません。
Fred Foo

2
また、この以前のStackOverflowの回答もご覧ください。stackoverflow.com/a/10262945/173292オブジェクト参照モデルによる呼び出しをかなり直感的に説明します。
Wilduck 2012年

1
非常に役立つ説明については、このSOの質問も参照してください。参照によって変数を渡す方法
Michael Ohlrogge 2016

回答:


78

Pythonの関数内で、strやなどの不変オブジェクトを変更することはできませんが、次のようなtupleことはできます。

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

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

ただし、配列内の特定の要素を常に2乗する必要がない限り、これは奇妙な方法です。

Pythonでは、複数の値を返すこともできるため、参照渡しのユースケースのいくつかはそれほど重要ではないことに注意してください。

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

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

このような値を返すと、タプルとして返され、次にアンパックされます。

編集: これについて考える別の方法は、Pythonで参照によって変数を明示的に渡すことはできませんが、渡されたオブジェクトのプロパティを変更できることです。私の例(およびその他)では、リストのメンバーを変更できます。渡されました。ただし、渡された変数を完全に再割り当てすることはできません。たとえば、次の2つのコードは、似たようなことをするように見えますが、結果は異なります。

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
プログラムの結果を見ると役に立ちます。たとえば、「print x [0]」は25を返しますか?(はい、そうですが、それを表示する必要があります。)
Ray Salemi 2017

11
@alexeyにclear_aは参照が与えられ、その参照をzに格納しますx。その後、すぐに変更さxれて別の配列を参照します。元のz配列はのスコープで忘れられますがclear_a、実際には変更されません。グローバルスコープに戻っても変更されません。新しい配列を作成したり、他の方法でポインティングしたりすることなくclear_b、参照を取得しzて直接操作するものとは対照的ですx
デイブマンコフ2017

ありがとうデイブ!clear_bで「新しい配列の作成」を試みた後y = xy: y.pop()と呼ばれましたがclear_b(z)、zはまだ空になっています...したがってy = list(x)、xのコピーを作成するようなものが必要です(ここで説明されているlist(x))
alexey 2017

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

Javaとは異なり、Pythonにはプリミティブ型はありません。すべてのタイプのインスタンスオブジェクト。
マルコ・スッラ

181

「関数呼び出し」には基本的に3種類あります。

  • 値渡し
  • 参照渡し
  • オブジェクト参照を渡す

PythonはPASS-BY-OBJECT-REFERENCEプログラミング言語です。

まず、変数と変数(オブジェクト)の値は2つの別個のものであることを理解することが重要です。変数 'は'オブジェクトを指します。変数はオブジェクトではありません。再び:

変数は目的ではありません

例:次のコード行:

>>> x = []

[]は空のリストであり、空のリストxを指す変数ですが、xそれ自体は空のリストではありません。

変数(x上記の場合は)をボックスと見なし、変数([])の「値」をボックス内のオブジェクトと見なします。

PASS BY OBJECT REFERENCE(Pythonの場合):

ここでは、「オブジェクト参照は値によって渡されます」。

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

ここで、ステートメントx = [0]xオブジェクトを指す変数(ボックス)を作成します[0]

呼び出される関数で、新しいボックスliが作成されます。の内容はli箱の中身と同じxです。両方のボックスに同じオブジェクトが含まれています。つまり、両方の変数がメモリ内の同じオブジェクトを指しています。したがって、が指すオブジェクトへの変更は、が指すオブジェクトliにも反映されますx

結論として、上記のプログラムの出力は次のようになります。

[0, 1]

注意:

変数liが関数で再割り当てされると、liはメモリ内の別のオブジェクトを指します。xただし、以前にポイントしていたメモリ内の同じオブジェクトを引き続きポイントします。

例:

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

プログラムの出力は次のようになります。

[0]

参照渡し:

呼び出し元の関数からのボックスは、呼び出された関数に渡されます。暗黙的に、ボックスの内容(変数の値)は呼び出された関数に渡されます。したがって、呼び出された関数のボックスの内容に対する変更は、呼び出し元の関数に反映されます。

値渡し:

呼び出された関数に新しいボックスが作成され、呼び出し元の関数からのボックスの内容のコピーが新しいボックスに格納されます。

お役に立てれば。


3
「オブジェクト参照渡し」の場合と同様に、「参照渡し」と「値渡し」の例を挙げてください。
tJain 2017年

@TJain参照渡しと値渡しはC ++で確認できます。この答えは良い説明を与えます:stackoverflow.com/a/430958/5076583
Shobhit Verma 2017

3
これが参照渡しとどのように異なるのか、私はまだ理解していません。この答えには、同一の擬似コードが3つのパラダイムすべてに対して異なる出力を生成する方法の例が必死に必要です。参照渡しと値渡しの違いを説明するだけの別の回答にリンクしても役に立ちません。
Jess Riedel

「Pythonのすべての変数はポインターである」および「関数呼び出しのパラメーターに割り当てられたすべての変数は新しいポインターに割り当てられる」と言うのは簡単ではありませんか?これに気付くと、PythonはJavaに似ていることがわかります。つまり、常に値でポインターを渡すだけですKirk Strauserの回答を
Marco Sulla

26

OK、これを刺します。Pythonはオブジェクト参照を渡します。これは、通常「参照による」または「値による」と考えるものとは異なります。この例を見てください:

def foo(x):
    print x

bar = 'some value'
foo(bar)

したがって、値が「some value」の文字列オブジェクトを作成し、それをbar。という名前の変数に「バインド」します。Cでは、これはbar「ある値」へのポインタに似ています。

あなたが電話するときfoo(bar)、あなたはbarそれ自体を通過していません。bar's value:' somevalue 'へのポインタを渡しています。その時点で、同じ文字列オブジェクトへの2つの「ポインタ」があります。

今それを比較してください:

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

bar = 'some value'
foo(bar)

ここに違いがあります。行で:

x = 'another value'

実際にはの内容を変更しているわけではありませんx。実際、それは不可能ですらあります。代わりに、値 'anothervalue'を使用して新しい文字列オブジェクトを作成しています。その代入演算子?「x指しているものを新しい値で上書きする」と言っているのではありません。「x代わりに新しいオブジェクトを指すように更新する」と言っています。その行の後に、2つの文字列オブジェクトがあります。「ある値」(それをbar指している)と「別の値」(それをx指している)です。

これは不器用ではありません。それがどのように機能するかを理解すると、それは美しくエレガントで効率的なシステムです。


3
しかし、関数で変数の内容を変更したい場合はどうなりますか?私はこれを行う方法がないと思いますか?
aquirdturtle 2016年

1
オブジェクトが変更可能である場合は、リストに追加するなどして、名前が指しているオブジェクトを変更できます。「引数リストの名前が私を呼び出したときとは異なるものを指すように、私を呼び出したスコープの名前空間を変更する」というPythonのセマンティクスはありません。
カークストラウザー2016年

2
事実上、私が求めているのは、Pythonに同じオブジェクトへの新しいポインターを作成せず、代わりに元のポインターを使用する簡単な方法があるかどうかです。私はこれを行う方法はないと思います。他の回答から、参照渡しが行うことを達成するための通常の回避策は、より多くのパラメーターを返すことであると考えています。その場合、a、b、c、d、e = foo(a、b、c、d、e)は単なるfoo(a、b、c、)よりも少し不格好であるという議論にある程度の妥当性が見られます。 d、e)。
aquirdturtle 2016年

2
@aquirdturtleこれは、新しいポインタが作成されるという事実とは関係ありません。strings、tuples、およびその他のオブジェクトは不変であるため、新しいオブジェクトを返す必要があります。のような可変オブジェクトを渡すlistと、すべての関数パラメーターが渡した同じオブジェクトへのポインターであるため、関数内でオブジェクトを返す必要なしに変更できます。
マルコ・スッラ

23

次の説明がそれをうまく要約していることを願っています:

ここで考慮すべきことが2つあります。変数とオブジェクトです。

  1. 変数を渡す場合は、値で渡します。つまり、関数内で変数に加えられた変更はその関数に対してローカルであるため、グローバルに反映されません。これは、「C」のような動作です。

例:

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. リストなどの可変オブジェクト内にパックされた変数を渡す場合、オブジェクトに加えられた変更は、オブジェクトが再割り当てされない限り、グローバルに反映されます。

例:

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. ここで、オブジェクトが再割り当てされた場合を考えてみましょう。この場合、オブジェクトは、これが発生する関数に対してローカルであるため、グローバルに反映されない新しいメモリ位置を参照します。

例:

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
これは最高の要約です!^^^ここに最高の要約^^^誰もがこの答えを読んでいます!1.単純な変数を渡します2.変更されたオブジェクト参照を渡します3.関数で再割り当てされたオブジェクト参照を渡します
gaoithe 2017年

3
この答えは非常に混乱しています。Pythonは、さまざまな種類の値を異なる方法で渡しません。Pythonのすべての関数引数は、割り当てによって値を受け取り、言語の他の場所での割り当ての動作とまったく同じように動作します。(「あなたがオランダ人でない限り、その方法は最初は明白ではないかもしれませんが。」)
DanielPryden18年

9

Pythonは、値渡しでも参照渡しでもありません。ここで説明するように、これは「オブジェクト参照は値によって渡される」ということです

  1. これが、値渡しではない理由です。 なぜなら

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

[0,1]を返します。これは、値渡しによって関数が親スコープをまったく変更できないため、ある種の参照が明確に渡されたことを示しています

それでは、参照渡しのように見えますね。いいえ。

  1. これが、参照渡しではない理由です。なぜなら

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

リストが再割り当てされたときに元の参照が破棄されたことを示す[0]を返します。 参照渡しは[0,1]を返します。

詳細については、こちらをご覧ください

関数がスコープ外を操作しないようにする場合は、新しいオブジェクトを作成する入力パラメーターのコピーを作成する必要があります。

from copy import copy

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

list = [0]
append(list)
print list

4

技術的には、Pythonは引数を値で渡しません。すべて参照で渡します。しかし... Pythonには不変と可変の2種類のオブジェクトがあるため、次のようになります。

  • 不変の引数は値によって効果的に渡されます。文字列、整数、タプルはすべて不変のオブジェクトタイプです。これらは技術的には(すべてのパラメーターと同様に)「参照によって渡されます」が、関数内でその場で変更できないため、値によって渡されたかのように見えます。

  • 可変引数は参照によって効果的に渡されます。リストまたは辞書はそのポインターによって渡されます。(appendまたはdel)のような関数内でのインプレース変更は、元のオブジェクトに影響します。

これがPythonの設計方法です。コピーはなく、すべてが参照によって渡されます。明示的にコピーを渡すことができます。

def sort(array):
    # do sort
    return array

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

最後に、関数には独自のスコープがあることを述べておきます。

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
この文は、自己矛盾があります:「不変の引数は、効果的に値で渡された文字列、整数、タプルはすべて、参照によって渡されます」
ERAL

@eral、完全なコンテキストを理解していれば、それ自体は矛盾しません。これをより明確にするために編集しました。
Watki02

2

Pythonは変数を値で渡すだけですが、Pythonのすべての変数は参照であるため、これは少し微妙な点です。関数呼び出しで値を変更できるようにしたい場合、必要なのは可変オブジェクトです。例えば:

l = [0]

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

set_3(l)
print(l[0])

上記のコードでは、関数はListオブジェクト(可変)の内容を変更するため、出力は0ではなく3になります。

この答えは、Pythonで「値による」が何を意味するかを説明するためだけに書いています。上記のコードは悪いスタイルです。本当に値を変更したい場合は、MPXが示唆するように、クラスを作成し、そのクラス内でメソッドを呼び出す必要があります。


これは質問の半分に答えるので、同じset_3(x)関数を記述して、lの値を変更せず、変更された新しいリストを返すにはどうすればよいですか?
ティモシーローマン2012年

ちょうど新しいリストを作成し、それを修正する:y = [a for a in x]; y[0] = 3; return y
アイザック

すべてはリストとリストのコピーを含み、いくつかの方法で答えますが、intをリストに変換せずにそれを行うにはどうすればよいですか?これは私たちが持っている最高のものですか?
ティモシーローマン2012年

intのようなプリミティブ引数を変更することはできません。あなたはそれをある種の容器に入れなければなりません。私の例ではそれをリストに入れました。クラスや辞書に入れることもできます。ただし、実際には、関数の外部でx = foo(x)。のようなものを使用して再割り当てする必要があります。
アイザック

0

与えられた答えは

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

そして

l = [0]

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

set_3(l)
print(l[0])

それが質問で言うことをする限り、これは最良の答えです。ただし、VBやPascalに比べると非常に不器用な方法のようですが、これが最善の方法ですか?

不器用であるだけでなく、元のパラメータを手動で変更することも含まれます。たとえば、元のパラメータをリストに変更するか、「このパラメータを値として使用する」または「これを使用する」と言うのではなく、別のリストにコピーします。参考として」。簡単な答えは、これに予約語ないということでしょうか?しかし、これらは素晴らしい回避策ですか?


0

変数がボックスであり、それが指す値がボックス内の「もの」であると考えてください。

1.参照渡し:関数は同じボックスを共有するため、内部のものも共有されます

2.値渡し:関数は、新しいボックス、古いボックスのレプリカを作成し、その中にあるもののコピーを含めます。例えば。Java-関数は、ボックスとその中にあるもののコピーを作成します。これは、プリミティブ/オブジェクトへの参照です。(新しいボックスにコピーされた参照と元の参照はどちらも同じオブジェクトを指していることに注意してください。ここでは、参照はボックス内のものであり、指しているオブジェクトではありません)

3.オブジェクト参照の受け渡し:関数はボックスを作成しますが、最初のボックスが囲んでいたものと同じものを囲みます。したがって、Pythonでは

a)上記のボックス内のものが変更可能である場合、行われた変更は元のボックスに反映されます(例:リスト)

b)ものが不変である場合(Python文字列や数値型など)、関数内のボックスは、値を変更しようとするまで同じものを保持します。一度変更すると、関数のボックス内のものは、元のものと比較してまったく新しいものになります。したがって、そのボックスのid()は、それが囲む新しいもののIDを与えるようになります。


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では、参照または値による受け渡しは、渡す実際のオブジェクトと関係があります。たとえば、リストを渡す場合、リストは可変オブジェクトであるため、実際には参照による受け渡しを行います。したがって、関数へのポインタを渡しており、関数本体のオブジェクト(リスト)を変更できます。

文字列を渡す場合、この受け渡しは値によって行われるため、新しい文字列オブジェクトが作成され、関数が終了すると破棄されます。つまり、すべては可変オブジェクトと不変オブジェクトに関係しています。


1
それは完全に間違っています。Pythonは常に「オブジェクト参照」(変数参照とは異なります)を渡します。
Kirk Strauser 2012年

アナロジーの可変オブジェクト(参照渡しと不変オブジェクト)は値渡しを意味しました。私が言っていることは完全に有効です。おそらく私が言っていることを理解できなかったでしょう
jkalivas 2012年

1
私はそれを捕まえました、そしてそれは間違っています。オブジェクトが可変であるか不変であるかとは関係ありません。それは常に同じ方法で行われます。
カークストラウザー2012年

-2

Pythonはすでに参照によって呼び出しています。

例を見てみましょう:

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


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

出力

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

いいえ、それは真実ではありませんdef 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 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.