CodeCrafters
773 subscribers
90 photos
50 videos
42 files
170 links
Download Telegram
Property and Descriptor-Based Attributes

Property
پایتون به شما امکان می‌دهد رفتاری شبیه به تابع را بر روی اتریبیوت های موجود در کلاس اضافه کنید و آن‌ها را به اتریبیوت های مدیریت‌شده (managed attribute) تبدیل کنید. این نوع ویژگی از بروز تغییرات اساسی در APIیعنی همان اینترفیس عمومی های شما جلوگیری می‌کند.

به عبارت دیگر، با اتریبیوت های مدیریت شده، شما می‌توانید همزمان از رفتار شبیه به تابع و دسترسی شبیه به اتریبیوت بهره‌مند شوید. نیازی به تغییر APIهای خود با جایگزین کردن اتریبیوت ها با متدها نیست، که ممکن است کد کاربران‌تان را دچار مشکل کند.
برای ایجاد یک اتریبیوت مدیریت شده با رفتار شبیه به تابع در پایتون، شما می‌توانید از یک property یا یک descriptor استفاده کنید، بسته به نیازهای خاص خود.
به عنوان مثال، به کلاس Circle خود برگردید و فرض کنید که نیاز دارید شعاع را تایید کنید تا اطمینان حاصل شود که فقط اعداد مثبت ذخیره می‌شوند. چگونه این کار را بدون تغییر اینترفیس کلاس انجام می‌دهید؟ سریع‌ترین روش برای حل این مشکل استفاده از یک property و پیاده‌سازی منطق ولیدیشن( اعتبارسنجی) در متد setter است.


import math

class Circle:
def __init__(self, radius):
self.radius = radius

@property
def radius(self):
return self._radius

@radius.setter
def radius(self, value):
if not isinstance(value, int | float) or value <= 0:
raise ValueError("positive number expected")
self._radius = value

def calculate_area(self):
return round(math.pi * self._radius**2, 2)

برای تبدیل اتریبیوت radius به یک پراپرتی، از دکوریتور property استفاده میکنیم یک متد getter بنویسیم. این متد هنگامی اجرا میشه که میخواهیم مقدار اتریبیوت radius رو مشاهده کنیم، پس قطعا باید مقدار شعاع رو به ما ریترن(return) کنه.
برای اینکه یک متد setter برای پراپرتی شعاع مون بنویسم از دکوریتور @ attr_name.setter استفاده میکنیم.
حال داخل متد setter میایم و منطق اعتبارسنجی که نیاز داشتیم رو پیاده میکنیم و اگر عدد طبق چیزی که انتظار داشتیم نبود به ما خطا میدهد در غیر این صورت در یک اتریبیوت non-public به نام _radius ذخیره میکنیم. حالا اتریبیوت radius ما تبدیل به یک پراپرتی شده و هرموقع که نیاز به مشاهده مقدار یا تغییر مقدار ان بودیم خود پایتون اتومات، ابتدا با توجه به عملیات یکی از متد های getter یا setter رو کال میکنه.
نحوه کارکرد اون به شکل زیر هست.
>>> from circle import Circle

>>> circle_1 = Circle(100)
>>> circle_1.radius
100
>>> circle_1.radius = 500
>>> circle_1.radius = 0
Traceback (most recent call last):
...
ValueError: positive number expected

>>> circle_2 = Circle(-100)
Traceback (most recent call last):
...
ValueError: positive number expected

>>> circle_3 = Circle("300")
Traceback (most recent call last):
...
ValueError: positive number expected


میبینید که با اتریبیوت radius دارید به طور عادی رفتار میکنید اما در پشت قضیه، یک رفتار متد مانند دارد و اگر مقدار نامعتبر دهید خطا میدهد.
منبع
#Property #Python
@Code_Crafters
2🔥1👏1
Descriptor
استفاده از descriptor ها هم یک روش قوی برای اضافه کردن رفتار متد مانند به یک اتریبیوت بدون تغییر دادن اینترفیس یک کلاس است. فرض کنید یک برنامه طراحی نوشتید و دو تا کلاس به نام های دایره و مربع دارید. میخواهیم عددی را به عنوان شعاع/ طول بگیریم و قبل ان چک کنیم که عدد حتما مثبت باشد. خب طبق چیزی که تا الان یاد گرفتیم میدانیم که با property میشود همین کار را انجام داد اما این کار باعث کد تکراری زدن میشود و خوب نیست پس از descriptor استفاده میکنیم.
با استفاده از descriptor کدی مانند زیر خواهیم داشت.

import math

class PositiveNumber:
def __set_name__(self, owner, name):
self._name = name

def __get__(self, instance, owner):
return instance.__dict__[self._name]

def __set__(self, instance, value):
if not isinstance(value, int | float) or value <= 0:
raise ValueError("positive number expected")
instance.__dict__[self._name] = value

class Circle:
radius = PositiveNumber()

def __init__(self, radius):
self.radius = radius

def calculate_area(self):
return round(math.pi * self.radius**2, 2)

class Square:
side = PositiveNumber()

def __init__(self, side):
self.side = side

def calculate_area(self):
return round(self.side**2, 2)


در قطعه کد بالا میبینیم که یک کلاس به نام PositiveNumber داریم.
این همان descriptor ما هست. سه داندر متد داره که به ترتیب به توضیح انها میپردازم.
متد اولی توسط پایتون به صورت اتومات هنگامی که از ان یک اینستنس بسازید صدا زده میشود و نام ان اینستنس هرچه که باشد به عنوان ارگومان name به متد پاس داده میشود. ارگومان owner هم نام کلاسی است که descriptor در ان استفاده شده است. ( در مثال ما Circle یا Square)
متد دوم همان متد getter و متد سوم هم همان متد setter همانند کاری که در پراپرتی انجام دادیم.
ارگومان instance که در دو متد getter,setter وجود داره درواقع همون اینستنسی هست که از کلاس Circle یا Square ما ساخته شده.
توجه داشته باشید که برای ذخیره یا مشاهده از اتریبیوت
__dict__
استفاده میکنیم که جلوتر متوجه علت ان خواهید شد.
این متد ها، اتومات توسط خود پایتون صدا زده میشوند.
حال در کلاس دایره خود، یک اتریبیوت کلاس به نام radius داریم که مقدارش، یک اینستنس از کلاس descriptor ما هست.
حال از این بعد هروقت بخواهیم مقداری در اتریبیوت radius ذخیره کنیم یا مقدار ان را مشاهده کنیم، بجای انکه پایتون از اتریبیوت
__dict__
ان را بخواند یا ذخیره کند، متد های getter, setter از کلاس PositiveNumber با توجه به عملیات مورد نیاز صدا زده میشود و ارگومان های مورد نیاز پاس داده خواهد شد.
منبع
#Descriptor #Python
@Code_Crafters
👍42👏1