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

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

ただいまの
回答率

89.10%

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

解決済

回答 4

投稿

  • 評価
  • クリップ 7
  • VIEW 2,308

jbe00214

score 1

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])
  • 気になる質問をクリップする

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

checkベストアンサー

+9

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

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

def test1(x): 
   print("-関数内 計算前:",id(x))
   x[0] = 9
   print("-関数内 計算後:",id(x))
   return x 

y=[0, 1, 2]
print(y)
print("関数  呼出前:",id(y))
test1(y)
print("関数  呼出後:",id(y))
print(y)


実行結果

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

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

def test2(x): 
   print("-関数内 計算前:",id(x))
   x[0] = 9
   print("-関数内 計算後:",id(x))
   return x 

y=np.arange(3)
print(y)
print("関数  呼出前:",id(y))
test2(y)
print("関数  呼出後:",id(y))
print(y)


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

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

def test3(x): 
   print("-関数内 計算前:",id(x))
   x = x + 1
   print("-関数内 計算後:",id(x))
   return x 

y=np.arange(3)
print(y)
print("関数  呼出前:",id(y))
test3(y)
print("関数  呼出後:",id(y))
print(y)


実行結果

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


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

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

def test4(x): 
   print("-関数内 計算前:",id(x))
   x += 1
   print("-関数内 計算後:",id(x))
   return x 

y=np.arange(3)
print(y)
print("関数  呼出前:",id(y))
test4(y)
print("関数  呼出後:",id(y))
print(y)

実行結果

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

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/07/09 04:40 編集

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

    キャンセル

  • 2020/07/09 21:03

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

    キャンセル

  • 2020/07/09 22:19

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

    キャンセル

+6

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

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


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

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

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/07/05 16:10

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

    キャンセル

  • 2020/07/05 17:05 編集

    いや、raccy さんの回答見て。

    > そうすることで、引数は 値渡し (call by value) で関数に渡されることになります (ここでの 値 (value) とは常にオブジェクトへの 参照(reference) をいい、オブジェクトの値そのものではありません) [1]。

    概念が、オブジェクトへの参照の値渡しへ収束されてるってことです。

    キャンセル

  • 2020/07/05 18:48

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

    キャンセル

+4

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

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

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

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

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

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

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

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/06/27 18:30

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

    キャンセル

  • 2020/06/27 19:00

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

    キャンセル

  • 2020/06/27 19:23

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

    キャンセル

-1

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

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

  • ただいまの回答率 89.10%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる