Function Annotations ที่นำมารวมใน Python 3.0 เพิ่มคุณสมบัติที่อนุญาตให้คุณเพิ่ม metadata อนุญาตให้เพิ่ม metadata เฉพาะเจาะจงไปยังพารามิเตอร์ฟังก์ชันและค่าที่คืนกลับ ตั้งแต่ Python 3 function annotations ได้ถูกเพิ่มอย่างเป็นทางการใน Python (PEP-3107) วัตถุประสงค์หลักคือมีวิธีมาตรฐานในการเชื่อมโยง metadata กับพารามิเตอร์ฟังก์ชันและค่าที่คืนกลับ
พื้นฐานของ Function Annotations
มาเข้าใจพื้นฐานของ function annotations กัน −
Function annotations เป็นสิ่งที่ optional ทั้งสำหรับพารามิเตอร์และค่าที่คืนกลับ
Function annotations ให้วิธีในการเชื่อมโยงส่วนต่างๆ ของฟังก์ชันกับนิพจน์ Python ที่ arbitrary ที่ compile time
PEP-3107 ไม่พยายามนำ semantic มาตรฐานใดๆ เข้ามา แม้แต่สำหรับ built-in types ทั้งหมดงานนี้ถูกทิ้งไว้ให้ไลบรารีของบุคคลที่สาม
ไวยากรณ์
Annotations ของพารามิเตอร์ธรรมดา
Annotations สำหรับพารามิเตอร์มีรูปแบบดังนี้ −
def foo(x: expression, y: expression = 20): ...
สำหรับ annotations ของ excess parameters เป็นดังนี้ −
def foo(**args: expression, **kwargs: expression): ...
ในกรณีของ nested parameters annotations จะอยู่หลังชื่อพารามิเตอร์เสมอ ไม่ใช่จนถึงวงเล็บปิดท้ายที่สุด ไม่จำเป็นต้อง annotate พารามิเตอร์ทั้งหมดของ nested parameter
def foo(x1, y1: expression), (x2: expression, y2: expression)=(None, None)): ...
สำคัญที่ต้องเข้าใจว่า Python ไม่ได้ให้ semantics ใดๆ กับ annotations มันเพียงให้ syntactic support ที่ดีสำหรับการเชื่อมโยง metadata รวมถึงวิธีเข้าถึงที่ง่ายดาย นอกจากนี้ annotations ไม่ได้เป็นบังคับ
>>> def func(x:'annotating x', y: 'annotating y', z: int) -> float: print(x + y + z)
ในตัวอย่างข้างต้น ฟังก์ชัน func() รับสามพารามิเตอร์คือ x,y และ z สุดท้ายพิมพ์ผลรวมของพวกมัน อาร์กิวเมนต์แรก x มี annotation เป็นสตริง 'annotating x' อาร์กิวเมนต์ที่สอง y มี annotation เป็นสตริง 'annotating y' และอาร์กิวเมนต์ที่สาม z มี annotation เป็น type int ค่าที่คืนกลับมี annotation เป็น type float นี่คือไวยากรณ์ '->' สำหรับ annotate ค่าที่คืนกลับ
ผลลัพธ์
>>> func(2,3,-4) 1 >>> func('Function','-','Annotation') Function-Annotation
ข้างต้นเราเรียก func() สองครั้ง ครั้งหนึ่งด้วยอาร์กิวเมนต์ int และอีกครั้งด้วยอาร์กิวเมนต์สตริง ในทั้งสองกรณี func() ทำงานถูกต้องและ annotations ถูกเพิกเฉยไป ดังนั้นเราเห็นว่า annotations ไม่มีผลต่อการทำงานของฟังก์ชัน func()
การเข้าถึง Function Annotations
Annotations ทั้งหมดถูกเก็บในดิกชันนารีที่ชื่อว่า __annotations__ ซึ่งเป็น attribute ของฟังก์ชัน −
>>> 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 ไม่ใช่การประกาศ type แม้จะน่าจะใช้เพื่อวัตถุประสงค์นั้นได้ และพวกมันคล้ายไวยากรณ์ typing ที่ใช้ในภาษาอื่นๆ ดังแสดงด้านล่าง −
>>> def func(a: 'python', b: {'category: ' 'language'}) -> 'yep': pass >>> func.__annotations__ {'a': 'python', 'b': {'category: language'}, 'return': 'yep'} >>>
พวกมันคือนิพจน์ arbitrary ซึ่งหมายความว่าค่า arbitrary สามารถเก็บใน __annotations__ dictionary ได้ แม้ว่าพวกมันจะไม่เพิ่มความสำคัญมากนักให้กับ Python เอง ยกเว้นว่ามันควรเก็บค่าไว้ นั้น कहाแล้ว การกำหนดพารามิเตอร์และชนิดค่าที่คืนกลับเป็นการใช้งาน function annotations ที่พบบ่อย
Decorator @no_type_check
หากคุณพบว่าตัวเองใช้เครื่องมือที่สมมติว่า annotations เป็นการประกาศ type แต่คุณต้องการใช้พวกมันเพื่อวัตถุประสงค์อื่น ให้ใช้ decorator @no_type_check มาตรฐานเพื่อยกเว้นฟังก์ชันของคุณจากการประมวลผลเช่นนั้น ดังแสดงที่นี่ −
>>> from typing import no_type_check >>> @no_type_check def func(a: 'python', b: {'category: ' 'language'}) -> 'yep': pass >>>
ปกติแล้ว ไม่จำเป็น เพราะเครื่องส่วนใหญ่ที่ใช้ annotations มีวิธีรู้จัก annotations ที่หมายถึงพวกมัน Decorator นี้สำหรับปกป้อง corner cases ที่สิ่งต่างๆ 不明确
Annotations เป็นอินพุตให้ Function Decorators
Annotations รวมตัวกับ decorators ได้ดี เพราะค่า annotations ทำเป็นวิธีที่ดีในการให้อินพุตแก่ decorator และ wrapper ที่ decorator สร้างเป็นที่ดีในการวางโค้ดที่ให้ความหมายกับ 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
ดังนั้น adapted decorator ห่อฟังก์ชันไว้ใน wrapper wrapper นี้รับเฉพาะ keyword arguments ซึ่งหมายถึงว่า แม้ฟังก์ชันต้นฉบับจะรับ positional arguments ได้ พวกมันต้องระบุโดยชื่อ
เมื่อฟังก์ชันถูกห่อแล้ว wrapper ยังค้นหา adapters ใน annotations ของพารามิเตอร์ฟังก์ชันและนำไปใช้ก่อนส่งอาร์กิวเมนต์ไปยังฟังก์ชันจริง
เมื่อฟังก์ชันคืนค่า wrapper ตรวจสอบ return value adapter หากพบ จะนำไปใช้กับค่าที่คืนกลับก่อนคืนค่าสุดท้าย
เมื่อพิจารณาผลลัพธ์ของสิ่งที่เกิดขึ้นที่นี่ พวกมันค่อนข้างน่าประทับใจ เราได้ปรับเปลี่ยนความหมายของการส่งพารามิเตอร์ให้ฟังก์ชันหรือคืนค่า
Keyword arguments
บางครั้ง พารามิเตอร์หนึ่งหรือมากกว่าของเมธอด ไม่ต้องการการประมวลผลใดๆ ยกเว้นการกำหนดให้เป็น attribute ของ self เราสามารถใช้ decorators และ annotations ทำให้สิ่งนี้เกิดขึ้นอัตโนมัติได้ไหม? แน่นอนว่าได้
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