۳۰ آذر ۱۴۰۳

Techboy

اخبار و اطلاعات روز تکنولوژی

نحوه استفاده از کلاس های داده پایتون

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

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

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

کلاس‌های داده که در Python 3.7 معرفی شده‌اند (و به Python 3.6 بکپورت شده‌اند)، یک ابزار مفید و کمتر ارائه می‌کنند. روشی پرمخاطب برای ایجاد کلاس ها بسیاری از کارهای رایجی که در یک کلاس انجام می‌دهید، مانند نمونه‌سازی ویژگی‌ها از آرگومان‌های ارسال شده به کلاس، را می‌توان به چند دستورالعمل اساسی تقلیل داد.

نمونه کلاس داده پایتون

در اینجا یک مثال ساده از یک کلاس معمولی در پایتون آمده است:


class Book:
    '''Object for tracking physical books in a collection.'''
    def __init__(self, name: str, weight: float, shelf_id:int = 0):
        self.name = name
        self.weight = weight # in grams, for calculating shipping
        self.shelf_id = shelf_id
    def __repr__(self):
        return(f"Book(name={self.name!r},
            weight={self.weight!r}, shelf_id={self.shelf_id!r})")

بزرگ‌ترین سردرد در اینجا روشی است که هر یک از آرگومان‌های ارسال شده به __init__ باید در ویژگی‌های شیء کپی شوند. اگر فقط با کتاب سر و کار دارید، این خیلی بد نیست، اما اگر مجبور باشید با  Bookshelf، Library،  انبار و غیره؟ به علاوه، هرچه کد بیشتری را باید با دست تایپ کنید، احتمال اشتباه شما بیشتر می‌شود.

در اینجا همان کلاس پایتون است که به عنوان یک کلاس داده پایتون پیاده سازی شده است:


from dataclasses import dataclass

@dataclass
class Book:
    '''Object for tracking physical books in a collection.'''
    name: str
    weight: float 
    shelf_id: int = 0

وقتی ویژگی‌هایی را که به آنها  فیلدها می‌گویند، در یک کلاس داده مشخص می‌کنید، تزئین‌کننده @dataclass به‌طور خودکار همه کدهای لازم برای مقداردهی اولیه آنها را تولید می‌کند. همچنین اطلاعات نوع را برای هر ویژگی حفظ می‌کند، بنابراین اگر از یک لایه کد مانند mypy استفاده می‌کنید، اطمینان حاصل می‌کند که انواع مناسبی از متغیرها را برای سازنده کلاس ارائه می‌دهید.

کار دیگری که @dataclass در پشت صحنه انجام می‌دهد، ایجاد خودکار کد برای تعدادی از متدهای dunder رایج در کلاس است. در کلاس معمولی بالا، ما باید __repr__ خود را ایجاد می‌کردیم. در کلاس داده، دکوراتور @dataclass __repr__ را برای شما ایجاد می‌کند.

هنگامی که یک کلاس داده ایجاد می شود، از نظر عملکردی با یک کلاس معمولی یکسان است. هیچ جریمه عملکردی برای استفاده از کلاس داده وجود ندارد. فقط یک جریمه عملکرد کوچک برای اعلام کلاس به عنوان یک کلاس داده وجود دارد، و این یک هزینه زمانی است که شیء کلاس داده ایجاد شود.

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

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

  • frozen: نمونه های کلاسی را تولید می کند که فقط خواندنی هستند. پس از تخصیص داده ها، نمی توان آنها را تغییر داد.
  • slots: به نمونه‌هایی از کلاس‌های داده اجازه می‌دهد تا تنها با اجازه دادن به فیلدهایی که به‌صراحت در کلاس تعریف شده‌اند، از حافظه کمتری استفاده کنند.
  • kw_only: وقتی تنظیم شود، تمام فیلدهای کلاس فقط کلیدواژه هستند.

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

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


from dataclasses import dataclass, field
from typing import List

@dataclass
class Book:
    '''Object for tracking physical books in a collection.'''
    name: str     
    condition: str = field(compare=False)    
    weight: float = field(default=0.0, repr=False)
    shelf_id: int = 0
    chapters: List[str] = field(default_factory=list)

وقتی یک مقدار پیش‌فرض را برای نمونه‌ای از field تنظیم می‌کنید، بسته به پارامترهایی که به field می‌دهید، نحوه تنظیم فیلد را تغییر می‌دهد. اینها رایج‌ترین گزینه‌های مورد استفاده برای فیلد  هستند (گزینه‌های دیگری نیز وجود دارد):

  • default: مقدار پیش‌فرض را برای فیلد تنظیم می‌کند. اگر الف) از  field برای تغییر سایر پارامترهای فیلد استفاده می‌کنید، و ب) می‌خواهید یک مقدار پیش‌فرض در قسمت بالای آن تنظیم کنید، باید از default استفاده کنید. . در این مورد، از پیش‌فرض برای تنظیم وزن به ۰.۰ استفاده می‌کنیم.
  • default_factory: نام تابعی را ارائه می‌کند که هیچ پارامتری ندارد و مقداری از شی را به عنوان مقدار پیش‌فرض فیلد برمی‌گرداند. در این مورد، ما می‌خواهیم فصل‌ها یک لیست خالی باشند.
  • repr: به‌طور پیش‌فرض (درست)، کنترل می‌کند که آیا فیلد مورد نظر در __repr__ برای کلاس داده‌ای که به‌طور خودکار ایجاد می‌شود، نشان داده شود. در این مورد، ما نمی‌خواهیم وزن کتاب در __repr__ نشان داده شود، بنابراین از repr=False برای حذف آن استفاده می‌کنیم.
  • مقایسه: به طور پیش‌فرض (True)، شامل فیلد در روش‌های مقایسه است که به طور خودکار برای کلاس داده تولید می‌شود. در اینجا، ما نمی‌خواهیم از شرط به عنوان بخشی از مقایسه دو کتاب استفاده شود، بنابراین compare=False را تنظیم می‌کنیم.

