افهم ما تفعله التعليقات التوضيحية فعليًا في وقت التشغيل، واستفد إلى أقصى حدّ من dataclasses عبر frozen وslots وdefault_factory.
افتح هذا الدرس في Kodokonإن التعليقات التوضيحية للأنواع بيانات وصفية، لا قيود: يخزّنها المفسّر في __annotations__ ولا يتحقّق منها أبدًا في وقت التشغيل. فالتحقّق مهمة محلّل ساكن مثل mypy أو pyright، قبل التنفيذ. والنتيجة المباشرة: برنامج سيّئ الأنواع يعمل دون أيّ اعتراض - وهذا مقصود بالتصميم. فتحديد الأنواع التدريجي يتيح لك توصيف قاعدة كود قائمة شيئًا فشيئًا دون كسر أيّ شيء.
from typing import get_type_hints
def add(a: int, b: int) -> int:
return a + b
print(add("py", "thon"))
print(add.__annotations__)
print(get_type_hints(add))منذ Python 3.9، أصبحت الأنواع المدمجة عامة (generic) مباشرة: اكتب list[int] أو dict[str, float] دون استيراد typing.List. وتُكتب الاتحادات int | None منذ الإصدار 3.10. ولربط نوع المُدخَل بنوع المُخرَج، استخدم TypeVar - بل إن Python 3.12 يقدّم الصياغة المختصرة def first[T](items: list[T]) -> T. ونقطة كثيرًا ما تُهمَل: from __future__ import annotations تجعل كل تعليق توضيحي كسولًا (يُخزَّن كسلسلة نصية)، ما يسمح بالإشارة إلى أصناف مُعرَّفة لاحقًا ويخفّض الكلفة عند الاستيراد.
from collections.abc import Iterable, Iterator
from typing import TypeVar
T = TypeVar("T")
def dedupe(items: Iterable[T]) -> Iterator[T]:
seen: set[T] = set()
for item in items:
if item not in seen:
seen.add(item)
yield item
print(list(dedupe([3, 1, 3, 2, 1])))from dataclasses import dataclass, field
@dataclass(frozen=True, slots=True)
class Point:
x: float
y: float
@dataclass
class Basket:
items: list[str] = field(default_factory=list)
total: int = 0
def __post_init__(self) -> None:
self.total = len(self.items)
point = Point(1.0, 2.0)
print(point, hash(point))
print(Basket(["book", "pen"]))ثلاثة وسائط تحوّل @dataclass إلى أداة جاهزة للإنتاج. يجعل frozen=True المثيلات غير قابلة للتغيير، ومع المساواة المولَّدة تلقائيًا، قابلة للتجزئة - أي صالحة كمفاتيح للقواميس. ويزيل slots=True (منذ Python 3.10) القاموس __dict__ الخاص بكل مثيل: ذاكرة أقل، ووصول أسرع إلى الخصائص، وفشل أيّ إسناد لخاصية غير متوقّعة. وأخيرًا يفرض kw_only=True الاستدعاء بكلمات مفتاحية صريحة. أما __post_init__ فهو المكان المخصّص للثوابت المنطقية والحقول المشتقّة. وبالنسبة لتجميعة بسيطة غير قابلة للتغيير، يبقى NamedTuple خيارًا وجيهًا؛ وحالما يظهر سلوك أو تحقّق من الصحة، فضّل dataclass.
add('py', 'thon') بينما الدالة موصّفة بـ (a: int, b: int) -> int؟items: list[str] = [] كقيمة افتراضية لحقل في dataclass؟list[str] ليست تعليقًا توضيحيًا صالحًا لحقلfrozen=True بـ dataclass؟