Type Hints و mypy در Python: راهنمای کامل ۲۰۲۶
دستهبندی: Python متوسط | سطح: Intermediate | زمان مطالعه: ۱۸ دقیقه | ۱۴۰۵
مقدمه: چرا تایپگذاری در Python مهم است؟
Python یک زبان Dynamic Typing است — یعنی نوع متغیرها در زمان اجرا تعیین میشود، نه زمان نوشتن کد. این انعطافپذیری یکی از دلایل محبوبیت Python است، اما در پروژههای بزرگ میتواند به باگهای پنهان و کد سختخوان منجر شود.
از Python 3.5 به بعد، قابلیت Type Hints اضافه شد که اجازه میدهد نوع متغیرها، پارامترها و خروجی توابع را مشخص کنید — بدون اینکه رفتار برنامه تغییر کند. سپس ابزارهایی مثل mypy این Hintها را بررسی میکنند و قبل از اجرای برنامه خطاها را پیدا میکنند.
| 💡 سه دلیل اصلی برای استفاده از Type Hints: 🐛 کاهش باگ: بسیاری از خطاها قبل از اجرا توسط mypy پیدا میشوند 📖 خوانایی کد: هر کسی که کد را میخواند میداند هر تابع چه میگیرد و چه برمیگرداند 🤖 تکمیل خودکار بهتر: IDEها با Type Hints پیشنهادهای دقیقتری میدهند
|
| 📌 قبل از شروع — پیشنیازها: Python 3.9+ (برای بهترین تجربه، Python 3.12 توصیه میشود) آشنایی با تعریف توابع و کلاسها در Python نصب mypy: pip install mypy
|
| 📘 بخش اول: مفاهیم پایه Type Hints |
۱. اولین قدم: Type Hints در توابع
سادهترین جایی که میتوانیم Type Hints اضافه کنیم، پارامترها و خروجی توابع است:
❌ بدون Type Hints:
def greet(name):
return 'Hello, ' + name
# کاربر نمیداند:
# - name باید str باشد؟
# - خروجی چیست؟✅ با Type Hints:
def greet(name: str) -> str:
return 'Hello, ' + name
# الان همه میدانند:
# - name باید str باشد
# - خروجی str است
در این مثال:
- name: str — نوع پارامتر را مشخص میکند
- -> str — نوع خروجی تابع را مشخص میکند
- اگر اشتباهاً greet(123) صدا بزنیم، mypy خطا میدهد
۲. Type Hints برای متغیرها
میتوانیم نوع متغیرها را هم مشخص کنیم:
# تعریف متغیر با نوع مشخص
name: str = 'Ali'
age: int = 30
height: float = 1.75
is_active: bool = True
# تعریف بدون مقدار اولیه (فقط Hint)
user_id: int
# لیست و دیکشنری
names: list[str] = ['Ali', 'Sara']
scores: dict[str, int] = {'Ali': 95, 'Sara': 88}
۳. انواع پایه در Python
| نوع | توضیح | مثال |
| int | عدد صحیح | age: int = 25 |
| float | عدد اعشاری | price: float = 9.99 |
| str | رشته متنی | name: str = ‘Ali’ |
| bool | بولین | active: bool = True |
| bytes | داده باینری | data: bytes = b’hello’ |
| None | مقدار خالی | result: None = None |
۴. Type Hints برای مجموعهها (Collections)
در Python 3.9+ میتوان مستقیم از list، dict، tuple، set استفاده کرد:
# Python 3.9+ — روش جدید (توصیهشده)
names: list[str] = ['Ali', 'Sara']
scores: dict[str, int] = {'Ali': 95}
coords: tuple[float, float] = (35.7, 51.4)
unique_ids: set[int] = {1, 2, 3}
# Python 3.8 و قدیمیتر — نیاز به import
from typing import List, Dict, Tuple, Set
names: List[str] = ['Ali', 'Sara']
scores: Dict[str, int] = {'Ali': 95}
۵. Optional و Union — وقتی مقدار میتواند None باشد
یکی از رایجترین سناریوها این است که یک متغیر میتواند یک مقدار یا None باشد:
from typing import Optional, Union
# Optional[X] یعنی X یا None
def find_user(user_id: int) -> Optional[str]:
if user_id == 1:
return 'Ali'
return None # کاربر پیدا نشد
# Python 3.10+ — روش جدید با |
def find_user(user_id: int) -> str | None:
...
# Union — چند نوع مختلف
def process(value: int | str | float) -> str:
return str(value)| 💡 نکته مهم: Optional[str] دقیقاً معادل Union[str, None] یا str | None است. در Python 3.10+ استفاده از | (pipe) توصیه میشود — خواناتر است. |
۶. Callable — تایپگذاری توابع به عنوان پارامتر
from typing import Callable
# تابعی که یک تابع دیگر میگیرد
def apply(func: Callable[[int, int], int], a: int, b: int) -> int:
return func(a, b)
def add(x: int, y: int) -> int:
return x + y
result = apply(add, 3, 5) # result: int = 8
# Callable بدون پارامتر مشخص
def run_later(callback: Callable[..., None]) -> None:
callback()
| 📗 بخش دوم: Type Hints پیشرفته |
۷. TypeVar — Generic Programming
وقتی میخواهید یک تابع با هر نوعی کار کند اما نوع ورودی و خروجی یکسان باشد:
from typing import TypeVar
T = TypeVar('T') # T میتواند هر نوعی باشد
def first_item(items: list[T]) -> T:
return items[0]
# mypy میداند که خروجی همان نوع ورودی است
first_item([1, 2, 3]) # -> int
first_item(['a', 'b']) # -> str
first_item([1.0, 2.0]) # -> float
# TypeVar با محدودیت
Number = TypeVar('Number', int, float)
def double(x: Number) -> Number:
return x * 2
۸. Protocol — Duck Typing با Type Safety
Protocol یکی از قدرتمندترین ابزارهای typing است. به جای Inheritance، میتوان رفتار (Interface) را تعریف کرد:
from typing import Protocol
# تعریف Protocol — هر شیئی که این متد را داشته باشد
class Drawable(Protocol):
def draw(self) -> None: ...
class Circle:
def draw(self) -> None:
print('Drawing circle')
class Square:
def draw(self) -> None:
print('Drawing square')
# Circle و Square هیچکدام از Drawable ارث نمیبرند
# اما هر دو Protocol را satisfy میکنند
def render(shape: Drawable) -> None:
shape.draw()
render(Circle()) # ✅ mypy تأیید میکند
render(Square()) # ✅ mypy تأیید میکند
۹. Literal — مقادیر ثابت مجاز
from typing import Literal
# فقط این مقادیر مجاز هستند
Direction = Literal['north', 'south', 'east', 'west']
Status = Literal['active', 'inactive', 'pending']
def move(direction: Direction, steps: int) -> None:
print(f'Moving {steps} steps {direction}')
move('north', 5) # ✅ درست
move('up', 5) # ❌ mypy خطا میدهد — 'up' مجاز نیست
def set_status(status: Status) -> None: ...
۱۰. TypedDict — دیکشنری با ساختار مشخص
from typing import TypedDict
class UserDict(TypedDict):
name: str
age: int
email: str
# Optional fields
class UserDictPartial(TypedDict, total=False):
phone: str
address: str
def create_user(data: UserDict) -> None:
print(f"Creating user: {data['name']}")
user: UserDict = {
'name': 'Ali',
'age': 30,
'email': 'ali@example.com'
}
create_user(user) # ✅
create_user({'name': 'Ali'}) # ❌ age و email کم است
۱۱. Final و ClassVar
from typing import Final, ClassVar
# Final — مقدار قابل تغییر نیست (مثل const)
MAX_CONNECTIONS: Final = 100
API_URL: Final[str] = 'https://api.example.com'
# MAX_CONNECTIONS = 200 # ❌ mypy خطا میدهد
class Config:
# ClassVar — متغیر کلاس، نه نمونه
debug: ClassVar[bool] = False
version: ClassVar[str] = '1.0.0'
def __init__(self, name: str) -> None:
self.name = name
۱۲. Annotated — متادیتا به همراه Type
در Python 3.9+ میتوانید اطلاعات اضافه به Type Hint متصل کنید — پایه Pydantic و FastAPI:
from typing import Annotated
# Annotated[نوع، متادیتا]
PositiveInt = Annotated[int, 'must be positive']
def set_age(age: Annotated[int, 'age must be 0-150']) -> None:
if not 0 <= age <= 150:
raise ValueError('Invalid age')
# FastAPI از Annotated برای validation استفاده میکند
# from fastapi import Query
# def search(q: Annotated[str, Query(min_length=3)]): ...
| 📙 بخش سوم: تایپگذاری در کلاسها |
۱۳. تایپگذاری در کلاسهای معمولی
class BankAccount:
owner: str
balance: float
transactions: list[float]
def __init__(self, owner: str, initial_balance: float = 0.0) -> None:
self.owner = owner
self.balance = initial_balance
self.transactions = []
def deposit(self, amount: float) -> None:
if amount <= 0:
raise ValueError('Amount must be positive')
self.balance += amount
self.transactions.append(amount)
def withdraw(self, amount: float) -> bool:
if amount > self.balance:
return False
self.balance -= amount
self.transactions.append(-amount)
return True
def get_statement(self) -> dict[str, float | list[float]]:
return {'balance': self.balance, 'transactions': self.transactions}
۱۴. dataclass — ترکیب قدرتمند با Type Hints
dataclass یکی از بهترین روشها برای ساخت کلاسهای داده با Type Hints است:
from dataclasses import dataclass, field
from datetime import datetime
@dataclass
class User:
name: str
age: int
email: str
created_at: datetime = field(default_factory=datetime.now)
tags: list[str] = field(default_factory=list)
is_active: bool = True
def greet(self) -> str:
return f'Hello, {self.name}!'
@dataclass(frozen=True) # immutable — مثل NamedTuple
class Point:
x: float
y: float
def distance_to(self, other: 'Point') -> float:
return ((self.x - other.x)**2 + (self.y - other.y)**2) ** 0.5
# استفاده
user = User(name='Ali', age=30, email='ali@example.com')
p1 = Point(0.0, 0.0)
p2 = Point(3.0, 4.0)
print(p1.distance_to(p2)) # 5.0
۱۵. Pydantic — Type Hints با اعتبارسنجی خودکار
Pydantic کتابخانهای است که Type Hints را به اعتبارسنجی واقعی در زمان اجرا تبدیل میکند. پایه FastAPI است:
# pip install pydantic
from pydantic import BaseModel, EmailStr, Field, validator
from typing import Optional
class UserModel(BaseModel):
name: str = Field(min_length=2, max_length=50)
age: int = Field(ge=0, le=150) # ge=greater or equal
email: str
phone: Optional[str] = None
@validator('name')
def name_must_not_be_empty(cls, v: str) -> str:
if not v.strip():
raise ValueError('Name cannot be empty')
return v.strip()
# اعتبارسنجی خودکار
user = UserModel(name='Ali', age=30, email='ali@test.com')
print(user.model_dump())
# خطای اتوماتیک
try:
bad_user = UserModel(name='A', age=200, email='bad')
except Exception as e:
print(e) # ValidationError با جزئیات| 🔥 چرا Pydantic مهم است؟ پایه FastAPI — هر endpoint FastAPI از Pydantic برای validation استفاده میکند. JSON Serialization/Deserialization خودکار. خطاهای واضح و قابل فهم برای API responses. Pydantic v2 با Rust بازنویسی شده — سرعت ۵-۵۰ برابر بیشتر. |
| 🔍 بخش چهارم: mypy — تحلیل استاتیک نوع |
۱۶. نصب و اجرای mypy
# نصب
pip install mypy
# بررسی یک فایل
mypy my_file.py
# بررسی کل پروژه
mypy .
# با گزینههای سختگیرانهتر
mypy --strict my_file.py
# نادیده گرفتن خطاهای import
mypy --ignore-missing-imports my_file.py
۱۷. مثال عملی: mypy در عمل
فایل زیر را با mypy بررسی میکنیم:
# user_service.py
def get_user_name(user_id: int) -> str:
users = {1: 'Ali', 2: 'Sara'}
return users.get(user_id) # ❌ مشکل: None برمیگرداند
def calculate_age(birth_year: int) -> int:
return 2025 - birth_year
name = get_user_name('123') # ❌ مشکل: str به جای int
age = calculate_age(1995)
print(age + ' years old') # ❌ مشکل: int + strخروجی mypy:
user_service.py:3: error: Incompatible return value type
(got 'str | None', expected 'str') [return-value]
user_service.py:8: error: Argument 1 to 'get_user_name'
has incompatible type 'str'; expected 'int' [arg-type]
user_service.py:10: error: Unsupported left operand type for +
('int' and 'str') [operator]
Found 3 errors in 1 file (checked 1 source file)نسخه اصلاحشده:
def get_user_name(user_id: int) -> str | None: # ✅ Optional
users = {1: 'Ali', 2: 'Sara'}
return users.get(user_id)
name = get_user_name(123) # ✅ int
age = calculate_age(1995)
print(f'{age} years old') # ✅ f-string
۱۸. تنظیمات mypy با فایل پیکربندی
فایل mypy.ini یا pyproject.toml برای تنظیم رفتار mypy:
# mypy.ini
[mypy]
python_version = 3.12
warn_return_any = True
warn_unused_configs = True
disallow_untyped_defs = True # همه توابع باید Type Hints داشته باشند
disallow_any_generics = True
check_untyped_defs = True
no_implicit_optional = True
# برای پکیجهای خاص
[mypy-requests.*]
ignore_missing_imports = True
[mypy-numpy.*]
ignore_missing_imports = True
# pyproject.toml (روش مدرنتر)
[tool.mypy]
python_version = '3.12'
strict = true
ignore_missing_imports = true
۱۹. type: ignore — نادیده گرفتن خطاهای خاص
# وقتی مطمئنید کد درست است اما mypy اشتباه میکند
result = some_external_function() # type: ignore[no-untyped-call]
# نادیده گرفتن یک خط
x: int = get_value() # type: ignore
# توصیه: همیشه دلیل ignore را بنویسید
data = legacy_api() # type: ignore[return-value] # TODO: fix in v2
۲۰. ابزارهای جایگزین و مکمل mypy
| ابزار | توضیح | مزیت |
| mypy | Static type checker استاندارد | محبوبترین، کاملترین |
| Pyright / Pylance | از Microsoft، پایه VS Code | سرعت بالا، ادغام VS Code |
| Pytype | از Google | inference قویتر |
| Pydantic | Runtime validation | خطا در زمان اجرا، نه فقط بررسی |
| beartype | Runtime decorator | بررسی نوع در زمان اجرا با سرعت |
| 🏆 بخش پنجم: بهترین شیوهها و الگوهای عملی |
۲۱. راهنمای تدریجی اضافه کردن Type Hints به پروژه
اگر پروژه موجودی دارید، لازم نیست همه چیز را یکشبه تغییر دهید:
- از توابع عمومی و پرکاربرد شروع کنید
- APIها و interfaceها — جایی که کاربران با کد شما تعامل دارند
- Modelها و Data Classها
- با –ignore-missing-imports شروع کنید، بعد سختگیرانهتر شوید
- از reveal_type() برای debug نوعها استفاده کنید
# reveal_type — ابزار debug برای mypy
x = [1, 2, 3]
reveal_type(x) # mypy میگوید: list[int]
def process(items):
reveal_type(items) # میبینید mypy چه نوعی inference کرده
۲۲. الگوهای رایج و نحوه تایپگذاری آنها
from typing import Any, Generator, Iterator, AsyncIterator
from collections.abc import Sequence, Mapping
# ۱. تابع که هیچ چیز برنمیگرداند
def log(message: str) -> None:
print(message)
# ۲. Generator
def count_up(n: int) -> Generator[int, None, None]:
for i in range(n):
yield i
# ۳. *args و **kwargs
def flexible(*args: int, **kwargs: str) -> None:
print(args, kwargs)
# ۴. Sequence به جای list (انعطافپذیرتر)
def process_items(items: Sequence[str]) -> int:
return len(items) # با list، tuple، str همه کار میکند
# ۵. Any — وقتی واقعاً نوع مهم نیست
def serialize(data: Any) -> str:
return str(data)
# ۶. Self در Python 3.11+
from typing import Self
class Builder:
def set_name(self, name: str) -> Self:
self.name = name
return self
def build(self) -> 'Builder':
return self
۲۳. تایپگذاری در پروژههای واقعی — مثال کامل
یک سرویس کاربری کوچک با تایپگذاری کامل:
from dataclasses import dataclass, field
from typing import Optional
from datetime import datetime
@dataclass
class User:
id: int
name: str
email: str
created_at: datetime = field(default_factory=datetime.now)
is_active: bool = True
class UserNotFoundError(Exception):
def __init__(self, user_id: int) -> None:
super().__init__(f'User {user_id} not found')
class UserRepository:
def __init__(self) -> None:
self._users: dict[int, User] = {}
def add(self, user: User) -> None:
self._users[user.id] = user
def get(self, user_id: int) -> User:
user = self._users.get(user_id)
if user is None:
raise UserNotFoundError(user_id)
return user
def find_by_email(self, email: str) -> Optional[User]:
return next(
(u for u in self._users.values() if u.email == email),
None
)
def get_active_users(self) -> list[User]:
return [u for u in self._users.values() if u.is_active]
# استفاده
repo = UserRepository()
repo.add(User(id=1, name='Ali', email='ali@test.com'))
user = repo.get(1) # mypy میداند: User
missing = repo.find_by_email('x@y.com') # User | None
سوالات متداول (FAQ)
آیا Type Hints سرعت Python را کند میکند؟
خیر. Type Hints در زمان اجرا نادیده گرفته میشوند و هیچ تأثیری روی سرعت برنامه ندارند. فقط mypy و IDE از آنها استفاده میکنند.
آیا باید همه کدها Type Hints داشته باشند؟
لازم نیست — اما توصیه میشود. حداقل برای توابع عمومی، APIها و کلاسهای مهم Type Hints اضافه کنید. برای اسکریپتهای ساده و یکبارمصرف، اهمیت کمتری دارد.
تفاوت Type Hints و Type Checking چیست؟
Type Hints فقط annotation هستند — اطلاعاتی که مینویسید. Type Checking (مثل mypy) ابزاری است که این annotationها را بررسی میکند و تضادها را پیدا میکند.
آیا mypy در همه پروژهها ضروری است؟
برای پروژههای بزرگ، تیمی یا libraryهایی که دیگران استفاده میکنند: بله. برای اسکریپتهای کوچک یا پروژههای شخصی: اختیاری است.
تفاوت Pydantic و mypy چیست؟
mypy در زمان نوشتن کد بررسی میکند (Static Analysis). Pydantic در زمان اجرا validation میکند. این دو مکمل هم هستند، نه جایگزین.
جمعبندی
Type Hints یکی از بهترین سرمایهگذاریهایی است که میتوانید در کیفیت کد Python خود انجام دهید. در این مقاله یاد گرفتیم:
- مفاهیم پایه: تایپگذاری توابع، متغیرها، و مجموعهها
- انواع پیشرفته: Optional، Union، TypeVar، Protocol، Literal، TypedDict
- کلاسها: dataclass و Pydantic برای ساختارهای داده
- mypy: نصب، اجرا، تنظیمات و رفع خطاها
- بهترین شیوهها: اضافه کردن تدریجی و الگوهای عملی
| 🚀 گامهای بعدی: mypy را روی پروژه فعلیتان اجرا کنید: mypy . –ignore-missing-imports از آموزشهای بیشتر Python در آکادمی تک بازدید کنید. برای اجرای کد مثالها، از کامپایلر آنلاین آکادمی تک (بهزودی) استفاده کنید.
|
نویسنده: تیم آکادمی تک | academy-tech.ir | آخرین بهروزرسانی: ۱۴۰۵
اگر مفید بود با دوستانتان به اشتراک بگذارید 🐍

