Homeنکات و ترفندهای برنامه‌نویسیType Hints و mypy در Python: راهنمای کامل تایپ‌گذاری ۲۰۲۶ | آکادمی تک

Type Hints و mypy در Python: راهنمای کامل تایپ‌گذاری ۲۰۲۶ | آکادمی تک

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

ابزارتوضیحمزیت
mypyStatic type checker استانداردمحبوب‌ترین، کامل‌ترین
Pyright / Pylanceاز Microsoft، پایه VS Codeسرعت بالا، ادغام VS Code
Pytypeاز Googleinference قوی‌تر
PydanticRuntime validationخطا در زمان اجرا، نه فقط بررسی
beartypeRuntime decoratorبررسی نوع در زمان اجرا با سرعت

 

 

🏆  بخش پنجم: بهترین شیوه‌ها و الگوهای عملی

 

۲۱. راهنمای تدریجی اضافه کردن Type Hints به پروژه

اگر پروژه موجودی دارید، لازم نیست همه چیز را یک‌شبه تغییر دهید:

  1. از توابع عمومی و پرکاربرد شروع کنید
  2. API‌ها و interface‌ها — جایی که کاربران با کد شما تعامل دارند
  3. Model‌ها و Data Class‌ها
  4. با –ignore-missing-imports شروع کنید، بعد سخت‌گیرانه‌تر شوید
  5. از 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  |  آخرین به‌روزرسانی: ۱۴۰۵

اگر مفید بود با دوستانتان به اشتراک بگذارید 🐍

Share: 

No comments yet! You be the first to comment.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *