Function annotations được giới thiệu trong Python 3.0, cho phép thêm metadata tùy ý cho tham số hàm và giá trị trả về. Kể từ Python 3, function annotations chính thức được thêm vào (PEP-3107). Mục đích chính: cung cấp cách chuẩn để liên kết metadata với tham số và giá trị trả về.
Cơ bản về Function Annotations
- Annotations hoàn toàn tùy chọn cho cả tham số và giá trị trả về.
- Cung cấp cách liên kết các phần của hàm với biểu thức Python tùy ý tại compile time.
- PEP-3107 không cố gắng đưa ra ngữ nghĩa chuẩn, ngay cả cho built-in types. Toàn bộ việc này để cho thư viện bên thứ ba.
Cú pháp
Annotations cho tham số đơn giản
def foo(x: expression, y: expression = 20):
...
Annotations cho tham số thừa (excess parameters)
def foo(*args: expression, **kwargs: expression):
...
Tham số lồng nhau (nested parameters)
Annotations luôn theo sau tên tham số, không phải sau ngoặc cuối. Không bắt buộc annotate tất cả tham số lồng nhau.
def foo((x1, y1: expression), (x2: expression, y2: expression)=(None, None)):
...
Python không cung cấp ngữ nghĩa với annotations. Chỉ cung cấp hỗ trợ cú pháp để liên kết metadata và cách truy cập dễ dàng. Annotations không bắt buộc.
>>> def func(x:'annotating x', y: 'annotating y', z: int) -> float:
... print(x + y + z)
Ví dụ: func() nhận 3 tham số x, y, z, in tổng. Tham số đầu x annotate chuỗi 'annotating x', y annotate 'annotating y', z annotate kiểu int. Giá trị trả về annotate kiểu float. Cú pháp -> dùng cho annotate giá trị trả về.
Kết quả
>>> func(2,3,-4)
1
>>> func('Function','-','Annotation')
Function-Annotation
Gọi func() hai lần: một lần với int, một lần với string. Cả hai đều chạy đúng, annotations bị bỏ qua. Annotations không ảnh hưởng đến thực thi hàm.
Truy cập Function Annotations
Tất cả annotations lưu trong dictionary __annotations__, thuộc tính của hàm:
>>> def func(x:'annotating x', y: 'annotating y', z: int) -> float:
... print(x + y + z)
>>> func.__annotations__
{'x': 'annotating x', 'y': 'annotating y', 'z': , 'return': }
Annotations không phải khai báo kiểu, tuy nhiên có thể dùng cho mục đích đó và giống cú pháp typing trong ngôn ngữ khác.
>>> def func(a: 'python', b: {'category: ' 'language'}) -> 'yep':
... pass
>>> func.__annotations__
{'a': 'python', 'b': {'category: language'}, 'return': 'yep'}
Chúng là biểu thức tùy ý, nghĩa là giá trị tùy ý có thể lưu trong __annotations__. Mặc dù không thêm ý nghĩa cho Python, nhưng định nghĩa kiểu tham số và trả về là trường hợp sử dụng phổ biến.
Decorator @no_type_check
Nếu dùng công cụ giả định annotations là khai báo kiểu nhưng muốn dùng cho mục đích khác, dùng decorator chuẩn @no_type_check để miễn trừ hàm khỏi xử lý đó:
>>> from typing import no_type_check
>>> @no_type_check
... def func(a: 'python', b: {'category: ' 'language'}) -> 'yep':
... pass
Annotations làm đầu vào cho Function Decorators
Annotations kết hợp tốt với decorators vì giá trị annotation cung cấp cách tốt để truyền đầu vào cho decorator, và wrapper do decorator tạo là nơi tốt để đặt code gán nghĩa cho annotations.
from functools import wraps
def adapted(func):
@wraps(func)
def wrapper(**kwargs):
final_args = {}
for name, value in kwargs.items():
adapt = func.__annotations__.get(name)
if adapt is not None:
final_args[name] = adapt(value)
else:
final_args[name] = value
result = func(**final_args)
adapt = func.__annotations__.get('result')
if adapt is not None:
return adapt(result)
return result
return wrapper
@adapted
def func(a: int, b: repr) -> str:
return a
Decorator adapted bao hàm hàm trong wrapper. Wrapper chỉ chấp nhận keyword arguments, nghĩa là ngay cả khi hàm gốc chấp nhận positional arguments, chúng phải được chỉ định bằng tên.
Keyword Arguments
Đôi khi một hoặc nhiều tham số của phương thức không cần xử lý ngoài việc gán cho thuộc tính của self. Có thể dùng decorators và annotations để tự động hóa?
from functools import wraps
def store_args(func):
@wraps(func)
def wrapper(self, **kwargs):
for name, value in kwargs.items():
attrib = func.__annotations__.get(name)
if attrib is True:
attrib = name
if isinstance(attrib, str):
setattr(self, attrib, value)
return func(self, **kwargs)
return wrapper
class A:
@store_args
def __init__(self, first: True, second: 'example'):
pass
a = A(first=5, second=6)
assert a.first == 5
assert a.example == 6