31. 関数呼び出し
#31. 関数呼び出し
関数呼び出しは、CPython の最も重要な実行パスの 1 つです。呼び出しは、バイトコードの実行、フレーム、引数バインディング、記述子、メソッド、クロージャ、C API、参照カウント、例外、戻り処理などの複数のシステムを同時に接続します。
簡単な呼び出し:python id="wauh11" result = f(1, 2) ソースレベルでは小さく見えます。実行時、CPython は次のことを行う必要があります。```text id="rhdr0d"
load the callable
load the arguments
choose the correct call protocol
bind arguments to parameters
create or initialize a frame if calling Python code
execute the callee
return a result or propagate an exception
store the result
## 31.1 通話の意味
Python では、呼び出し式は次の一般的な形式になります。```python id="a7ltua"
callable_object(arguments)
```括弧の前のオブジェクトは呼び出し可能である必要があります。
例:```python id="pqkmhl"
f()
len(xs)
obj.method(1)
C(10)
decorator(fn)
callback(event)
```多くの種類のオブジェクトを呼び出すことができます。
|呼び出し可能な種類 |例 |実行時の動作 |
|---|---|---|
| Python関数 |`f()`| Python フレームを作成または初期化します。
|組み込み関数 |`len(xs)`| C 実装を呼び出します |
|バインドされたメソッド |`obj.method()`|バインドされた関数を呼び出します`self`|
|クラス |`C()`|メタクラス構築パスを呼び出します。
|呼び出し可能なインスタンス |`obj()`|電話`obj.__call__`|
| C 拡張関数 |`mod.func()`|ネイティブ拡張コードを呼び出します |
|コルーチン関数 |`async_fn()`|コルーチン オブジェクトを作成します |
|ジェネレーター関数 |`gen()`|ジェネレーターオブジェクトを作成します |
|記述子の結果 |`obj.attr()`|呼び出し前にバインドする可能性があります |
構文は統一されています。ランタイム パスは、呼び出し可能なオブジェクトのタイプによって異なります。
## 31.2 バイトコードの呼び出し
呼び出しは、呼び出し可能オブジェクトとその引数をロードするバイトコードにコンパイルされてから、呼び出し命令を実行します。
のために:```python id="xszy0u"
def g(a, b):
return f(a, b)
```概念的には:```text id="gwqjm5"
LOAD_GLOBAL f
LOAD_FAST a
LOAD_FAST b
CALL 2
RETURN_VALUE
```呼び出し前のスタックには、呼び出し可能ファイルと引数が含まれています。```text id="rc6gwy"
[f, a, b]
```呼び出し命令はそれらを消費し、戻り値をプッシュします。```text id="q4x2va"
[result]
```正確な命令シーケンスは Python のバージョンによって異なります。最新の CPython は、パフォーマンスを向上させるために呼び出しプロトコルを数回変更しました。安定したコンセプトは次のとおりです。```text id="oc3h6f"
prepare callable and arguments
perform call
push result or raise exception
```## 31.3 コールスタックのレイアウト
呼び出し命令にはスタック レイアウトが必要です。位置呼び出しの簡略化されたレイアウトは次のとおりです。```text id="2dz11l"
callable
arg0
arg1
arg2
...
```のために:```python id="n90xfk"
f(x, y, z)
```呼び出し前のスタックは概念的には次のようになります。```text id="0zk4e7"
[f, x, y, z]
```電話の後:```text id="1ef6n3"
[result]
```キーワード呼び出しには追加のメタデータが必要です。```python id="zewq6w"
f(x, y=10)
```概念的には、CPython は以下を表現する必要があります。```text id="c35rsw"
callable = f
positional args = [x]
keyword names = ["y"]
keyword values = [10]
```最新の CPython は、可能な限り一時的なタプルや辞書を構築せずにこれを表現しようとします。
## 31.4 引数の評価順序
Python は、定義された順序で呼び出しコンポーネントを評価します。
のために:```python id="r11rqm"
f(a(), b(), c())
```呼び出しは左から右に行われます。```text id="rg0lon"
evaluate f
evaluate a()
evaluate b()
evaluate c()
call f with the three results
```もし`b()`上げる、`c()`走らない。
引数式には副作用がある可能性があるため、これは重要です。```python id="9b37sl"
f(print("a"), print("b"))
```評価ループは、一時値としてスタックを使用する間、Python のソースレベルの順序を保持する必要があります。
## 31.5 位置引数
単純な Python 関数:```python id="55c6o4"
def add(a, b):
return a + b
```には 2 つの位置パラメータがあります。
電話をかける:```python id="wkxmb9"
add(2, 3)
```バインド:```text id="jlsbey"
a = 2
b = 3
```CPython では、呼び出し先フレームは高速ローカル スロットを使用します。
概念的には:```text id="6u63lj"
callee frame localsplus:
slot 0: a = 2
slot 1: b = 3
```次に、バイトコードが実行されます。```text id="chrvpi"
LOAD_FAST a
LOAD_FAST b
BINARY_OP +
RETURN_VALUE
```バインディング ステップは、呼び出しオーバーヘッドの主要な部分です。
## 31.6 キーワード引数
キーワード引数は名前によってバインドされます。```python id="n28umo"
def area(width, height):
return width * height
area(height=10, width=20)
```引数はパラメータの順序から外れて到着しますが、呼び出し先のスロットは正しく埋められている必要があります。```text id="j16i8j"
width = 20
height = 10
```CPython は以下をチェックする必要があります:```text id="x7xbwk"
whether each keyword matches a parameter
whether the same parameter was supplied twice
whether required parameters are missing
whether unexpected keywords should be rejected or collected by **kwargs
```エラーの例:```python id="rbyfsw"
area(20, width=10)
```この供給品`width`2 回: 位置的に 1 回、キーワードごとに 1 回。
## 31.7 デフォルトの引数
デフォルトの引数は関数オブジェクトに保存され、呼び出しごとに再作成されるわけではありません。```python id="d2kqmt"
def f(x, step=1):
return x + step
```関数オブジェクトには、次のデフォルト値が格納されます。`step`。
概念的には:```text id="zw5t1r"
function f
code object
globals
defaults: (1,)
```次のように呼び出された場合:```python id="24ks4e"
f(10)
```CPython は次のように入力します。```text id="n9k9rw"
x = 10
step = 1
```有名な変更可能なデフォルトの動作は次のとおりです。```python id="kjt1ip"
def append_item(x, xs=[]):
xs.append(x)
return xs
```リスト オブジェクトは関数のデフォルトに保存され、呼び出し間で再利用されます。
## 31.8 キーワードのみの引数
キーワードのみのパラメータはキーワードで指定する必要があります。```python id="53ae7c"
def connect(host, *, timeout, retries=3):
...
```有効:```python id="kjp0p3"
connect("example.com", timeout=10)
```無効:```python id="g6g3ha"
connect("example.com", 10)
```関数コード オブジェクトには、カウントとレイアウト メタデータが格納されます。引数のバインド中に、CPython はこのメタデータを使用して、どの高速ローカル スロットが値を受け取るかを決定します。
概念的には:```text id="w1u0wk"
positional slots
keyword-only slots
*args slot, if present
**kwargs slot, if present
```## 31.9 可変個の位置引数
あ`*args`パラメータは追加の位置引数を収集します。```python id="hggzqm"
def f(a, *args):
return args
```電話:```python id="ve43p4"
f(1, 2, 3)
```バインディング:```text id="72r8al"
a = 1
args = (2, 3)
```CPython は追加の位置引数のタプルを作成します。追加の引数が渡されない場合は、空のタプルが使用されます。
の`*args`バイトコードの観点からは、slot は通常のローカル変数スロットです。
## 31.10 可変個引数のキーワード引数
あ`**kwargs`パラメータは追加のキーワード引数を収集します。```python id="842uzh"
def f(a, **kwargs):
return kwargs
```電話:```python id="e27hzk"
f(1, x=2, y=3)
```バインディング:```text id="z7vjci"
a = 1
kwargs = {"x": 2, "y": 3}
```CPython は、一致しないキーワード引数の辞書を作成します。
機能がない場合`**kwargs`、予期しないキーワード引数はエラーです。
## 31.11 スター付き呼び出し引数
呼び出しでは位置引数をアンパックできます。```python id="2t6bo9"
args = (1, 2)
f(*args)
```CPython は評価する必要があります`args`を反復または位置引数に変換してから、呼び出しにマージします。
複数の解凍が許可されます。```python id="dlam5o"
f(0, *xs, *ys)
```ランタイムは左から右の順序を維持する必要があります。
概念的には:```text id="g86xe4"
positional arguments =
[0] + list(xs) + list(ys)
```解凍されたオブジェクトが反復可能でない場合、CPython は次のエラーを発生させます。`TypeError`。
## 31.12 二重星の呼び出し引数
キーワード引数はマッピングから解凍できます。```python id="gst2dz"
kwargs = {"x": 1, "y": 2}
f(**kwargs)
```複数のマッピングを指定できます。```python id="1obqtk"
f(**a, **b)
```CPython は、通常の方法で Python 関数を呼び出すときにキーが文字列であることを確認し、重複したキーワードの割り当てを検出する必要があります。
例:```python id="xha3n8"
f(x=1, **{"x": 2})
```これはエラーです。`x`2回供給されます。
## 31.13 引数バインドエラー
呼び出し失敗の多くは、関数本体が開始される前に発生します。
例:```python id="fou3a6"
def f(a, b):
pass
f(1)
f(1, 2, 3)
f(a=1, c=2)
f(1, a=2)
```これらは引き上げます`TypeError`。
評価ループは呼び出しを開始しますが、呼び出し機構はバインディング チェックを実行します。
呼び出しはいくつかのフェーズで失敗する可能性があります。```text id="1fh4l4"
callable lookup fails
argument expression raises
argument unpacking fails
object is not callable
argument binding fails
callee body raises
return handling fails indirectly through cleanup
```## 31.14 Python 関数呼び出し
通常の Python 関数呼び出しの場合、CPython は新しいフレーム実行状態を準備します。```python id="u6ew7w"
def f(a, b):
c = a + b
return c
```電話:```python id="ry7s81"
f(2, 3)
```概念的には:```text id="kdm5oh"
function object:
code object
globals
defaults
closure
call:
bind a = 2
bind b = 3
allocate or initialize frame
run evaluation loop
return result
```新しいフレームは呼び出しごとに同じコード オブジェクトを指しますが、ローカル スロットは呼び出しごとに異なります。
## 31.15 組み込み関数呼び出し
組み込み関数は C で実装されます。```python id="47h9j2"
len(xs)
```この呼び出しでは、本体の Python フレームは作成されません。`len`。代わりに、CPython は C 関数を呼び出します。
概念的には:```text id="v2dcxi"
LOAD_GLOBAL len
LOAD_FAST xs
CALL 1
call C implementation of len
push result
```C 実装は引き続き Python オブジェクトを受け取り、Python オブジェクトを返します。
のために`len(xs)`、オブジェクトの長さスロットを呼び出して、Python 整数を返す場合があります。
組み込みは、本体の Python バイトコードの実行を回避することが部分的に高速です。
## 31.16 C 拡張関数の呼び出し
C 拡張関数は、組み込み関数と同様の原則に従います。
C 拡張関数は、引数を Python オブジェクトとして受け取り、検証し、ネイティブ作業を実行して、Python オブジェクトを返すか、例外を通知します。
概念的な C シェイプ:```c id="fugpzb"
static PyObject *
mod_func(PyObject *self, PyObject *args)
{
int x;
if (!PyArg_ParseTuple(args, "i", &x)) {
return NULL;
}
return PyLong_FromLong(x + 1);
}
```最新の拡張関数は、引数をタプルにパックすることを避ける、より高速な呼び出し規則を使用する場合があります。
規則に関係なく、ルールは次のとおりです。```text id="1edgq5"
return PyObject* on success
return NULL with exception set on failure
```評価ループは結果をチェックします。
## 31.17 ベクトルコール
Vectorcall は、多くの呼び出し可能な型に対する CPython の高速呼び出し規約です。
その目的は、引数を C 配列として渡すことです。`PyObject *`一時的なタプルや辞書オブジェクトを常に構築するのではなく、値を使用します。
概念的には:```text id="y0ul2m"
old-style call:
build args tuple
build kwargs dict
call function
vectorcall-style:
pass pointer to argument array
pass argument count
pass keyword names separately
call function
```これにより、ホット コール パスでの割り当てとコピーが削減されます。
次のような呼び出しの場合:```python id="i9fxmj"
f(a, b, c)
```引数はインタープリタ スタック上ですでに連続している可能性があります。 Vectorcall を使用すると、CPython はその配列のような領域を呼び出し可能な実装に直接渡すことができます。
## 31.18 呼び出しとバインドされたメソッド
メソッド呼び出しにはバインディングが必要です。```python id="yvl8kj"
obj.method(10)
```言語レベルでは、これは次のことを意味します。```text id="ztwg7z"
look up method on obj
bind obj as self
call method with self and 10
```もし`method`はクラスで定義された関数であり、インスタンスを通じてアクセスすると、概念的にバインドされたメソッドが作成されます。```python id="t8uh02"
bound = obj.method
bound(10)
```バインドされたメソッドには以下が含まれます。```text id="qceh4k"
function
self object
```それからそれを消耗品と呼びます`self`自動的に。
CPython は、メソッドがすぐに呼び出されるときに一時的にバインドされたメソッド オブジェクトが作成されるのを避けるために、一般的なメソッド呼び出しを最適化します。
## 31.19 記述子のバインディング
メソッド呼び出しは記述子プロトコルに基づいています。
クラスに格納される関数は記述子です。インスタンス経由でアクセスすると、`__get__`動作によりバインドされたメソッドが生成されます。
例:```python id="bkvw5y"
class C:
def f(self, x):
return x
obj = C()
obj.f(10)
```概念的には:```text id="42zlm9"
C.__dict__["f"].__get__(obj, C)
returns bound method
bound method called with x = 10
```その理由は次のとおりです。```python id="pmz8uo"
C.f(obj, 10)
```も機能します。この形式では、インスタンス属性アクセスによる自動インスタンス バインディングは行われません。あなたは合格します`obj`明示的に。
## 31.20 クラス呼び出し
クラスを呼び出すと、インスタンスが作成または初期化されます。```python id="0bc8io"
obj = C(1, 2)
```クラスはメタクラスが呼び出し可能であるため、呼び出し可能です。通常、メタクラスは次のとおりです。`type`。
概念的なフロー:```text id="wy9ffx"
C.__call__(*args, **kwargs)
calls C.__new__(C, *args, **kwargs)
if result is instance of C:
calls result.__init__(*args, **kwargs)
returns result
```例:```python id="j7g0io"
class C:
def __new__(cls, value):
obj = super().__new__(cls)
return obj
def __init__(self, value):
self.value = value
```電話`C(10)`単なる割り当てではありません。それは次のようなプロトコルです。`__new__`、`__init__`、およびメタクラスの動作。
## 31.21 呼び出し可能なインスタンス
オブジェクトの型が定義されている場合、オブジェクトは呼び出し可能になります。`__call__`。```python id="jv1zpx"
class Adder:
def __init__(self, n):
self.n = n
def __call__(self, x):
return self.n + x
add10 = Adder(10)
print(add10(5))
```電話の内容:```python id="f9obht"
add10(5)
```概念的には以下を呼び出します。```python id="o2fo9r"
type(add10).__call__(add10, 5)
```CPython レベルでは、オブジェクトの型は型スロットを通じて呼び出し動作を提供します。
## 31.22 デコレータと呼び出し
デコレータは関数定義時に実行されます。```python id="hxiutv"
@decorator
def f():
pass
```概念的には:```python id="57azij"
def f():
pass
f = decorator(f)
```デコレーターの呼び出しは、含まれているモジュール、クラス、または関数本体の実行中に発生します。
複数のデコレータ:```python id="ew1w3e"
@d1
@d2
def f():
pass
```平均:```python id="hq0xkt"
f = d1(d2(f))
```デコレータは通常の呼び出しであるため、これは重要です。これらは、呼び出し可能または呼び出し不可能なオブジェクトを返すことができます。
## 31.23 呼び出しと終了
クロージャ呼び出しは、セル オブジェクトを通じてキャプチャされた変数を運びます。```python id="ld9q93"
def outer(x):
def inner(y):
return x + y
return inner
f = outer(10)
f(5)
```機能`inner`もっている:```text id="bmsuz9"
code object
globals
closure tuple containing cell for x
```呼び出されると、そのフレームはアクセスできます。`x`閉鎖セルを通って。
概念的には:```text id="lojpq8"
inner frame:
local y = 5
free variable x -> cell -> 10
```呼び出しパスは通常の引数を設定し、クロージャ セルを可視にします。`LOAD_DEREF`。
## 31.24 呼び出しとジェネレーター
ジェネレーター関数を呼び出しても、その本体はすぐには実行されません。```python id="1wn1lz"
def gen():
yield 1
g = gen()
```この呼び出しによりジェネレーター オブジェクトが作成されます。
ジェネレーターが再開されると、関数本体が開始されます。```python id="xvobsw"
next(g)
```概念的には:```text id="1gh51l"
call generator function:
create generator object with suspended frame
return generator
next(generator):
resume frame
execute until yield or return
```これが通常の関数呼び出しとの大きな違いです。
## 31.25 呼び出しとコルーチン
async 関数を呼び出すと、コルーチン オブジェクトが作成されます。```python id="z4c0rv"
async def fetch():
return 1
coro = fetch()
```通常、本体は呼び出し時に完了するまで実行されません。待機時またはスケジュールされたときに実行されます。```python id="tmq7yx"
result = await coro
```概念的には:```text id="3o4iuh"
call async function:
create coroutine object
return coroutine
await coroutine:
execute or resume coroutine frame
```関数呼び出しにより実行オブジェクトが作成されます。ドライブの実行を待ちます。
## 31.26 再帰呼び出し
再帰呼び出しでは、同じコード オブジェクトに対して複数のアクティブなフレームが作成されます。```python id="mmzccu"
def fact(n):
if n <= 1:
return 1
return n * fact(n - 1)
```のために`fact(4)`:
```text id="v16tkx"
fact frame: n = 4
fact frame: n = 3
fact frame: n = 2
fact frame: n = 1
```各フレームには個別の高速ローカルとスタック状態があります。
CPython は再帰の深さをチェックして、制御されていない再帰によってランタイム リソースが使い果たされるのを防ぎます。
## 31.27 呼び出しと例外
呼び出しは、リターンするか、上げることによって終了できます。```python id="zv234y"
def f():
raise ValueError("bad")
def g():
return f()
```いつ`f`発生した場合、通常の戻り値はプッシュされません。例外はコールスタックを通じて伝播します。
概念的には:```text id="p6uolo"
frame g calls frame f
frame f raises ValueError
frame f unwinds
frame g receives exception instead of return value
frame g unwinds unless it handles the exception
```もし`g`ハンドラーがあります:```python id="g7gg6a"
def g():
try:
return f()
except ValueError:
return 0
```例外がキャッチされました`g`そしてハンドラーで通常の実行が再開されます。
## 31.28 戻り値
すべての Python 関数は値を返します。
明示的な戻り値が存在しない場合は、戻り値が返されます。`None`。```python id="46x94k"
def f():
pass
```概念的には:```text id="99ydts"
LOAD_CONST None
RETURN_VALUE
```呼び出し式は常に次のいずれかを期待します。```text id="q8p8m0"
a returned Python object
or an exception
```3 番目の正常な結果はありません。
## 31.29 通話中の参照の所有権
呼び出しは参照に依存します。
呼び出し中、CPython は生き続ける必要があります。```text id="hinb3j"
callable object
argument objects
keyword name objects
temporary bound method state
callee frame
return value
exception objects, if any
```呼び出し命令は、呼び出しが成功または失敗した後に一時参照を解放する必要があります。
簡略化された呼び出しパス:```c id="s4hvbw"
result = PyObject_Call(callable, args, kwargs);
if (result == NULL) {
goto error;
}
push(result);
```ただし、この前後で、インタープリタは引数参照をクリーンアップし、エントリを正しくスタックする必要があります。
呼び出しパスでの不適切な参照処理により、引数がリークしたり、使用中のオブジェクトが破壊されたりする可能性があります。
## 31.30 呼び出しは Python に再入力できる
1 つのフレーム内の呼び出しで、任意の Python コードを実行できます。
これは、Python 関数の直接呼び出しの場合には明らかですが、呼び出しのように見えない操作にも当てはまります。
例:```python id="yv3kfl"
obj.x
```電話できる`obj.__getattribute__`。
例:```python id="44ak0w"
a + b
```電話できる`a.__add__`。
例:```python id="72yoig"
for x in xs:
...
```イテレータメソッドを呼び出すことができます。
したがって、インタープリタは、多くの操作が Python の実行に再び入る可能性があることを想定する必要があります。
呼び出しスタックは、明示的または暗黙的な呼び出しによって増大する可能性があります。```text id="5p626a"
frame A
executes LOAD_ATTR
calls Python descriptor
frame B
```## 31.31 メソッド呼び出しの最適化
このソース パターンは一般的です。```python id="8wm8xu"
obj.method(arg)
```単純な実装では次のようになります。```text id="ywi46w"
load obj
load method attribute
create bound method object
load arg
call bound method
destroy bound method object
```CPython は、一般的なメソッド呼び出しパターンを認識することでこれを最適化します。
最適化されたパスでは、可能な場合、一時的にバインドされたメソッドの割り当てが回避されます。```text id="d28yc3"
load method function and self separately
call function with self and arguments
```これにより、割り当てと参照カウントのトラフィックが削減されます。
言語の動作は変わりません。最適化では内部呼び出しパスのみが変更されます。
## 31.32 呼び出しとインラインキャッシュ
呼び出しバイトコードはインライン キャッシュを使用できます。
呼び出しサイトでは、同じ呼び出し可能オブジェクトが繰り返し表示されることがよくあります。```python id="on96mz"
for x in xs:
total += f(x)
```このソース位置の呼び出し命令は、同じ関数を繰り返し呼び出す可能性があります。`f`。
CPython は次のような情報をキャッシュできます。```text id="ec11pe"
callable type
function version
argument shape
method lookup result
global lookup version
descriptor result
```警備員が通過した場合、通訳者は高速パスを使用できます。
呼び出し可能オブジェクトが変更されると、一般的な動作に戻ります。
## 31.33 呼び出しと専門化
特殊化により、呼び出しの多いコードを最適化できます。
例:```python id="sky0ma"
def loop(xs):
total = 0
for x in xs:
total += int(x)
return total
```の通話サイト`int(x)`同じ呼び出し可能および引数の形状を繰り返し確認すると、特殊化される可能性があります。
特化しても Python のセマンティクスは変わりません。ガード下では一般的なケースが加速するだけです。
スタック コントラクトは残ります。```text id="nx4fmo"
before call: callable and arguments
after call: result
or exception
```## 31.34 コールと GIL
従来の CPython では、GIL を保持している間に Python バイトコードの実行が発生します。
Python コードの呼び出しはそのモデル内で継続されます。
C 実装が長時間実行される作業の前後で GIL を解放することを選択した場合、C コードへの呼び出しによって GIL が解放される可能性があります。
C コードが GIL を解放する例:```text id="99b4lz"
blocking I/O
compression
hashing
numeric kernels
database drivers
system calls
```これは、呼び出し先が GIL を解放するネイティブ コードである場合、呼び出しによって一時的に別の Python スレッドの実行が許可される可能性があることを意味します。
純粋な Python 呼び出しの場合、バイトコードの実行は従来のビルドの GIL によってシリアル化されたままになります。
## 31.35 呼び出しと組み込みメソッド
組み込みメソッドは通常、C レベルの記述子です。
例:```python id="lz9y2o"
xs.append(1)
```方法`append`リストオブジェクト用に C で実装されています。
高度に最適化されたパスでは、Python バインドされたメソッド オブジェクトの作成を回避し、リスト追加実装を直接呼び出すことができます。
ソースレベルの呼び出し:```python id="bx4yzs"
xs.append(1)
```したがって、次のことが含まれます。```text id="2d6rn4"
attribute or method resolution
argument setup
C method call
mutation of list object
return None
```ループと変更ロジックが C で実行されるため、Python で実装された同等のメソッドよりもはるかに高速です。
## 31.36 通話と`super`
`super()`メソッドの解決を変更する、呼び出し可能で記述子認識のオブジェクトです。```python id="xskypw"
class Base:
def f(self):
return 1
class Child(Base):
def f(self):
return super().f() + 1
```電話の内容:```python id="onpk7n"
super().f()
```以下が含まれます:```text id="s1yqxl"
create or resolve super object
perform attribute lookup using adjusted MRO position
bind method to original instance
call method
```これは、通常の呼び出し機構に特別な属性解決を加えたものです。
## 31.37 呼び出しとプロパティ
プロパティへのアクセスでは、明示的な呼び出しが開始される前にコードが呼び出される場合があります。```python id="utkq7j"
obj.factory()
```もし`factory`はプロパティです:```python id="q5y4ib"
class C:
@property
def factory(self):
return lambda: 42
```それから:```text id="7onci0"
obj.factory
calls property getter
returns callable
(...)
calls returned callable
```したがって、ソース式には暗黙的な呼び出しとその後に続く明示的な呼び出しが含まれています。
これが、CPython が属性アクセスを潜在的に効果的なものとして扱う必要がある理由です。
## 31.38 呼び出しとエラーのクリーンアップ
この呼び出しが引数を部分的に評価して失敗したとします。```python id="xwgmze"
f(g(), h())
```もし`h()`レイズすると、次のようになります。```text id="9lvmys"
f has been loaded
g() has returned
h() raises
the call to f never happens
temporaries must be cleaned up
exception propagates
```スタックには、への一時的な参照が含まれる場合があります。`f`そして`g()`の結果です。 CPython はエラーの巻き戻し中にそれらを正しく解放する必要があります。
これが、バイトコード呼び出しと評価ループのエラー パスが注意される理由の 1 つです。
## 31.39 呼び出しの検査
使用する`dis`呼び出しバイトコードを検査します。```python id="rgh73f"
import dis
def target(a, b):
return a + b
def caller(x):
return target(x, 10)
dis.dis(caller)
```関数のメタデータを検査します。```python id="i0pt0v"
print(target.__code__.co_argcount)
print(target.__code__.co_posonlyargcount)
print(target.__code__.co_kwonlyargcount)
print(target.__defaults__)
print(target.__kwdefaults__)
print(target.__code__.co_varnames)
```Python レベルで署名を検査します。```python id="dcrnsg"
import inspect
print(inspect.signature(target))
```の`inspect`ビューはソースレベルであり、ユーザーフレンドリーです。コード オブジェクト ビューは、CPython がフレーム セットアップに使用するものに似ています。
## 31.40 最小限の通話通訳
おもちゃの通訳者は通話モデルを示すことができます。```python id="47u5eu"
LOAD_CONST = "LOAD_CONST"
LOAD_FAST = "LOAD_FAST"
CALL = "CALL"
RETURN = "RETURN"
def run(code, consts, locals_):
stack = []
for op, arg in code:
if op == LOAD_CONST:
stack.append(consts[arg])
elif op == LOAD_FAST:
stack.append(locals_[arg])
elif op == CALL:
argc = arg
args = stack[-argc:]
del stack[-argc:]
func = stack.pop()
result = func(*args)
stack.append(result)
elif op == RETURN:
return stack.pop()
raise RuntimeError("missing RETURN")
```プログラム:```python id="lq4boa"
def add(a, b):
return a + b
code = [
(LOAD_CONST, 0), # add
(LOAD_FAST, "x"),
(LOAD_CONST, 1), # 10
(CALL, 2),
(RETURN, None),
]
print(run(code, [add, 10], {"x": 5}))
```出力:```text id="5boj0q"
15
```これにより、Python の実引数バインディング、記述子、C API、例外、フレーム、vectorcall、および参照カウントが省略されます。これは依然としてスタックの概念を捉えています。つまり、呼び出しは呼び出し可能オブジェクトと引数を消費し、結果をプッシュします。
## 31.41 よくある誤解
|誤解 |正しいモデル |
|---|---|
|呼び出しでは常に Python フレームが作成されます。組み込み関数と C 関数は、本体の Python フレームを作成しません。
|非同期関数を呼び出すと、すぐに実行されます。コルーチン オブジェクトを作成します。
|ジェネレーター関数の呼び出しは、最初の yield | まで実行されます。ジェネレーター オブジェクトを作成します。
|メソッドは常にバインドされたメソッド オブジェクトを割り当てます。 CPython は多くの場合、即時呼び出しではこれを回避します。
|キーワード引数は常に dict | として渡されます。高速パスを使用すると、辞書の構築を回避できます。
|`def`関数を宣言するだけです |`def`関数オブジェクトを実行して作成します。
|クラスは関数ではないため、呼び出すことはできません。クラスはメタクラス ロジックを通じて呼び出すことができます。
|`obj()`関数呼び出しでなければなりません |電話するかもしれない`type(obj).__call__`|
## 31.42 読書戦略
呼び出しを研究するには、単純なソース フォームから始めます。```python id="s1he1e"
f()
f(a, b)
f(a=1)
f(*args)
f(**kwargs)
obj.method(x)
C(x)
async_fn()
gen_fn()
```それぞれについて:```python id="cvm0yb"
import dis
dis.dis(function_containing_call)
```次に次のように尋ねます。```text id="8l87gs"
How is the callable loaded?
How are arguments loaded?
Are keywords present?
Is unpacking present?
Does attribute lookup happen before the call?
Does binding happen?
Does the call create a Python frame?
Can the call return a coroutine or generator instead of running the body?
What cleanup is needed if argument evaluation fails?
```このアプローチは、呼び出し構文を具体的なインタープリター操作に変換します。
## 31.43 章の概要
CPython の関数呼び出しは、構造化されたランタイム操作です。インタプリタは呼び出し可能オブジェクトと引数を評価し、フレーム スタック上に配置し、適切な呼び出しプロトコルを選択し、必要に応じて引数をバインドし、Python フレームまたはネイティブ C 実装を実行して、返されたオブジェクトをプッシュするか例外を伝播します。
中心的なモデルは次のとおりです。```text id="oiwfbx"
load callable
load arguments
CALL
bind arguments
run Python frame or native callable
return object or raise exception
store or use result
```呼び出しは、スタック レイアウト、引数バインド、記述子のバインド、フレーム セットアップ、C API 規約、参照の所有権、例外処理、特殊化など、多くのランタイム境界を越えるため、コストがかかります。
CPython のパフォーマンスに関する作業の多くは、Python の動的セマンティクスを変更せずに呼び出しを安くすることに重点を置いています。