Hopp til innhold

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

Skip to content

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

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

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

JSON (تسمية كائن JavaScript) هو تنسيق لتخزين ونقل البيانات بشكل مستقل عن لغة البرمجة، بينما الفئة هي بنية في لغة برمجة محددة.

عندما نرسل بيانات عبر الشبكة أو نخزنها في ملف، غالبًا ما نستخدم 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 تويوتا كورولا

برمجة بايثون في بريطانيا، يقول أحدهم "هذا مُنشئ؟ __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. يجب أن تحتوي هذه الفئة على الخصائص التالية (اتجاهات/سِمة):

  • name: اسم الشخص
  • eye_color: لون عيون الشخص
  • phone_number: رقم هاتف الشخص
  • email: عنوان البريد الإلكتروني للشخص

قم بإنشاء كائن من الفئة 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)
الحل: فئة بيانات (DataClass) للشخص

إليك أحد الحلول الممكنة:

from dataclasses import dataclass

@dataclass
class Person:
    name: str
    eye_color: str
    phone_number: str
    email: str

متوسط المهمة 2 - التحقق داخل الفصل

في المثال أعلاه، لم نضف أي تحقق من الصحة. هذا يعني أنه يمكننا إنشاء كائن شخص بقيم غير صالحة، مثل ما يلي:

@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')

هذا أمرٌ مُشكِل (محتمل)، وقد يؤدي بسرعة إلى ديون تقنية في المستقبل. لحسن الحظ، توجد طرق بسيطة لإضافة التحقق من الصحة إلى الفئات.

سنبدا بالنظر إلى تحقق البريد الإلكتروني. هناك مكتبات مدمجة في بايثون يمكن أن تساعدنا في ذلك، ولكن لأننا نفعل هذا للتعلّم، سنقوم بإنشاء تحقق بسيط خاص بنا، وذلك عن طريق إنشاء فئة جديدة لـ”البريد الإلكتروني”، وفحص دالة __post_init__ (خاصةً ببيانات الفئة فقط).

ملاحظة

يمكننا أيضًا القيام بذلك في فئة Person نفسها، ولكن من الأفضل غالبًا إنشاء فئات خاصة للأشياء التي يمكن إعادة استخدامها.

مثال على دالة __post_init__
from dataclasses import dataclass

@dataclass
class Email:
    address: str

    def __post_init__(self):
        print(f"تحقق من البريد الإلكتروني: {self.address}")
        # ضع كودك هنا

لتحقق بسيط من البريد الإلكتروني، يمكننا على سبيل المثال التحقق مما إذا كان البريد الإلكتروني يحتوي على كل من علامتي @ و . . يمكنك أيضًا التحقق مما إذا كان البريد الإلكتروني يطابق نمط regex. (أكثر تقدمًا، ولكن ابحث عنه على الإنترنت!)

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

فيما يلي حل محتمل، حيث نستخدم الاستثناءات لـ”السقوط” إذا كان البريد الإلكتروني غير صالح، مما سيوقف البرنامج على الفور ويعطي رسالة خطأ.

from dataclasses import dataclass

@dataclass
class Email:
    address: str

    def __post_init__(self):
        if "@" not in self.address:
            raise ValueError(f"Nقصت @ في عنوان البريد الإلكتروني: {self.address}")
        if "." not in self.address.split("@")[1]:
            raise ValueError(f"Nقصة . في نطاق عنوان البريد الإلكتروني: {self.address}")
        if " " in self.address:
            raise ValueError(f"Eنوان البريد الإلكتروني لا يمكن أن يحتوي على مسافات: {self.address}")

# اختبار الكود 
test = Email("hei@example.com")  # صحيح
try:
    test = Email("heiexample.com")
except ValueError as e:
    print(e) # غير صالح، ينقصه @

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

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

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

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

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

متوسط المهمة 4 - استخدام التحقق في فئة الشخص

الآن، بما أننا أنشأنا تحققًا لبريد الإلكتروني ورقم الهاتف، يمكننا استخدام هذه في فئة الشخص.

@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(?)

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

ملاحظة

الخصائص فريدة من نوعها لأنها لا تحتاج إلى معلمات، ولا تتطلب قوسين لتنفيذها. في المثال، نستخرج person.value بدون أقواس (وليس person.value())، على الرغم من أنها تقنياً دالة.

مثال بديل يقبل كلًا من 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("يجب أن يكون نوع veri هو str أو EksempelVerdi")

    @property
    def verdi(self) -> str:
        return self._verdi.attributt  # استرجاع القيمة الفرعية مباشرةً

# اختبار الكود
person = Person(name="أليس", verdi="نص ما")
print(person.verdi)  # الإخراج: نص ما

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)