آنچه در این مقاله میخوانید [پنهانسازی]
اگر می خواهید خطاهای ناشی از نوع داده را قبل از اجرا پیدا کنید و خوانایی کد را بالا ببرید، از Type Hint در پایتون همراه با ابزارهایی مثل typing و mypy استفاده کنید. کافی است انواع را به امضاهای توابع و متغیرها اضافه کنید و با اجرای mypy، ناسازگاری ها و اشتباهات معمول را سریع ببینید.
چرا و چه زمانی از Type Hint استفاده کنیم؟
Type Hint در پایتون قراردادی است که مشخص می کند هر تابع چه ورودی می گیرد و چه خروجی می دهد. پایتون در زمان اجرا معمولا این قرارداد را اجبار نمی کند، اما ابزارهایی مانند mypy آن را پیش از اجرا تحلیل می کنند. نتیجه این کار:
- خوانایی بالاتر و قراردادی روشن برای API ها
- تکمیل خودکار و ناوبری بهتر در IDE
- کشف خطاهای رایج قبل از اجرا (مثل بازگرداندن None وقتی انتظار رشته دارید)
- امن تر شدن تغییرات و ریفکتور
زمانی که ماژول عمومی می نویسید، با داده های خارجی (JSON، فایل، شبکه) کار می کنید یا تیمی توسعه می دهید، Type Hint ها بیشترین فایده را دارند.
شروع سریع: افزودن انوتیشن و اجرای mypy
- نصب mypy:
pip install mypy
- یک تابع را با Type Hint مشخص کنید:
def add(a: int, b: int) -> int:
return a + b
- mypy را روی فایل یا پوشه اجرا کنید:
mypy your_module.py
# یا روی کل پروژه
mypy src/
اگر نوع ها ناسازگار باشد، mypy خطا می دهد؛ در غیر این صورت «Success: no issues found» می بینید.
مثال عملی: کشف خطا قبل از اجرا
سناریو: می خواهید میانگین اعداد یک ستون CSV را حساب کنید. اما رشته ها را به اعداد تبدیل نکرده اید. در زمان اجرا ممکن است با TypeError مواجه شوید. mypy این را قبل از اجرا هشدار می دهد.
from typing import Iterable
def average(nums: Iterable[int]) -> float:
total = 0
count = 0
for n in nums:
total += n
count += 1
return total / count
# داده خام: رشته هایی که از فایل خوانده شده اند
raw = ["10", "20", "30"]
print(average(raw)) # اشتباه: Iterable[str] به جای Iterable[int]
خروجی mypy:
your_module.py:14: error: Argument 1 to "average" has incompatible type "list[str]"; expected "Iterable[int]"
Found 1 error in 1 file (checked 1 source file)
اصلاح درست:
clean = [int(x) for x in raw]
print(average(clean))
تایپ های پرکاربرد در typing
برای نسخه های جدید پایتون، از شکل ساده list[int]، dict[str, int] و مانند آن استفاده کنید. اگر نسخه شما این نحو را ندارد، همتای typing.List و typing.Dict را به کار ببرید.
# کالکشن ها
numbers: list[int] = [1, 2, 3]
user_by_id: dict[int, str] = {1: "Ali"}
# Optional و Union
from typing import Optional, Union
def find_name(user_id: int) -> Optional[str]:
...
def parse(value: str) -> int | float:
...
# Callable
from typing import Callable
Op = Callable[[int, int], int]
def apply(a: int, b: int, op: Op) -> int:
return op(a, b)
# Any در برابر object
from typing import Any
def log_any(x: Any) -> None:
# Any همه چیز را می پذیرد؛ با احتیاط استفاده کنید
print(x)
def accepts_any_object(x: object) -> None:
# برای استفاده از متدهای خاص باید نوع را بررسی/تبدیل کنید
if isinstance(x, str):
print(x.upper())
نکته: Any بررسی نوع را دور می زند. اگر می خواهید ایمن تر باشید، از object برای ورودی های کلی استفاده کنید و در بدنه تابع نوع را بررسی کنید.
مدل داده تایپ شده: dataclass و TypedDict
برای داده های ساختارمند درون برنامه، ترکیب Type Hint با dataclass کد را تمیز و قابل نگهداری می کند.
from dataclasses import dataclass
@dataclass
class User:
id: int
name: str
email: str | None = None
اگر با JSON یا دیکشنری های برآمده از API سروکار دارید، TypedDict ساختار کلیدها و نوع مقادیر را مشخص می کند.
from typing import TypedDict
class UserPayload(TypedDict):
id: int
name: str
email: str | None
def from_payload(p: UserPayload) -> User:
return User(id=p["id"], name=p["name"], email=p["email"])
برای آشنایی با طراحی مدل های تمیز در پایتون می توانید به آموزش dataclass در پایتون برای مدل های داده تمیز مراجعه کنید.
پروتکل و جنریک ها: تایپ قدرتمند بدون قفل شدن به کلاس خاص
Protocol به شما اجازه می دهد بر اساس «شکل» یا رفتار آبجکت ها تایپ کنید، نه کلاس دقیقشان (duck typing).
from typing import Protocol
class Writer(Protocol):
def write(self, data: str) -> int: ...
def dump_config(w: Writer, text: str) -> None:
w.write(text)
حالا هر آبجکتی که متد write(str) -> int داشته باشد پذیرفته می شود؛ چه فایل باشد، چه بافر سفارشی.
با TypeVar هم جنریک می نویسید:
from typing import TypeVar, Sequence
T = TypeVar("T")
def first(items: Sequence[T]) -> T | None:
return items[0] if items else None
پیکربندی mypy: از حداقل تا سختگیرانه
به صورت پیش فرض، mypy نرم گیر است. بهتر است قواعد را به تدریج سخت تر کنید. یک فایل mypy.ini کنار پروژه بگذارید:
[mypy]
python_version = 3.10
ignore_missing_imports = True
disallow_untyped_defs = True
no_implicit_optional = True
warn_return_any = True
warn_unused_ignores = True
[mypy-tests.*]
# به طور مثال روی پوشه تست سختگیری کمتر باشد
disallow_untyped_defs = False
نکته ها:
- ignore_missing_imports اگر کتابخانه ای فایل تایپ نداشته باشد، خطا نمی دهد. بعدا می توانید آن را هدفمند کم کنید.
- disallow_untyped_defs از تعریف توابع بدون انوتیشن جلوگیری می کند.
- no_implicit_optional نیازمند مشخص کردن Optional است وقتی None محتمل باشد.
می توانید همین تنظیم ها را در pyproject.toml هم قرار دهید.
پذیرش تدریجی در پروژه موجود
- از ماژول های مرکزی و API های عمومی شروع کنید.
- برای هر PR، توابع جدید را تایپ دار کنید.
- خطاهای غیرحیاتی را موقتا خاموش کنید و بعدا به صورت هدفمند فعال کنید.
- اگر کتابخانه ای تایپ ندارد، پکیج های stubs مانند types-requests را نصب کنید.
ابزار reveal_type به درک استنتاج نوع کمک می کند:
from typing import reveal_type
x = {"a": 1}
reveal_type(x) # mypy: Revealed type is "builtins.dict[builtins.str, builtins.int]"
خطاها و دام های رایج
- None را فراموش نکنید: اگر تابعی ممکن است چیزی پیدا نکند، نوع بازگشتی را T | None بگذارید و قبل از استفاده چک کنید.
- نوع خیلی کلی برای ورودی ها ندهید: برای پارامترها، Sequence[T] یا Iterable[T] انتخاب بهتری از list[T] است چون انعطاف بیشتری می دهد.
- Any درمان موقت است: استفاده زیاد از Any عملا مزیت های تایپ را از بین می برد.
- ارجاع رو به جلو: اگر به کلاسی ارجاع می دهید که پایین تر تعریف شده، نام را به صورت رشته ‘User’ بنویسید تا از خطای زمان import جلوگیری شود.
- مقادیر پیش فرض قابل تغییر: این موضوع ربط مستقیم به Type Hint ندارد، اما در کنار تایپ باید از default های ایمن (مثل None) استفاده کنید و داخل تابع مقدار بسازید.
ادغام در گردش کار: IDE، اسکریپت، CI
برای گرفتن بازخورد سریع، اجرای mypy را به اسکریپت های پروژه اضافه کنید:
# در makefile یا اسکریپت
mypy src/ tests/
استفاده از Type Hint ها در ابزارهای پیرامونی هم سودمند است. مثلا در ساخت ابزار خط فرمان، کتابخانه هایی مانند Typer از انوتیشن ها برای اعتبارسنجی و تولید راهنما استفاده می کنند. نمونه کاربردی را در آموزش Typer در پایتون: ساخت ابزار خط فرمان حرفه ای ببینید.
اگر کد async می نویسید، تایپ کردن امضاهای async و وظایف، خطاهای رقابتی یا ناسازگاری await را زودتر آشکار می کند. برای مدیریت همزمانی ساخت یافته نگاهی به مدیریت همزمان چند وظیفه با asyncio TaskGroup در پایتون داشته باشید.
یک الگوی بررسی نتیجه
پس از اضافه کردن Type Hint ها، چرخه زیر را اجرا کنید:
- mypy را اجرا کنید و خطاها را بخوانید.
- اگر لازم است امضا یا بدنه تابع را اصلاح کنید.
- برای مسیرهای ورودی مبهم، نوع دقیق تری انتخاب کنید یا تبدیل صریح انجام دهید.
- تست های واحد را اجرا کنید تا از درستی رفتار اطمینان بگیرید.
چه زمانی گزینه دیگری بهتر است؟
Type Hint ها اعتبارسنجی زمان اجرا نیستند. اگر ورودی های بیرونی (HTTP، فرم، فایل) را باید در لحظه دریافت بررسی کنید، از اعتبارسنجی زمان اجرا یا کتابخانه های مناسب این کار استفاده کنید. Type Hint در کنار آن ها به شما تصویر دقیقی از قراردادها می دهد، اما جایگزینشان نیست.
گام بعدی چیست؟
یک فایل کوچک از پروژه را انتخاب کنید، امضاهای توابع را تایپ دار کنید و mypy را اجرا کنید. سپس یک فایل پیکربندی ساده اضافه کنید و به تدریج قواعد را سخت تر کنید. اگر با مدل های داده سروکار دارید، مطالعه آموزش dataclass در پایتون برای مدل های داده تمیز نقطه شروع خوبی است.







