آموزش دیتا کلاس(dataclass) در پایتون

November 2021

 

 #  معرفی dataclass پایتون

پایتون یک زبان شی گرا است و هر چیزی که در آن میبینید یک آبجکت است. برای ساخت آبجکت سفارشی خود میتوانید از class استفاده کنید. اما استفاده این روش به معنای نوشتن کد تکراری زیاد است. با استفاده از dataclass پایتون که در نسخه 3.7 معرفی شد میتوانید بخش بزرگی از ساخت کلاس را به پایتون بسپارید. با استفاده از dataclass پایتون ساخت متدهایی مانند __init__, __repr__ یا __eq__ را به عهده میگیرد.

 

همانطور که از نام این ماژول میتوان فهمید، از این روش برای ساخت کلاس‌‌هایی استفاده میشود که وظیفه ذخیره اطلاعات را دارند. اگر قرار است کلاسی بسازید در آن متدهای زیاد و پیچیده وجود دارند، بهتر است از همان کلاس‌های معمولی استفاده کنید.

 

فرض کنید که قرار است اطلاعات افراد را ذخیره کنید. در حالت عادی باید یک کلاس به شکل زیر ایجاد کنید:

class Person:
	def __init__(self, name, age):
		self.name = name
		self.age = age

	def __repr__(self):
		return f'{self.__class__.__name__}(name={self.name}, age={self.age})'

	def __eq__(self, other):
		if other.__class__ is not self.__class__:
			return NotImplemented
		return self.age == other.age

 

در کلاس Person بالا سه متد داریم. متد init که نام و سن را گرفته و ذخیره میکند. متد repr اطلاعات مناسب مربوط به هر آبجکت را نشان میدهد و متد eq برای زمانی استفاده میشود که بخواهید دو آبجکت از کلاس Person را با هم مقایسه کنید.

 

اگر دقت کنید، در مثال بالا کد زیادی را تکرار کرده‌ایم. فقط در متد init سه بار از کلمه age یا name استفاده شده. این در صورتی است که فقط دو مقدار ورودی داریم. اگر تعداد ورودی‌ها بیشتر باشد، کد تکراری بیشتری خواهیم داشت.

 

اگر همان کد بالا را بخواهیم با dataclassها بازنویسی کنیم،‌ چیزی شبیه به این میشود:

from dataclasses import dataclass

@dataclass
class Person:
	name: str
	age: int

 

اگر از دیتاکلاس استفاده کنید فقط کافیست مقادیر ورودی که قبلا در متد init مینوشتید، به عنوان field به کلاس بفرستید. بعد از نوشتن نام فیلد باید نوع ورودی را هم با استفاده از annotation مشخص کنید. این annotationها فقط راهنما بوده و در زمان ساخت آبجکت، اعتبارسنجی نخواهند شد. مثلا در کد بالا که مقدار ورودی name را str گذاشته‌ایم، اجباری نبوده و میتوانید مقادیر دیگری هم ارسال کنید.

 

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

from dataclasses import dataclass


@dataclass
class Person:
	name: str
	age: int
	
	def show(self):
		return f'{self.name} is {self.age} years old.'

 

در کد بالا متدی به نام show را اضافه کردیم که نام و سن کاربر را چاپ میکند. حالا برای استفاده از این دیتاکلاس باید از آن یک آبجکت جدید ایجاد کنید:

from dataclasses import dataclass


@dataclass
class Person:
	name: str
	age: int

	def show(self):
		return f'{self.name} is {self.age} years old.'

a = Person('amir', 20)
print(a.show())

# OUTPUT
amir is 20 years old.

 

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

 

برای دادن مقدار پیشفرض به مقادیر میتوانید این کار را با یک مساوی انجام دهید:

@dataclass
class Person:
	name: str
	age: int = 0

همانطور که در کد بالا میبنید به فیلد age مقدار پیشفرض 0 را دادیم. در این حالت در زمان ساخت آبجکت دیگر اجباری نیست که مقداری به age ارسال کنید.

 

برای دیدن متدهایی که در یک دیتاکلاس وجود دارد، میتوانید از help استفاده کنید تا اطلاعات کاملی از آن dataclass را به شما نشان دهد:

>>> help(Person)


Help on class Person in module __main__:

