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

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

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

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

Q&A

解決済

3回答

6402閲覧

pythonでクラス内で関数ポインタのようなことをしたい

am111

総合スコア1

Python

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

1グッド

3クリップ

投稿2020/10/16 16:24

pythonで関数ポインタのようなことをしたいのですが
ただの関数であれば正しく参照されるのですが
クラスのインスタンスメソッドになると正しく参照されないです。
何故こうなるのか教えてほしいです。

ただの関数の場合、出力結果は同じになりました。

python

1def b(): 2 print('b') 3 4refb = b 5print(id(b)) 6print(id(refb))

インスタンスメソッドの場合、出力結果が異なります。

python

1class A(): 2 def __init__(self): 3 refx=self.x 4 print(id(self.x)) 5 print(id(refx)) 6 def x(self): 7 print("x") 8a=A()

バージョン:python3.6.1
よろしくお願いします。

LouiS0616👍を押しています

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

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

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

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

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

guest

回答3

0

ベストアンサー

リファレンスで下記のような記述を見つけました。
3. データモデル — Python 3.9.0 ドキュメント の「インスタンスメソッド」の項。

なお、関数オブジェクトからインスタンスメソッドオブジェクトへの変換は、インスタンスから属性が取り出されるたびに行われます。場合によっては、属性をローカル変数に代入しておき、そのローカル変数を呼び出すようにするのが効果的な最適化になります。

この項全体を読むのが良いと思いますが、self.xという属性参照する度に新たに「インスタンスメソッドオブジェクト」が生成されるようです。

投稿2020/10/16 22:38

編集2020/10/16 23:50
otn

総合スコア85901

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

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

am111

2020/10/17 01:56

回答ありがとうございます。毎回別のインスタンスメソッドオブジェクトが作られてそれを参照しているんですね。 何故こうなるのか理解できました。ありがとうございます。
guest

0

回答が間違っていましたので、解決済ですが修正します。
・・・という回答も間違っていたので再度修正します。
@otn様の回答で合っているのですが、私がその回答の理解に手間取りました。参考になるかもしれませんので記載します。

まず@otn様の回答にありますように、「関数オブジェクトからインスタンスメソッドオブジェクトが毎回生成される」ことになります。具体的に今回の場合にはclass 'function'>であるA.xclass '__main__.A'>であるselfから<class 'method'>であるself.xが新規生成されます。以下のコードの実行でそれがわかります。

ですので、「コードの中のself.xは全て新規生成されたオブジェクト」になっています。
よって、以下のコードで、self.xとrefxとrefx2が別IDになります。細かく言うと、print(type(self.x), id(self.x))の中の2箇所のself.xも、別オブジェクトになっているはずです。

ところが、以下のコードで**refx3 = refxではオブジェクトの新規生成は行われません。よってそれぞれの変数に同一のオブジェクトが代入され、IDは同一になります**。

わかりにくいのが、以下のコードの2番目のprint(type(self.x), id(self.x))です。別オブジェクトのはずなのに、前と同じIDを示しています。これは、同一の識別子の変数への代入では、Python環境によっては、オブジェクトのIDを使い回す可能性があるためです。性能改善やメモリ効率化のためだと思われます。この動作はPython環境依存です。質疑において、別の方とIDの出方(前と同じか異なるか)が食い違ったため、環境依存であるとわかりました。なお、識別子が同じでIDを使い回す場合に、インスタンスメソッドオブジェクトが毎回同じIDで新規作成されるのか、それとも以前のオブジェクトを新しい変数(識別子は同じ)に代入して使い回すのか、はたまた変数そのものも同一なのか、は厳密には謎です。最初の方が汎用的ですが、実装依存で高速性を追求して、2番目以降の可能性もあります

Python

