dataclasses --- کلاس‌های داده

کد منبع: Lib/dataclasses.py


این ماژول یک دکوراتور و توابعی را برای افزودن خودکار متدهای ویژه تولیدشده مانند __init__() و __repr__() به کلاس‌های تعریف‌شده توسط کاربر فراهم می‌کند. این ماژول در ابتدا در PEP 557 توصیف‌شده است.

متغیرهای عضوی که در این متدهای تولیدشده به کار می‌روند، با حاشیه‌نویسی‌های نوع PEP 526 تعریف می‌شوند. برای مثال، این کد:

from dataclasses import dataclass

@dataclass
class InventoryItem:
    """Class for keeping track of an item in inventory."""
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

خواهد افزود، از جمله موارد دیگر، یک __init__() که به شکل زیر است:

def __init__(self, name: str, unit_price: float, quantity_on_hand: int = 0):
    self.name = name
    self.unit_price = unit_price
    self.quantity_on_hand = quantity_on_hand

توجه داشته باشید که این متد به‌طور خودکار به کلاس افزوده می‌شود: این متد به‌طور مستقیم در تعریف InventoryItem که در بالا نشان داده شده است، مشخص نشده است.

اضافه شده در نسخه‌ی 3.7.

محتوای ماژول

@dataclasses.dataclass(*, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False, weakref_slot=False)

این تابع یک دکوراتور است که برای افزودن متدهای ویژه تولیدشده به کلاس‌ها به کار می‌رود، همان‌طور که در ادامه توضیح داده شده است.

دکوراتور @dataclass کلاس را بررسی می‌کند تا fieldها را پیدا کند. یک field به‌عنوان متغیر کلاس تعریف می‌شود که دارای یک حاشیه‌نویسی نوع است. به‌جز دو استثنا که در زیر توضیح داده شده‌اند، هیچ چیزی در @dataclass نوع مشخص‌شده در حاشیه‌نویسی متغیر را بررسی نمی‌کند.

ترتیب فیلدها در تمام متدهای تولیدشده، همان ترتیبی است که در تعریف کلاس دارند.

دکوراتور @dataclass متدهای مختلف دو-زیرخطی (dunder) را به کلاس می‌افزاید که در زیر توضیح داده شده‌اند. اگر هر یک از متدهای افزوده‌شده از قبل در کلاس وجود داشته باشند، رفتار به پارامتر بستگی دارد، همان‌طور که در زیر مستند شده است. دکوراتور همان کلاسی را که روی آن فراخوانی‌شده است برمی‌گرداند؛ هیچ کلاس جدیدی ایجاد نمی‌شود.

اگر @dataclass فقط به‌عنوان یک دکوراتور ساده بدون پارامتر استفاده شود، به‌گونه‌ای عمل می‌کند که گویی مقادیر پیش‌فرض مستندشده در این امضا را دارد. یعنی این سه کاربرد @dataclass معادل‌اند:

@dataclass
class C:
    ...

@dataclass()
class C:
    ...

@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False,
           match_args=True, kw_only=False, slots=False, weakref_slot=False)
class C:
    ...

