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

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

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

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

Q&A

解決済

3回答

1084閲覧

「Pythonでは、代入するときはすべて参照渡しだ」と本に書いてあったのですが、イミュータブルなオブジェクトは代入するときに値渡しですよね...

N---------

総合スコア46

Python

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

0グッド

2クリップ

投稿2019/02/12 09:48

編集2019/02/12 09:49

「Pythonでは、代入するときはすべて参照渡しだ」と本に書いてあったのですが、イミュータブルなオブジェクトは代入するときに値渡しですよね。
では、本に書いてあった「Pythonでは、代入するときはすべて参照渡しだ」という記述は嘘ですよね。

多くのサイトで、「Pythonでは、代入するときはすべて参照渡しだ」と書いてあるのですが、なぜそうではないのにそんなことを書いているのですか。なにか意図があったりするのでしょうか...

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

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

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

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

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

quickquip

2019/02/12 10:06

なんという本のどこに書いてありますか? 正確な文言も知りたいです。
guest

回答3

0

「~渡し」は関数呼び出しの際の言葉なので、そもそも適切な語の使用ではありませんがそれはともかく。


代入だろうと関数呼び出しだろうと、すべて参照です。

python

1>>> i = 1 2>>> id(i) 39169088 4>>> j = i 5>>> id(j) 69169088 7>>> def f(x): 8... print(id(x)) 9... 10>>> f(i) 119169088

id関数については以下を参照。
2. 組み込み関数 — Python 3.6.5 ドキュメント | id


イミュータブルなオブジェクトは代入するときに値渡しですよね。

そういう仕様はpythonにはありません。immutableだからといって、mutableなオブジェクトと扱いが区別される訳ではありません。

JavaやPHPなどプリミティブ型と参照型で扱いを変える言語があるので、もしかしたらそれらと混同しているのかもしれませんが、pythonの仕様をプリミティブ型と参照型という用語で説明するなら「すべて参照型で、プリミティブ型は存在しない」ということになります。


たしかに値渡しかな? と思うこともあります。

python

1>>> def f(x): 2... x = x + 1 3... 4>>> a = 10 5>>> f(a) 6>>> a 710

これをちゃんと説明しようとすると長くなるので大まかな流れだけ説明すると、pythonの足し算は、通常左側のオブジェクトの__add__メソッドが呼ばれる仕組みになっています。x + 1x.__add__(1)として扱われます。__add__がオブジェクト自身を書き換えるように実装することも不可能ではありませんが(mutableなオブジェクトなら)、通常は結果を表す新たなオブジェクトを返すようになっています。その結果のオブジェクトをローカル変数のxに束縛する……というのがfの中のx = x + 1の挙動です。この処理は呼び出し時の実引数(オブジェクトaないし10)には何ら影響を及ぼさないことがわかるかと思います。

投稿2019/02/12 09:58

編集2019/02/12 11:04
hayataka2049

総合スコア30933

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

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

N---------

2019/02/12 10:19

回答ありがとうございます。 以下のコードで理解しようとしてみました...そしたら新たな疑問が... ''' >>> a = 1 >>> b = a >>> print(id(a), id(b)) 140733665817424 140733665817424 # aとbは同じアドレスだった。つまり参照渡しだった。 ''' ''' >>> a = 7 >>> print(id(a), id(b)) 140733665817616 140733665817424 # aの値を変更したら、aとbは別のアドレスになった。 ''' ''' >>> id(7) 140733665817616 >>> id(a) 140733665817616 # 数字の7と、変数aのアドレスが同じだった。 ''' a = 7というコードの本質は、「7という数字」のアドレスをaが格納してるということですか?
hayataka2049

2019/02/12 11:06 編集

それはpythonインタプリタの実装が行っている最適化です。小さい整数などは適当にキャッシュされます。 言語仕様的にはa=7のあと7を評価して同じオブジェクトになることは保証されません。 実際にキャッシュされない場合もあります。というかそれが基本的な動作です。 >>> a = 10**6 >>> id(a) 139662363988784 >>> id(10**6) 139662363988880 >>> a = 0.1 >>> id(a) 139662387466816 >>> id(0.1) 139662387466888
hayataka2049

2019/02/12 16:44 編集

>a = 7というコードの本質は、「7という数字」のアドレスをaが格納してるということですか? この文自体は一応ほぼ正解です。 (「ほぼ」正解というのは、実際にはa=7すると名前空間のテーブル上にaという名前(事実上ほとんど文字列だがpythonのstrやbytesではない可能性もある)とオブジェクトへの参照(事実上ほとんどメモリ番地だと思うがそうである必然性はない)の対応付けが記録される、という動作になるからです。C言語みたいにaという変数が生まれて直接メモリ領域と対応づけられる訳ではありません) そういう意味ではpythonの変数はすべて型なしのポインタみたいなものです。 (ただしアドレスそのものは基本的にpython言語の側からは見えないしいじれない。仕様上はアドレスである必然性もない) ただし、「7という数字」(のオブジェクト)が複数存在しないことは特段保証されません。たまたま同じものを指すかもしれないし、違うものを指す可能性もあります。
KSwordOfHaste

2019/02/12 14:04

