32. メソッド呼び出し
#32. メソッド呼び出し
メソッド呼び出しは、通常は属性アクセスで始まる関数呼び出しです。ほとんどのオブジェクトの動作はメソッドを通じて公開されるため、これらは Python のオブジェクト モデルの中心となります。
次のようなメソッド呼び出し:python id="l3owku" obj.method(arg) 1 つの操作のように見えますが、CPython はいくつかの概念的なステップを実行します。```text id="j39rdj"
load obj
look up attribute method
bind obj if needed
load arg
call the resolved callable
return result or raise exception
## 32.1 メソッド呼び出しは属性アクセスと呼び出しである
ソース式:```python id="xgyvgk"
obj.method(10)
```は次のように理解できます。```python id="gg912h"
tmp = obj.method
tmp(10)
```これは正しいセマンティック モデルです。属性の検索が最初に行われます。その後、その検索の結果が呼び出されます。
その結果は次のようになります。```text id="mfp3rd"
a bound method
a function
a built-in method
a callable object
a descriptor result
a property result
any object returned by __getattribute__
```呼び出しは、解決された値が呼び出し可能な場合にのみ成功します。
例:```python id="hsu4g1"
class C:
value = 10
obj = C()
obj.value()
```これは失敗します。`obj.value`に解決します`10`、整数は呼び出すことができません。
## 32.2 クラスに格納されたプレーン関数
クラス内で定義された関数は記述子になります。```python id="ys9rpg"
class User:
def greet(self, name):
return "hello " + name
```クラス ディクショナリには関数オブジェクトが含まれています。```python id="vcrq8f"
print(User.__dict__["greet"])
```インスタンス経由でアクセスした場合:```python id="moa6m1"
u = User()
u.greet
```関数の記述子の動作はインスタンスを次のようにバインドします。`self`。
概念的には:```text id="ho345m"
User.__dict__["greet"].__get__(u, User)
-> bound method
```バインドされたメソッドには以下が格納されます。```text id="3tilp4"
function object
self object
```電話をかける:```python id="tnkuwp"
u.greet("Ada")
```概念的には以下と同等です。```python id="zdsl7s"
User.greet(u, "Ada")
```自動`self`引数は、関数呼び出しと通常のインスタンス メソッド呼び出しの主な違いです。
## 32.3 バインドされたメソッド
バインドされたメソッドは、関数をオブジェクトとともにパッケージ化します。```python id="xz4yfy"
class C:
def f(self, x):
return x + 1
obj = C()
m = obj.f
print(m(10))
```ここ、`m`バインドされたメソッドです。それは覚えています`obj`。
概念的には:```text id="vqipye"
bound method
__func__ -> C.f
__self__ -> obj
```これを検査できます:```python id="e0hgzo"
print(m.__func__)
print(m.__self__)
```いつ`m(10)`が呼び出されると、CPython は基になる関数を呼び出します。`obj`最初の引数として挿入されます。```python id="9rt8ja"
m.__func__(m.__self__, 10)
```## 32.4 クラスを介した非バインド関数アクセス
クラスを通じてメソッドにアクセスすると、同じ方法でインスタンスをバインドしない関数のようなオブジェクトが得られます。```python id="kyy416"
class C:
def f(self, x):
return x + 1
obj = C()
print(C.f(obj, 10))
```ここ、`obj`明示的に渡されます。
クラスのアクセス:```python id="0trmyu"
C.f
```特定のインスタンスをバインドしません`self`。
概念的には:```text id="xgfyue"
C.__dict__["f"].__get__(None, C)
-> function object or descriptor result suitable for class access
```したがって、これらは通常のインスタンス メソッドと同等です。```python id="1yhu3p"
obj.f(10)
C.f(obj, 10)
```最初の形式はインスタンスのバインドを実行します。 2 番目の形式では、インスタンスを明示的に渡します。
## 32.5 記述子プロトコル
メソッド バインディングは記述子プロトコルの一部です。
記述子は、次のメソッドを 1 つ以上持つオブジェクトです。```text id="02rkfq"
__get__
__set__
__delete__
```関数は、定義するため、非データ記述子です。`__get__`。
CPython が評価するとき:```python id="8f0fmh"
obj.method
```クラス内で関数オブジェクトを見つけると、関数の記述子ロジックを呼び出します。
概念的には:```python id="q89qnh"
method = function.__get__(obj, type(obj))
```これにより、バインドされたメソッドが生成されます。
記述子プロトコルは次の機能も提供します。```text id="7keg5l"
methods
staticmethod
classmethod
property
slots
many built-in attributes
ORM fields
cached attributes
validation descriptors
```メソッド呼び出しは、記述子なしでは完全に理解できません。
## 32.6 データ記述子と非データ記述子
記述子には、大きく 2 つのカテゴリがあります。
|記述子の種類 |定義 |優先順位 |
|---|---|---|
|データ記述子 |`__get__`そして`__set__`または`__delete__`|インスタンス辞書より上位 |
|非データ記述子 |通常のみ`__get__`|インスタンス辞書より低い |
クラスに格納される通常の関数は非データ記述子です。
これは、インスタンス属性がメソッドをシャドウできることを意味します。```python id="8vtwg1"
class C:
def f(self):
return "method"
obj = C()
obj.f = lambda: "instance function"
print(obj.f())
```インスタンス辞書には以下が含まれます`f`、そのため、クラス内の非データ記述子よりも優先されます。
プロパティはデータ記述子であるため、インスタンス ディクショナリよりも優先されます。```python id="tfs80s"
class C:
@property
def x(self):
return 10
```ルックアップ ルールは、最終的にどのオブジェクトが呼び出されるかに影響します。
## 32.7 メソッドの属性検索順序
一般的なインスタンスの属性アクセスの場合:```python id="ebj6os"
obj.name
```CPython は、大まかに次のような検索プロセスに従います。```text id="r7zrsx"
1. Determine type(obj)
2. Look for name in type and base classes
3. If a data descriptor is found, call its __get__
4. Otherwise, look in obj.__dict__ if present
5. If found in instance dictionary, return it
6. Otherwise, if a non-data descriptor was found, call its __get__
7. Otherwise, return the class attribute
8. Otherwise, call __getattr__ if defined
9. Otherwise, raise AttributeError
```メソッド呼び出しはこの検索に依存します。メソッドは名前だけで選択されるわけではありません。これは、完全な属性検索プロトコルによって選択されます。
## 32.8 メソッド呼び出しバイトコード
対象:```python id="fwr5qr"
def call(obj, x):
return obj.method(x)
```概念的なバイトコードは次のようになります。```text id="yqk6u7"
LOAD_FAST obj
LOAD_METHOD method
LOAD_FAST x
CALL 1
RETURN_VALUE
```正確な手順は Python のバージョンによって異なりますが、最新の CPython では、一般的なケースではメソッドの読み込みと汎用属性の読み込みが区別されます。
目標は、このパターンを最適化することです。```python id="2kv28s"
obj.method(arg)
```セマンティックな結果は変更されません。
## 32.9 なぜ`LOAD_METHOD`存在する
単純なメソッド呼び出しでは次のようになります。```text id="dg1ri1"
LOAD_FAST obj
LOAD_ATTR method
LOAD_FAST arg
CALL 1
```もし`method`はクラスの通常の関数です。`LOAD_ATTR`バインドされたメソッド オブジェクトを作成します。
それから`CALL`すぐにそれを呼び出します。
一時的にバインドされたメソッドの割り当ては、多くの場合不要です。
最適化されたパスでは代わりに以下をロードできます。```text id="eeqqg9"
underlying function
self object
```そして関数を直接呼び出します`self`挿入されました。
概念的には:```text id="q3brdt"
obj.method(arg)
optimized:
function = C.__dict__["method"]
self = obj
call function(self, arg)
```これにより、一般的な即時呼び出しの場合にバインドされたメソッド オブジェクトを作成することが回避されます。
## 32.10 バインドされたメソッド割り当ての回避
ループを考えてみましょう。```python id="v4zxb6"
for item in items:
obj.process(item)
```単純な実装では、反復ごとに 1 つのバインドされたメソッドを割り当てることができます。```text id="th33f3"
obj.process -> new bound method
call bound method
discard bound method
```これにより、不必要な割り当てと参照カウントのトラフィックが発生します。
CPython のメソッド呼び出しの最適化により、一般的なケースではこれが回避されます。
セマンティック モデルは残ります。```python id="vxkr9d"
tmp = obj.process
tmp(item)
```ただし、実装では具体化がスキップされる可能性があります`tmp`メソッドがすぐに呼び出されるときは、ヒープ オブジェクトとして保存されます。
## 32.11 バインドされたメソッドがまだ作成されている場合
メソッド アクセス自体が結果である場合でも、バインドされたメソッド オブジェクトが作成されます。
例:```python id="vnqqc0"
m = obj.method
```ここで、Python コードは属性値を要求します。 CPython はバインドされたメソッド オブジェクトを生成する必要があります。これは、プログラムがそれを保存したり、検査したり、渡したり、後で呼び出したりする可能性があるためです。```python id="ipgvev"
callbacks.append(obj.method)
```この場合、バインドされたメソッド オブジェクトは、Python で表示される正しい結果です。
最適化は主に、バインドされたメソッドをエスケープする必要がない即時呼び出しパターンに適用されます。
## 32.12 組み込みメソッド
組み込み型は、C で実装された多くのメソッドを公開します。```python id="dkd1sx"
xs = []
xs.append(1)
```リスト方式`append`Cで実装されています。
組み込みメソッドの呼び出しには以下が含まれます。```text id="8n6a77"
method lookup
binding to list object
argument setup
C method call
mutation of list object
return None
```バインドされたオブジェクトは概念的にはまだ存在しますが、実装は高度に最適化できます。
リストの追加の場合、操作により基になるリスト構造が C で直接変更されます。
## 32.13 メソッド記述子
組み込みメソッドは、通常の Python 関数オブジェクトではなく、記述子オブジェクトによって表されることがよくあります。
例:```python id="sfrzai"
print(list.__dict__["append"])
```これは通常の Python 関数ではありません。これは組み込みのメソッド記述子です。
インスタンス経由でアクセスした場合:```python id="20nfof"
[].append
```そのリストにバインドされた組み込みメソッド オブジェクトを生成します。
すぐに呼び出された場合:```python id="n6aps6"
[].append(1)
```CPython は特殊なネイティブ パスをたどることができます。
##32.14`staticmethod`
`staticmethod`インスタンスのバインドを無効にします。```python id="i095gy"
class Math:
@staticmethod
def add(a, b):
return a + b
Math.add(2, 3)
Math().add(2, 3)
```どちらの場合も、いいえ`self`が挿入されます。
記述子は、インスタンスをバインドせずに基になる関数を返します。
概念的には:```text id="1s9dk7"
staticmethod.__get__(obj, cls)
-> original function
```したがって、これは機能します:```python id="opmkar"
Math().add(2, 3)
```これは、関数が 3 つではなく 2 つの引数を受け取るためです。
##32.15`classmethod`
`classmethod`インスタンスではなくクラスをバインドします。```python id="dlj84i"
class C:
@classmethod
def make(cls, value):
return cls(value)
```電話をかける:```python id="g1ck47"
C.make(10)
```パスする`C`最初の引数として。
インスタンスを介した呼び出し:```python id="frurij"
obj = C()
obj.make(10)
```通常はクラスに合格します`C`、 ない`obj`。
概念的には:```text id="2cfzmr"
classmethod.__get__(obj, cls)
-> bound method with cls as first argument
```これが、クラス メソッドが代替コンストラクターやポリモーフィックな構築に役立つ理由です。
##32.16`property`およびメソッドのようなアクセス
プロパティは、メソッドのロジックを属性アクセスに変換します。```python id="b2eu4v"
class C:
@property
def value(self):
return 42
obj = C()
print(obj.value)
```これは呼びません`obj.value()`。
呼び出しは属性アクセス中に発生します。```text id="m2zdkj"
obj.value
property.__get__(obj, C)
calls getter function
returns result
```プロパティが呼び出し可能オブジェクトを返した場合、後で呼び出しが発生する可能性があります。```python id="ylxm9w"
obj.factory()
```もし`factory`はプロパティです。これは次のことを意味します。```text id="95d6z5"
call property getter
call returned object
```したがって、1 つのソースレベルの呼び出しには、明示的な呼び出しの前に隠された記述子の呼び出しが含まれる可能性があります。
##32.17`__getattribute__`通常の属性検索はすべて実行されます。`__getattribute__`。```python id="e8vyww"
class C:
def __getattribute__(self, name):
print("lookup", name)
return super().__getattribute__(name)
def f(self):
return 1
obj = C()
obj.f()
```表現`obj.f()`最初の電話`obj.__getattribute__("f")`。
これは、メソッド呼び出しが傍受される可能性があることを意味します。
カスタム`__getattribute__`できる:```text id="xczvn9"
return a normal bound method
return a different callable
return a non-callable
raise AttributeError
log access
implement proxies
implement lazy loading
```呼び出し機構は、元のソースの意図を知りません。これは、属性検索で返されたものをすべて呼び出します。
##32.18`__getattr__`
`__getattr__`通常の検索が失敗した後にのみ呼び出されます。```python id="zewpol"
class Dynamic:
def __getattr__(self, name):
if name == "run":
return lambda: "dynamic"
raise AttributeError(name)
obj = Dynamic()
print(obj.run())
```ここ、`run`インスタンスまたはクラスに存在しません。`__getattr__`呼び出し可能なものを返します。次に、呼び出しにより、返された callable が呼び出されます。
これは次の場合によく見られます。```text id="xud82d"
proxies
RPC clients
ORM models
mock objects
lazy APIs
dynamic wrappers
```これは、メソッド呼び出しを実行時に動的に解決できることも意味します。
## 32.19 モジュールのメソッド呼び出し
モジュールは、モジュールレベルでの動的な属性アクセスもサポートできます。`__getattr__`。```python id="m9w5tu"
# module.py
def __getattr__(name):
if name == "run":
return lambda: 42
raise AttributeError(name)
```それから:```python id="pkzy1x"
import module
module.run()
```動的に解決できます。
ルックアップ パスはインスタンス メソッド バインディングとは異なりますが、ソース パターンは依然として属性アクセスとそれに続く呼び出しです。
## 32.20 メソッド解決の順序
クラス インスタンスの場合、メソッド検索では、メソッド解決順序を使用してクラスとその基本クラスが検索されます。```python id="pvvfui"
class A:
def f(self):
return "A"
class B(A):
pass
obj = B()
print(obj.f())
```ルックアップ検索`B`、 それから`A`。
多重継承の場合:```python id="f6sw15"
class A:
def f(self):
return "A"
class B:
def f(self):
return "B"
class C(A, B):
pass
```選択される方法は次によって異なります。`C.__mro__`。```python id="ig5h2o"
print(C.__mro__)
```MRO は、クラス属性の検索で記述子が見つかる場所を決定するため、メソッド呼び出しの中心となります。
##32.21`super()`メソッド呼び出し`super()`MRO でメソッド検索が開始される場所が変更されます。```python id="ujmrwo"
class Base:
def f(self):
return 1
class Child(Base):
def f(self):
return super().f() + 1
```電話の内容:```python id="w4m10h"
super().f()
```「親クラスを名前で呼び出す」という意味ではありません。これは、現在のインスタンスにバインドして、現在のクラスの後の MRO を検索することを意味します。
概念的には:```text id="nlfcwi"
current class = Child
instance = self
MRO = [Child, Base, object]
search after Child
find Base.f
bind to self
call
```結果は、同じインスタンスを使用するバインドされたメソッドになります。
## 32.22 メソッドと継承
メソッド呼び出しでは、基本クラスから継承されたメソッドを使用できます。```python id="xtljqg"
class Base:
def save(self):
return "saved"
class User(Base):
pass
u = User()
u.save()
```ルックアップで見つかるのは、`save`で`Base`、それをバインドします`u`。
概念的には:```text id="e2s1rr"
find Base.__dict__["save"]
bind with self = u
call Base.save(u)
```関数の定義クラスとインスタンスの実際のクラスは異なる場合があります。これは正常です。
## 32.23 メソッドのオーバーライド
サブクラス メソッドは基本メソッドをオーバーライドします。```python id="3eakv8"
class Base:
def f(self):
return "base"
class Child(Base):
def f(self):
return "child"
print(Child().f())
```検索結果`Child.f`前に`Base.f`。
これは実行時ルックアップです。呼び出しは変数の型によって静的にバインドされません。```python id="43x5xb"
def call_f(obj):
return obj.f()
```選択される方法は以下によって異なります`type(obj)`実行時。
## 32.24 多態性メソッド呼び出し
Python メソッド呼び出しは動的にディスパッチされます。```python id="uwb85l"
def speak(animal):
return animal.speak()
```オブジェクトが異なれば、異なる実装を提供できます。```python id="fcv0ik"
class Dog:
def speak(self):
return "woof"
class Cat:
def speak(self):
return "meow"
```同じバイトコードでも、ランタイム オブジェクトに応じて異なるメソッドを呼び出すことができます。
概念的には:```text id="cje20f"
LOAD_FAST animal
LOAD_METHOD speak
CALL 0
```実際のターゲットは実行中に検出されます。
この動的なディスパッチは柔軟ですが、最適化の課題が生じます。
## 32.25 モノモーフィックおよびポリモーフィック呼び出しサイト
メソッド呼び出しサイトが通常 1 つのオブジェクト タイプを認識する場合、そのメソッド呼び出しサイトは単相的です。```python id="j7bxa8"
for user in users:
user.validate()
```すべての場合`user`の型が同じである場合、呼び出しサイトは単相的です。
呼び出しサイトに複数のタイプがある場合、その呼び出しサイトは多態性です。```python id="kw1tz2"
for shape in shapes:
shape.area()
```どこ`shape`多分`Circle`、`Square`、 または`Triangle`。
インライン キャッシュは、呼び出しサイトが安定している場合に最適に機能します。モノモーフィック呼び出しサイトは、型とメソッドの検索情報をより効果的にキャッシュできます。
## 32.26 メソッド呼び出しのインラインキャッシュ
CPython は、バイトコード命令の近くにメソッド ルックアップ情報をキャッシュできます。
メソッド キャッシュには次のような事実が記録される場合があります。```text id="cdvqx9"
expected receiver type
type version tag
resolved descriptor
method object or function pointer
offset or lookup result
call shape
```次回の実行時:```text id="9o02ps"
if receiver type still matches
and type version is unchanged
use cached method path
else
fall back to generic lookup
```これにより、セマンティクスを変更せずに繰り返しの呼び出しが高速化されます。
クラスが変更されると、バージョン タグまたはキャッシュ ガードによって高速パスが無効になります。
## 32.27 クラスの変更とキャッシュの無効化
Python では、実行時にクラスを変更できます。```python id="jtw9f0"
class C:
def f(self):
return 1
obj = C()
print(obj.f())
def new_f(self):
return 2
C.f = new_f
print(obj.f())
```2 番目の呼び出しでは新しいメソッドを使用する必要があります。
したがって、古い方式を前提としたキャッシュはすべて無効化または保護する必要があります。
安全ルール:```text id="89t5m7"
fast method path is valid only while class and lookup assumptions remain true
```動的クラスの突然変異は、CPython 最適化でガードが使用される理由の 1 つです。
## 32.28 インスタンス辞書の突然変異
インスタンス属性は、非データ記述子のメソッド検索にも影響を与える可能性があります。```python id="4tnwq5"
class C:
def f(self):
return "class method"
obj = C()
obj.f = lambda: "instance value"
print(obj.f())
```通常の関数はデータ記述子ではないため、インスタンス ディクショナリ エントリはクラス関数をシャドウします。
メソッド キャッシュでは、関連する場合、インスタンス ディクショナリの状態を考慮する必要があります。
これは、メソッド ルックアップがクラス テーブルへの直接ジャンプよりも複雑であるもう 1 つの理由です。
## 32.29 スロットとメソッド呼び出し
とのクラス`__slots__`通常のインスタンス辞書を持たない可能性があります。```python id="vhbgrn"
class C:
__slots__ = ("x",)
def f(self):
return self.x
```スロットは属性のストレージに影響しますが、メソッドの検索では引き続きクラスとそのベースが検索されます。
の不在`__dict__`いくつかの属性のケースを単純化できますが、記述子、継承、および動的クラスの変更は依然として重要です。
## 32.30 特別なメソッド
特別な方法など`__len__`、`__add__`、 そして`__iter__`通常のインスタンス属性の検索ではなく、タイプ スロットを通じて検索されることがよくあります。
例:```python id="k6wyb3"
len(obj)
```単に実行するだけではありません:```python id="9dm1lp"
obj.__len__()
```あらゆる面で。 CPython は通常、型のスロットを長さに使用します。
この区別は重要です。```python id="0t598r"
class C:
def __len__(self):
return 10
obj = C()
obj.__len__ = lambda: 20
print(len(obj))
print(obj.__len__())
```明示的なメソッド呼び出しでは、インスタンス属性が使用される場合があります。の`len()`この操作では、型を介した特別なメソッド検索を使用します。
特別なメソッドが最適化され、オブジェクト プロトコル スロットに統合されます。
## 32.31 演算子呼び出しとメソッド呼び出し
演算子は多くの場合、特別なメソッドにマップされます。```python id="3pof5j"
a + b
```次のように呼び出すことができます:```text id="pzts11"
a.__add__(b)
b.__radd__(a)
```しかし、CPython は通常の動作をしません`obj.__add__`すべての追加の属性検索。型オブジェクトの数値スロットを使用します。
それで:```python id="rizu1b"
obj.method()
```そして:```python id="g6n28n"
obj + other
```どちらも動的ディスパッチですが、異なる内部パスを使用します。
メソッド呼び出しでは、属性検索と呼び出し機構が使用されます。オペレーターは、フォールバック動作でプロトコル スロットを使用します。
## 32.32 メソッド呼び出しと参照数
メソッド呼び出しは生き続ける必要があります。```text id="nb20se"
receiver object
resolved callable
arguments
temporary bound method, if created
return value
exception state, if raised
```バインドされたメソッド オブジェクトを回避する最適化されたメソッド呼び出しの場合、CPython は、基になる関数の実行中にレシーバーが生きていることを確認する必要があります。
概念的には:```text id="7pxphh"
load receiver
resolve method
prepare self and args
call
release temporaries
push result
```ここでの参照処理が正しくないと、メソッド呼び出しが Python に再入力されることが多く、任意のコードが実行される可能性があるため、重大なバグが発生する可能性があります。
## 32.33 メソッド呼び出しは Python に再入力できる
メソッド ルックアップ自体は Python コードを実行できます。
例:```text id="av2mft"
custom __getattribute__
descriptor __get__
property getter
__getattr__
metaclass attribute lookup
```その後、解決されたメソッド呼び出しでさらに Python コードを実行できるようになります。
単一のソース式:```python id="1eq7mu"
obj.method(arg)
```以下が関係する可能性があります:```text id="44juqs"
call __getattribute__
call descriptor __get__
call method body
```各呼び出しでは、状態を生成したり、変更したり、将来の検索動作を変更したりすることができます。
## 32.34 メソッド呼び出しと例外
メソッド呼び出しは複数の時点で失敗する可能性があります。```text id="8b856f"
receiver expression raises
attribute lookup raises AttributeError or another exception
descriptor binding raises
argument expression raises
resolved object is not callable
argument binding fails
method body raises
return cleanup raises indirectly
```例:```python id="q0kkqy"
obj.missing()
```属性の検索中に失敗します。
例:```python id="68k1hw"
obj.method(bad())
```メソッドが呼び出される前の引数の評価中に失敗する可能性があります。
例:```python id="ex1xt4"
obj.method()
```メソッド本体内で失敗する可能性があります。
バイトコード エラー パスは、いかなる場合でも一時スタック値をクリーンアップする必要があります。
## 32.35 メソッド呼び出しと`None`よくあるエラー:```python id="ov87fl"
xs = []
result = xs.append(1)
result.append(2)
list.append返品None。
2 行目は失敗します。resultはNone、リストではありません。
メソッド呼び出しレベルでは次のようになります。text id="dx8gic" xs.append(1) mutates xs returns None 戻り値は依然として call 命令によってプッシュされ、次に格納されます。result。
メソッド呼び出しは、メソッドが明示的に返さない限り、流暢な連鎖を意味しません。selfまたは別のオブジェクト。
32.36 連鎖メソッド呼び出し
連鎖呼び出しは左から右に実行されます。python id="jxorjs" obj.a().b().c() 概念的には:```text id="dylm9m"
tmp1 = obj.a()
tmp2 = tmp1.b()
tmp3 = tmp2.c()
もし`a()`返品`None`、 それから`.b()`失敗します。
バイトコードはスタックを使用して、次のメソッドにアクセスするのに十分な期間、各中間結果を保持します。
## 32.37 流暢な API
一部の API は意図的に返す`self`:
```python id="36isx5"
class Builder:
def set_name(self, name):
self.name = name
return self
def set_age(self, age):
self.age = age
return self
builder = Builder().set_name("Ada").set_age(37)
```各メソッドはオブジェクトを変更してそれを返します。
メソッド呼び出し機構は普通です。 Fluent スタイルはライブラリの規約であり、特別なインタープリタ機能ではありません。
## 32.38 第一級オブジェクトとしてのメソッド
メソッドは保存して渡すことができます。```python id="63bqx2"
class C:
def f(self, x):
return x + 1
obj = C()
callback = obj.f
print(callback(10))
```バインドされたメソッドは保持します`obj`生きている。
概念的には:```text id="jrk395"
callback
function = C.f
self = obj
```これはメモリの寿命に影響を与える可能性があります。```python id="ka89yy"
callbacks.append(obj.method)
```コールバック リストは、バインドされたメソッドを通じてオブジェクトを存続させるようになりました。
## 32.39 メソッド呼び出しとガベージコレクション
バインドされたメソッドは参照グラフに参加できます。
例:```python id="7qj87e"
class C:
def f(self):
return 1
obj = C()
obj.callback = obj.f
```今:```text id="7udx0g"
obj
-> callback bound method
-> self obj
```これによりサイクルが生まれます。
CPython の循環ガベージ コレクターは、そのようなサイクルが到達不能になった場合、およびファイナライゼーション ルールでクリーンアップが許可されている場合に、そのようなサイクルを収集できます。
これは、メソッド オブジェクトがメモリ管理にどのように接続されるかを示す実際的な例です。
## 32.40 メソッドの検査
メソッド オブジェクトを検査できます。```python id="dcn13l"
class C:
def f(self, x):
return x + 1
obj = C()
m = obj.f
print(type(m))
print(m.__func__)
print(m.__self__)
```クラス ディクショナリの内容を検査できます。```python id="brp4q2"
print(C.__dict__["f"])
```バイトコードを検査できます。```python id="xq1plc"
import dis
def call(obj, x):
return obj.f(x)
dis.dis(call)
```これにより、コンパイラが Python バージョンのメソッド固有の命令を発行するかどうかを確認できます。
## 32.41 最小限のメソッドバインディングモデル
おもちゃ記述子モデル:```python id="b5ukc4"
class Function:
def __init__(self, code):
self.code = code
def __get__(self, obj, cls):
if obj is None:
return self
return BoundMethod(self, obj)
def __call__(self, *args):
return self.code(*args)
class BoundMethod:
def __init__(self, func, self_obj):
self.__func__ = func
self.__self__ = self_obj
def __call__(self, *args):
return self.__func__(self.__self__, *args)
```使用してください:```python id="f8zqma"
def body(self, x):
return self.value + x
class C:
value = 10
C.f = Function(body)
obj = C()
print(obj.f(5))
```これは CPython の実装ではありませんが、バインディングのアイデアを捉えています。```text id="lybvyc"
function stored on class
access through instance
descriptor creates bound method
call inserts self
```## 32.42 よくある誤解
|誤解 |正しいモデル |
|---|---|
|`obj.method(x)`に保存されている関数を直接呼び出します。`obj`|属性の検索、バインドを実行して、 | を呼び出します。
|`self`| は構文によって挿入されるキーワードです。`self`慣例により、これは単なる最初の引数です。
|メソッドは常にインスタンス内に存在します。通常のメソッドはクラス上に存在し、インスタンスにバインドされます。
|バインドされたメソッドは常に割り当てられます。 CPython は即時呼び出しの割り当てを回避できます。
|`staticmethod`受け取る`self`|自動の最初の引数は受け取りません。
|`classmethod`インスタンスを受け取ります |クラス | を受け取ります。
|特別なメソッドは常に通常のメソッドと同様に検索されます。多くはタイプ スロットを通じて解決されます。
|メソッドの検索は静的です。それはランタイムのタイプ、MRO、記述子、インスタンスの状態によって異なります。
## 32.43 読書戦略
メソッド呼び出しを学習するには、次のプログラムから始めます。```python id="fybmbx"
class C:
def f(self, x):
return x + 1
def call(obj):
return obj.f(10)
```検査:```python id="r4y4v3"
import dis
dis.dis(call)
obj = C()
m = obj.f
print(m.__func__)
print(m.__self__)
print(C.__dict__["f"])
```次に、クラスを変更します。```python id="10svlx"
@staticmethod
def s(x): ...
@classmethod
def c(cls, x): ...
@property
def p(self): ...
```また、次のテストも行ってください。```python id="p178dp"
obj.f = lambda x: x * 2
```ルックアップがどのように変化するかを調べます。
これにより、記述子、インスタンス辞書、バイトコード、および呼び出し機構の間の相互作用が明らかになります。
## 32.44 章の概要
メソッド呼び出しは、属性の検索とそれに続く呼び出しです。通常のインスタンス メソッドの場合、関数記述子は最初の引数としてレシーバー オブジェクトをバインドし、インスタンスを挿入してクラス関数を呼び出すのと概念的に同等のバインドされたメソッドを生成します。
コアモデルは次のとおりです。```text id="xs3s0t"
obj.method(arg)
↓
lookup "method" on obj
↓
apply descriptor binding if needed
↓
obtain callable
↓
call callable with arguments
↓
return result or raise exception
```CPython はこのパスを大幅に最適化します。一時的なバインドされたメソッドの割り当てを回避し、メソッドの検索をキャッシュし、安定した呼び出しサイトを特殊化し、高速な C パスを介して組み込みメソッドを呼び出すことができます。
セマンティクスは動的のままです。ランタイム タイプ、MRO、記述子、インスタンス ディクショナリ、カスタム属性フック、クラスの変更、特別なメソッド ルールはすべて、実際に呼び出されるメソッドに影響します。