پارامترهای @dataclass عبارتند از:

  • init: اگر درست باشد (پیش‌فرض)، یک متد __init__() تولید خواهد شد.

    اگر کلاس از قبل __init__() را تعریف کرده باشد، این پارامتر نادیده گرفته می‌شود.

  • repr: اگر true باشد (پیش‌فرض)، یک متد __repr__() تولید خواهد شد. رشته‌ی repr تولیدشده، نام کلاس و نام و repr هر فیلد را به ترتیبی که در کلاس تعریف شده‌اند، خواهد داشت. فیلدهایی که برای حذف از repr علامت‌گذاری شده باشند، گنجانده نمی‌شوند. برای مثال: InventoryItem(name='widget', unit_price=3.0, quantity_on_hand=10).

    اگر کلاس از قبل __repr__() را تعریف کرده باشد، این پارامتر نادیده گرفته می‌شود.

  • eq: اگر true باشد (پیش‌فرض)، یک متد __eq__() تولید خواهد شد.

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

    اگر کلاس از قبل __eq__() را تعریف کرده باشد، این پارامتر نادیده گرفته می‌شود.

    تغییر یافته در نسخه‌ی 3.13: متد __eq__ تولیدشده اکنون هر فیلد را به‌صورت جداگانه مقایسه می‌کند (برای مثال، self.a == other.a and self.b == other.b)، نه اینکه مانند نسخه‌های پیشین تاپل‌های فیلدها را مقایسه کند.

    این تغییر، مقایسه را سریع‌تر می‌کند، اما ممکن است نتایج را در مواردی که ویژگی‌ها از نظر هویت برابر هستند ولی از نظر مقدار برابر نیستند (مانند float('nan')) تغییر دهد.

    در پایتون 3.12 و نسخه‌های پیشین، مقایسه با ایجاد تاپل‌هایی از فیلدها و مقایسه‌ی آن‌ها انجام می‌شد (برای مثال، (self.a, self.b) == (other.a, other.b)).

  • order: اگر درست باشد (پیش‌فرض False است)، متدهای __lt__()، __le__()، __gt__() و __ge__() تولید می‌شوند. این متدها کلاس را به‌گونه‌ای مقایسه می‌کنند که گویی یک تاپل از فیلدهای آن به‌ترتیب است. هر دو نمونه در مقایسه باید از نوع یکسان باشند. اگر order درست باشد و eq نادرست باشد، یک ValueError پرتاب می‌شود.

    اگر کلاس از قبل هر یک از __lt__()، __le__()، __gt__()، یا __ge__() را تعریف کرده باشد، TypeError پرتاب می‌شود.

  • unsafe_hash: اگر true باشد، dataclasses را وادار به ایجاد یک متد __hash__() می‌کند، هرچند ممکن است این کار امن نباشد. در غیر این صورت، یک متد __hash__() با توجه به نحوه‌ی تنظیم eq و frozen تولید می‌شود. مقدار پیش‌فرض False است.

    __hash__() توسط hash() توکار، و هنگام اضافه شدن شیء‌ها به مجموعه‌های هش‌شده مانند دیکشنری‌ها و مجموعه‌ها استفاده می‌شود. وجود __hash__() به این معناست که نمونه‌های کلاس تغییرناپذیر هستند. تغییرپذیری ویژگی پیچیده‌ای است که به قصد برنامه‌نویس، وجود و رفتار __eq__()، و مقادیر پرچم‌های eq و frozen در دکوراتور @dataclass بستگی دارد.

    به‌طور پیش‌فرض، @dataclass متد __hash__() را به‌صورت ضمنی اضافه نمی‌کند، مگر اینکه انجام این کار امن باشد. همچنین متد __hash__() موجودی را که به‌صورت صریح تعریف‌شده است، اضافه یا تغییر نمی‌دهد. تنظیم ویژگی کلاس __hash__ = None برای پایتون معنای مشخصی دارد، همان‌طور که در مستندات __hash__() آمده است.

    اگر __hash__() به‌صراحت تعریف نشده باشد، یا اگر روی None تنظیم شده باشد، آنگاه @dataclass ممکن است یک متد __hash__() ضمنی اضافه کند. اگرچه توصیه نمی‌شود، اما می‌توانید @dataclass را وادار کنید تا یک متد __hash__() را با unsafe_hash=True ایجاد کند. این حالت ممکن است زمانی رخ دهد که کلاس شما از نظر منطقی تغییرناپذیر است اما همچنان می‌تواند تغییر یابد. این یک مورد استفاده تخصصی است و باید با دقت در نظر گرفته شود.

    در ادامه، قواعد حاکم بر ایجاد ضمنی یک متد __hash__() آمده است. توجه داشته باشید که نمی‌توانید همزمان یک متد __hash__() صریح در دیتاکلاس خود داشته باشید و unsafe_hash=True را تنظیم کنید؛ این کار منجر به TypeError می‌شود.

    اگر eq و frozen هر دو درست باشند، به‌طور پیش‌فرض @dataclass یک متد __hash__() برای شما تولید خواهد کرد. اگر eq درست باشد و frozen نادرست باشد، __hash__() روی None تنظیم خواهد شد، که آن را به‌عنوان هش‌ناپذیر (unhashable) علامت‌گذاری می‌کند (که همین‌طور است، زیرا تغییرپذیر است). اگر eq نادرست باشد، __hash__() دست‌نخورده باقی خواهد ماند، به این معنا که متد __hash__() ابرکلاس استفاده خواهد شد (اگر ابرکلاس object باشد، این بدان معناست که به هش مبتنی بر شناسه (id-based hashing) بازگشت خواهد شد).

  • frozen: اگر true باشد (پیش‌فرض False است)، انتساب به فیلدها باعث ایجاد یک استثنا می‌شود. این حالت، نمونه‌های فریزشده فقط‌خواندنی را شبیه‌سازی می‌کند. به بحث در زیر مراجعه کنید.

    اگر __setattr__() یا __delattr__() در کلاس تعریف شده باشد و frozen برابر true باشد، TypeError پرتاب می‌شود.

  • match_args: اگر درست باشد (پیش‌فرض True است)، تاپل __match_args__ از فهرست پارامترهای متد تولیدشده‌ی __init__() که فقط‌کلیدواژه‌ای نیستند ایجاد می‌شود (حتی اگر __init__() تولید نشده باشد، بالا را ببینید). اگر نادرست باشد، یا اگر __match_args__ از قبل در کلاس تعریف شده باشد، __match_args__ تولید نخواهد شد.

اضافه شده در نسخه‌ی 3.10.

  • kw_only: اگر درست باشد (مقدار پیش‌فرض False است)، تمام فیلدها به‌عنوان فقط کلیدواژه‌ای علامت‌گذاری می‌شوند. اگر فیلدی به‌عنوان فقط کلیدواژه‌ای علامت‌گذاری شده باشد، تنها اثر آن این است که پارامتر __init__() که از یک فیلد فقط کلیدواژه‌ای تولید شده است، باید هنگام فراخوانی __init__() با یک کلیدواژه مشخص شود. برای جزئیات، مدخل پارامتر در واژه‌نامه را ببینید. همچنین بخش KW_ONLY را ببینید.

    فیلدهای فقط‌کلیدواژه‌ای در __match_args__ گنجانده نمی‌شوند.

اضافه شده در نسخه‌ی 3.10.

  • slots: اگر درست باشد (پیش‌فرض False است)، ویژگی __slots__ ایجاد می‌شود و کلاس جدید به جای کلاس اصلی برگردانده می‌شود. اگر __slots__ از قبل در کلاس تعریف شده باشد، TypeError پرتاب می‌شود.

هشدار

