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
فیلدهای نوع توصیفگر¶
فیلدهایی که اشیای توصیفگر (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
توجه داشته باشید که اگر یک فیلد با یک نوع توصیفگر حاشیهنویسی شده باشد، اما یک شیء توصیفگر بهعنوان مقدار پیشفرض آن تخصیص داده نشده باشد، آن فیلد مانند یک فیلد معمولی رفتار خواهد کرد.