teratail header banner
teratail header banner
質問するログイン新規登録

回答編集履歴

3

「参照渡し」の表現を厳密化

2020/10/17 09:45

投稿

toast-uz
toast-uz

スコア3266

answer CHANGED
@@ -7,9 +7,9 @@
7
7
  ですので、「**コードの中のself.xは全て新規生成されたオブジェクト**」になっています。
8
8
  よって、以下のコードで、self.xとrefxとrefx2が別IDになります。細かく言うと、`print(type(self.x), id(self.x))`の中の2箇所のself.xも、別オブジェクトになっているはずです。
9
9
 
10
- ところが、以下のコードで**`refx3 = refx`ではオブジェクトの新規生成は行われません。よって単なる参照渡しがされるためIDは同一になります**。
10
+ ところが、以下のコードで**`refx3 = refx`ではオブジェクトの新規生成は行われません。よってそれぞれの変数に同一のオブジェクト代入されIDは同一になります**。
11
11
 
12
- わかりにくいのが、以下のコードの2番目の`print(type(self.x), id(self.x))`です。別オブジェクトのはずなのに、前と同じIDを示しています。これは、**同一の識別子では、Python環境によっては、オブジェクトのIDを使い回す可能性がある**ためです。性能改善やメモリ効率化のためだと思われます。この動作はPython環境依存です。質疑において、別の方とIDの出方(前と同じか異なるか)が食い違ったため、環境依存であるとわかりました。なお、**識別子が同じ場合に、インスタンスメソッドオブジェクト毎回新規作成されるのか、それとも以前のオブジェクトを参照渡しして使い回すのか、は厳密には謎**です。前者の方が汎用的ですが、後者の方が高速なのと、実装依存であることから、**後者の可能性もあります**。
12
+ わかりにくいのが、以下のコードの2番目の`print(type(self.x), id(self.x))`です。別オブジェクトのはずなのに、前と同じIDを示しています。これは、**同一の識別子の変数への代入では、Python環境によっては、オブジェクトのIDを使い回す可能性がある**ためです。性能改善やメモリ効率化のためだと思われます。この動作はPython環境依存です。質疑において、別の方とIDの出方(前と同じか異なるか)が食い違ったため、環境依存であるとわかりました。なお、**識別子が同じでIDを使い回す場合に、インスタンスメソッドオブジェクト毎回同じIDで新規作成されるのか、それとも以前のオブジェクトをい変数(識別子は同じ)に代入して使い回すのか、はたまた変数そのものも同一なのか、は厳密には謎**です。最初の方が汎用的ですが、実装依存で高速性を追求して、**2番目以降の可能性もあります**。
13
13
 
