هذا نص مترجم آليًا وقد يحتوي على أخطاء!
في بايثون (ينطبق هذا أيضًا على معظم اللغات الأخرى) من الممكن إنشاء كائنات مخصصة، بقيم وقواعد ووظائف خاصة بها. يطلق على هذا فئات (classes باللغة الإنجليزية). نستخدم الفئات لتجميع البيانات والوظائف ذات الصلة في وحدة واحدة، على سبيل المثال، فئة Ordre (طلب) تحتوي على بيانات مثل ordre_id (معرف الطلب) و kunde_navn (اسم العميل) و produkter (المنتجات) ووظائف مثل legg_til_produkt() (إضافة منتج) و beregn_total() (حساب الإجمالي) ، إلخ.
Dictionary (JSON) vs Klasser (Objekter)
JSON (JavaScript Object Notation) هو تنسيق لتخزين ونقل البيانات بغض النظر عن لغة البرمجة، بينما الفئة هي هيكل في لغة برمجة معينة.
عندما نحتاج إلى إرسال البيانات عبر الشبكة، أو تخزينها في ملف، فإننا غالبًا ما نستخدم JSON (أو الجداول في قواعد البيانات).
عندما نحتاج إلى العمل مع البيانات المنظمة في الكود، فإننا نستخدم الفئات.
في هذه الوحدة، سننظر في كيفية استخدام الفئات للتحقق من صحة البيانات.
الأسهل هو استخدام @dataclass من مكتبة dataclasses. هذا يسمح لنا بتجنب كتابة الكثير من التعليمات البرمجية القياسية لإنشاء فئة. (مثل وظائف __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: عنوان البريد الإلكتروني للشخص
قم بإنشاء (instantiate) كائن من الفئة Person بقيم صالحة لجميع الخصائص كما في المثال أدناه.
@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)
# Output: Person(name='', eye_color='yes', phone_number='12345', email='not-an-email')
هذا (من المحتمل) إشكالي، وقد يؤدي بسهولة إلى تراكم الديون التقنية في المستقبل. لحسن الحظ، هناك طرق بسيطة لإضافة التحقق من الصحة إلى الفئات.
سنبدأ بالنظر إلى التحقق من صحة البريد الإلكتروني. توجد مكتبات مدمجة في Python يمكن أن تساعدنا في ذلك، ولكن لأننا نفعل هذا للتعلم، فسوف نصنع التحقق من الصحة البسيط الخاص بنا، عن طريق إنشاء فئة جديدة لـ “Email”، وفحص وظيفة __post_init__ (لفئة البيانات فقط).
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.
@dataclass
class Person:
name: str
eye_color: str
phone_number: PhoneNumber # استخدم فئة PhoneNumber
email: Email # استخدم فئة Email
Ny utfordring oppstår!
الآن بعد أن قمنا بتغيير فئة 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. وذلك لأننا لا يمكننا استخدام نفس الاسم لكل من الخاصية والسمة في فئة بيانات.
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 må være av type str eller EksempelVerdi")
@property
def verdi(self) -> str:
return self._verdi.attributt # Henter ut underverdien direkte
# Test koden
person = Person(name="Alice", verdi="Noe tekst")
print(person.verdi) # Output: Noe tekst
المهمة 6 - الخصائص مع المنطق (اختياري)
قم بتحديث Person بإضافة سمة جديدة تسمى birthday (تاريخ الميلاد). يجب أن يكون هذا من النوع datetime.date (من مكتبة datetime).
بعد ذلك، قم بإنشاء الخصائص التالية:
- قم بإنشاء خاصية
ageتحسب عمر الشخص بناءً علىbirthdayوالتاريخ الحالي. - قم بإنشاء خاصية
is_adultالتي تُرجعTrueإذا كان الشخص يبلغ من العمر 18 عامًا أو أكثر، وإلا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:
"""إرجاع True إذا كان الشخص يبلغ من العمر 18 عامًا أو أكثر"""
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)