以前にたような話題で参照渡しとか値渡しというよりcall by sharingという言葉の方がいいんじゃないかという話が出て「なるほど確かにそうかも」と思いました。どうも参照渡しっていわれると def f(ref):  ref = 1 v = 2 f(v) print(v) # 1が印字される? というイメージを持ってしまうのです。もちろんpythonではそういう意味での「参照わたし」の機能はありません。どちらかといえばmaisumakunさん回答のように「参照値を値渡ししている」というイメージの方がしっくりきます。また引数に渡す際に一々インスタンスのコピーはされず渡したインスタンスそのものの参照値が渡されてそのインスタンスの属性を変更したら呼び出し元も当然影響を受けるということをも含めてcall by sharingでいいんじゃないかと落ち着いた・・・ような曖昧な記憶がw;
hayataka2049

2019/02/12 15:38 編集

C++やJavaの人がそれをどう呼ぶかは勝手ですが、Pythonとかスクリプト言語の多くはたぶん参照値の概念がないので、参照値ありきの参照の値渡しを使うのは違和感あるなー・・・と。 (そもそもコンパイル言語的なメモリ領域のalias「変数」すらなくPythonの変数はただの名前空間のキー。なので参照型変数では変数に参照値が格納されている的な議論と噛み合わない)
KSwordOfHaste

2019/02/12 16:34

ここでの「参照渡し」とか「値渡し」という用語は特定の言語の用語というより、異なる複数の言語の間の機能の比較をする上でいわば仕様を表す用語として考えたくなる気がしたのです。たしか以前似た議論があったとき https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_reference こんな説明をみて参照渡しだとはいいにくいかなぁと感じたように思います。尤もそのあたりどんな用語が何を意味するかあまり議論しても・・・と思いますので、あくまで質問者さんの疑問が解消できてみなさんが違和感を持たなければよいと思います。ぶっちゃけ用語云々より「実際はどういう意味か」がちゃんとわかっていることの方が大事ですものね。
guest

0

ベストアンサー

「Pythonでは、代入するときはすべて参照渡しだ」

厳密に言えば、参照の値渡しです。「変数としては別だけどオブジェクトは共通」というものです。

イミュータブルなオブジェクトは代入するときに値渡し

そうかも知れませんが、それは実装の最適化でたまたまそうなっているだけ、とも解釈できます。

イミュータブルなオブジェクトであれば、参照を渡そうが値として渡そうが、どちらにしても破壊的変更は不可能なので、Pythonコード上からはどちらでも動作に差はありません

投稿2019/02/12 09:52

maisumakun

総合スコア145183

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

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

otn

2019/02/12 13:03

値を渡すつまりコピーが発生すれば別オブジェクトになるので、Pythonコード上で違いが出てくるケースがあるのでは?
KSwordOfHaste

2019/02/12 14:12 編集

maisumakunさん回答での「イミュータブルなオブジェクトの値渡し」はPythonの実装がどうこうではなく「言語仕様からいってどちらで実装しても同じ意味になるから気にしなくていいんじゃない?」という話だと解釈しました。実際にはコピーが渡されることは決してないと思います。それを意識する必要があるかどうかは個人的には「ある」です。callerが渡した引数インスタンスの参照をcalleeがどこかに覚えていたら必ずcallerにとって影響がでますよね。つまり引数に渡したインスタンスが解放されなくなる可能性が出てくるという意味で。それはimmutableかどうかとは独立した問題だと思います。 (otnさんコメントを拝見してそんなことを思いました)
hayataka2049

2019/02/12 14:34 編集

otnさんの指摘するような「Pythonコード上で違いが出てくるケース」と、実際の動作。 >>> a = 10 >>> (lambda x: x is a)(a) True 値渡しならFalseになります。 実際には、KSwordOfHasteさんの「実際にはコピーが渡されることは決してない」が正しいです。 >前提として、Python では引数は代入によって渡されます。 https://docs.python.jp/3/faq/programming.html#how-do-i-write-a-function-with-output-parameters-call-by-reference
guest

0

immutable なオブジェクトは変更しようとしたタイミングで別オブジェクトとして複製されるコピーオンライト戦略をとっています。

Wikipediaより引用

複製を要求されても、コピーをした振りをして、とりあえず原本をそのまま参照させるが、ただし、そのままで本当に書き換えてはまずい。原本またはコピーのどちらかを書き換えようとしたときに、それを検出し、その時点ではじめて新たな空き領域を探して割り当て、コピーを実行する。これが「書き換え時にコピーする」、すなわちコピーオンライト (Copy-On-Write) の基本的な形態である。

コピーオンライト

実際、以下のようにこの挙動を確認できます。
id() でオブジェクトの識別子が取得できるので(Cでいうオブジェクトのアドレスと思えばよい)
これで確認してみると、

python

1a = 1 # int は immutable 2print(id(a)) # 9145152 3b = a 4print(id(b)) # 9145152 この時点で a と指す実態は同じ 5b = 2 # immutable なオブジェクトを変更しようとすると.... 6print(id(b)) # 9145184 a とは別のオブジェクトとなる

変更するまでは a と b のオブジェクトは同じであることがわかります。

投稿2019/02/12 10:04

編集2019/02/12 10:30
tiitoi

総合スコア21956

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

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

hayataka2049

2019/02/12 10:12 編集

これはコピーオンライトとみなすべきなのでしょうか? b = 2 # immutable なオブジェクトを変更しようとすると.... のコメントが疑問点で、eval("2")相当のことをして結果をbという名前に束縛しているだけ、と捉えていましたが・・・
tiitoi

2019/02/12 10:31

言われてみたらコピーオンライトは関係ないですね。。 勘違いしてました。ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問