توجه داشته باشید که باید ترتیب فیلدها را طوری تنظیم کنیم که فیلدهای غیرپیش‌فرض اول باشند.

کنترل مقداردهی اولیه کلاس داده پایتون

در این مرحله احتمالاً از خود می‌پرسید: اگر روش __init__ یک کلاس داده به‌طور خودکار تولید می‌شود، چگونه می‌توانم برای ایجاد تغییرات دقیق‌تر روی فرآیند init کنترل داشته باشم؟

>

__post_init__

روش __post_init__ را وارد کنید. اگر روش __post_init__ را در تعریف کلاس داده خود بگنجانید، می‌توانید دستورالعمل‌هایی برای اصلاح فیلدها یا سایر داده‌های نمونه ارائه کنید:


from dataclasses import dataclass, field
from typing import List

@dataclass
class Book:
    '''Object for tracking physical books in a collection.'''
    name: str    
    weight: float = field(default=0.0, repr=False)
    shelf_id: Optional[int] = field(init=False)
    chapters: List[str] = field(default_factory=list)
    condition: str = field(default="Good", compare=False)

    def __post_init__(self):
        if self.condition == "Discarded":
            self.shelf_id = None
        else:
            self.shelf_id = 0

در این مثال، ما یک روش __post_init__ برای تنظیم shelf_id به هیچک  ایجاد کرده‌ایم، اگر شرط کتاب به‌عنوان  مقداردهی اولیه شده باشد." حذف شد". توجه داشته باشید که چگونه از field  برای مقداردهی اولیه shelf_id استفاده می‌کنیم و init را به‌عنوان False به فیلد این بدان معنی است که shelf_id در __init__ مقداردهی اولیه نمی‌شود.

InitVar

یک راه دیگر برای سفارشی کردن تنظیمات کلاس داده پایتون استفاده از نوع  InitVar است. این به شما امکان می‌دهد فیلدی را مشخص کنید که به __init__ و سپس به __post_init__ ارسال شود، اما در نمونه کلاس ذخیره نخواهد شد.

با استفاده از InitVar، می‌توانید هنگام تنظیم کلاس داده، پارامترهایی را دریافت کنید که فقط در هنگام تنظیم اولیه استفاده می‌شوند. این یک مثال است:


from dataclasses import dataclass, field, InitVar
from typing import List

@dataclass
class Book:
    '''Object for tracking physical books in a collection.'''
    name: str     
    condition: InitVar[str] = "Good"
    weight: float = field(default=0.0, repr=False)
    shelf_id: int = field(init=False)
    chapters: List[str] = field(default_factory=list)

    def __post_init__(self, condition):
        if condition == "Unacceptable":
            self.shelf_id = None
        else:
            self.shelf_id = 0

تنظیم نوع یک فیلد به InitVar  (که نوع فیلد فرعی آن نوع فیلد واقعی است) به @dataclass علامت می‌دهد تا آن فیلد را به یک فیلد کلاس داده تبدیل نکنید، بلکه به آن منتقل شود. داده ها همراه با __post_init__ به عنوان آرگومان.

در این نسخه از کلاس Book مان، condition را به‌عنوان یک فیلد در نمونه کلاس ذخیره نمی‌کنیم. ما فقط از شرط در مرحله اولیه سازی استفاده می کنیم. اگر متوجه شدیم که شرط روی «غیرقابل قبول» تنظیم شده است، shelf_id را روی هیچکدام تنظیم می‌کنیم — اما این کار را نمی‌کنیم. خود را condition در نمونه کلاس ذخیره کنید.

چه زمانی از کلاس‌های داده پایتون استفاده کنیم—و چه زمانی از آنها استفاده نکنیم

یک سناریوی رایج برای استفاده از کلاس‌های داده، جایگزینی برای namedtuple. کلاس‌های داده رفتارهای مشابه و بیشتر را ارائه می‌دهند، و می‌توان آنها را تغییرناپذیر کرد (همانطور که namedtupleها هستند) با استفاده از @dataclass(frozen=True) ) به عنوان دکوراتور.

یک مورد استفاده احتمالی دیگر جایگزینی دیکشنری‌های تودرتو است که کار با آن‌ها ممکن است ناشیانه باشد، با نمونه‌های تودرتو از کلاس‌های داده. اگر یک کلاس داده کتابخانه دارید، با ویژگی فهرستی از قفسه‌ها، می‌توانید از یک کلاس داده ReadingRoom برای پر کردن آن فهرست استفاده کنید، سپس روش‌هایی را به آن اضافه کنید. دسترسی به اقلام تو در تو (مانند کتاب روی قفسه در یک اتاق خاص) را آسان کنید.

اما لازم نیست هر کلاس پایتون یک کلاس داده باشد. اگر یک کلاس را عمدتاً به‌عنوان روشی برای گروه‌بندی دسته‌ای از روش‌های استاتیک ایجاد می‌کنید، نه به‌عنوان محفظه‌ای برای داده‌ها، نیازی نیست آن را به یک کلاس داده تبدیل کنید. به عنوان مثال، یک الگوی رایج با تجزیه کننده ها، داشتن کلاسی است که یک درخت نحو انتزاعی را می گیرد، درخت را راه می اندازد، و بر اساس نوع گره، فراخوانی ها را به روش های مختلف در کلاس ارسال می کند. از آنجایی که کلاس تجزیه کننده داده های بسیار کمی دارد، کلاس داده در اینجا مفید نیست.