質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.46%
NumPy

NumPyはPythonのプログラミング言語の科学的と数学的なコンピューティングに関する拡張モジュールです。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

4回答

2845閲覧

Python は参照渡しではなかったのか

jbe00214

総合スコア63

NumPy

NumPyはPythonのプログラミング言語の科学的と数学的なコンピューティングに関する拡張モジュールです。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

1グッド

7クリップ

投稿2020/06/27 07:26

Python とNumpyを利用しています。

以下のsoftmax関数で,関数に渡す際に,リストであれば参照渡しだと書かれているのですが,元のyは変更されないのは何故でしょうか。挙動がわかりません。9行目のfuncとはどこが異なるのでしょうか。教えていただければと思います。

In [3]: def softmax(x): ...: x = x - np.max(x) ...: x = np.exp(x) / np.sum(np.exp(x)) ...: return x ...: In [4]: y = np.arange(3) In [5]: y Out[5]: array([0, 1, 2]) In [6]: softmax(y) Out[6]: array([0.09003057, 0.24472847, 0.66524096]) In [7]: y Out[7]: array([0, 1, 2]) In [9]: def func(n): ...: n[0]=7 ...: return n ...: In [10]: func(y) Out[10]: array([7, 1, 2])In [11]: y Out[11]: array([7, 1, 2])
DrqYuto👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

ベストアンサー

「pythonは参照渡しではなく参照値の値渡し」云々についてここでは他の方に譲ります。
純粋にリストの場合との挙動の比較、また同じnumpyであっても計算式が異なると結果が異なる点について、説明します。

リストの場合、下記のように、関数呼び出し元と呼び出し先で、変数のid(識別値)が変わっておらず、同じオブジェクトにたいして要素の書き換えが行われていることがわかります。

lang

1def test1(x): 2 print("-関数内 計算前:",id(x)) 3 x[0] = 9 4 print("-関数内 計算後:",id(x)) 5 return x 6 7y=[0, 1, 2] 8print(y) 9print("関数  呼出前:",id(y)) 10test1(y) 11print("関数  呼出後:",id(y)) 12print(y)

実行結果

[0, 1, 2] 関数  呼出前: 2971572413192 -関数内 計算前: 2971572413192 -関数内 計算後: 2971572413192 関数  呼出後: 2971572413192 [9, 1, 2]

同様にnumpy配列でも、要素を置き換える場合は同じオブジェクトにたいしてデータ操作が行われます。

lang

1def test2(x): 2 print("-関数内 計算前:",id(x)) 3 x[0] = 9 4 print("-関数内 計算後:",id(x)) 5 return x 6 7y=np.arange(3) 8print(y) 9print("関数  呼出前:",id(y)) 10test2(y) 11print("関数  呼出後:",id(y)) 12print(y)

(実行結果はtest1と同様のため省略)

しかしながら、元コードのようにx=x-np.max(x)という記述をすると、新しいnumpy配列が作成され、それに対して演算が行われるため、異なるオブジェクトの識別値が返ってくることになります。
下記のコードのようにx=x+1で試してみましょう。

lang

1def test3(x): 2 print("-関数内 計算前:",id(x)) 3 x = x + 1 4 print("-関数内 計算後:",id(x)) 5 return x 6 7y=np.arange(3) 8print(y) 9print("関数  呼出前:",id(y)) 10test3(y) 11print("関数  呼出後:",id(y)) 12print(y)

実行結果

[0 1 2] 関数  呼出前: 2238140571440 -関数内 計算前: 2238140571440 -関数内 計算後: 2237889684544 <= 関数内ではidが変わっている! 関数  呼出後: 2238140571440 <= 関数を抜けた後は元のid [0 1 2] <= yそのものは元の配列と変わらない(計算されたのは関数内で新たに作られたローカルオブジェクトx)

このように関数内でx=x+1とした時点で新しいオブジェクトが作成され、そのオブジェクトに対して演算が行われるため、呼び出し元のオブジェクトyは変わっていません。

ただし、「+=」という演算子を使うと、元のオブジェクトを書き換えることになります。

lang

