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

Skip to content

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

في لغة Python (ينطبق الأمر نفسه على معظم اللغات الأخرى)، من الممكن إنشاء كائنات خاصة، بقيم وقواعد ووظائف خاصة بها. يُطلق على هذا اسم الفئات (classes بالإنجليزية). نحن نستخدم الفئات لتجميع البيانات والوظائف ذات الصلة في وحدة واحدة، على سبيل المثال، فئة Ordre التي تحتوي على بيانات مثل ordre_id و kunde_navn و produkter ووظائف مثل legg_til_produkt() و beregn_total()، إلخ.

القاموس (JSON) مقابل الفئات (الكائنات)

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 decorator)، والذي يحقق نفس ما سبق (ولكن بكود أقل):

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)

الحل: فئة بيانات لـ 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. (أكثر تقدمًا، ولكن ابحث عنه على الإنترنت!)

الحل: كود للتحقق البسيط من البريد الإلكتروني

إليك حل ممكن، نحن نستخدم الاستثناءات (exceptions) لـ “إيقاف” البرنامج في حال كان البريد الإلكتروني غير صالح، سيؤدي هذا إلى إيقاف البرنامج فوراً وإظهار رسالة خطأ.

from dataclasses import dataclass

@dataclass
class Email:
    address: str

    def __post_init__(self):
        if "@" not in self.address:
            raise ValueError(f"Mangler @ i epostadressen: {self.address}")
        if "." not in self.address.split("@")[1]:
            raise ValueError(f"Mangler . i domenet til epostadressen: {self.address}")
        if " " in self.address:
            raise ValueError(f"Epostadressen kan ikke inneholde mellomrom: {self.address}")

# Test koden 
test = Email("hei@example.com")  # Gyldig
try:
    test = Email("heiexample.com")
except ValueError as e:
    print(e) # Ugyldig, mangler @

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

تحدٍ جديد يظهر!

الآن بعد أن قمنا بتغيير فئة Person لاستخدام فئات PhoneNumber و Email ، يجب علينا أيضاً تغيير كيفية إنشاء (instantiate) كائن من نوع 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

# اختبار الكود
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)