ارسال پارامترها به __init_subclass__() کلاس پایه، هنگام استفاده از slots=True، به TypeError منجر می‌شود. یا از __init_subclass__ بدون پارامتر استفاده کنید یا از مقادیر پیش‌فرض به‌عنوان راه‌حل موقت استفاده کنید. برای جزئیات کامل، gh-91126 را ببینید.

اضافه شده در نسخه‌ی 3.10.

تغییر یافته در نسخه‌ی 3.11: اگر نام فیلدی از قبل در __slots__ یک کلاس پایه گنجانده شده باشد، برای جلوگیری از بازنویسی آن‌ها، در __slots__ تولیدشده گنجانده نخواهد شد. بنابراین، برای بازیابی نام فیلدهای یک دیتاکلاس از __slots__ استفاده نکنید. در عوض از fields() استفاده کنید. برای این که بتوان جایگاه‌های ارث‌بری‌شده را تعیین کرد، __slots__ کلاس پایه می‌تواند هر پیمایش‌پذیری باشد، اما نه یک پیمایش‌گر .

  • weakref_slot: اگر درست باشد (پیش‌فرض False است)، یک جایگاه به نام "__weakref__" افزوده می‌شود، که برای weakref-able کردن یک نمونه لازم است. تعیین weakref_slot=True بدون این‌که slots=True نیز تعیین شود، خطا است.

اضافه شده در نسخه‌ی 3.11.

fields می‌توانند به‌اختیار یک مقدار پیش‌فرض را با استفاده از سینتکس عادی پایتون مشخص کنند:

@dataclass
class C:
    a: int       # 'a' has no default value
    b: int = 0   # assign a default value for 'b'

در این مثال، هر دو a و b در متد __init__() افزوده‌شده گنجانده خواهند شد، که به‌صورت زیر تعریف خواهد شد:

def __init__(self, a: int, b: int = 0):

اگر یک فیلد بدون مقدار پیش‌فرض پس از یک فیلد دارای مقدار پیش‌فرض قرار بگیرد، TypeError پرتاب خواهد شد. این موضوع چه در یک کلاس واحد رخ دهد و چه در نتیجه وراثت کلاس، صادق است.

dataclasses.field(*, default=MISSING, default_factory=MISSING, init=True, repr=True, hash=None, compare=True, metadata=None, kw_only=MISSING, doc=None)

برای موارد استفاده‌ی رایج و ساده، به قابلیت دیگری نیاز نیست. با این حال، برخی از قابلیت‌های دیتاکلاس به اطلاعات اضافی به‌ازای هر فیلد نیاز دارند. برای برآورده کردن این نیاز به اطلاعات اضافی، می‌توانید مقدار پیش‌فرض فیلد را با فراخوانی تابع ارائه‌شده field() جایگزین کنید. برای مثال:

@dataclass
class C:
    mylist: list[int] = field(default_factory=list)

c = C()
c.mylist += [1, 2, 3]

همان‌طور که در بالا نشان داده شد، مقدار MISSING یک شیء نشانگر (sentinel) است که برای تشخیص این‌که آیا برخی پارامترها توسط کاربر ارائه شده‌اند، استفاده می‌شود. این نشانگر به این دلیل استفاده می‌شود که None برای برخی پارامترها یک مقدار معتبر با معنایی متمایز است. هیچ کدی نباید به‌طور مستقیم از مقدار MISSING استفاده کند.

پارامترهای field() عبارتند از:

  • default: اگر ارائه شود، این، مقدار پیش‌فرض این فیلد خواهد بود. این امر لازم است، زیرا فراخوانی field() به‌خودی‌خود جایگزین جایگاه عادی مقدار پیش‌فرض می‌شود.

  • default_factory: در صورت ارائه، باید یک شیء فراخوانی‌پذیر بدون آرگومان باشد که هنگام نیاز به یک مقدار پیش‌فرض برای این فیلد فراخوانی می‌شود. از جمله کاربردهای دیگر، می‌توان از آن برای مشخص کردن فیلدهایی با مقادیر پیش‌فرض تغییرپذیر استفاده کرد، همان‌طور که در ادامه بحث شده است. مشخص کردن هر دو default و default_factory خطا است.

  • init: اگر true باشد (پیش‌فرض)، این فیلد به‌عنوان پارامتری به متد تولیدشده‌ی __init__() اضافه می‌شود.

  • repr: اگر true باشد (پیش‌فرض)، این فیلد در رشته‌ی برگردانده‌شده توسط متد تولیدشده‌ی __repr__() گنجانده می‌شود.

  • hash: این مقدار می‌تواند یک بولی یا None باشد. اگر True باشد، این فیلد در متد __hash__() که تولید می‌شود گنجانده می‌شود. اگر False باشد، این فیلد از متد __hash__() که تولید می‌شود حذف می‌شود. اگر None (پیش‌فرض) باشد، از مقدار compare استفاده می‌شود: این معمولاً رفتار مورد انتظار است، زیرا اگر از فیلدی برای مقایسه‌ها استفاده شود، باید آن فیلد در هش گنجانده شود. تنظیم این مقدار به مقداری غیر از None توصیه نمی‌شود.

    یکی از دلایل ممکن برای تنظیم hash=False اما compare=True می‌تواند این باشد که محاسبه‌ی مقدار هش برای یک فیلد پرهزینه باشد، آن فیلد برای آزمون برابری لازم باشد و فیلدهای دیگری وجود داشته باشند که در مقدار هشِ نوع نقش دارند. حتی اگر یک فیلد از هش حذف شده باشد، همچنان برای مقایسه‌ها استفاده خواهد شد.

  • compare: اگر true باشد (پیش‌فرض)، این فیلد در متدهای تولیدشده‌ی برابری و مقایسه (__eq__()، __gt__() و غیره) گنجانده می‌شود.

  • metadata: این مقدار می‌تواند یک نگاشت یا None باشد. با None به‌عنوان یک دیکشنری خالی رفتار می‌شود. این مقدار در MappingProxyType() قرار می‌گیرد تا فقط‌خواندنی شود، و بر روی شیء Field نمایان می‌شود. این مقدار به‌هیچ‌وجه توسط Data Classes استفاده نمی‌شود، و به‌عنوان سازوکاری برای توسعه توسط شخص ثالث ارائه شده است. چندین شخص ثالث می‌توانند هرکدام کلید خودشان را داشته باشند تا از آن به‌عنوان فضای نام در metadata استفاده کنند.

  • kw_only: اگر true باشد، این فیلد به‌عنوان فقط کلیدواژه‌ای علامت‌گذاری می‌شود. این موضوع زمانی استفاده می‌شود که پارامترهای متدِ تولیدشده‌ی __init__() محاسبه می‌شوند.

    فیلدهای فقط کلیدواژه‌ای نیز در __match_args__ گنجانده نمی‌شوند.

