ویرگول
ورودثبت نام
امیرحسین رحمتی
امیرحسین رحمتی
خواندن ۵ دقیقه·۳ سال پیش

type hints در پایتون

منبع
منبع

پایتون یک زبان برنامه‌نویسی Dynamic است. یعنی نوع متغیرها از قبل مشخص نیست و حتی می‌تواند چند بار عوض شود. ولی دانستن نوع یک متغیر از قبل چند مزیت دارد:

  1. در صورتی که بتوان نوع یک متغیر را در کد نوشت، IDE می‌تواند در نوشتن کد به ما کمک کند. اگر IDE نداند که نوع یک متغیر چیست، باید برنامه‌نویس نام متدها و خواص آن متغیر را حفظ باشد و بنویسد، ولی اگر IDE نوع متغیر را بداند، می‌تواند به برنامه‌نویس در نوشتن کد کمک کند و نوشتن برنامه بسیار راحت‌تر می‌شود.
  2. خوانایی کد بهتر می‌شود.
  3. می‌توان بعداً با برنامه‌هایی مانند mypy کد را چک کرد و خطاهای احتمالی را پیدا کرد.

در پایتون 3 برای مشخص کردن نوع داده از annotation استفاده می‌شود.

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 می‌کند.

Type Hint

حالا از 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 هم می‌فهمد برنامه‌نویس احتمالاً می‌خواهد چه کار کند:

ماژول typing

تا اینجا می‌توانیم نوع‌های ساده مانند int و str را مشخص کنیم اما کارهای پیچیده‌تری هم وجود دارند. فرض کنید تابعی داریم که لیستی از اعداد می‌گیرد. خب، list را می‌توانیم این شکلی مشخص کنیم:

def f(a: list): ...

ولی با دانش فعلی نمی‌توانیم مشخص کنیم که آن لیستْ حاوی اعداد است یا استرینگ یا چیز دیگری.

برای این کار ماژولی به نام typing وجود دارد که کمک می‌کند typeهای پیچیده‌تری را annotate کنیم. می‌توانید جزئیات این ماژول را در مستنداتش بخوانید. ولی در اینجا به چند مورد کاربردش اشاره می‌کنیم.

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

اگر تعداد اعضای تاپل را ندانیم، می‌توان از '...' استفاده کرد:

b: Tuble[int, ...] = (5, 9, 3)

این یعنی یک تاپل از اعداد داریم.

Dict

برای دیکشنری هم از براکت استفاده می‌شود. در براکت اول نوع کلید و سپس، نوعِ value را می‌نویسیم:

d: Dict[str, int] = {'one': 1, 'two': 2}

Set

برای مجموعه‌ها همانطور که احتمالاً حدث می‌زنید نوع را در میان براکت قرار می‌دهیم:

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 استفاده می‌کنیم. می‌توان این کار را هر چند بار که نیاز باشد تکرار کرد.

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)

سعی می‌کنم این مقاله کامل‌تر کنم.

در صورتی که در مطلب ایرادی دیدید یا پیشنهادی داشتید، خوشحال می‌شوم در قسمت نظرات بنویسید.

منابع بیش‌تر

در این مورد شاید مهم‌ترین مرجع PEP 484 باشد که جامع توضیح داده - از آنجایی که زیادی جامع و طولانی بود، خودم نخواندم.

https://www.python.org/dev/peps/pep-0484/

سایت Real Python مقاله‌ی خوبی در این زمینه دارد.

https://realpython.com/python-type-checking/

مستندات ماژول typing وقتی که برای یک نوع خاص سوال دارید، به درد می‌خورد.

https://docs.python.org/3/library/typing.html


پایتونpythonبرنامه نویسیtype hintpython 3
شاید از این پست‌ها خوشتان بیاید