پایتون یک زبان برنامهنویسی Dynamic است. یعنی نوع متغیرها از قبل مشخص نیست و حتی میتواند چند بار عوض شود. ولی دانستن نوع یک متغیر از قبل چند مزیت دارد:
در پایتون 3 برای مشخص کردن نوع داده از annotation استفاده میشود.
در پایتون 3 میتوان برای یک متغیر توضیحاتی نوشت تا خوانایی برنامه افزایش پیدا کند. این توضیحات میتواند هر چیزی باشد مانند یک string یا عدد یا نام یک کلاس.
یک متغیر را به این شکل annotate میکنند:
a: 'first name' = 'ali' b: 'last name' = 'kazemi'
در تعریف تابع هم میتوان برای آرگومانها و خروجی تابع annotation قرار داد:
def full_name(f: 'first name', l: 'last name') -> 'full name': return (f + ' ' + l).title()
قسمت full name خروجی تابع را annotate میکند.
حالا از annotation برای مشخص کردن نوع متغییر استفاده میکنیم. برای این کار در قسمت annotation نوع را مینویسیم:
first_name: str = 'ali' last_name: str = 'kazemi' def full_name(first_name: str, last_name: str) -> str: return (first_name + ' ' + last_name).title()
خوبی این کار این است که حالا IDE میتواند به ما در نوشتن کد کمک کند.
بدون این کار IDE نمیتواند در نوشتن کد کمکی بکند چون نمیداند چطور باید کد را کامل کند:
اما زمانی که نوع متغیرها مشخص شده باشند، IDE هم میفهمد برنامهنویس احتمالاً میخواهد چه کار کند:
تا اینجا میتوانیم نوعهای ساده مانند int و str را مشخص کنیم اما کارهای پیچیدهتری هم وجود دارند. فرض کنید تابعی داریم که لیستی از اعداد میگیرد. خب، list را میتوانیم این شکلی مشخص کنیم:
def f(a: list): ...
ولی با دانش فعلی نمیتوانیم مشخص کنیم که آن لیستْ حاوی اعداد است یا استرینگ یا چیز دیگری.
برای این کار ماژولی به نام typing وجود دارد که کمک میکند typeهای پیچیدهتری را annotate کنیم. میتوانید جزئیات این ماژول را در مستنداتش بخوانید. ولی در اینجا به چند مورد کاربردش اشاره میکنیم.
برای همین لیستی که مثال زدیم، میتوان اینگونه نوشت:
from typing import List def f(a: List[int]): ...
همین طور که مشخص است، پس از ایمپورت کردن، میتوان نوع محتویات لیست را داخل [ و ] نوشت. البته مستندات پیشنهاد میدهد که برای آرگومان تابع از لیست استفاده نکنیم. به جایش از Sequence یا Iterable استفاده کنیم.
تاپل مشابه لیست است اما کمی با لیست تفاوت دارد. از آنجا که تاپل غیرقابل تغییر است، میتوان برای تک تک اعضایش نوع تعیین کرد:
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
اگر تعداد اعضای تاپل را ندانیم، میتوان از '...' استفاده کرد:
b: Tuble[int, ...] = (5, 9, 3)
این یعنی یک تاپل از اعداد داریم.
برای دیکشنری هم از براکت استفاده میشود. در براکت اول نوع کلید و سپس، نوعِ value را مینویسیم:
d: Dict[str, int] = {'one': 1, 'two': 2}
برای مجموعهها همانطور که احتمالاً حدث میزنید نوع را در میان براکت قرار میدهیم:
a: Set[int] = {1, 5, 9}
فرض کنید یک لیست از دیکشنریها داریم به این شکل:
a = [ {1: 'one', 2: 'two', 3: 'three'}, {11: 'eleven', 12: 'twelve', 13: 'thirteen'} ]
برای این اشکال تودرتو، باید از از ماژول typing به شکل تودرتو استفاده کنیم:
List[Dict[int, str]]
همانطور که مشخص است، در براکتِ لیست دوباره از یکی از انواع ماژول typing استفاده میکنیم. میتوان این کار را هر چند بار که نیاز باشد تکرار کرد.
در بخش 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]): ...
نوشتن این تایپهای تودرتو خیلی سخت نیست. اما اگر بخواهید از این تایپ را چندین بار استفاده کنید، استفاده از آن سخت و زمانبر و البته زشت میشود. برای راحتتر شدن کار بهتر است که یک بار برای همیشه برای آن نوع خاص یک نام کوتاه انتخاب کنیم و هر بار از آن نام استفاده کنیم، به جای اینکه آن نوع طولانی را باز بنویسیم.
فرض کنید که من میخواهم برنامهای برای کار با بردارهای سهبعدی بنویسم. یک بردار را با یک تاپل که 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)
سعی میکنم این مقاله کاملتر کنم.
در صورتی که در مطلب ایرادی دیدید یا پیشنهادی داشتید، خوشحال میشوم در قسمت نظرات بنویسید.
در این مورد شاید مهمترین مرجع PEP 484 باشد که جامع توضیح داده - از آنجایی که زیادی جامع و طولانی بود، خودم نخواندم.
سایت Real Python مقالهی خوبی در این زمینه دارد.
مستندات ماژول typing وقتی که برای یک نوع خاص سوال دارید، به درد میخورد.