class Person(builtins.object)
 |  Person(name: str, age: int = 0) -> None
 |  
 |  Person(name: str, age: int = 0)
 |  
 |  Methods defined here:
 |  
 |  __eq__(self, other)
 |  
 |  __init__(self, name: str, age: int = 0) -> None
 |  
 |  __repr__(self)
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)
 |  
 |  ----------------------------------------------------------------------
 |  Data and other attributes defined here:
 |  
 |  __annotations__ = {'age': <class 'int'>, 'name': <class 'str'>}
 |  
 |  __dataclass_fields__ = {'age': Field(name='age',type=<class 'int'>,def...
 |  
 |  __dataclass_params__ = _DataclassParams(init=True,repr=True,eq=True,or...
 |  
 |  __hash__ = None
 |  
 |  age = 0

 

همانطور که صفحه help هم نشان میدهد، دیتاکلاس به طور اتوماتیک متدهای init, repr و eq را ایجاد کرده است. اما اگر نخواهید دیتاکلاس این متدها را برای ایجاد کند چه؟ بخش بعدی را بخوانید.

 

 #  سفارشی کردن dataclass پایتون

برای تغییر در رفتار پیشفرض دیتاکلاس‌ها میتوانید مقادیری که در دکوراتور وجود دارد را تغییر دهید. دکوراتور dataclass به طور پیشفرض به شکل زیر است:

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

 

برخی از این مقادیر True و برخی دیگر False هستند. مثلا مقدار repr برابر با True است که در اینصورت به طور اتوماتیک متد repr برای شما ایجاد خواهد شد. اگر بخواهید میتوانید این مقادیر را تغییر دهید. فرض کنید که نیازی به متد repr ندارید و میخواهید آن را حذف کنید. تنها کافیست مقدار repr را در دکوراتور dataclass برابر با False قرار دهید:

@dataclass(repr=False)
class Person:
	name: str
	age: int = 0

>>> help(Person)


...
 |  
 |  __eq__(self, other)
 |  
 |  __init__(self, name: str, age: int = 0) -> None
...

 

متدهای دیگری که ممکن است نیاز داشته باشید، متدها مقایسه‌ای هستند. به طور پیشفرض پایتون متد eq را برای مقایسه برابری ایجاد میکند. اگر بخواهید بقیه متدهای مقایسه‌ای را نیز داشته باشید، باید مقدار order را True قرار دهید:

@dataclass(order=True)
class Person:
	name: str
	age: int = 0

>>> help(Person)


...
 |  
 |  __eq__(self, other)
 |  
 |  __ge__(self, other)
 |  
 |  __gt__(self, other)
 |  
 |  __init__(self, name: str, age: int = 0) -> None
 |  
 |  __le__(self, other)
 |  
 |  __lt__(self, other)
 |  
 |  __repr__(self)
...

همانطور که میبیند پایتون متدهای gt, ge, le و lt را نیز برای شما ایجاد میکند.

 

تغییر بعدی که میتوانید در دیتاکلاس ایجاد کنید frozen است. اگر میخواهید امکان تغییر مقادیر فیلدها وجود نداشته باشد، میتوانید مقدار forzen را True قرار دهید:

@dataclass(frozen=True)
class Person:
	name: str
	age: int = 0

>>> a = Person('amir', 10)
>>> a.name = 'kevin'
>>> print(a.name)

Traceback (most recent call last):
  File "/home/amir/Desktop/python/two.py", line 10, in <module>
    a.name = 'kevin'
  File "<string>", line 4, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'name'

 

در کد بالا میبینید که چون مقدار forzen را True قرار داده‌ایم امکان تغییر مقدار فیلدها وجود ندارد و ارور FrozenInstanceError اتفاق افتاده است.

 

 

 #  سفارشی کردن فیلدهای dataclass پایتون

تا الآن هر کاری که تغییری که ایجاد کردیم در دکوراتور dataclass بوده و روی تمام کلاس اعمال میشد اما میتوانید تغییرات را به فیلدها نیز محدود کنید. در داخل ماژول dataclass یک متد به نام field وجود دارد که وظیفه تغییر در فیلدها را دارد. به طور پیشفرض field به شکل زیر است:

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

 

از نام آرگومان‌ها میتوانید بفهمید که هر کدام از این مقادیر چه کاری انجام میدهند. مثلا default برای مشخص کردن مقدار پیشفرض، init برای غیر فعال کردن فیلد در متد __init__ یا repr برای حذف فیلد از متد __repr__ استفاده میکند. به مثال زیر دقت کنید. در زمان چاپ یک آبجکت تمام فیلدها در خروجی متد __repr__ وجود دارند:

from dataclasses import dataclass

@dataclass
class Person:
	name: str
	age: int = 0

a = Person('amir', 10)
print(a)

# OUTPUT
Person(name='amir', age=10)

 

اگر بخواهید یکی از این فیلدها را حذف کنید میتوانید در field مقدار repr را False قرار دهید:

from dataclasses import dataclass, field

@dataclass
class Person:
	name: str
	age: int = field(repr=False)

a = Person('amir', 10)
print(a)

# OUTPUT
Person(name='amir')

 

در خروجی بالا دیگر فیلد age را نمیبیند چون مشخص کردید که نمیخواهید در متد __repr__ وجود داشته باشد.

ارسال نظر

تلاش میکنم سوالات شما را در کمتر از یک روز پاسخ بدم