اضافه شده در نسخه‌ی 3.10.

  • doc: رشته‌ی مستندسازی اختیاری برای این فیلد.

اضافه شده در نسخه‌ی 3.14.

اگر مقدار پیش‌فرض یک فیلد با فراخوانی field() مشخص شده باشد، آنگاه صفت کلاس مربوط به این فیلد با مقدار default مشخص‌شده جایگزین می‌شود. اگر default ارائه نشده باشد، آنگاه صفت کلاس حذف می‌شود. هدف این است که پس از اجرای دکوراتور @dataclass، صفات کلاس همگی حاوی مقادیر پیش‌فرض فیلدها خواهند بود، درست همان‌گونه که اگر خود مقدار پیش‌فرض مشخص شده باشد. برای مثال، پس از:

@dataclass
class C:
    x: int
    y: int = field(repr=False)
    z: int = field(repr=False, default=10)
    t: int = 20

صفت کلاس C.z برابر 10 خواهد بود، صفت کلاس C.t برابر 20 خواهد بود، و صفات کلاس C.x و C.y تنظیم نخواهند شد.

class dataclasses.Field

اشیای Field هر فیلد تعریف‌شده را توصیف می‌کنند. این اشیاء به‌صورت داخلی ایجاد می‌شوند و توسط متد fields() در سطح ماژول برگردانده می‌شوند (در زیر ببینید). کاربران هرگز نباید یک شیء Field را مستقیماً نمونه‌سازی کنند. ویژگی‌های مستندشده‌ی آن عبارت‌اند از:

  • name: نام فیلد.

  • type: نوع فیلد.

  • default، default_factory، init، repr، hash، compare، metadata و kw_only دقیقاً همان معنا و مقادیری را دارند که در تابع field() دارند.

ممکن است ویژگی‌های دیگری نیز وجود داشته باشند، اما آن‌ها خصوصی هستند و نباید بررسی شوند یا به آن‌ها اتکا شود.

class dataclasses.InitVar

حاشیه‌نویسی‌های نوع InitVar[T] متغیرهایی را توصیف می‌کنند که فقط init هستند. فیلدهایی که با InitVar حاشیه‌نویسی شده‌اند، شبه‌فیلد محسوب می‌شوند، و بنابراین نه توسط تابع fields() برگردانده می‌شوند و نه به هیچ شکلی استفاده می‌شوند، مگر اینکه به‌عنوان پارامتر به __init__() و یک __post_init__() اختیاری اضافه شوند.

dataclasses.fields(class_or_instance)

تاپلی از اشیای Field برمی‌گرداند که فیلدهای این دیتاکلاس را تعریف می‌کنند. یک دیتاکلاس یا نمونه‌ای از دیتاکلاس را می‌پذیرد. اگر دیتاکلاس یا نمونه‌ای از آن پاس داده نشود، TypeError را پرتاب می‌کند. شبه‌فیلدهایی که ClassVar یا InitVar باشند را برنمی‌گرداند.

dataclasses.asdict(obj, *, dict_factory=dict)

دیتاکلاس obj را به یک دیکشنری تبدیل می‌کند (با استفاده از تابع کارخانه‌ای dict_factory). هر دیتاکلاس به دیکشنری از فیلدهای آن، به‌صورت جفت‌های name: value تبدیل می‌شود. دیتاکلاس‌ها، دیکشنری‌ها، فهرست‌ها و تاپل‌ها به‌صورت بازگشتی پردازش می‌شوند. سایر اشیاء با copy.deepcopy() کپی می‌شوند.

مثالی از استفاده از asdict() روی دیتاکلاس‌های تودرتو:

@dataclass
class Point:
     x: int
     y: int

@dataclass
class C:
     mylist: list[Point]

p = Point(10, 20)
assert asdict(p) == {'x': 10, 'y': 20}

c = C([Point(0, 0), Point(10, 4)])
assert asdict(c) == {'mylist': [{'x': 0, 'y': 0}, {'x': 10, 'y': 4}]}

