関数が、呼び出し元が認識しているように一部の引数を変更できるのに、他の引数は変更できないのはなぜですか?


182

変数スコープに対するPythonのアプローチを理解しようとしています。この例でf()x、内main()で認識されるようにの値を変更できるが、の値は変更できないのはなぜ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()

出力:

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

7
ここでよく説明されていますnedbatchelder.com/text/names.html
Roushan

回答:


212

一部の回答には、関数呼び出しのコンテキストで「コピー」という単語が含まれています。私はそれを混乱させます。

Pythonはコピーされませんオブジェクト関数の呼び出し中にあなたが合格に今までに

関数パラメータは名前です。関数を呼び出すと、Pythonはこれらのパラメーターを(呼び出し元のスコープ内の名前を介して)渡すオブジェクトにバインドします。

オブジェクトは、(リストのように)変更可能または(Pythonの整数、文字列のように)不変にすることができます。変更可能な可変オブジェクト。名前を変更することはできません。別のオブジェクトにバインドするだけです。

あなたの例はスコープや名前空間ではなく、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

他の言語での変数とPythonでの名前の違いについての素晴らしい写真を以下に示します。


3
この記事は問題をよりよく理解するのに役立ち、回避策といくつかの高度な使用法を示唆しています:Pythonのデフォルトのパラメーター値
Gfy

@Gfy、私は以前に同様の例を見たことがありますが、私にとってそれは実際の状況を説明していません。渡されたものを変更する場合、デフォルトを設定しても意味がありません。
Mark Ransom 2012

@MarkRansom、次のようにオプションの出力先を提供したい場合は理にかなっていると思いますdef foo(x, l=None): l=l or []; l.append(x**2); return l[-1]
Janusz Lenar 2012

セバスチャンのコードの最終行については、「#上記は元のリストに影響を与えない」と述べています。しかし、私の意見では、「n」には影響しませんが、main()関数の「x」を変更しました。私は正しいですか?
user17670

1
@ user17670:x = []in f()xmain関数のリストには影響しません。コメントをより具体的にするために更新しました。
jfs 2015

15

あなたはすでに多くの答えを持っています、そして私はJF Sebastianに広く同意しますが、これはショートカットとして役立つかもしれません:

が表示されているときはいつでも、関数のスコープ内に新しい名前バインディングをvarname =作成しています。以前にバインドされていた値はすべて、このスコープ内で失われますvarname

varname.foo()メソッドを呼び出しているのを見るときはいつでもvarname。このメソッドはvarnameを変更する場合があります(例:)list.appendvarname(または、むしろvarname名前の付いたオブジェクト)は複数のスコープに存在する可能性があり、同じオブジェクトであるため、変更はすべてのスコープで表示されます。

[ globalキーワードは最初のケースの例外を作成することに注意してください]


13

f実際には値を変更しませんx(これは常にリストのインスタンスへの同じ参照です)。むしろ、それはこのリストの内容を変更します。

どちらの場合も、参照のコピーが関数に渡されます。関数内では、

  • n新しい値が割り当てられます。関数内の参照のみが変更され、関数外の参照は変更されません。
  • x新しい値は割り当てられません。関数の内部または外部の参照は変更されません。代わりに、xが変更されます。

x関数の内部と外部の両方が同じ値を参照しているので、どちらも変更を確認できます。対照的に、n関数の内部と外部では、関数内で再割り当てされた後の異なる値を参照しnています。


8
「コピー」は誤解を招くものです。PythonにはCのような変数はありません。Pythonのすべての名前は参照です。名前を変更することはできません。名前を別のオブジェクトにバインドするだけです。名前ではなく、Pythonでの可変オブジェクトと不変オブジェクトについて話すことは意味があります。
jfs

1
@JFセバスチャン:あなたの発言はせいぜい誤解を招くだけです。数値を参照として考えることは役に立ちません。
ピタロウ

9
@dysfunctor:数値は不変オブジェクトへの参照です。他の方法でそれらを考えたい場合は、説明しなければならない奇妙な特別なケースがたくさんあります。それらを不変であると考える場合、特別なケースはありません。
S.Lott、2009

@ S.Lott:内部で何が起こっているかに関係なく、Guido van Rossumは、プログラマーが数字を単なる...数字であると見なすことができるように、Pythonの設計に多大な労力を費やしました。
ピタロウ、2009

1
@JF、参照がコピーされます。
habnabit 2009

7

混乱を減らすために変数の名前を変更します。 n- > nfまたはnmainx- > xfまたは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()

関数fを呼び出すと、Pythonランタイムはxmainのコピーを作成してxfに割り当て、同様にnmainのコピーをnfに割り当てます 。

nの場合、コピーされる値は1です。

以下の場合にはXコピーされる値はないリテラルリスト[0、1、2、3] 。それはそのリストへの参照です。 xfxmainは同じリストを指しているため、xfを変更するとxmainも変更されます