1class A(): 2 def __init__(self): 3 refx=self.x 4 refx2=self.x 5 print(type(A.x)) 6 #<class 'function'> 7 print(type(self)) 8 #<class '__main__.A'> 9 print(type(self.x), id(self.x)) 10 #<class 'method'> 140270956725312 11 print(type(refx), id(refx)) 12 #<class 'method'> 140270936531840 13 print(type(refx2), id(refx2)) 14 #<class 'method'> 140270936573056 15 refx3 = refx 16 print(type(refx3), id(refx3)) 17 #<class 'method'> 140270936531840 18 print(type(self.x), id(self.x)) 19 #<class 'method'> 140270956725312 20 def x(self): 21 print("x") 22 23refa=A()

投稿2020/10/16 16:43

編集2020/10/17 09:45
toast-uz

総合スコア3266

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

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

TakaiY

2020/10/16 17:35

そのように'self.x()'としてしまうと関数を呼び出した結果を指してしまうので、メソッドのオブジェクトを取りたいという意図には合わないと思います。
LouiS0616

2020/10/17 03:34

> コピーする際に、オブジェクトの参照渡しではなく、新規にオブジェクトを生成して中身をコピーする動作をしているようです。ただし、下記コードにあるように、refx2 = refxではしっかりとコピーされており、オブジェクト固有の動作だけでは理由がわかりません。 字面どおりに受け取ると『代入時の挙動が異なる』との主張に見えますが、正しいでしょうか。
otn

2020/10/17 03:42

> 実行例からはself.xそのものは既に<class 'method'>になっておりfunctionではありません。 > 関数オブジェクトからインスタンスメソッドオブジェクトへの変換 の文に於ける、「関数オブジェクト」はx自体(A.x)で、それとselfから「インスタンスメソッドオブジェクト」self.xが作られるということでしょう。
toast-uz

2020/10/17 03:57 編集

> 字面どおりに受け取ると『代入時の挙動が異なる』との主張に見えますが、正しいでしょうか。 実行結果からはそう見えます。厳密に言うと、同じ<class 'method'>間のコピーでも、その表現(バインドされているクラスオブジェクトを明示しているかどうか)で新規作成と参照渡しが切り替わっているように見えます。 クラスの外で f1 = a.x f2 = a.x f3 = f1 として、a.x、f1、f2、f3のtypeとidを調べても、同様の関係になっています。 (f3のみがf1の参照になっており、a.xとf1とf2はそれぞれ異なる) > 「関数オブジェクト」はx自体(A.x)で、それとselfから「インスタンスメソッドオブジェクト」self.xが作られるということでしょう そうすると、インスタンスメソッドオブジェクトであるself.xからのコピーである、refx = self.x でオブジェクトが新規に作られることが、説明つかないんですよね。 みなさま、これを説明できる何かがありましたら、お知らせください。
LouiS0616

2020/10/17 03:57 編集

for _ in range(10): print(id(self.x)) の出力が毎度異なることから考えても、 新たなオブジェクトが作られるのは 代入時 ではなく 属性の取得時 です。
toast-uz

2020/10/17 04:02

私の環境で__init__に上記のfor文入れましたら、全て同じidを出力しています。なぜでしょう?
otn

2020/10/17 04:03

> そうすると、インスタンスメソッドオブジェクトであるself.xからのコピーである、refx = self.x でオブジェクトが新規に作られることが、説明つかないんですよね。 refa = A() print(id(refa)) print(id(A())) で、ちがうIDが表示されるのは分かりますか?それと同じです。
LouiS0616

2020/10/17 04:35 編集

@toast-uz さん 変数に保持するかしないかで『新たなオブジェクトを生成するか否か』の判断に違いが出ることは考えられます。どちらにせよ代入時にコピーしたりしなかったりの決定はされません。 どんな環境か分かりませんが、 for _ in range(10): m = self.x; print(id(m)) とかも試してみると良いかも。
toast-uz

2020/10/17 04:49