برای ایجاد یک کپی سطحی، می‌توان از راه‌حل جایگزین زیر استفاده کرد:

{field.name: getattr(obj, field.name) for field in fields(obj)}

asdict() در صورتی که obj نمونه‌ای از دیتاکلاس نباشد، TypeError را پرتاب می‌کند.

dataclasses.astuple(obj, *, tuple_factory=tuple)

دیتاکلاس obj را به یک تاپل تبدیل می‌کند (با استفاده از تابع کارخانه‌ای tuple_factory). هر دیتاکلاس به یک تاپل از مقادیر فیلدهای آن تبدیل می‌شود. دیتاکلاس‌ها، دیکشنری‌ها، فهرست‌ها و تاپل‌ها به‌صورت بازگشتی پیمایش می‌شوند. سایر اشیاء با copy.deepcopy() کپی می‌شوند.

در ادامه از مثال پیشین:

assert astuple(p) == (10, 20)
assert astuple(c) == ([(0, 0), (10, 4)],)

برای ایجاد یک کپی سطحی، می‌توان از راه‌حل جایگزین زیر استفاده کرد:

tuple(getattr(obj, field.name) for field in dataclasses.fields(obj))

astuple() در صورتی که obj نمونه‌ای از dataclass نباشد، TypeError را پرتاب می‌کند.

dataclasses.make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False, weakref_slot=False, module=None, decorator=dataclass)

یک دیتاکلاس جدید با نام cls_name، فیلدهای تعریف‌شده در fields، کلاس‌های پایه داده‌شده در bases ایجاد می‌کند و آن را با فضای نام داده‌شده در namespace مقداردهی اولیه می‌کند. fields یک پیمایش‌پذیر است که هر یک از عناصر آن یکی از name، (name, type) یا (name, type, Field) است. اگر فقط name ارائه شود، برای type از typing.Any استفاده می‌شود. مقادیر init، repr، eq، order، unsafe_hash، frozen، match_args، kw_only، slots و weakref_slot همان معنایی را دارند که در @dataclass دارند.

اگر module تعریف‌شده باشد، ویژگی __module__ دیتاکلاس به آن مقدار تنظیم می‌شود. به‌طور پیش‌فرض، این ویژگی به نام ماژول فراخواننده تنظیم می‌شود.

پارامتر decorator یک شیء فراخوانی‌پذیر است که برای ایجاد دیتاکلاس استفاده خواهد شد. این فراخوانی‌پذیر باید شیء کلاس را به‌عنوان اولین آرگومان و همان آرگومان‌های کلیدواژه‌ایِ @dataclass را نیز بپذیرد. به‌طور پیش‌فرض، از تابع @dataclass استفاده می‌شود.

این تابع به‌طور اکید الزامی نیست، زیرا هر سازوکار پایتون برای ایجاد یک کلاس جدید با __annotations__ می‌تواند سپس تابع @dataclass را برای تبدیل آن کلاس به یک دیتاکلاس اعمال کند. این تابع برای سهولت ارائه شده است. برای مثال:

C = make_dataclass('C',
                   [('x', int),
                     'y',
                    ('z', int, field(default=5))],
                   namespace={'add_one': lambda self: self.x + 1})

معادل است با:

@dataclass
class C:
    x: int
    y: 'typing.Any'
    z: int = 5

    def add_one(self):
        return self.x + 1

اضافه شده در نسخه‌ی 3.14: پارامتر decorator اضافه شد.

dataclasses.replace(obj, /, **changes)

یک شیء جدید از همان نوع obj ایجاد می‌کند و فیلدها را با مقادیر changes جایگزین می‌کند. اگر obj یک دیتاکلاس نباشد، TypeError را پرتاب می‌کند. اگر کلیدهای موجود در changes نام فیلدهای دیتاکلاس‌شده نباشند، TypeError را پرتاب می‌کند.

شیء تازه بازگشت‌داده‌شده با فراخوانی متد __init__() دیتاکلاس ایجاد می‌شود. این امر تضمین می‌کند که __post_init__()، در صورت وجود، نیز فراخوانی شود.

متغیرهای فقط مقداردهی اولیه بدون مقدار پیش‌فرض، در صورت وجود، باید در فراخوانی replace() مشخص شوند تا بتوان آن‌ها را به __init__() و __post_init__() ارسال کرد.

وجود هر فیلدی در changes که با init=False تعریف شده باشد، خطا است. در این حالت یک ValueError پرتاب خواهد شد.

از پیش آگاه باشید که فیلدهای init=False در هنگام فراخوانی replace() چگونه کار می‌کنند. آن‌ها از شیء مبدأ کپی نمی‌شوند، بلکه در __post_init__() مقداردهی اولیه می‌شوند، اگر اصلاً مقداردهی اولیه شوند. انتظار می‌رود فیلدهای init=False به‌ندرت و با تدبیر مورد استفاده قرار گیرند. اگر از آن‌ها استفاده شود، شاید بهتر باشد سازنده‌های جایگزین کلاس یا شاید یک متد سفارشی replace() (یا با نام مشابه) داشته باشید که کپی نمونه را مدیریت می‌کند.

تابع عام copy.replace() نیز از نمونه‌های دیتاکلاس پشتیبانی می‌کند.

dataclasses.is_dataclass(obj)

اگر پارامتر آن یک دیتاکلاس باشد (شامل زیرکلاس‌های یک دیتاکلاس، اما نه شامل نام‌های مستعار عام) یا نمونه‌ای از آن باشد، True و در غیر این صورت False برمی‌گرداند.

