التحقق من صحة البيانات والفئات

Skip to content

هذا نص مترجم آليًا وقد يحتوي على أخطاء!

في بايثون (ينطبق هذا أيضًا على معظم اللغات الأخرى) من الممكن إنشاء كائنات مخصصة، بقيم وقواعد ووظائف خاصة بها. يطلق على هذا فئات (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

British python devs be like thats a constructor, __init__?

مثال مع مُزخرف 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)

Easy المهمة 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

Medium المهمة 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) # غير صالح، مفقود @

Medium مهمة 3 - التحقق من رقم الهاتف

قم بإنشاء فئة مماثلة لتلك التي أنشأتها لرسائل البريد الإلكتروني، ولكن الآن لأرقام الهواتف.

تحدٍ في التحقق من رقم الهاتف!

هل يمكنك إصلاح التحقق من أرقام الهواتف لقبول كل من الأحرف (str) والأرقام (int)؟ على سبيل المثال، يجب أن يكون كل من 12345678 و "12345678" صالحين.

حاول أيضًا إضافة رموز البلد كسمة (قيمة فرعية للفئة). على سبيل المثال، 47 للنرويج، 46 للسويد.

Medium المهمة 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(?)

Hard المهمة 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

Hard المهمة 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)