[Pythonクイズ]メソッドの呼び出し方、あれこれ そんな呼び出し方もできるのかな?:Pythonステップアップクイズ
メソッドって、オブジェクトにドットを付けて名前を書いて……というやり方以外にはたくさんのやり方で呼び出せます。どんなやり方があるのか、ちょっと考えてみませんか?
【問題】
以下はFooクラスを定義して、そのインスタンスを作成し、foo_methodメソッドを呼び出すコードである。選択肢1から選択肢4の中で例外を発生させることなく呼び出しが成功するのはどれだろう(複数選択可能)。
class Foo:
def foo_method(self, msg):
print(f'{msg} from Foo')
f = Foo()
# 選択肢1
f.foo_method('hello')
# 選択肢2
Foo.foo_method('hello')
# 選択肢3
getattr(Foo, 'foo_method')('hello')
# 選択肢4
Foo.__dict__['foo_method'](f, 'hello')
幾つあるかな?
上のコード例では幾つかの選択肢が例外を発生させます。そこで、今回はChatGPT(GPT 5.1)とGemini(Gemini 3 Proの思考モード)、それからClaude(Opus 4.5)の3つのLLMに例外を発生させないメソッド呼び出しのやり方を考えてもらい、一番多くのやり方を考えたLLMがエラいことにしました。どうなったかな?
どうもHPかわさきです。
先週はクイズを公開したよーってポストを完全に忘れてしまっていました。これ、実はXのパスワードを忘れていたというのが正解です。お仕事で甲府にいたのですが、パスワード管理をブラウザ任せにしていたために、ノートPCからXにログインできなかったのでした(普段使いのブラウザとこのアカウントを運用しているブラウザが違うんです)。こういうところが、いかにも自分だなあと……(笑)。次はもう少し気を付けます。
【答え】
選択肢1の「f.foo_method('hello')」と選択肢4の「Foo.__dict__['foo_method'](f, 'hello')」です。
選択肢1は通常のメソッド呼び出しで、選択肢4は実質的には「Foo.foo_method(f, 'hello')」呼び出しとなります。選択肢2と選択肢3は実質的にはFoo.foo_method関数を呼び出そうとしています。その場合はselfとmsgに2つの引数を渡す必要がありますが、'hello'しか渡していないのでTypeError例外が発生します。
【解説】
問題文のコードは次のようなものでした。
class Foo:
def foo_method(self, msg):
print(f'{msg} from Foo')
f = Foo()
# 選択肢1
f.foo_method('hello')
# 選択肢2
Foo.foo_method('hello')
# 選択肢3
getattr(Foo, 'foo_method')('hello')
# 選択肢4
Foo.__dict__['foo_method'](f, 'hello')
以下でちょっと深掘りしてみます。
バウンドメソッドと関数
Fooクラスにはfoo_methodというメソッドが定義されています。選択肢1は通常のメソッド呼び出しなので、もちろん、これは呼び出しに成功します。次の選択肢2「Foo.foo_method('hello')」について考えてみましょう。
これは「Fooクラスのfoo_methodという呼び出し可能な属性にアクセスしている」と見ることができます。つまり、Foo.foo_methodはメソッドではなくて、「Fooクラスの辞書(名前空間)に登録されているfoo_methodという属性(関数オブジェクト)」として扱われていると言い換えてもいいでしょう。
実際にそうなのかを試してみます。Fooクラスのインスタンスであるfの属性としてアクセスする「f.foo_method」とFooクラスの属性としてアクセスする「Foo.foo_method」に差があるのかを見てみましょう。
print(f.foo_method)
print(Foo.foo_method)
実行結果を以下に示します。
ご覧の通り、「f.foo_method」は「bound method」(バウンドメソッド)で、「Foo.foo_method」は「function」(関数)であることが分かります。ここでいう「バウンドメソッド」とは「特定のオブジェクトに結び付けられたメソッド」という意味で、ここではFooクラスのインスタンスであるfに結び付けられたメソッドであることを示しています(技術的な詳細はまだまだ深掘りできますが、ここでは「結び付けられた」と表現するだけで、それ以上のことは述べないことにしましょう)。
このことを表しているのが最初の出力の「Foo.foo_method of <__main__.Foo object at 0x10a4b2f90」というところです。これは「0x10a4b2f90にあるFooクラスのオブジェクトのFoo.foo_methodに結び付けられている」ことを示すものです。というわけで、fオブジェクトのIDを調べてみます。
print(hex(id(f)))
これを実行すると、結果は次の通り。「f.foo_method」の表示にあった「0x10a4b2f90」と同じものが出てきました。
f.foo_methodはfオブジェクトに結び付けられたバウンドメソッドであることが確認できました。バウンドメソッドを呼び出す際には、Pythonの処理系はselfとして、結び付けられているオブジェクトを自動的に渡してくれるようになっているのがミソです。つまり、「f.foo_method('hello')」とすると、foo_methodの第0パラメーターであるselfに、fオブジェクトが自動的に渡されるということです。
これがメソッド呼び出しに、そのオブジェクトを指定しなくても済む理由といえます。
これに対して、Foo.foo_methodは先ほどの出力結果からも分かるように関数です。バウンドメソッドではないので、Foo.foo_method関数を呼び出すときにはselfとmsgの2つのパラメーターに値を渡さなければいけません。しかし、選択肢2は「Foo.foo_method('hello')」と1つしか引数を渡していません。そのため、例外が発生したというわけです。
属性にアクセスする幾つかの方法
f.foo_methodはバウンドメソッドで、Foo.foo_methodは関数であることは分かりました。そして、その違いがあるために選択肢1は問題なく呼び出せて、選択肢2は例外を発生させることも分かりました。
では選択肢3と選択肢4はどうでしょう。
# 選択肢3
getattr(Foo, 'foo_method')('hello')
# 選択肢4
Foo.__dict__['foo_method'](f, 'hello')
選択肢3と選択肢4の違いは前者はgetattr関数を、後者はFoo.__dict__属性を使っている点です。ここでFoo.__dict__属性はこのクラスの属性を保存する辞書的なオブジェクトです。
そして、これら2つはいずれもFooクラスから'foo_method'属性の値を取り出すために使われています。'foo_method'属性の値はもちろん呼び出し可能なオブジェクトなので、さらにかっこを付けて引数を渡して、実際に呼び出しているというのが上のコードの説明です。分かりやすく書き直せば次のようになります。
# 選択肢3
f0 = getattr(Foo, 'foo_method')
f0('hello')
# 選択肢4
f1 = Foo.__dict__['foo_method']
f1(f, 'hello')
ここで重要なのは、いずれの方法もFooクラスの属性としてfoo_methodを取り出している点です。特にgetattr関数にクラスを渡した場合には、クラスから属性を取り出すため、選択肢2と同様にバウンドメソッドではなく「単なる関数」が返ってきます。そのため、呼び出しには2つの引数が必要になります(getattr関数にクラスではなく、そのインスタンスを渡した場合については後述します)。選択肢4の__dict__辞書を使った場合も同様に、辞書から'foo_method'の値である関数オブジェクトを直接取得することになり、こちらも呼び出しには2つの引数が必要になります。
この辺りのお話を詳しく書こうとするとディスクリプタが云々となって、深掘りし過ぎると危険な(上に筆者の知識も追いつかない)ので、とてもザックリとしたものになっていますので、よろしくご了承いただければと思います(笑)。
このように、選択肢3と選択肢4では、どちらも関数を取り出していて、呼び出しには2つの引数が必要です。このとき、選択肢3では1つの引数しか渡していないために例外が発生し、選択肢4では引数を2つ渡しているので問題なく呼び出せるということです。
最後に、getattr関数にFooクラスではなく、そのオブジェクトであるfを渡した場合について見てみましょう(選択肢にはないですが)。
m = getattr(f, 'foo_method')
print(m)
m('hello')
実行結果を以下に示します。
実はこのときにはバウンドメソッドが得られます。ということは、selfには自動的に元のオブジェクトが渡されるので、得られた呼び出し可能オブジェクトに引数を1つだけ渡すだけで呼び出せます。
選択肢3が「getattr(Foo, 'foo_method')(f, 'hello')」や「getattr(f, 'foo_method')('hello')」であったなら例外を発生させずに呼び出しが可能だったのですが、ここではピンポイントでダメな場合が選択肢になっていたというわけですね。
では、3つのLLMはどのように答えてくれたでしょう。以下は自己申告でのやり方の種類やパターンです。
- ChatGPT:無限にあるけど、代表的なものは5種類
- Gemini:主に4つのパターン
- Claude:だいたい12パターン
筆者の尋ね方が悪かったのもありますが、皆さん、たくさんのやり方を挙げてくれました。ここまで登場していないやり方としては以下のようなものが挙がっています。
- operator.methodcallerを使う
- ディスクリプタを使う
- types.MethodTypeを使う
- vars関数を使う
- __getattribute__、__func__を使う
- type関数を使う
- etc
下の方には「確かに可能だけど、ChatGPTやGeminiはあえて挙げなかったのでは?」と思うようなものもありますが、まあ、今回はClaudeさんが一番エラかったということにしましょう。おめでとうございます。めでたいかは知らんけど(笑)。
Copyright© Digital Advantage Corp. All Rights Reserved.