اگر نیاز دارید بدانید که آیا یک کلاس نمونه‌ای از یک دیتاکلاس است (و نه خود یک دیتاکلاس)، آنگاه یک بررسی دیگر برای not isinstance(obj, type) اضافه کنید:

def is_dataclass_instance(obj):
    return is_dataclass(obj) and not isinstance(obj, type)
dataclasses.MISSING

یک مقدار نشانگر (sentinel) که نشان‌دهنده‌ی نبود default یا default_factory است.

dataclasses.KW_ONLY

یک مقدار نشانگر (sentinel value) که به‌عنوان حاشیه‌نویسی نوع استفاده می‌شود. هر فیلدی که پس از یک شبه‌فیلد (pseudo-field) با نوع KW_ONLY قرار گیرد، به‌عنوان فیلد فقط کلیدواژه‌ای علامت‌گذاری می‌شود. توجه داشته باشید که یک شبه‌فیلد از نوع KW_ONLY در موارد دیگر به‌طور کامل نادیده گرفته می‌شود. این موضوع شامل نام چنین فیلدی نیز می‌شود. طبق قرارداد، برای یک فیلد KW_ONLY از نام _ استفاده می‌شود. فیلدهای فقط کلیدواژه‌ای نشان می‌دهند که پارامترهای __init__() باید هنگام نمونه‌سازی از کلاس به‌صورت کلیدواژه مشخص شوند.

در این مثال، فیلدهای y و z به‌عنوان فیلدهای فقط کلیدواژه‌ای علامت‌گذاری خواهند شد:

@dataclass
class Point:
    x: float
    _: KW_ONLY
    y: float
    z: float

p = Point(0, y=1.5, z=2.0)

در یک دیتاکلاس واحد، مشخص کردن بیش از یک فیلد که نوع آن KW_ONLY باشد، خطا است.

اضافه شده در نسخه‌ی 3.10.

exception dataclasses.FrozenInstanceError

هنگامی پرتاب می‌شود که یک __setattr__() یا __delattr__() تعریف‌شده به‌صورت ضمنی، روی یک دیتاکلاس که با frozen=True تعریف شده باشد، فراخوانی شود. این استثنا زیرکلاسی از AttributeError است.

پردازش پس از init

dataclasses.__post_init__()

هنگامی که در کلاس تعریف شود، توسط __init__() تولیدشده فراخوانی می‌شود، معمولاً به‌صورت self.__post_init__(). با این حال، اگر فیلدهای InitVar تعریف شده باشند، آن‌ها نیز به __post_init__() به همان ترتیبی که در کلاس تعریف شده‌اند ارسال می‌شوند. اگر متد __init__() تولید نشود، در این صورت __post_init__() به‌صورت خودکار فراخوانی نمی‌شود.

در میان سایر کاربردها، این امر امکان مقداردهی اولیه‌ی مقادیر فیلدهایی را فراهم می‌کند که به یک یا چند فیلد دیگر وابسته‌اند. برای مثال:

@dataclass
class C:
    a: float
    b: float
    c: float = field(init=False)

    def __post_init__(self):
        self.c = self.a + self.b

متد __init__() تولیدشده توسط @dataclass، متدهای __init__() کلاس پایه را فراخوانی نمی‌کند. اگر کلاس پایه یک متد __init__() داشته باشد که باید فراخوانی شود، رایج است که این متد در یک متد __post_init__() فراخوانی شود:

class Rectangle:
    def __init__(self, height, width):
        self.height = height
        self.width = width

@dataclass
class Square(Rectangle):
    side: float

    def __post_init__(self):
        super().__init__(self.side, self.side)

با این حال، توجه داشته باشید که به‌طور کلی نیازی به فراخوانی متدهای __init__() تولیدشده توسط دیتاکلاس نیست، زیرا دیتاکلاس مشتق‌شده مقداردهی اولیه‌ی تمام فیلدهای هر کلاس پایه‌ای که خود یک دیتاکلاس است را بر عهده می‌گیرد.

برای روش‌های ارسال پارامترها به __post_init__()، بخش زیر درباره‌ی متغیرهای فقط مقداردهی اولیه (init-only) را ببینید. همچنین هشدار درباره‌ی چگونگی مدیریت فیلدهای init=False توسط replace() را ببینید.

متغیرهای کلاس

یکی از معدود مواردی که @dataclass در واقع نوع یک فیلد را بررسی می‌کند، تشخیص این است که آیا یک فیلد یک متغیر کلاس است، همان‌طور که در PEP 526 تعریف شده است. این کار با بررسی این‌که آیا نوع فیلد typing.ClassVar است انجام می‌شود. اگر یک فیلد ClassVar باشد، از در نظر گرفته شدن به‌عنوان یک فیلد خارج می‌شود و سازوکارهای dataclass آن را نادیده می‌گیرند. چنین شبه‌فیلدهایی از جنس ClassVar توسط تابع fields() در سطح ماژول برگردانده نمی‌شوند.

متغیرهای فقط مقداردهی اولیه