1def test4(x): 2 print("-関数内 計算前:",id(x)) 3 x += 1 4 print("-関数内 計算後:",id(x)) 5 return x 6 7y=np.arange(3) 8print(y) 9print("関数  呼出前:",id(y)) 10test4(y) 11print("関数  呼出後:",id(y)) 12print(y)

実行結果

[0 1 2] 関数  呼出前: 1797431238704 -関数内 計算前: 1797431238704 -関数内 計算後: 1797431238704 関数  呼出後: 1797431238704 <= 関数内外ですべて同じid [1 2 3] <= 配列の中身が書き換わっている

上記の挙動の違いは、「x=x+1」と「x+=1」の書き方によって、呼び出される足し算の計算挙動が異なっていることから生じているものと推測されます。

投稿2020/06/27 09:32

編集2020/06/27 09:40
patapi

総合スコア749

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

jbe00214

2020/06/27 09:55

ふうむ。何となく分かったような。参照渡し云々は別として,x=x+1とすると,x=のxと,x+1のxは,別のものと言うことですね。z=x+1と書いたのと同じと言うことですよね。ひとつの関数内で同じ識別名が別の変数であるというのは,初心者の私にとって混乱の何ものでもない感じがありますが,理由があってこのような仕様なのでしょうかね。このような仕様にした理由がどこかにあって,より便利だからだと思いますが,想像がしにくいですね。もしお分かりであれば教えてもらえますか。
patapi

2020/06/27 12:33 編集

すみませんが、pythonの言語仕様の正確な意図については知らないです。
jbe00214

2020/06/27 12:47

ありがとうございました。少なくとも違いがわかりました。でも,理解しにくいですよね。
frodo821

2020/06/29 23:19

これはPythonの言語仕様とはあまり関係ないですね。むしろ、numpy側がそういう実装になっているからというべきです。 +=と+では、内部的に呼ばれる関数が異なります。これはPythonの言語仕様です。 numpy.arrayでは、__add__を非破壊的演算として、__iadd__を破壊的演算として定義しているためにこういう風な挙動の違いが生まれます。
otn

2020/06/30 04:16

> ひとつの関数内で同じ識別名が別の変数であるというのは,初心者の私にとって混乱の何ものでもない感じがありますが, 同じスコープで同じ識別名であれば同じ変数ですよ。 同じ変数が指し示す物が、代入するたびに変わっていくということです。
jbe00214

2020/06/30 14:08 編集

代入するタイミングで別の実体に変わるのですね。勉強になります。 ``` struct S{ S(int a):a(a){} int a; }; ``` かつてはこのa の違い分からなかったのですが,今では慣れて理解できるように,言語への慣れですね。
otn

2020/07/05 10:03

> 代入するタイミングで別の実体に変わるのですね。 合ってるかも知れないけど、ニュアンスが微妙。誤解がある可能性もあり。 実体はオブジェクトで、変数名はそれに貼るラベルです。 変数への代入とは、オブジェクトにラベルを貼ることです。 同じオブジェクトに複数のラベルが貼られているときもあるし、ラベルを剥がして別のオブジェクトに張り直すことも出来る。ラベルを剥がした途端に、ラベルとさっきまで貼られていたオブジェクトは、無関係になります。 つまり、引き数オブジェクトに貼っていたラベルを剥がして別のオブジェクトに貼れば、引き数オブジェクトにはラベルが貼られていない状態になり、アクセス出来なくなります。
jbe00214

2020/07/06 20:14

>実体はオブジェクトで、変数名はそれに貼るラベルです。 この部分はおそらく伝統的な言語にも通用する説明ですね。 >変数への代入とは、オブジェクトにラベルを貼ることです。 この部分がよく分からないですね。これってPython特有の概念なのですかね。 例えばx=x+1としたときに,引数のxに1を加えた結果の実体変数が別に生成されて,その実体変数に対して,新たなxというラベルを貼るということですかね。そうだとすると,引数に対するアクセスを継続したければ,y=x+1のように,別の名前を使うことを考えなければならないということですね。
hentaiman

2020/07/06 22:09

>新たなxというラベルを貼る pythonの場合は新たなxでは無く、同じxです
otn

