این یک متن ترجمه شده ماشینی است که ممکن است حاوی خطا باشد!
در پایتون (و همچنین بیشتر زبانهای دیگر) امکان ایجاد اشیاء سفارشی با مقادیر، قوانین و توابع خاص وجود دارد. به این کار کلاس (classes به انگلیسی) گفته میشود. ما از کلاسها برای جمعآوری دادهها و توابع مرتبط در یک واحد استفاده میکنیم، مثلاً یک کلاس Ordre (سفارش) که دادههایی مانند ordre_id (شناسه سفارش)، kunde_navn (نام مشتری)، produkter (محصولات) و توابعی مانند legg_til_produkt() (افزودن محصول)، beregn_total() (محاسبه مجموع) و غیره دارد.
Dictionary (JSON) vs Klasser (Objekter)
JSON (JavaScript Object Notation) یک فرمت برای ذخیره و انتقال دادهها مستقل از زبان برنامهنویسی است، در حالی که یک کلاس یک ساختار در یک زبان برنامهنویسی خاص است.
وقتی میخواهیم دادهها را از طریق شبکه ارسال کنیم، یا آن را در یک فایل ذخیره کنیم، اغلب از JSON (یا جداول در پایگاههای داده) استفاده میکنیم.
وقتی میخواهیم با دادههای ساختاریافته در کد کار کنیم، از کلاسها استفاده میکنیم.
در این ماژول، نحوه استفاده از کلاسها برای اعتبارسنجی دادهها را بررسی خواهیم کرد.
آسانترین راه استفاده از @dataclass از کتابخانه dataclasses است. این کار باعث میشود که از نوشتن کدهای boilerplate زیادی برای ایجاد یک کلاس صرفهنظر کنیم. (مانند توابع داخلی __init__ و __repr__ (نمایندگی)).
مثالی از یک کلاس بدون استفاده از دکوراتور dataclass:
class Car:
def __init__(self, make: str, model: str, year: int):
self.make = make
self.model = model
self.year = year
def __repr__(self):
return f"{self.year} {self.make} {self.model}"
my_car = Car("Toyota", "Corolla", 2020)
print(my_car) # خروجی: 2020 Toyota Corolla
مثال با دکوراتور dataclass، که به همان نتیجهی بالا میرسد (اما با کد کمتر):
from dataclasses import dataclass
@dataclass
class Car:
make: str
model: str
year: int
my_car = Car("Toyota", "Corolla", 2020)
print(my_car) # خروجی: Car(make='Toyota', model='Corolla', year=2020)
تکالیف 1 - ایجاد یک کلاس
یک کلاس به نام Person ایجاد کنید. این کلاس باید ویژگیهای (attributes) زیر را داشته باشد:
name: نام شخصeye_color: رنگ چشم شخصphone_number: شماره تلفن شخصemail: آدرس ایمیل شخص
یک شیء (instance) از کلاس Person با مقادیر معتبر برای همه ویژگیها مانند مثال زیر ایجاد (instantiate) کنید.
@dataclass
class Person:
... # کد شما اینجا
bob_kaare = Person(name="Bob Kåre",
eye_color="blue",
phone_number="12345678",
email="bob_kaare@example.com")
print(bob_kaare)
Løsning: En dataclass for Person
در اینجا یک راه حل ممکن وجود دارد:
from dataclasses import dataclass
@dataclass
class Person:
name: str
eye_color: str
phone_number: str
email: str
وظیفه 2 - اعتبارسنجی در کلاس
در مثال بالا، ما هیچ اعتبارسنجی اضافه نکردهایم. این بدان معناست که ما میتوانیم یک Person با مقادیر نامعتبر مانند این ایجاد کنیم:
@dataclass
class Person:
... # کد شما اینجا
invalid_person = Person(name="",
eye_color="yes",
phone_number="12345",
email="not-an-email")
print(invalid_person)
# خروجی: Person(name='', eye_color='yes', phone_number='12345', email='not-an-email')
این (احتمالاً) مشکلساز است و ممکن است در آینده منجر به بدهی فنی شود. خوشبختانه، راههای سادهای برای افزودن اعتبارسنجی به کلاسها وجود دارد.
ما با بررسی اعتبارسنجی ایمیل شروع خواهیم کرد. کتابخانههای داخلی در پایتون وجود دارند که میتوانند در این زمینه به ما کمک کنند، اما از آنجایی که این کار را برای یادگیری انجام میدهیم، اعتبارسنجی سادهی خود را با ایجاد یک کلاس جدید به نام “Email” و بررسی یک تابع __post_init__ (فقط برای dataclass) ایجاد خواهیم کرد.
Merk
ما همچنین میتوانیم این کار را در خود کلاس Person انجام دهیم، اما اغلب بهتر است کلاسهای جداگانهای برای مواردی که میتوانند دوباره استفاده شوند، ایجاد کنیم.
نمونهای از یک تابع post_init
from dataclasses import dataclass
@dataclass
class Email:
address: str
def __post_init__(self):
print(f"Validating email: {self.address}")
# کد شما اینجا
برای یک اعتبارسنجی ساده ایمیل، میتوانیم مثلاً بررسی کنیم که ایمیل شامل هر دو علامت @ و . باشد. در صورت تمایل، میتوانید بررسی کنید که ایمیل با یک الگوی regex مطابقت داشته باشد. (پیشرفتهتر، اما در اینترنت جستجو کنید!)
راه حل: کد برای اعتبارسنجی ساده ایمیل
در اینجا یک راه حل ممکن وجود دارد، ما از استثناها برای “خراب کردن” برنامه در صورت نامعتبر بودن ایمیل استفاده میکنیم، این کار برنامه را بلافاصله متوقف میکند و یک پیام خطا ارائه میدهد.
from dataclasses import dataclass
@dataclass
class Email:
address: str
def __post_init__(self):
if "@" not in self.address:
raise ValueError(f"Mفقصد @ در آدرس ایمیل: {self.address}")
if "." not in self.address.split("@")[1]:
raise ValueError(f"فقدان . در دامنه آدرس ایمیل: {self.address}")
if " " in self.address:
raise ValueError(f"آدرس ایمیل نمیتواند شامل فاصله باشد: {self.address}")
# تست کد
test = Email("hei@example.com") # معتبر
try:
test = Email("heiexample.com")
except ValueError as e:
print(e) # نامعتبر، @ از دست رفته
وظیفه 3 - اعتبارسنجی شماره تلفن
یک کلاس مشابه آنچه برای ایمیلها ساختید ایجاد کنید، اما این بار برای شماره تلفن.
چالش با اعتبارسنجی تلفن!
آیا میتوانید اعتبارسنجی شماره تلفن را طوری اصلاح کنید که هم حروف (str) و هم اعداد (int) را بپذیرد؟ به عنوان مثال، هم 12345678 و هم "12345678" باید معتبر باشند.
همچنین سعی کنید کدهای کشور را به عنوان یک ویژگی (زیرمقدار کلاس) اضافه کنید. به عنوان مثال، 47 برای نروژ، 46 برای سوئد
وظیفه 4 - استفاده از اعتبارسنجی در کلاس Person
اکنون که اعتبارسنجی برای ایمیل و شماره تلفن ایجاد کردهایم، میتوانیم از این اعتبارسنجیها در کلاس Person استفاده کنیم.
@dataclass
class Person:
name: str
eye_color: str
phone_number: PhoneNumber # از کلاس PhoneNumber استفاده کنید
email: Email # از کلاس Email استفاده کنید
چالش جدیدی به وجود آمد!
اکنون که کلاس Person را تغییر دادهایم تا از کلاسهای PhoneNumber و Email استفاده کند، باید نحوه ایجاد نمونه (ساخت) یک Person را نیز تغییر دهیم. اکنون ابتدا باید یک شیء PhoneNumber و یک شیء Email ایجاد کنیم، قبل از اینکه بتوانیم یک Person ایجاد کنیم.
bob_kaare = Person(name="Bob Kåre",
eye_color="blue",
phone_number=PhoneNumber("12345678"), # توجه به این تغییر
email=Email("bob_kaare@example.com")) # توجه به این تغییر
print(bob_kaare)
# توجه: یک تغییر در نحوه استخراج مقادیر نیز باید ایجاد شود
print(bob_kaare.email.address)
print(bob_kaare.phone_number.number) # .country_code(?)
وظیفه 5 - ویژگیها در کلاسها (اختیاری)
وقتی از اشیاء برای نمایش مقادیری مانند ایمیل و شماره تلفن استفاده میکنیم، مجبوریم هر بار که میخواهیم مقدار را استخراج کنیم، مقدار زیرین (مثلاً address برای ایمیل و number برای شماره تلفن) را مشخص کنیم. این میتواند در دراز مدت کمی دست و پا گیر شود. خوشبختانه راه حلی برای این وجود دارد، با استفاده از دکوراتور @property در یک کلاس، که به ما امکان میدهد مقدار را مستقیماً از شیء استخراج کنیم، بدون اینکه مجبور باشیم مقدار زیرین را مشخص کنیم.
این البته چالش دیگری را نیز به همراه دارد، و آن این است که ما به تابع __init__ در کلاس Person نیاز داریم. این به این دلیل است که ما نمیتوانیم از یک نام هم برای یک ویژگی و هم برای یک ویژگی در یک dataclass استفاده کنیم.
from dataclasses import dataclass
@dataclass
class EksempelVerdi:
attributt: str
@dataclass
class Person:
name: str
_verdi: EksempelVerdi # متغیر داخلی (با _ شروع میشود تا نشان دهد که "خصوصی" است)
def __init__(self, name: str, verdi: EksempelVerdi):
self.name = name
self._verdi = verdi
@property
def verdi(self):
return self._verdi.attributt # مقدار زیرین را مستقیماً دریافت میکند
# تست کد
person = Person(name="Alice", verdi=EksempelVerdi("Noe tekst"))
print(person.verdi) # خروجی: Noe tekst
Merk
ویژگیها در این که نیازی به پارامتر ندارند و برای اجرا نیازی به پرانتز ندارند، منحصربهفرد هستند. در مثال، ما person.verdi را بدون پرانتز استخراج میکنیم (نه person.verdi())، اگرچه از نظر فنی یک تابع است.
مثال جایگزین که هم str و هم EksempelVerdi را میپذیرد
@dataclass
class Person:
name: str
_verdi: str
def __init__(self, name: str, verdi: str | EksempelVerdi):
self.name = name
if isinstance(verdi, EksempelVerdi):
self._verdi = verdi
elif isinstance(verdi, str):
self._verdi = EksempelVerdi(verdi)
else:
raise TypeError("verdi باید از نوع str یا EksempelVerdi باشد")
@property
def verdi(self) -> str:
return self._verdi.attributt # مقدار زیرین را مستقیماً دریافت میکند
# کد را تست کنید
person = Person(name="Alice", verdi="Noe tekst")
print(person.verdi) # خروجی: Noe tekst
وظیفه 6 - ویژگیها با منطق (اختیاری)
Person را با افزودن یک ویژگی جدید به نام birthday (تاریخ تولد) بهروزرسانی کنید. این باید از نوع datetime.date (از کتابخانه datetime) باشد.
سپس ویژگیهای زیر را ایجاد کنید:
- یک ویژگی
ageایجاد کنید که سن شخص را بر اساسbirthdayو تاریخ امروز محاسبه کند. - یک ویژگی
is_adultایجاد کنید که اگر شخص 18 سال یا بیشتر باشدTrueو در غیر این صورتFalseرا برگرداند.
راه حل: ویژگیهای سن و بزرگسال
در اینجا یک راه حل ممکن وجود دارد:
from dataclasses import dataclass
from datetime import date
@dataclass
class Person:
name: str
birthday: date
@property
def age(self) -> int:
"""سن را بر اساس تاریخ تولد و تاریخ امروز محاسبه میکند"""
today = date.today()
age = today.year - self.birthday.year
# اگر تولد شخص هنوز در این سال نرسیده باشد، 1 واحد کم کنید
if (today.month, today.day) < (self.birthday.month, self.birthday.day):
age -= 1
return age
@property
def is_adult(self) -> bool:
"""اگر شخص 18 سال یا بیشتر باشد True را برمیگرداند"""
return self.age >= 18
# تست کد
person = Person(name="Alice", birthday=date(2005, 5, 15))
print(person.age) # مثلاً 18 اگر تاریخ امروز بعد از 15 می 2023 باشد
print(person.is_adult) # True
چالش دیگری!
آیا میتوانید نمونهسازی Person را طوری تنظیم کنید که هم datetime.date و هم یک متن با فرمت "DD-MM-YYYY" را برای birthday بپذیرد؟ (راهنما: از datetime.strptime برای تبدیل رشته به datetime.date استفاده کنید)