مورد دیگری که در آن @dataclass یک حاشیه‌نویسی نوع را بررسی می‌کند، برای تشخیص این است که آیا یک فیلد یک متغیر فقط مقداردهی اولیه است یا خیر. این کار را با بررسی این انجام می‌دهد که آیا نوع یک فیلد از نوع InitVar است یا خیر. اگر یک فیلد از نوع InitVar باشد، به‌عنوان یک شبه‌فیلد به نام فیلد فقط مقداردهی اولیه در نظر گرفته می‌شود. از آنجا که فیلد واقعی نیست، توسط تابع fields() در سطح ماژول برگردانده نمی‌شود. فیلدهای فقط مقداردهی اولیه به‌عنوان پارامتر به متد تولیدشده‌ی __init__() اضافه می‌شوند، و به متد اختیاری __post_init__() ارسال می‌شوند. به‌جز این موارد، دیتاکلاس‌ها از آن‌ها استفاده نمی‌کنند.

برای مثال، فرض کنید اگر هنگام ایجاد کلاس مقداری ارائه نشود، یک فیلد از پایگاه داده مقداردهی اولیه می‌شود:

@dataclass
class C:
    i: int
    j: int | None = None
    database: InitVar[DatabaseType | None] = None

    def __post_init__(self, database):
        if self.j is None and database is not None:
            self.j = database.lookup('j')

c = C(10, database=my_database)

در این حالت، fields() اشیاء Field را برای i و j برمی‌گرداند، اما نه برای database.

نمونه‌های فریزشده

ایجاد اشیای پایتونی که واقعاً تغییرناپذیر باشند، امکان‌پذیر نیست. با این حال، با ارسال frozen=True به دکوراتور @dataclass می‌توانید تغییرناپذیری را شبیه‌سازی کنید. در این صورت، دیتاکلاس‌ها متدهای __setattr__() و __delattr__() را به کلاس اضافه می‌کنند. این متدها هنگام فراخوانی، FrozenInstanceError را پرتاب می‌کنند.

هنگام استفاده از frozen=True، هزینه عملکردی بسیار کمی وجود دارد: __init__() نمی‌تواند از انتساب ساده برای مقداردهی اولیه فیلدها استفاده کند و باید از object.__setattr__() استفاده کند.

وراثت

هنگامی که دیتاکلاس توسط دکوراتور @dataclass ایجاد می‌شود، این دکوراتور همه‌ی کلاس‌های پایه‌ی کلاس را در MRO معکوس (یعنی با شروع از object) بررسی می‌کند و برای هر دیتاکلاسی که پیدا کند، فیلدهای آن کلاس پایه را به یک نگاشت مرتب از فیلدها اضافه می‌کند. پس از افزودن همه‌ی فیلدهای کلاس‌های پایه، فیلدهای خودش را به نگاشت مرتب اضافه می‌کند. همه‌ی متدهای تولیدشده از این نگاشت مرتبِ محاسبه‌شده و ترکیبی فیلدها استفاده خواهند کرد. چون فیلدها به ترتیب درج قرار دارند، کلاس‌های مشتق‌شده کلاس‌های پایه را می‌پوشانند. یک مثال:

@dataclass
class Base:
    x: Any = 15.0
    y: int = 0

@dataclass
class C(Base):
    z: int = 10
    x: int = 15

فهرست نهایی فیلدها، به ترتیب، x، y، z است. نوع نهایی x، int است، همان‌طور که در کلاس C مشخص شده است.

متد __init__() تولیدشده برای C به‌شکل زیر خواهد بود:

def __init__(self, x: int = 15, y: int = 0, z: int = 10):

تغییر ترتیب پارامترهای فقط کلیدواژه‌ای در __init__()

پس از این که پارامترهای مورد نیاز برای __init__() محاسبه شدند، همه‌ی پارامترهای فقط کلیدواژه‌ای جابه‌جا می‌شوند تا پس از تمام پارامترهای معمولی (غیر فقط کلیدواژه‌ای) قرار بگیرند. این یک الزام در نحوه‌ی پیاده‌سازی پارامترهای فقط کلیدواژه‌ای در پایتون است: آن‌ها باید پس از پارامترهای غیر فقط کلیدواژه‌ای بیایند.

در این مثال، Base.y، Base.w و D.t فیلدهای فقط کلیدواژه‌ای هستند و Base.x و D.z فیلدهای معمولی هستند:

@dataclass
class Base:
    x: Any = 15.0
    _: KW_ONLY
    y: int = 0
    w: int = 1

@dataclass
class D(Base):
    z: int = 10
    t: int = field(kw_only=True, default=0)

متد تولیدشده‌ی __init__() برای D به‌صورت زیر خواهد بود:

def __init__(self, x: Any = 15.0, z: int = 10, *, y: int = 0, w: int = 1, t: int = 0):

توجه داشته باشید که پارامترها نسبت به آنچه در فهرست فیلدها آمده است دوباره مرتب شده‌اند: پس از پارامترهای حاصل از فیلدهای عادی، پارامترهای حاصل از فیلدهای فقط کلیدواژه‌ای می‌آیند.

ترتیب نسبی پارامترهای فقط کلیدواژه‌ای در فهرست پارامترهای __init__() که دوباره مرتب‌شده است، حفظ می‌شود.

توابع کارخانه پیش‌فرض

اگر یک field() یک default_factory تعیین کند، هنگامی که به یک مقدار پیش‌فرض برای آن فیلد نیاز باشد، با صفر آرگومان فراخوانی می‌شود. برای مثال، برای ایجاد یک نمونه جدید از فهرست، به‌صورت زیر عمل کنید:

mylist: list = field(default_factory=list)