14
14
  ```Python
15
15
  class A():

2

間違った回答の修正

2020/10/17 09:45

投稿

toast-uz
toast-uz

スコア3266

answer CHANGED
@@ -1,44 +1,38 @@
1
1
  回答が間違っていましたので、解決済ですが修正します。
2
+ ・・・という回答も間違っていたので再度修正します。
3
+ @otn様の回答で合っているのですが、私がその回答の理解に手間取りました。参考になるかもしれませんので記載します。
2
4
 
3
- クラス内メソッドは、`<class 'method'>`という型なっています。いわゆる「クスメソッド」とは異る意味です。`<class 'method'>`は、selffunctionを内部に記録してい構造になっています。またfunctionを呼び出す際には第一引数としてselfを与えます。**単なるfunctionとは異なります**
5
+ まず@otn様回答ありますように、**関数オブジェトからインタンスメソッドオブジェクトが毎回生成される**ります。具体的に今回の場合には`class 'function'>であるA.x`と`class '__main__.A'>であるself`から`<class 'method'>であるself.x`が新規生成されます。以下のコードの実行でそれがわかります。
4
6
 
7
+ ですので、「**コードの中のself.xは全て新規生成されたオブジェクト**」になっています。
5
- この時、Pythonの動作として、質問者様あるような、`refx = self.x`という形**コピーする際に、オブジェクト参照渡しではなく新規にオブジェクトを生成し中身をコピーする動作をしているようです**ただし、下記コードにあるように、`refx2 = refx`ではしっかりとコピーされており、**オブジェクト固有の動作だけでは理由がわかりません**。
8
+ よって、以下コードで、self.xとrefxとrefx2が別IDになります。細かく言うと、`print(type(self.x), id(self.x))`の2箇所のself.xもオブジェクトになっているはずです。
6
9
 
7
- なお、下コードでは、別オブジェクトになってself.xとrefxにおいても、中身のselfとfuncは同じオブジェクトを指してお、selfはクラス定義外で作成したオブジェクトaを指している構造がわかります。
10
+ ところがコードで**`refx3 = refx`ではオブジェクトの新規生成は行われません。よって単な参照渡しがされるためIDは同一になります**
8
11
 
9
- いくつ動作をレポートしているやはあるのですが、**公式な理由は見当たりませんでした**。
12
+ りにくいが、以下のコドの2番目の`print(type(self.x), id(self.x))`です。別オブジェクのはずなのに、前と同じIDを示しています。これは、**同一の識別子では、Python環境によっては、オブジェクトのIDを使い回す可能性があ**ためです。性能改善メモリ効率化のためだ思われます。この動作Python環境依存です。質疑において、別の方とIDの出方(前と同じか異なるか)が食い違ったため、環境依存であるとわかりました。なお、**識別子が同じ場合に、インスタンスメソッドオブジェクトは毎回新規作成されるか、それとも以前のオブジェクトを参照渡しして使い回すのか、は厳密には謎**です。前者の方汎用的ですが後者の方が高速なのと、実装依存であることから、**後者の可能性もありま**。
10
13
 
11
- なお、ベストアンサーにあるドキュメントの記述「関数オブジェクトからインスタンスメソッドオブジェクトへの変換」が、このことを表しているかどうか、**確証が得られませんでした**。実行例からはself.xそのものは既に`<class 'method'>`になっておりfunctionではありません。`self.x.__func__`がfunctionです。よって、`refx = self.x`については、「関数オブジェクトからインスタンスメソッドオブジェクトへの変換」**ではありません**。
12
-
13
- 推測としては、refxという形式に変えることで、変数としてのselfを表す手段が無くなったため、selfの値を確定させる必要があり、新規のオブジェクトを生成しているものかと思います。
14
-
15
14
  ```Python
16
- class A:
15
+ class A():
17
16
  def __init__(self):
17
+ refx=self.x
18
- refx = self.x
18
+ refx2=self.x
19
+ print(type(A.x))
20
+ #<class 'function'>
21
+ print(type(self))
22
+ #<class '__main__.A'>
19
- print('id(self.x) =', id(self.x))
23
+ print(type(self.x), id(self.x))
20
- # id(self.x) = 140318265099392
24
+ #<class 'method'> 140270956725312
21
- print('id(refx) =', id(refx))
25
+ print(type(refx), id(refx))
22
- # id(refx) = 140318265058176
23
- print('type(self.x) =', type(self.x))
24
- # type(self.x) = <class 'method'>
26
+ #<class 'method'> 140270936531840
25
- print('type(refx) =', type(refx))
27
+ print(type(refx2), id(refx2))
26
- # type(refx) = <class 'method'>
28
+ #<class 'method'> 140270936573056
27
- print('id(self.x.__func__) =', id(self.x.__func__))
28
- # id(self.x.__func__) = 140318285919856
29
- print('id(refx.__func__) =', id(refx.__func__))
30
- # id(refx.__func__) = 140318285919856
31
- print('id(self.x.__self__) =', id(self.x.__self__))
32
- # id(self.x.__self__) = 140318285934896
33
- print('id(refx.__self__) =', id(refx.__self__))
34
- # id(refx.__self__) = 140318285934896
35
- refx2 = refx
29
+ refx3 = refx
36
- print('id(refx2) =', id(refx2))
30
+ print(type(refx3), id(refx3))
31
+ #<class 'method'> 140270936531840
37
- # id(refx2) = 140318265058176
32
+ print(type(self.x), id(self.x))
33
+ #<class 'method'> 140270956725312
38
- def x(self, num):
34
+ def x(self):
39
- return num + 1
35
+ print("x")
40
36
 
