31. Gọi hàm
31. Lệnh gọi hàm
Lệnh gọi hàm là một trong những đường dẫn thực thi quan trọng nhất trong CPython. Một cuộc gọi kết nối nhiều hệ thống cùng một lúc: thực thi mã byte, khung, liên kết đối số, bộ mô tả, phương thức, bao đóng, API C, đếm tham chiếu, ngoại lệ và xử lý trả về.
Một cuộc gọi đơn giản:python id="wauh11" result = f(1, 2) trông nhỏ ở cấp độ nguồn. Khi chạy, CPython phải:```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 Ý nghĩa của cuộc gọi
Trong Python, biểu thức cuộc gọi có dạng chung như sau:```python id="a7ltua"
callable_object(arguments)
```Đối tượng trước dấu ngoặc đơn phải có thể gọi được.
Ví dụ:```python id="pqkmhl"
f()
len(xs)
obj.method(1)
C(10)
decorator(fn)
callback(event)
```Nhiều loại đối tượng có thể được gọi:
| Loại có thể gọi được | Ví dụ | Hành vi thời gian chạy |
|---|---|---|
| Hàm Python |`f()`| Tạo hoặc khởi tạo khung Python |
| Chức năng tích hợp |`len(xs)`| Thực hiện cuộc gọi C |
| Phương pháp ràng buộc |`obj.method()`| Chức năng gọi có giới hạn`self`|
| Lớp |`C()`| Đường dẫn xây dựng siêu dữ liệu cuộc gọi |
| Ví dụ có thể gọi được |`obj()`| Cuộc gọi`obj.__call__`|
| Hàm mở rộng C |`mod.func()`| Mã mở rộng cuộc gọi gốc |
| Hàm coroutine |`async_fn()`| Tạo đối tượng coroutine |
| Chức năng tạo |`gen()`| Tạo đối tượng máy phát điện |
| Kết quả mô tả |`obj.attr()`| Có thể ràng buộc trước cuộc gọi |
Cú pháp là thống nhất. Đường dẫn thời gian chạy phụ thuộc vào loại đối tượng có thể gọi được.
## 31.2 Mã byte cuộc gọi
Cuộc gọi biên dịch thành mã byte tải một lệnh gọi có thể gọi và các đối số của nó, sau đó thực thi lệnh gọi.
Vì:```python id="xszy0u"
def g(a, b):
return f(a, b)
```Về mặt khái niệm:```text id="gwqjm5"
LOAD_GLOBAL f
LOAD_FAST a
LOAD_FAST b
CALL 2
RETURN_VALUE
```Ngăn xếp trước cuộc gọi chứa các đối số và có thể gọi được:```text id="rc6gwy"
[f, a, b]
```Lệnh gọi tiêu thụ chúng và đẩy giá trị trả về:```text id="q4x2va"
[result]
```Trình tự hướng dẫn chính xác thay đổi tùy theo phiên bản Python. CPython hiện đại đã thay đổi giao thức cuộc gọi nhiều lần để cải thiện hiệu suất. Khái niệm ổn định là:```text id="oc3h6f"
prepare callable and arguments
perform call
push result or raise exception
```## 31.3 Bố cục ngăn xếp cuộc gọi
Lệnh gọi cần có bố cục ngăn xếp. Bố cục đơn giản hóa cho các lệnh gọi vị trí là:```text id="2dz11l"
callable
arg0
arg1
arg2
...
```Vì:```python id="n90xfk"
f(x, y, z)
```ngăn xếp trước cuộc gọi về mặt khái niệm:```text id="0zk4e7"
[f, x, y, z]
```Sau cuộc gọi:```text id="1ef6n3"
[result]
```Cuộc gọi từ khóa cần siêu dữ liệu bổ sung.```python id="zewq6w"
f(x, y=10)
```Về mặt khái niệm, CPython phải đại diện cho:```text id="c35rsw"
callable = f
positional args = [x]
keyword names = ["y"]
keyword values = [10]
```CPython hiện đại cố gắng thể hiện điều này mà không cần xây dựng các bộ dữ liệu và từ điển tạm thời khi có thể.
## 31.4 Thứ tự đánh giá đối số
Python đánh giá các thành phần cuộc gọi theo thứ tự xác định.
Vì:```python id="r11rqm"
f(a(), b(), c())
```các cuộc gọi xảy ra từ trái sang phải:```text id="rg0lon"
evaluate f
evaluate a()
evaluate b()
evaluate c()
call f with the three results
```Nếu như`b()`tăng,`c()`không chạy.
Điều này quan trọng vì các biểu thức đối số có thể có tác dụng phụ:```python id="9b37sl"
f(print("a"), print("b"))
```Vòng đánh giá phải duy trì thứ tự cấp nguồn của Python trong khi sử dụng ngăn xếp cho các giá trị tạm thời.
## 31.5 Đối số vị trí
Một hàm Python đơn giản:```python id="55c6o4"
def add(a, b):
return a + b
```có hai tham số vị trí.
Gọi:```python id="wkxmb9"
add(2, 3)
```ràng buộc:```text id="jlsbey"
a = 2
b = 3
```Trong CPython, khung callee sử dụng các khe cục bộ nhanh.
Về mặt khái niệm:```text id="6u63lj"
callee frame localsplus:
slot 0: a = 2
slot 1: b = 3
```Sau đó, mã byte thực thi:```text id="chrvpi"
LOAD_FAST a
LOAD_FAST b
BINARY_OP +
RETURN_VALUE
```Bước ràng buộc là một phần chính của chi phí cuộc gọi.
## 31.6 Đối số từ khóa
Đối số từ khóa liên kết theo tên.```python id="n28umo"
def area(width, height):
return width * height
area(height=10, width=20)
```Các đối số đến không theo thứ tự tham số, nhưng các khe callee phải được điền chính xác:```text id="j16i8j"
width = 20
height = 10
```CPython phải kiểm tra:```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
```Lỗi ví dụ:```python id="rbyfsw"
area(20, width=10)
```Nguồn cung cấp này`width`hai lần: một lần theo vị trí và một lần theo từ khóa.
## 31.7 Đối số mặc định
Đối số mặc định được lưu trữ trên đối tượng hàm, không được tạo lại trong mỗi lệnh gọi.```python id="d2kqmt"
def f(x, step=1):
return x + step
```Đối tượng hàm lưu trữ giá trị mặc định cho`step`.
Về mặt khái niệm:```text id="zw5t1r"
function f
code object
globals
defaults: (1,)
```Khi được gọi là:```python id="24ks4e"
f(10)
```CPython điền:```text id="n9k9rw"
x = 10
step = 1
```Hành vi mặc định có thể thay đổi nổi tiếng sau đây:```python id="kjt1ip"
def append_item(x, xs=[]):
xs.append(x)
return xs
```Đối tượng danh sách được lưu trữ trong hàm mặc định và được sử dụng lại trong các cuộc gọi.
## 31.8 Đối số chỉ từ khóa
Các thông số chỉ có từ khóa phải được cung cấp theo từ khóa.```python id="53ae7c"
def connect(host, *, timeout, retries=3):
...
```Có hiệu lực:```python id="kjp0p3"
connect("example.com", timeout=10)
```Không hợp lệ:```python id="g6g3ha"
connect("example.com", 10)
```Đối tượng mã chức năng lưu trữ siêu dữ liệu về số lượng và bố cục. Trong quá trình liên kết đối số, CPython sử dụng siêu dữ liệu này để quyết định vị trí cục bộ nhanh nào nhận giá trị.
Về mặt khái niệm:```text id="w1u0wk"
positional slots
keyword-only slots
*args slot, if present
**kwargs slot, if present
```## 31.9 Đối số vị trí đa dạng
A`*args`tham số thu thập các đối số vị trí bổ sung.```python id="hggzqm"
def f(a, *args):
return args
```Gọi:```python id="ve43p4"
f(1, 2, 3)
```ràng buộc:```text id="72r8al"
a = 1
args = (2, 3)
```CPython tạo một bộ dữ liệu cho các đối số vị trí bổ sung. Nếu không có đối số bổ sung nào được truyền, nó sẽ sử dụng một bộ dữ liệu trống.
các`*args`slot vẫn là một slot biến cục bộ thông thường theo quan điểm của mã byte.
## 31.10 Đối số từ khóa đa dạng
A`**kwargs`tham số thu thập các đối số từ khóa bổ sung.```python id="842uzh"
def f(a, **kwargs):
return kwargs
```Gọi:```python id="e27hzk"
f(1, x=2, y=3)
```ràng buộc:```text id="z7vjci"
a = 1
kwargs = {"x": 2, "y": 3}
```CPython tạo một từ điển cho các đối số từ khóa chưa khớp.
Nếu chức năng không có`**kwargs`, các đối số từ khóa không mong muốn là lỗi.
## 31.11 Đối số cuộc gọi được gắn dấu sao
Một cuộc gọi có thể giải nén các đối số vị trí:```python id="2t6bo9"
args = (1, 2)
f(*args)
```CPython phải đánh giá`args`, lặp lại hoặc chuyển đổi nó thành đối số vị trí, sau đó hợp nhất nó vào cuộc gọi.
Cho phép giải nén nhiều lần:```python id="dlam5o"
f(0, *xs, *ys)
```Thời gian chạy phải duy trì thứ tự từ trái sang phải.
Về mặt khái niệm:```text id="g86xe4"
positional arguments =
[0] + list(xs) + list(ys)
```Nếu một đối tượng được giải nén không thể lặp lại được, CPython sẽ tăng`TypeError`.
## 31.12 Đối số cuộc gọi được gắn dấu sao kép
Đối số từ khóa có thể được giải nén khỏi ánh xạ:```python id="gst2dz"
kwargs = {"x": 1, "y": 2}
f(**kwargs)
```Nhiều ánh xạ có thể được cung cấp:```python id="1obqtk"
f(**a, **b)
```CPython phải kiểm tra xem khóa có phải là chuỗi khi gọi hàm Python theo cách thông thường không và phải phát hiện việc gán từ khóa trùng lặp.
Ví dụ:```python id="xha3n8"
f(x=1, **{"x": 2})
```Đây là một lỗi vì`x`được cung cấp hai lần.
## 31.13 Lỗi liên kết đối số
Nhiều lỗi cuộc gọi xảy ra trước khi phần thân hàm bắt đầu.
Ví dụ:```python id="fou3a6"
def f(a, b):
pass
f(1)
f(1, 2, 3)
f(a=1, c=2)
f(1, a=2)
```Những nâng cao`TypeError`.
Vòng đánh giá bắt đầu cuộc gọi nhưng máy gọi thực hiện kiểm tra ràng buộc.
Một cuộc gọi có thể thất bại ở một số giai đoạn:```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 Lệnh gọi hàm Python
Đối với lệnh gọi hàm Python thông thường, CPython chuẩn bị trạng thái thực thi khung mới.```python id="u6ew7w"
def f(a, b):
c = a + b
return c
```Gọi:```python id="ry7s81"
f(2, 3)
```Về mặt khái niệm:```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
```Khung mới trỏ tới cùng một đối tượng mã trong mỗi cuộc gọi, nhưng các vị trí cục bộ khác nhau đối với mỗi cuộc gọi.
## 31.15 Lệnh gọi hàm tích hợp
Các hàm dựng sẵn được triển khai trong C.```python id="47h9j2"
len(xs)
```Cuộc gọi không tạo khung Python cho phần thân của`len`. Thay vào đó, CPython gọi hàm C.
Về mặt khái niệm:```text id="v2dcxi"
LOAD_GLOBAL len
LOAD_FAST xs
CALL 1
call C implementation of len
push result
```Việc triển khai C vẫn nhận các đối tượng Python và trả về một đối tượng Python.
Vì`len(xs)`, nó có thể gọi khe độ dài của đối tượng và trả về số nguyên Python.
Các phần mềm tích hợp nhanh một phần vì chúng tránh được việc thực thi mã byte Python cho phần thân của chúng.
## 31.16 Lệnh gọi hàm mở rộng C
Các hàm mở rộng C tuân theo các nguyên tắc tương tự như các hàm dựng sẵn.
Hàm mở rộng C nhận các đối số dưới dạng đối tượng Python, xác thực chúng, thực hiện công việc gốc và trả về đối tượng Python hoặc báo hiệu một ngoại lệ.
Hình chữ C khái niệm:```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);
}
```Các hàm mở rộng hiện đại có thể sử dụng các quy ước gọi nhanh hơn để tránh đóng gói các đối số vào một bộ dữ liệu.
Bất kể quy ước, quy tắc là:```text id="1edgq5"
return PyObject* on success
return NULL with exception set on failure
```Vòng đánh giá kiểm tra kết quả.
## 31.17 Vectorcall
Vectorcall là quy ước gọi nhanh của CPython dành cho nhiều loại có thể gọi được.
Mục đích của nó là truyền các đối số dưới dạng một mảng C gồm`PyObject *`giá trị thay vì luôn xây dựng các đối tượng tuple và dict tạm thời.
Về mặt khái niệm:```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
```Điều này làm giảm việc phân bổ và sao chép trên các đường dẫn cuộc gọi nóng.
Đối với một cuộc gọi như:```python id="i9fxmj"
f(a, b, c)
```các đối số có thể đã nằm liền kề trên ngăn xếp trình thông dịch. Vectorcall cho phép CPython chuyển trực tiếp vùng giống mảng đó sang phần triển khai có thể gọi được.
## 31.18 Cuộc gọi và phương thức ràng buộc
Cuộc gọi phương thức cần ràng buộc.```python id="yvl8kj"
obj.method(10)
```Ở cấp độ ngôn ngữ, điều này có nghĩa là:```text id="ztwg7z"
look up method on obj
bind obj as self
call method with self and 10
```Nếu như`method`là một hàm được định nghĩa trên lớp, việc truy cập nó thông qua một thể hiện sẽ tạo ra một phương thức ràng buộc về mặt khái niệm:```python id="t8uh02"
bound = obj.method
bound(10)
```Phương thức ràng buộc mang:```text id="qceh4k"
function
self object
```Sau đó gọi nó là vật tư`self`tự động.
CPython tối ưu hóa các lệnh gọi phương thức phổ biến để tránh tạo đối tượng phương thức bị ràng buộc tạm thời khi phương thức đó được gọi ngay lập tức.
## 31.19 Liên kết mô tả
Các cuộc gọi phương thức dựa trên giao thức mô tả.
Một hàm được lưu trữ trên một lớp là một bộ mô tả. Khi được truy cập thông qua một thể hiện, nó`__get__`hành vi tạo ra một phương pháp ràng buộc.
Ví dụ:```python id="bkvw5y"
class C:
def f(self, x):
return x
obj = C()
obj.f(10)
```Về mặt khái niệm:```text id="42zlm9"
C.__dict__["f"].__get__(obj, C)
returns bound method
bound method called with x = 10
```Đây là lý do tại sao:```python id="pmz8uo"
C.f(obj, 10)
```cũng hoạt động. Ở dạng đó, không có ràng buộc cá thể tự động nào xảy ra thông qua quyền truy cập thuộc tính cá thể. Bạn vượt qua`obj`một cách rõ ràng.
## 31.20 Cuộc gọi lớp
Việc gọi một lớp sẽ tạo hoặc khởi tạo một thể hiện.```python id="0bc8io"
obj = C(1, 2)
```Một lớp có thể gọi được vì siêu dữ liệu của nó có thể gọi được. Thông thường siêu dữ liệu là`type`.
Dòng khái niệm:```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
```Ví dụ:```python id="j7g0io"
class C:
def __new__(cls, value):
obj = super().__new__(cls)
return obj
def __init__(self, value):
self.value = value
```cuộc gọi`C(10)`không chỉ đơn thuần là sự phân bổ. Đó là một giao thức liên quan đến`__new__`, `__init__`và hành vi siêu dữ liệu.
## 31.21 Phiên bản có thể gọi được
Một đối tượng có thể gọi được nếu kiểu của nó xác định`__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))
```Cuộc gọi:```python id="f9obht"
add10(5)
```gọi một cách khái niệm:```python id="o2fo9r"
type(add10).__call__(add10, 5)
```Ở cấp độ CPython, loại đối tượng cung cấp hành vi cuộc gọi thông qua các vị trí loại.
## 31.22 Trang trí và lời gọi
Trình trang trí được thực thi tại thời điểm định nghĩa hàm.```python id="hxiutv"
@decorator
def f():
pass
```Về mặt khái niệm:```python id="57azij"
def f():
pass
f = decorator(f)
```Lệnh gọi trang trí xảy ra trong khi thực thi mô-đun, lớp hoặc nội dung hàm chứa.
Nhiều trang trí:```python id="ew1w3e"
@d1
@d2
def f():
pass
```nghĩa là:```python id="hq0xkt"
f = d1(d2(f))
```Điều này quan trọng vì trang trí là các lệnh gọi thông thường. Họ có thể trả về bất kỳ đối tượng có thể gọi được hoặc không thể gọi được.
## 31.23 Cuộc gọi và Kết thúc
Lệnh gọi đóng mang các biến đã được ghi lại thông qua các đối tượng ô.```python id="ld9q93"
def outer(x):
def inner(y):
return x + y
return inner
f = outer(10)
f(5)
```chức năng`inner`có:```text id="bmsuz9"
code object
globals
closure tuple containing cell for x
```Khi được gọi, khung của nó có thể truy cập`x`thông qua tế bào đóng cửa.
Về mặt khái niệm:```text id="lojpq8"
inner frame:
local y = 5
free variable x -> cell -> 10
```Đường dẫn cuộc gọi thiết lập các đối số thông thường và cũng hiển thị các ô đóng đối với`LOAD_DEREF`.
## 31.24 Cuộc gọi và Trình tạo
Việc gọi hàm tạo không chạy phần thân của nó ngay lập tức.```python id="1wn1lz"
def gen():
yield 1
g = gen()
```Cuộc gọi tạo ra một đối tượng máy phát điện.
Phần thân hàm khởi động khi trình tạo được nối lại:```python id="xvobsw"
next(g)
```Về mặt khái niệm:```text id="1gh51l"
call generator function:
create generator object with suspended frame
return generator
next(generator):
resume frame
execute until yield or return
```Đây là điểm khác biệt lớn so với các lệnh gọi hàm thông thường.
## 31.25 Cuộc gọi và Coroutine
Việc gọi hàm async sẽ tạo ra một đối tượng coroutine.```python id="z4c0rv"
async def fetch():
return 1
coro = fetch()
```Phần thân thường không chạy đến mức hoàn thành vào thời điểm gọi. Nó chạy khi được chờ đợi hoặc lên lịch.```python id="tmq7yx"
result = await coro
```Về mặt khái niệm:```text id="3o4iuh"
call async function:
create coroutine object
return coroutine
await coroutine:
execute or resume coroutine frame
```Cuộc gọi hàm tạo ra đối tượng thực thi. Đang chờ thực thi ổ đĩa.
## 31.26 Cuộc gọi đệ quy
Cuộc gọi đệ quy tạo nhiều khung hoạt động cho cùng một đối tượng mã.```python id="mmzccu"
def fact(n):
if n <= 1:
return 1
return n * fact(n - 1)
```Vì`fact(4)`:
```text id="v16tkx"
fact frame: n = 4
fact frame: n = 3
fact frame: n = 2
fact frame: n = 1
```Mỗi khung có trạng thái ngăn xếp và cục bộ nhanh riêng biệt.
CPython kiểm tra độ sâu đệ quy để ngăn chặn đệ quy không kiểm soát được làm cạn kiệt tài nguyên thời gian chạy.
## 31.27 Lệnh gọi và ngoại lệ
Cuộc gọi có thể thoát bằng cách quay lại hoặc nâng lên.```python id="zv234y"
def f():
raise ValueError("bad")
def g():
return f()
```Khi`f`tăng lên, nó không đẩy giá trị trả về bình thường. Ngoại lệ lan truyền qua ngăn xếp cuộc gọi.
Về mặt khái niệm:```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
```Nếu như`g`có một trình xử lý:```python id="g7gg6a"
def g():
try:
return f()
except ValueError:
return 0
```ngoại lệ bị bắt trong`g`và việc thực thi bình thường sẽ tiếp tục ở trình xử lý.
## 31,28 Giá trị trả về
Mỗi hàm Python đều trả về một giá trị.
Nếu không tồn tại giá trị trả về rõ ràng, nó sẽ trả về`None`.
```python id="46x94k"
def f():
pass
```Về mặt khái niệm:```text id="99ydts"
LOAD_CONST None
RETURN_VALUE
```Một biểu thức cuộc gọi luôn mong đợi:```text id="q8p8m0"
a returned Python object
or an exception
```Không có kết quả bình thường thứ ba.
## 31.29 Quyền sở hữu tham chiếu trong các cuộc gọi
Các cuộc gọi có tính chất tham chiếu.
Trong cuộc gọi, CPython phải duy trì hoạt động:```text id="hinb3j"
callable object
argument objects
keyword name objects
temporary bound method state
callee frame
return value
exception objects, if any
```Lệnh gọi phải giải phóng các tham chiếu tạm thời sau khi cuộc gọi thành công hoặc thất bại.
Đường dẫn cuộc gọi được đơn giản hóa:```c id="s4hvbw"
result = PyObject_Call(callable, args, kwargs);
if (result == NULL) {
goto error;
}
push(result);
```Nhưng trước và sau điều này, trình thông dịch phải dọn sạch các tham chiếu đối số và xếp chồng các mục một cách chính xác.
Việc xử lý tham chiếu không tốt trong đường dẫn cuộc gọi có thể làm rò rỉ các đối số hoặc phá hủy các đối tượng vẫn đang được sử dụng.
## 31.30 Cuộc gọi có thể nhập lại Python
Một lệnh gọi bên trong một khung có thể chạy mã Python tùy ý.
Điều này hiển nhiên đối với các lệnh gọi hàm Python trực tiếp, nhưng cũng đúng đối với các thao tác không giống các lệnh gọi.
Ví dụ:```python id="yv3kfl"
obj.x
```có thể gọi`obj.__getattribute__`.
Ví dụ:```python id="44ak0w"
a + b
```có thể gọi`a.__add__`.
Ví dụ:```python id="72yoig"
for x in xs:
...
```có thể gọi các phương thức lặp.
Vì vậy, trình thông dịch phải giả định rằng nhiều thao tác có thể thực thi lại Python.
Ngăn xếp cuộc gọi có thể phát triển từ các cuộc gọi rõ ràng hoặc ẩn:```text id="5p626a"
frame A
executes LOAD_ATTR
calls Python descriptor
frame B
```## 31.31 Tối ưu hóa cuộc gọi phương thức
Mẫu nguồn này là phổ biến:```python id="8wm8xu"
obj.method(arg)
```Việc triển khai ngây thơ sẽ:```text id="ywi46w"
load obj
load method attribute
create bound method object
load arg
call bound method
destroy bound method object
```CPython tối ưu hóa điều này bằng cách nhận dạng các mẫu lệnh gọi phương thức phổ biến.
Đường dẫn được tối ưu hóa tránh phân bổ phương thức ràng buộc tạm thời khi có thể:```text id="d28yc3"
load method function and self separately
call function with self and arguments
```Điều này làm giảm lưu lượng phân bổ và số lượng tham chiếu.
Hành vi ngôn ngữ không thay đổi. Việc tối ưu hóa chỉ thay đổi đường dẫn cuộc gọi nội bộ.
## 31.32 Cuộc gọi và bộ đệm nội tuyến
Mã byte cuộc gọi có thể sử dụng bộ đệm nội tuyến.
Một trang cuộc gọi thường thấy cùng một cuộc gọi lặp đi lặp lại:```python id="on96mz"
for x in xs:
total += f(x)
```Lệnh gọi tại vị trí nguồn này có thể liên tục gọi cùng một chức năng`f`.
CPython có thể lưu trữ thông tin như:```text id="ec11pe"
callable type
function version
argument shape
method lookup result
global lookup version
descriptor result
```Nếu lính canh vượt qua, thông dịch viên có thể sử dụng đường dẫn nhanh.
Nếu có thể gọi thay đổi, nó sẽ quay trở lại hành vi chung.
## 31.33 Cuộc gọi và Chuyên môn
Chuyên môn hóa có thể tối ưu hóa mã nặng cuộc gọi.
Ví dụ:```python id="sky0ma"
def loop(xs):
total = 0
for x in xs:
total += int(x)
return total
```Trang web cuộc gọi cho`int(x)`có thể trở nên chuyên biệt nếu nó liên tục nhìn thấy cùng một hình dạng đối số và có thể gọi được.
Chuyên môn hóa không thay đổi ngữ nghĩa Python. Nó chỉ tăng tốc các trường hợp phổ biến dưới sự bảo vệ.
Hợp đồng ngăn xếp vẫn còn:```text id="nx4fmo"
before call: callable and arguments
after call: result
or exception
```## 31.34 Cuộc gọi và GIL
Trong CPython truyền thống, việc thực thi mã byte Python xảy ra trong khi giữ GIL.
Lệnh gọi tới mã Python vẫn tiếp tục trong mô hình đó.
Lệnh gọi tới mã C có thể giải phóng GIL nếu việc triển khai C chọn thực hiện như vậy đối với công việc kéo dài.
Ví dụ về mã C có thể giải phóng GIL:```text id="99b4lz"
blocking I/O
compression
hashing
numeric kernels
database drivers
system calls
```Điều này có nghĩa là một cuộc gọi có thể tạm thời cho phép một luồng Python khác chạy nếu callee là mã gốc giải phóng GIL.
Đối với các lệnh gọi Python thuần túy, việc thực thi mã byte vẫn được GIL tuần tự hóa trong các bản dựng truyền thống.
## 31.35 Cuộc gọi và Phương thức dựng sẵn
Các phương thức dựng sẵn thường là các bộ mô tả cấp độ C.
Ví dụ:```python id="lz9y2o"
xs.append(1)
```phương pháp`append`được triển khai trong C cho các đối tượng danh sách.
Đường dẫn được tối ưu hóa cao có thể tránh tạo đối tượng phương thức ràng buộc Python và gọi trực tiếp việc triển khai nối thêm danh sách.
Cuộc gọi cấp nguồn:```python id="bx4yzs"
xs.append(1)
```do đó bao gồm:```text id="2d6rn4"
attribute or method resolution
argument setup
C method call
mutation of list object
return None
```Nó nhanh hơn nhiều so với một phương thức tương đương được triển khai trong Python vì logic vòng lặp và đột biến chạy trong C.
## 31.36 Cuộc gọi và`super`
`super()`là một đối tượng có thể gọi và nhận biết mô tả, thay đổi độ phân giải của phương thức.```python id="xskypw"
class Base:
def f(self):
return 1
class Child(Base):
def f(self):
return super().f() + 1
```Cuộc gọi:```python id="onpk7n"
super().f()
```liên quan đến:```text id="s1yqxl"
create or resolve super object
perform attribute lookup using adjusted MRO position
bind method to original instance
call method
```Đây là máy gọi thông thường cộng với độ phân giải thuộc tính đặc biệt.
## 31.37 Lệnh gọi và Thuộc tính
Quyền truy cập thuộc tính có thể gọi mã trước khi cuộc gọi rõ ràng bắt đầu.```python id="utkq7j"
obj.factory()
```Nếu như`factory`là một tài sản:```python id="q5y4ib"
class C:
@property
def factory(self):
return lambda: 42
```Sau đó:```text id="7onci0"
obj.factory
calls property getter
returns callable
(...)
calls returned callable
```Vì vậy, biểu thức nguồn chứa một lệnh gọi ẩn theo sau là một lệnh gọi rõ ràng.
Đây là lý do tại sao CPython phải coi quyền truy cập thuộc tính là có hiệu quả.
## 31.38 Dọn dẹp cuộc gọi và lỗi
Giả sử lệnh gọi này đánh giá một phần các đối số, sau đó không thành công:```python id="xwgmze"
f(g(), h())
```Nếu như`h()`tăng thì:```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
```Ngăn xếp có thể chứa các tham chiếu tạm thời tới`f`Và`g()`kết quả của. CPython phải giải phóng chúng một cách chính xác trong quá trình gỡ lỗi.
Đây là một trong những lý do khiến đường dẫn lỗi mã byte và vòng lặp đánh giá phải cẩn thận.
## 31.39 Kiểm tra cuộc gọi
sử dụng`dis`để kiểm tra mã byte cuộc gọi.```python id="rgh73f"
import dis
def target(a, b):
return a + b
def caller(x):
return target(x, 10)
dis.dis(caller)
```Kiểm tra siêu dữ liệu chức năng:```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)
```Kiểm tra chữ ký ở cấp độ Python:```python id="dcrnsg"
import inspect
print(inspect.signature(target))
```các`inspect`chế độ xem ở cấp độ nguồn và thân thiện với người dùng. Chế độ xem đối tượng mã gần với những gì CPython sử dụng để thiết lập khung.
## 31.40 Phiên dịch cuộc gọi tối thiểu
Một thông dịch viên đồ chơi có thể hiển thị mô hình cuộc gọi.```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")
```Chương trình:```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}))
```Đầu ra:```text id="5boj0q"
15
```Điều này bỏ qua ràng buộc đối số thực, bộ mô tả, API C, ngoại lệ, khung, lệnh gọi vector và tính tham chiếu của Python. Nó vẫn nắm bắt được ý tưởng ngăn xếp: một cuộc gọi sử dụng các đối số và có thể gọi được, sau đó đưa ra một kết quả.
## 31.41 Những hiểu lầm phổ biến
| Hiểu lầm | Đúng mẫu |
|---|---|
| Cuộc gọi luôn tạo khung Python | Các hàm dựng sẵn và hàm C không tạo khung Python cho phần thân của chúng |
| Gọi một hàm async sẽ chạy nó ngay lập tức | Nó tạo ra một đối tượng coroutine |
| Việc gọi hàm tạo sẽ chạy cho đến khi mang lại lợi nhuận đầu tiên | Nó tạo ra một đối tượng máy phát điện |
| Các phương thức luôn phân bổ các đối tượng phương thức bị ràng buộc | CPython thường tránh điều này đối với các cuộc gọi ngay lập tức |
| Đối số từ khóa luôn được truyền dưới dạng lệnh | Đường dẫn nhanh có thể tránh việc xây dựng một lệnh |
|`def`chỉ khai báo một hàm |`def`thực thi và tạo một đối tượng hàm |
| Các lớp không phải là hàm nên chúng không thể gọi được | Các lớp có thể gọi được thông qua logic siêu dữ liệu |
|`obj()`phải là một cuộc gọi hàm | Nó có thể gọi`type(obj).__call__`|
## 31.42 Chiến lược đọc
Để nghiên cứu các cuộc gọi, hãy bắt đầu với các biểu mẫu nguồn đơn giản:```python id="s1he1e"
f()
f(a, b)
f(a=1)
f(*args)
f(**kwargs)
obj.method(x)
C(x)
async_fn()
gen_fn()
```Đối với mỗi người:```python id="cvm0yb"
import dis
dis.dis(function_containing_call)
```Sau đó hỏi:```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?
```Cách tiếp cận này biến cú pháp cuộc gọi thành các hoạt động thông dịch cụ thể.
## 31.43 Tóm tắt chương
Lệnh gọi hàm trong CPython là một hoạt động thời gian chạy có cấu trúc. Trình thông dịch đánh giá khả năng gọi và đối số, sắp xếp chúng trên ngăn xếp khung, chọn giao thức gọi thích hợp, liên kết các đối số khi cần, thực thi khung Python hoặc triển khai C gốc, sau đó đẩy một đối tượng được trả về hoặc truyền một ngoại lệ.
Mô hình trung tâm là:```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ác cuộc gọi rất tốn kém vì chúng vượt qua nhiều ranh giới thời gian chạy: bố cục ngăn xếp, liên kết đối số, liên kết mô tả, thiết lập khung, quy ước API C, quyền sở hữu tham chiếu, xử lý ngoại lệ và chuyên môn hóa.
Phần lớn công việc về hiệu suất của CPython tập trung vào việc thực hiện các cuộc gọi rẻ hơn mà không thay đổi ngữ nghĩa động của Python.