اگر یک فیلد از __init__() حذف‌شده باشد (با استفاده از init=False) و برای آن فیلد همچنین default_factory مشخص‌شده باشد، آنگاه تابع کارخانه‌ی پیش‌فرض همیشه از تابع __init__() تولیدشده فراخوانی می‌شود. این امر به این دلیل است که هیچ راه دیگری برای دادن یک مقدار اولیه به فیلد وجود ندارد.

مقادیر پیش‌فرض تغییرپذیر

پایتون مقادیر پیش‌فرض متغیرهای عضو را در ویژگی‌های کلاس ذخیره می‌کند. این مثال را در نظر بگیرید که از dataclasses استفاده نمی‌کند:

class C:
    x = []
    def add(self, element):
        self.x.append(element)

o1 = C()
o2 = C()
o1.add(1)
o2.add(2)
assert o1.x == [1, 2]
assert o1.x is o2.x

توجه داشته باشید که دو نمونه از کلاس C، همان‌طور که انتظار می‌رود، همان متغیر کلاس x را به اشتراک می‌گذارند.

با استفاده از dataclasses، اگر این کد معتبر بود:

@dataclass
class D:
    x: list = []      # This code raises ValueError
    def add(self, element):
        self.x.append(element)

کدی مشابه زیر تولید می‌شود:

class D:
    x = []
    def __init__(self, x=x):
        self.x = x
    def add(self, element):
        self.x.append(element)

assert D().x is D().x

این همان مشکلی است که مثال اصلی با استفاده از کلاس C داشت. یعنی دو نمونه از کلاس D که هنگام ایجاد یک نمونه کلاس، مقداری برای x تعیین نمی‌کنند، نسخه یکسانی از x را به اشتراک می‌گذارند. از آن‌جا که کلاس‌های داده صرفاً از ایجاد کلاس معمولی پایتون استفاده می‌کنند، آن‌ها نیز همین رفتار را دارند. کلاس‌های داده هیچ راه کلی برای تشخیص این وضعیت ندارند. در عوض، دکوراتور @dataclass در صورت تشخیص یک پارامتر پیش‌فرض هش‌ناپذیر (unhashable)، یک ValueError پرتاب می‌کند. فرض بر این است که اگر یک مقدار هش‌ناپذیر (unhashable) باشد، تغییرپذیر است. این یک راه‌حل نسبی است، اما در برابر بسیاری از خطاهای رایج محافظت می‌کند.

استفاده از توابع کارخانه پیش‌فرض راهی برای ایجاد نمونه‌های جدید از انواع تغییرپذیر به‌عنوان مقادیر پیش‌فرض برای فیلدها است:

@dataclass
class D:
    x: list = field(default_factory=list)

assert D().x is not D().x

تغییر یافته در نسخه‌ی 3.11: به‌جای جست‌وجو و ممنوع‌کردن اشیایی از نوع list، dict یا set، اکنون اشیای هش‌ناپذیر به‌عنوان مقادیر پیش‌فرض مجاز نیستند. از هش‌ناپذیری برای تقریب‌زدن تغییرپذیری استفاده می‌شود.

فیلدهای نوع توصیف‌گر

فیلدهایی که اشیای توصیف‌گر (descriptor objects) به‌عنوان مقدار پیش‌فرض آن‌ها تخصیص داده شده‌اند، رفتارهای ویژه زیر را دارند:

  • مقداری که برای فیلد به متد __init__() دیتاکلاس ارسال می‌شود، به متد __set__() توصیف‌گر ارسال می‌شود، نه اینکه شیء توصیف‌گر بازنویسی شود.

  • به همین ترتیب، هنگام دریافت یا تنظیم فیلد، به جای بازگرداندن یا بازنویسی شیء توصیف‌گر، متد __get__() یا __set__() توصیف‌گر فراخوانی می‌شود.

  • برای تشخیص اینکه آیا یک فیلد دارای مقدار پیش‌فرض است، @dataclass متد __get__() توصیف‌گر را با استفاده از شکل دسترسی کلاسی آن فراخوانی می‌کند: descriptor.__get__(obj=None, type=cls). اگر توصیف‌گر در این حالت مقداری برگرداند، آن مقدار به‌عنوان مقدار پیش‌فرض فیلد استفاده خواهد شد. از سوی دیگر، اگر توصیف‌گر در این موقعیت AttributeError را پرتاب کند، هیچ مقدار پیش‌فرضی برای فیلد ارائه نخواهد شد.

class IntConversionDescriptor:
    def __init__(self, *, default):
        self._default = default

    def __set_name__(self, owner, name):
        self._name = "_" + name

    def __get__(self, obj, type):
        if obj is None:
            return self._default

        return getattr(obj, self._name, self._default)

    def __set__(self, obj, value):
        setattr(obj, self._name, int(value))

@dataclass
class InventoryItem:
    quantity_on_hand: IntConversionDescriptor = IntConversionDescriptor(default=100)

i = InventoryItem()
print(i.quantity_on_hand)   # 100
i.quantity_on_hand = 2.5    # calls __set__ with 2.5
print(i.quantity_on_hand)   # 2

توجه داشته باشید که اگر یک فیلد با یک نوع توصیف‌گر حاشیه‌نویسی شده باشد، اما یک شیء توصیف‌گر به‌عنوان مقدار پیش‌فرض آن تخصیص داده نشده باشد، آن فیلد مانند یک فیلد معمولی رفتار خواهد کرد.