41
- a=A()
37
+ refa=A()
42
- print('id(a) =', id(a))
43
- # id(a) = 140318285934896
44
38
  ```

1

間違った回答の修正

2020/10/17 06:27

投稿

toast-uz
toast-uz

スコア3266

answer CHANGED
@@ -1,15 +1,44 @@
1
- 質問者様コードはx()というメソッドではなくxというオブジェクト型のメンバ変数を参照ています。
1
+ 回答が間違っていましたので、解決済ですが修正します。
2
2
 
3
- 以下うに修正すると、メソッドを参照できるように関数同じ扱いになっています。
3
+ クラス内メソッドは、`<class 'method'>`といなっていま。いわゆ「クラスメソッド」とは異る意味です。`<class 'method'>`はselffunctionを内部に記録してる構造になっています。またfunctionを呼び出す際には第一引数としてselfを与えます。**単なるfunctionとは異なります**。
4
4
 
5
+ この時、Pythonの動作として、質問者様の例にあるような、`refx = self.x`という形の**コピーする際に、オブジェクトの参照渡しではなく、新規にオブジェクトを生成して中身をコピーする動作をしているようです**。ただし、下記コードにあるように、`refx2 = refx`ではしっかりとコピーされており、**オブジェクト固有の動作だけでは理由がわかりません**。
5
6
 
7
+ なお、下記コードでは、別オブジェクトになっているself.xとrefxにおいても、中身のselfとfuncは同じオブジェクトを指しており、selfはクラス定義外で作成したオブジェクトaを指している構造がわかります。
8
+
9
+ いくつかこの動作をレポートしているやりとりはあるのですが、**公式な理由は見当たりませんでした**。
10
+
11
+ なお、ベストアンサーにあるドキュメントの記述「関数オブジェクトからインスタンスメソッドオブジェクトへの変換」が、このことを表しているかどうか、**確証が得られませんでした**。実行例からはself.xそのものは既に`<class 'method'>`になっておりfunctionではありません。`self.x.__func__`がfunctionです。よって、`refx = self.x`については、「関数オブジェクトからインスタンスメソッドオブジェクトへの変換」**ではありません**。
12
+
13
+ 推測としては、refxという形式に変えることで、変数としてのselfを表す手段が無くなったため、selfの値を確定させる必要があり、新規のオブジェクトを生成しているものかと思います。
14
+
6
15
  ```Python
7
- class A():
16
+ class A:
8
17
  def __init__(self):
9
- refx=self.x()
18
+ refx = self.x
10
- print(id(self.x()))
19
+ print('id(self.x) =', id(self.x))
20
+ # id(self.x) = 140318265099392
21
+ print('id(refx) =', id(refx))
22
+ # id(refx) = 140318265058176
23
+ print('type(self.x) =', type(self.x))
24
+ # type(self.x) = <class 'method'>
25
+ print('type(refx) =', type(refx))
26
+ # type(refx) = <class 'method'>
27
+ print('id(self.x.__func__) =', id(self.x.__func__))
28
+ # id(self.x.__func__) = 140318285919856
29
+ print('id(refx.__func__) =', id(refx.__func__))
30
+ # id(refx.__func__) = 140318285919856
31
+ print('id(self.x.__self__) =', id(self.x.__self__))
32
+ # id(self.x.__self__) = 140318285934896
33
+ print('id(refx.__self__) =', id(refx.__self__))
34
+ # id(refx.__self__) = 140318285934896
11
- print(id(refx))
35
+ refx2 = refx
36
+ print('id(refx2) =', id(refx2))
37
+ # id(refx2) = 140318265058176
12
- def x(self):
38
+ def x(self, num):
13
- print("x")
39
+ return num + 1
40
+
14
41
  a=A()
42
+ print('id(a) =', id(a))
43
+ # id(a) = 140318285934896
15
44
  ```