for _ in range(10): m = self.x; print(id(m)) は面白いですね。2種類のIDが交互交互に出ます。 基本的にはmはselfを消した形をしているので、私の解釈だと「新たなオブジェクトを生成」するパターンなのですが、mそのものを使いまわしているので、古くなったidが再利用されているように見えます。 @LouiS0616 さんの環境ではどう出るのでしょうか?
toast-uz

2020/10/17 05:01

@otn さん > ちがうIDが表示されるのは分かりますか?それと同じです すみません。ちがうIDが表示されるのは分かりますが、どう「それと同じ」なのかわかりません。これは、refaというインスタンスと、A()という(代入されていない)インスタンスは別のものである、という理解ができます。refa = A()は単なる代入ではなく、クラスをインスタンスとして実体化することと、代入の組み合わせだからだと思います。これが、今回の話と同じ、として理解できておりません。もう少しご説明いただけますでしょうか?
otn

2020/10/17 05:06

self.x でも新たにオブジェクトが生成されて返されます。それが代入されます。
toast-uz

2020/10/17 05:39

なんとなく理解してきました。以下のような解釈でよいでしょうか? 関数A.xとselfからメソッドオブジェクトself.xが新規作成される。これは公式記載通り。 refx = self.x は、新規作成されたself.xをrefxに代入する。 print(id(self.x)) もself.xを新規作成している。なので、id(refx)とid(self.x)が異なるのは当たり前。 refx2 = refx では新規オブジェクトは作成されない。なので、id(refx)とid(refx2)が同じなのは当たり前。 同じ識別子を使い回す時に、新規作成したオブジェクトのIDが使いまわされる(新規作成は前提でIDの使いまわしだけ)ことがあり、それはPythonの実装に依存する。なので、何度もid(self.x)をprintしたときに、値が同じになったりすることはありうる。逆に識別子が異なれば新規作成したオブジェクトのIDは必ず異なる。よって、質問コードでのid(refx)とid(self.x)は必ず異なる。
am111

2020/10/17 07:54

回答・修正・再度修正ありがとうございます。 大変詳しく纏めていただき、非常に理解が深まりました。 ありがとうございます。
LouiS0616

2020/10/17 08:26

『idが変わらない代入』のことを『参照渡し』と呼称していることがもやっとしますが、大筋では説得力のある回答に修正されたように思います。
toast-uz

2020/10/17 08:34

ありがとうございます! 参照渡しと呼称したところは2箇所あり、モヤッとしたのはどちらでしょうか?
LouiS0616

2020/10/17 08:51

両方です。 ① 代入のことを『〇〇渡し』と呼ぶことはありません。関数の引数の受け渡し方を示す言葉です。 ② 参照渡しは話者に依って使い方が異なりますが、少なくとも狭義の参照渡しはPythonにはありません。公式ドキュメントにも存在しない旨書かれています。https://docs.python.org/ja/3/faq/programming.html#how-do-i-write-a-function-with-output-parameters-call-by-reference 個人の意見ですが、広義の参照渡し(引数評価時点で参照先のオブジェクトが同じ)は単に誤用だと考えており、『参照渡し』と呼ぶことは推奨しません。 狭義の参照渡しが存在する言語は決して多くないです。(有名どころだとC++, C#, PHP, VBA など) いくつかリンクを添付します。 かなりボリューミーですが、このあたりの議論について関心があるようでしたら読んでみて下さい。 ・ https://qiita.com/raccy/items/59a6ac6c818918dd9651https://qiita.com/mdstoy/items/2ef4ada6f88341466783https://qiita.com/Yametaro/items/21f7b1a4ae09f16f323ehttps://teratail.com/questions/84660
toast-uz

2020/10/17 08:56

丁寧に教えて頂き、ありがとうございます!
guest

0

なぜ、id()の結果が異なるのかは不明ですが、ref() とすることで、self.x()を実行したのと同じ結果が得られるので、refにはメソッドのオブジェクトがちゃんと入ってることがわかります。

投稿2020/10/16 17:38

TakaiY

総合スコア13790

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問