ただし、次のように記述した場合:

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

あなたはそれを見つけるだろうxmainが変更されていません。これは、xf = ["foo"、 "bar"]という行で、新しいリストを指すようにxfを変更したためです。この新しいリストに加えた変更は、xmainがまだ指しているリストには影響しません。

お役に立てば幸いです。:-)


2
「nの場合、コピーされる値...」-これは誤りです。ここでコピーは行われません(参照をカウントしない限り)。代わりに、Pythonは実際のオブジェクトを指す「名前」を使用します。nfとxfはnmainとxmainを指し、までnf = 2、名前nfはを指すように変更され2ます。数値は不変で、リストは変更可能です。
Casey Kuball

2

リストは変更可能なオブジェクトだからです。xを[0,1,2,3]の値に設定するのではなく、オブジェクト[0,1,2,3]にラベルを定義します。

次のように関数f()を宣言する必要があります。

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

3
可変性とは何の関係もありません。のx = x + [4]代わりに行う場合x.append(4)、リストは変更可能ですが、呼び出し元にも変化はありません。それが実際に変異している場合、それは関係しいます。
glglgl 2014

1
OTOH、あなたがしなければx += [4]、その後xだけで何が起こるかのように、変異しているx.append(4)ので、呼び出し側は変更が表示されます。
PM 2Ring 2017年

2

nはint(不変)であり、コピーが関数に渡されるため、関数内でコピーを変更します。

Xはリスト(可変)であり、ポインタのコピーが関数に渡されるため、x.append(4)はリストの内容を変更します。ただし、関数でx = [0,1,2,3,4]と言ったので、main()でxの内容を変更しません。


3
「ポインターのコピー」の言い回しに注意してください。どちらの場所もオブジェクトへの参照を取得します。nは不変オブジェクトへの参照です。xは可変オブジェクトへの参照です。
S.Lott、2009

2

関数が完全に異なる変数で書き直され、それらに対してidを呼び出すと、ポイントがよくわかります。私は最初にこれを取得できず、jfsの投稿を素晴らしい説明で読んだので、自分自身を理解/納得させようとしました:

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とxは同じIDを持っています。記事にあるように、同じ基本構造のタグが異なるだけです。


0

Pythonを正しい方法で考えると、Pythonは純粋な値渡し言語です。Python変数は、オブジェクトの場所をメモリに格納します。Python変数はオブジェクト自体を格納しません。関数に変数を渡すと、変数が指すオブジェクトのアドレスのコピーが渡されます。

これら2つの機能を比較してください

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

def goo(x):
    x = []

さて、シェルに入力すると

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

これをgooと比較してください。

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

最初のケースでは、cowのアドレスのコピーをfooに渡し、fooはそこにあるオブジェクトの状態を変更しました。オブジェクトが変更されます。

2番目のケースでは、牛の住所のコピーをgooに渡します。その後、gooはそのコピーを変更します。効果:なし。

私はこれをピンクハウスの原則と呼んでいます。住所のコピーを作成し、画家にその住所の家にピンクを塗るように指示すると、ピンクの家ができあがります。画家に住所のコピーを渡して、新しい住所に変更するように指示しても、家の住所は変更されません。

説明は多くの混乱を取り除きます。Pythonは、変数変数ストアを値で渡します。


ポインタの値による純粋な受け渡しは、それを正しい方法で考えれば、参照による受け渡しと大差ありません...
ガリネット

gooを見てください。あなたが純粋に参照渡しだったとしたら、その議論は変わっていただろう。いいえ、Pythonは純粋な参照渡し言語ではありません。参照を値で渡します。
ncmathsadist

0

Pythonは参照の値によってコピーされます。オブジェクトはメモリ内のフィールドを占有し、参照はそのオブジェクトに関連付けられていますが、それ自体がメモリ内のフィールドを占有しています。また、名前/値は参照に関連付けられています。Python関数では常に参照の値をコピーするので、コードではnが新しい名前になるようにコピーされます。これを割り当てると、呼び出し元スタックに新しいスペースができます。ただし、リストの場合、名前もコピーされますが、同じメモリを参照します(リストに新しい値を割り当てることはないため)。それはpythonの魔法です!


0

私の一般的な理解は、オブジェクト変数(リストや辞書など)は、その関数を介して変更できるということです。あなたができないと私が信じているのは、パラメーターを再割り当てすることです。つまり、呼び出し可能な関数内で参照によりパラメーターを割り当てます。

それは他の多くの言語と一致しています。

次の短いスクリプトを実行して、動作を確認します。

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

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

-3

私は何度も答えを変更しましたが、私は何も言う必要がないことに気付きました。Pythonはすでに説明しました。

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)

この悪魔は参照/値/変更可能かどうか/インスタンス、名前空間または変数/リストまたはstrではありません、それは構文、等号です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.