2020/07/06 22:47 編集

> これってPython特有の概念なのですかね。 オブジェクトを扱うほとんどの言語で共通でしょう。 > 新たなxというラベルを貼るということですかね。 新たにというか、1を足す前の値に貼られていたラベルを剥がして、貼り直しです。 > 引数に対するアクセスを継続したければ,y=x+1のように,別の名前を使うことを考えなければならないということですね。 そうですね。ラベルを剥がしちゃ駄目です。
jbe00214

2020/07/08 12:30

>オブジェクトを扱うほとんどの言語で共通でしょう。 C++では変数に代入しても変数についている名前(ラベル)は変更しないから,代入によってラベルを貼ることにはならないように思いますが... >そうですね。ラベルを剥がしちゃ駄目です。 はい,だいぶ理解できるようになりました。
otn

2020/07/08 12:40

C系はちょっと話が違いますね。変数がラベルじゃなくて入れ物だったりするので。 > 変数についている名前(ラベル) とは何のことですかね?変数が入れ物(箱)と言うことを言いたい?
jbe00214

2020/07/08 12:54

そうです。変数は入れ物(箱)という概念の言語しか知らない古い人間なので。C++では,変数(オブジェクトでも同じですが)というメモリ上にある実体に名前を付けてアクセスする,と理解しています。少なくとも,昔の人間はそれで通用したはずです。
otn

2020/07/08 13:16

C++だと、a = b; で、「bという箱に入っていたオブジェクトがaという箱の中にコピーされた」だと思いますが、jbe00214さんの理解は「aというオブジェクトが、bというオブジェクトと同じ内容を持つように書き換わった」ということとなのでしょうか? 同じ事をどう表現するかだけの違いだと思いますが、私はC++では本格的なプログラムを書いたことが無く、Cの延長線上で捉えているので認識不足があるかも知れません。
jbe00214

2020/07/08 13:49

>「bという箱に入っていたオブジェクトがaという箱の中にコピーされた」だと思いますが、 このとおりの理解です。bという名前を付けたメモリにある変数(オブジェクト)の内容が,aという名前のメモリ部分にコピーされた,のですよね。表現の仕方の違いなのでしょうかね。
otn

2020/07/08 15:15

であれば、 > 変数(オブジェクトでも同じですが)というメモリ上にある実体に名前を付けてアクセスする という理解と矛盾してませんか? 変数が箱なら、変数は実体では無いです。実体なのはオブジェクト。
jbe00214

2020/07/08 19:43 編集

C++の開発者であるビャーネ・ストラウストラップの教科書(プログラミング言語C++第4版p48)によれば,C++において,オブジェクトとは「メモリ上に存在して何らかの値を保持する」とあり,変数は「名前のついたオブジェクト」と定義されています。この定義によると,プリミティブなint 型の変数aも,class Aのインスタンスbもオブジェクトになります。 aもbもいずれもメモリ上に「実体」として存在し,a,bという名前がついており,両者の扱いは一緒です。私は,メモリ上を占有しているそれぞれのa,bの領域を「箱」という例えで表現し,それらの箱を「実体」と表現しました。おそらくPythonや最近の言語は,このあたりの用語の定義が異なるのでしょう。だから私も理解がしにくいのだと思います。なお,もともと「参照」が話題でしたが,C++でいう「参照」は上記の教科書によると,「オブジェクトの別名」であり,オブジェクトのアドレスを保持していることから,関数にオブジェクトを参照で渡すと,関数内部からアドレスを使って呼び出し元のオブジェクトにアクセスできるので値も変更できることになります。この点においても,皆さんの説明で,C++とは「参照」という用語の扱いが異なっていることが何となくわかりました。
otn

2020/07/09 12:03

なるほど。「箱」という言葉の意味が合ってないので、話がかみ合わないようです。 「箱」は「中身」と対比する表現だったのですが。
jbe00214

2020/07/09 13:19

