مفهوم Annotation در پایتون

آشنایی با مفهوم Annotation در پایتون
در این مقاله به آشنایی با مفهوم Annotation در پایتون میپردازیم . پایتون یک زبان برنامهنویسی Dynamic است. یعنی نوع متغیرها از قبل مشخص نیست و حتی میتواند چند بار عوض شود.Annotation یکی از ویژگیهای زبان برنامهنویسی پایتون که از نسخۀ 3.0 به بعد به این زبان افزوده شده است، مفهومی میباشد تحت عنوان Annotation که این امکان را برای دولوپرها فراهم میکند تا بتوانند توضیحات مورد نظر و همچنین نوع دادۀ مربوط به آرگومان ورودی، دستورات داخلی آن و همچنین مقدار بازگشتی مورد انتظار خود را اضافه نمایند.
Annotation

بهکارگیری مفهوم Annotation جهت تعریف Type پارامترهای ورودی فانکشن
همانطور که اشاره کردیم، با استفاده از قابلیت Annotation میتوانیم نوع دادۀ مد نظر برای آرگومانهای ورودی به یک فانکشن در هنگام فراخوانی آن را مشخص نماییم که برای این منظور میتوانیم سینتکس کلی زیر را مد نظر قرار دهیم:
def myFunction(a: expression, b: expression):
expression
در سینتکس فوق، به منظور استفاده ازمفهوم Annotation در پایتون برای پارامترهای ورودی در تعریف فانکشن مد نظر ابتدا نام پارامتر ورودی را نوشته و به دنبال آن یک علامت :
قرار داده سپس توضیحات یا نوع دادۀ مورد نظر را درج مینماییم که برای درک بهتر این موضوع داریم:
>>> def multiply(a: int, b: 'annotating b', c: int):
print(a * b * c)
>>> multiply(1, 2, 3)
6
در کد فوق، فانکشنی تحت عنوان ()multiply
با سه پارامتر ورودی تحت عناوین b
،a
و c
تعریف کردهایم که در آن گفتهایم جهت دستیابی به عملکرد مورد انتظار از فانکشن در راستای ضرب اعداد صحیح، آرگومان ورودی منتسب به متغیر a
در هنگام فراخوانی فانکشن میباید از نوع دادۀ int
یا عدد صحیح باشد و در ادامه برای پارامتر دوم استرینگی دلخواه در قالب عبارت «annotating b» درج نمودهایم که به عنوان مثال میتوان در چنین مواردی توضیحی در رابطه با ماهیت و همچنین نحوۀ عملکرد پارامتر ورودی به سورسکد اضافه کرد.
نکته مهم این است که مفس پایتون خود را ملزم به رعایت این نوع داده ای نمیداند و فقط در جهت توضیح بیشتر کد میتوانیم از آن استفاده کنیم .
بعد از تعریف در IDE متوجه تغییر می شوید :
زمانی که نوع متغیرها مشخص شده باشند، IDE هم میفهمد برنامهنویس احتمالاً میخواهد چه کار کند:
ماژول typing
تا اینجا میتوانیم نوعهای ساده مانند int, str را مشخص کنیم اما کارهای پیچیدهتری هم وجود دارند. فرض کنید تابعی داریم که لیستی از اعداد میگیرد. خب، list را میتوانیم این شکلی مشخص کنیم:
def f(a: list):
...
ولی با دانش فعلی نمیتوانیم مشخص کنیم که آن لیستْ حاوی اعداد است یا استرینگ یا چیز دیگری.
برای این کار ماژولی به نام typing وجود دارد که کمک میکند typeهای پیچیدهتری annotate کنیم. میتوانید جزئیات این ماژول را در مستنداتش بخوانید. ولی در اینجا به چند مورد کاربردش اشاره میکنیم.
مقالات دیگر : تکنیک Agile در برنامه نویسی
List
برای همین لیستی که مثال زدیم، میتوان اینگونه نوشت:
from typing import List
def f(a: List[int]):
...
همین طور که مشخص است، پس ایمپورت کردن، میتوان نوع داخل لیست را داخل [ و ] نوشت. البته مستندات پیشنهاد میدهد که برای آرگومان تابع از لیست استفاده نکنیم. به جایش از Sequence یا Iterable استفاده کنیم.
Tuple
تاپل مشابه لیست است اما کمی با لیست تفاوت دارد. از آنجا که تاپل غیرقابل تغییر است، میتوان برای تک تک اعضایش نوع تعیین کرد:
from typing import Tuple
a: Tuple[int, str] = (2, ‘ali’)
b: Tuble[int, int, int] = (5, 9, 3)
c: Tuple[str, str, str] = (‘a’, ‘b’, 9) # problem
اگر تعداد اعضای تاپل را ندانیم، میتوان از ‘…’ استفاده کرد:
Tuble[int, ...] = (5, 9, 3)
این یعنی یک تاپل از اعداد داریم.
CORS چیست؟
Dict
برای دیکشنری هم از براکت استفاده میشود. در براکت اول نوع کلید و سپس، نوعِ value را مینویسیم:
Dict[str, int] = {'one': 1, 'two': 2}
Set
برای مجموعهها همانطور که احتمالاً حدث میزنید نوع را در میان براکت قرار میدهیم:
1a: Set[int] = {1, 5, 9}
انواع پیچیدهتر
فرض کنید یک لیست از دیکشنریها داریم به این شکل:
a = [
{1: 'one', 2: 'two', 3: 'three'},
{11: 'eleven', 12: 'twelve', 13: 'thirteen'}
]
برای این اشکال تودرتو، باید از از ماژول typing به شکل تودرتو استفاده کنیم:
List[Dict[int, str]]
همانطور که مشخص است، در براکتِ لیست دوباره از یکی از انواع ماژول typing استفاده میکنیم. میتوان این کار را هر چند بار که نیاز باشد تکرار کرد.
Sequence
در بخش List گفتیم که مستندات پیشنهاد میدهد که برای آرگومانهای توابع حتی اگر قرار است لیست باشند، از Sequence استفاده کنیم. خود Sequence یک کلاس است و لیست هم یکی از subclassهای Sequence است:
>>> from collections.abc import Sequence
>>> issubclass(list, Sequence)
True
اکثر قابلیتهای پرکاربرد لیست، از Sequence ارثبری شدهاند. پس اگر من یک کلاس برای خودم درست کنم و از Sequence ارثبری کنم، به احتمال زیاد میتوانم اشیاءِ این کلاس جدید را هم در تابع استفاده کنم. خلاصه، بهتر است تابعی که در بخش List دیدیم را با Sequence تعریف کنیم:
from typing import Sequence
def f(a: Sequence[int]):
...
Type Aliases
نوشتن این تایپهای تودرتو خیلی سخت نیست. اما اگر بخواهید از این تایپ را چندین بار استفاده کنید، استفاده از آن سخت و زمانبر و البته زشت میشود. برای راحتتر شدن کار بهتر است که یک بار برای همیشه برای آن نوع خاص یک نام کوتاه انتخاب کنیم و هر بار از آن نام استفاده کنیم، به جای اینکه آن نوع طولانی را دوباره بنویسیم.
فرض کنید که من میخواهم برنامهای برای کار با بردارهای سهبعدی بنویسم. یک بردار را با یک تاپل که 3 آیتمِ int دارد نشان میدهم و تعداد زیادی تابع دارم که آرگومانها و خروجیشان از نوع همین تاپل هستند. به جای اینکه چندین بار عبارت Tuple[int, int, int] را برای هر تابع تکرار کنم، میتوانم در ابتدای برنامه یک اسم – مثلاً Vector – انتخاب کنم:
Vector = Tuple[int, int, int]
و از این به بعد فقط از Vector استفاده کنم تا هم سریعتر بنویسم، هم کمتر اشتباه کنم و هم کدم خواناتر و زیباتر باشد:
def cross(v1: Vector, v2: Vector) -> Vector:
...
تابع بدون مقدار بازگشتی
در پایتون همیشه چیزی بازمیگرداند. معمولاً در بدنهی تابع عبارت return وجود دارد که مشخص کند چه چیزی برگردد. اگر return وجود نداشت، تابع مقدار None را بر میگرداند. خوب است حتی وقتی تابع چیزی را بر نمیگرداند، نوع بازگشتی را مشخص کنیم:
def f(message: str) -> None:
print(message)
دیدگاهتان را بنویسید