quiquiさんが言っているように,pythonには変数というものはなくて,名前への束縛しかないと。リンク先のマニュアルを読んでみると確かに「名前への束縛」という項目がありますが,これって普通に理解できる用語として意味がわかりませんよね。bindの訳のようですが,むしろotnのいうようにラベル(名前)を貼って「紐づける」くらいの意味だと理解しやすくなります。そしてpythonにはオブジェクトという実体はあるけど,変数という実体はないということですね。参照の話はもう少し勉強してからにします。
guest

0

リストであれば参照渡しだと書かれているのですが

間違いです。
Pythonに参照渡しはありません。値渡しだけです。


(追記)
これは感想なのですが(本当は回答に書くのはあまりよろしくないかもしれませんが)

「参照渡し」という用語がここまで紛らわしいのは
参照渡しを持つ言語が多くないし残ってない
からかと思います。(まっとうに残っていると言えるのはC++やC#ぐらい? Delphiなんかはあやしい)
参照渡しを持っている言語を使ったことがない人の方が圧倒的に多数派で、だから参照渡しというものが正しく伝わらないのではないかと。

あと、Pythonでは
実行モデル上は変数という概念をPythonは持ってない
ことが話をややこしくします。
Pythonにあるのは**名前への束縛**なのです。
束縛された名前のことを便宜上"変数"と読んでいますが、C言語でいう変数とはそもそも"違うもの"なのがややこしいです。
C言語でいう"ポインタのようなもの"だけがあるのです。
(ただ、そういう言語は少数派というわけでもないかと思うので、これはPython言語仕様の用語の問題でしかないでしょうけれども)

投稿2020/06/27 07:39

編集2020/06/27 10:50
quickquip

総合スコア11055

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

jbe00214

2020/06/27 09:05

9行目のfunc関数との区別がつきませんが,この違いはなんでしょうか。
Daregada

2020/06/27 09:40 編集

np.exp(x)などで新たなnumpy配列が生成されるからでは。 ってもう別の回答に書いてあったわ
jbe00214

2020/06/27 10:05

どうもそのようだと言うことがわかりました。ありがとうございました。
otn

2020/06/27 11:39

VB/VBAの ByRef とかもありますね。
quickquip

2020/06/27 13:08

VB! 確かにありました! (あんなメジャーな言語にあった機能がなんでこんなに誤解されているのだろう……)
退会済みユーザー

退会済みユーザー

2020/06/28 00:53

php にもありますよ。使わんけどw
maisumakun

2020/06/28 01:14 編集

(勘違いしていたので削除)
退会済みユーザー

退会済みユーザー

2020/06/28 01:10

ゴミ記事乱立が混乱の原因でしょうね。 参照の値渡しを取り扱ってないのも多いし。誤解を加速させてる。
hentaiman

2020/06/28 01:13

phpでは使うが クロージャー使わんならいらんな
miyabi_takatsuk

2020/06/29 16:55

> 参照渡しを持つ言語が多くないし残ってない そこもそうだと思うんですが、 参照渡しも値渡しもそもそもないのに、便宜上だったり、言葉のなんとなくの語感でそうだと謳ってる記事が多いからに思います。 って、JSの話ですけどね 汗 渡し自体がない、あくまで参照:値でしかないのに、オブジェクトは参照渡ししてるって、勘違い書いてる記事が多いのですよ。 (私もよくわかってない時は勘違いしてましたが) te2jiさん > ゴミ記事乱立が混乱の原因でしょうね。 ほんそれです。
jbe00214

2020/06/30 13:42

初心者には,ゴミ記事でも,ゴミなのかどうか分からないのが現状です。ゴミ記事と言われる記事でも,これまで結構助けられていましたので,感謝はしています。で,その混乱をこの場で整理して教えてもらっているわけです。これも感謝です。
Bindi

2020/07/05 06:03

すべてがオブジェクトで、変数なんてありませんね。 私は python をつかって、初めてオブジェクトはなんぞや?というのを理解しました。 >>> help(1) とかやれば、理解できるのかな? 1 はオブジェクトなので。 あとは、 >>> 'nice meet you'.split() とか。 'nice meet you' はオブジェクトなので、 split() が使える。 参照渡しとか、値渡しだとか、そういう概念がない。 だってオブジェクトだもん。 ナイス追記!
miyabi_takatsuk

2020/07/05 07:10

Bindiさん > そうか、そういことですか。 めちゃ納得です。 プリミティブな値も、プロパティやメソッドを持つ言語は、値自体もオブジェクトである、とは気付いてましたが、だから、参照渡しも値渡しも無いわけですね。
退会済みユーザー

退会済みユーザー

2020/07/05 08:06 編集

いや、raccy さんの回答見て。 > そうすることで、引数は 値渡し (call by value) で関数に渡されることになります (ここでの 値 (value) とは常にオブジェクトへの 参照(reference) をいい、オブジェクトの値そのものではありません) [1]。 概念が、オブジェクトへの参照の値渡しへ収束されてるってことです。
miyabi_takatsuk

2020/07/05 09:48

te2jiさん > 大変失礼しました。 raccyさんの回答拝見して、理解できました。
guest

0

リストであれば参照渡しだと書かれている

そのようなことが間違いが書かれている本や記事は捨てましょう。

4.6. 関数を定義する — Python 3.8.3 ドキュメント

関数を呼び出す際の実際の引数 (実引数) は、関数が呼び出されるときに関数のローカルなシンボルテーブル内に取り込まれます。そうすることで、引数は 値渡し (call by value) で関数に渡されることになります (ここでの 値 (value) とは常にオブジェクトへの 参照(reference) をいい、オブジェクトの値そのものではありません) [1]。ある関数がほかの関数を呼び出すときには、新たな呼び出しのためにローカルなシンボルテーブルが新たに作成されます。

脚注
[1] 実際には、オブジェクトへの参照渡し (call by object reference) と書けばよいのかもしれません。というのは、変更可能なオブジェクトが渡されると、関数の呼び出し側は、呼び出された側の関数がオブジェクトに行ったどんな変更 (例えばリストに挿入された要素) にも出くわすことになるからです。

公式ドキュメントに「値渡し」であると明記されています。

値渡しですので、呼び出し先の関数内で仮引数に代入をしても、呼び出し元の実引数が変わることはありません。しかし、渡されるのはオブジェクトへの参照という値であるため、呼び出し先の関数で、仮引数が示しているオブジェクトそのものを変更するような副作用を伴う処理を行った場合、実引数が示しているオブジェクトも変化します。

C++で言えば、動作的にはポインタ渡し(アドレス渡し)に近いです。変数は実はC/C++でのポインタのような物と考えるとわかりやすいかも知れません。ポインタ渡しも値渡しの一種なので、同じような動作になります。

投稿2020/06/27 09:26

編集2020/06/27 09:38
raccy

総合スコア21735

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

raccy

2020/06/27 09:30

ついでに、Python等で「参照渡し」と書かれた記事や書籍を趣味で集めています。 https://qiita.com/raccy/items/d4c5e7995a8fc90109ee よろしければ、「参照渡し」と書かれた記事や書籍を紹介頂けると助かります。
jbe00214

2020/06/27 10:00

ありがとうございます。やっぱり皆さん,Pythonの参照渡しという仕様に混乱があるのでしょうか。わかりにくいですね。
jbe00214

2020/06/27 10:23

CやC++では,ポインタを渡すことで,ポインタを介して実体変数(やオブジェクト)を変更できます。C++ではさらに実体変数(やオブジェクト)の参照を渡して,実体変数(やオブジェクト)を変更できます。Pythonでも用語の使い方はともかくとして,raccyさんがいうように,ポインタのようなものが渡されているといことであれば,関数内部から呼出し元の変数(やオブジェクト)を変更できる場合があるということがわかりました。
guest

0

ここまでの私の理解としては,各言語の用語の話になれば,用語の定義の仕方になるのでそれは避けますが,
リストを引数として関数に渡した時の動作として,Pythonは引数を通じて呼出側のリストの内容を変更することができるという仕様だということ。一方で,同じ名称のオブジェクトであっても,代入により実体が異なるオブジェクトが生成されることもPythonの仕様であり,Python初心者だとこの両者の仕様が関数の呼出しの場面で混乱をきたしてた,というまとめになります。

投稿2020/06/27 23:59

jbe00214

総合スコア63

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.46%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問