مقدمة لفئات البيانات

واحدة من الميزات الجديدة المقدمة في Python 3.7 هي فئات البيانات. وهي مصممة لأتمتة عملية إنشاء التعليمات البرمجية للفئات التي يتم استخدامها لتخزين البيانات. على الرغم من حقيقة أنهم يستخدمون آليات عمل أخرى ، إلا أنه يمكن مقارنتهم بـ "صفوف اسمية قابلة للتغيير مع قيم افتراضية".



مقدمة


تتطلب كل الأمثلة المذكورة أعلاه Python 3.7 أو أعلى لتشغيلها.

يجب على معظم مطوري الثعبان كتابة هذه الفصول بانتظام:


class RegularBook: def __init__(self, title, author): self.title = title self.author = author 

بالفعل في هذا المثال ، التكرار مرئي. يتم استخدام معرفات المؤلف والعنوان عدة مرات. سيحتوي الفصل الحقيقي أيضًا على الطرق __eq__ و __repr__ .


تحتوي وحدة dataclasses على مصمم @dataclass . باستخدامه ، سيبدو رمز مشابه كما يلي:


 from dataclasses import dataclass @dataclass class Book: title: str author: str 

من المهم ملاحظة أن أنواع التعليقات التوضيحية مطلوبة . سيتم تجاهل جميع الحقول التي ليس لها علامات نوع. بالطبع ، إذا كنت لا ترغب في استخدام نوع معين ، يمكنك تحديد Any من وحدة typing .


ما الذي تحصل عليه نتيجة لذلك؟ ستحصل تلقائيًا على فصل __repr__ ، باستخدام الأساليب التي تم تنفيذها __init__ و __repr__ و __str__ و __eq__ . بالإضافة إلى ذلك ، سيكون فصلًا عاديًا ويمكنك أن ترثه أو تضيف أساليب تعسفية.


 >>> book = Book(title="Fahrenheit 451", author="Bradbury") >>> book Book(title='Fahrenheit 451', author='Bradbury') >>> book.author 'Bradbury' >>> other = Book("Fahrenheit 451", "Bradbury") >>> book == other True 

البدائل


Tuple أو القاموس


بالطبع ، إذا كانت البنية بسيطة للغاية ، يمكنك حفظ البيانات في قاموس أو مجموعة:


 book = ("Fahrenheit 451", "Bradbury") other = {'title': 'Fahrenheit 451', 'author': 'Bradbury'} 

ومع ذلك ، فإن هذا النهج له عيوب:


  • يجب أن نتذكر أن المتغير يحتوي على بيانات تتعلق بهذه البنية.
  • في حالة القاموس ، يجب أن تتبع أسماء المفاتيح. هذا التهيئة للقاموس {'name': 'Fahrenheit 451', 'author': 'Bradbury'} سيكون صحيحًا أيضًا.
  • في حالة وجود مجموعة ، يجب عليك تتبع ترتيب القيم ، حيث ليس لديهم أسماء.

هناك خيار أفضل:


مسمى


 from collections import namedtuple NamedTupleBook = namedtuple("NamedTupleBook", ["title", "author"]) 

إذا استخدمنا الفئة التي تم إنشاؤها بهذه الطريقة ، نحصل عمليا على نفس الشيء مثل استخدام فئة البيانات.


 >>> book = NamedTupleBook("Fahrenheit 451", "Bradbury") >>> book.author 'Bradbury' >>> book NamedTupleBook(title='Fahrenheit 451', author='Bradbury') >>> book == NamedTupleBook("Fahrenheit 451", "Bradbury")) True 

ولكن على الرغم من التشابه العام ، إلا أن الصفوف المسماة لها حدودها. إنهم يأتون من حقيقة أن الصفوف المسماة لا تزال الصف.


أولاً ، لا يزال بإمكانك مقارنة مثيلات الفئات المختلفة.


 >>> Car = namedtuple("Car", ["model", "owner"]) >>> book = NamedTupleBook("Fahrenheit 451", "Bradbury")) >>> book == Car("Fahrenheit 451", "Bradbury") True 

ثانيًا ، الصفوف المسماة غير قابلة للتغيير. هذا مفيد في بعض الحالات ، لكني أرغب في مزيد من المرونة.
أخيرًا ، يمكنك العمل على مجموعة محددة بالإضافة إلى واحدة عادية. على سبيل المثال ، كرر الأمر.


مشاريع أخرى


إذا لم تقتصر على المكتبة القياسية ، يمكنك العثور على حلول أخرى لهذه المشكلة. على وجه الخصوص ، attrs المشروع. يمكن أن يفعل أكثر من dataclass ويعمل على الإصدارات الأقدم من الثعبان مثل 2.7 و 3.4. ومع ذلك ، فإن حقيقة أنها ليست جزءًا من المكتبة القياسية قد تكون غير ملائمة


الخلق


يمكنك استخدام الديكور @dataclass لإنشاء فئة بيانات. في هذه الحالة ، سيتم استخدام جميع حقول الفئة المحددة مع التعليق التوضيحي للنوع في الأساليب المقابلة للفئة الناتجة.


كبديل ، هناك وظيفة make_dataclass ، والتي تعمل بشكل مشابه لإنشاء tuples مسمى.


 from dataclasses import make_dataclass Book = make_dataclass("Book", ["title", "author"]) book = Book("Fahrenheit 451", "Bradbury") 

القيم الافتراضية


ميزة مفيدة واحدة هي سهولة إضافة القيم الافتراضية إلى الحقول. لا تزال هناك حاجة لإعادة تعريف طريقة __init__ ، فقط حدد القيم مباشرة في الفصل.


 @dataclass class Book: title: str = "Unknown" author: str = "Unknown author" 

سيتم أخذها في الاعتبار في طريقة __init__ ولدت


 >>> Book() Book(title='Unknown', author='Unknown author') >>> Book("Farenheit 451") Book(title='Farenheit 451', author='Unknown author') 

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


بالإضافة إلى ذلك ، من المهم مراقبة الترتيب الذي يتم فيه تحديد الحقول ذات القيم الافتراضية ، حيث إنها تتطابق تمامًا مع ترتيبها في طريقة __init__


فئات البيانات الثابتة


مثيلات tuples المسماة ثابتة. في العديد من المواقف ، هذه فكرة جيدة. بالنسبة لفئات البيانات ، يمكنك القيام بذلك أيضًا. ما عليك FrozenInstanceError تحديد المعلمة frozen=True عند إنشاء الفئة ، وإذا حاولت تغيير حقولها ، فسيتم FrozenInstanceError استثناء FrozenInstanceError


 @dataclass(frozen=True) class Book: title: str author: str 

 >>> book = Book("Fahrenheit 451", "Bradbury") >>> book.title = "1984" dataclasses.FrozenInstanceError: cannot assign to field 'title' 

إعداد فئة البيانات


بالإضافة إلى المعلمة frozen ، فإن @dataclass decorator لديه معلمات أخرى:


  • init : إذا كان True (افتراضي) ، يتم إنشاء طريقة __init__ . إذا كان للفصل بالفعل طريقة __init__ محددة ، فسيتم تجاهل المعلمة.
  • repr : يمكّن (افتراضيًا) إنشاء طريقة __repr__ . تحتوي السلسلة التي تم إنشاؤها على اسم الفئة واسم وتمثيل جميع الحقول المحددة في الفئة. في هذه الحالة ، يمكن استبعاد الحقول الفردية (انظر أدناه)
  • eq : يتيح (افتراضيًا) إنشاء طريقة __eq__ . تتم مقارنة الكائنات بنفس الطريقة كما لو كانت مجموعات تحتوي على قيم الحقول المقابلة. بالإضافة إلى ذلك ، يتم تحديد نوع المطابقة.
  • يمكّن order ( __lt__ الافتراضي هو إيقاف) إنشاء __ge__ و __ge__ و __ge__ و __ge__ . تتم مقارنة الكائنات بالطريقة نفسها التي تتم بها مقارنة قيم tuples لقيم المجال. في نفس الوقت ، يتم فحص نوع الأشياء أيضًا. إذا order تحديد الطلب ، ولكن لم order تحديد eq ، فسيتم طرح استثناء ValueError . أيضا ، لا يجب أن يحتوي الفصل على طرق مقارنة محددة بالفعل.
  • يؤثر unsafe_hash في __hash__ طريقة __hash__ . يعتمد السلوك أيضًا على قيم المعلمات eq frozen

تخصيص الحقول الفردية


في معظم الحالات القياسية ، هذا غير مطلوب ، ولكن من الممكن تخصيص سلوك فئة البيانات بما يصل إلى الحقول الفردية باستخدام وظيفة الحقل.


افتراضيات قابلة للتعديل


الحالة النموذجية المذكورة أعلاه هي استخدام القوائم أو القيم الافتراضية الأخرى القابلة للتغيير. قد ترغب في فصل "رف كتب" يحتوي على قائمة بالكتب. إذا قمت بتشغيل التعليمات البرمجية التالية:


 @dataclass class Bookshelf: books: List[Book] = [] 

سيبلغ المترجم عن خطأ:


 ValueError: mutable default <class 'list'> for field books is not allowed: use default_factory 

ومع ذلك ، بالنسبة للقيم الأخرى القابلة للتغيير ، لن يعمل هذا التحذير وسيؤدي إلى سلوك برنامج غير صحيح.


لتجنب المشاكل ، يقترح استخدام المعلمة default_factory للدالة field . يمكن أن تكون قيمته أي كائن أو وظيفة تسمى بدون معلمات.
تبدو النسخة الصحيحة من الفصل كما يلي:


 @dataclass class Bookshelf: books: List[Book] = field(default_factory=list) 

خيارات أخرى


بالإضافة إلى default_factory المحدد ، فإن الدالة الميدانية لها المعلمات التالية:


  • default : القيمة default . هذه المعلمة مطلوبة لأن الاستدعاء field يحل محل قيمة الحقل الافتراضية.
  • init : يمكّن (افتراضيًا) استخدام حقل في طريقة __init__
  • repr : يمكّن (افتراضيًا) استخدام حقل في طريقة __repr__
  • تشمل compare (افتراضيًا) استخدام الحقل في طرق المقارنة ( __eq__ و __le__ وغيرها)
  • hash : قد تكون قيمة منطقية أو None . إذا كان True ، يتم استخدام الحقل لحساب التجزئة. إذا None تحديد بلا (افتراضيًا) ، يتم استخدام قيمة معلمة compare .
    أحد أسباب تحديد hash=False compare=True معينة compare=True قد يكون صعوبة حساب التجزئة المجال أثناء الضرورة للمقارنة.
  • metadata : قاموس مخصص أو بلا. يتم تغليف القيمة في MappingProxyType بحيث تصبح ثابتة. لا يتم استخدام هذه المعلمة من قبل فئات البيانات نفسها وهي مخصصة لملحقات الجهات الخارجية.

المعالجة بعد التهيئة


تستدعي طريقة __init__ إنشاؤها __post_init__ طريقة __post_init__ ، إذا تم تعريفها في الفصل. كقاعدة ، يتم استدعاؤها في النموذج self.__post_init__() ، ومع ذلك ، إذا تم تحديد متغيرات من النوع InitVar في الفئة ، فسيتم تمريرها كمعلمات الأسلوب.


إذا لم يتم إنشاء طريقة __init__ ، فلن يتم استدعاء __post_init__ .


على سبيل المثال ، قم بإضافة وصف كتاب تم إنشاؤه


 @dataclass class Book: title: str author: str desc: str = None def __post_init__(self): self.desc = self.desc or "`%s` by %s" % (self.title, self.author) 

 >>> Book("Fareneheit 481", "Bradbury") Book(title='Fareneheit 481', author='Bradbury', desc='`Fareneheit 481` by Bradbury') 

معلمات التهيئة فقط


أحد الاحتمالات المرتبطة بطريقة __post_init__ هي المعلمات المستخدمة __post_init__ فقط. إذا ، عند تعريف حقل ، حدد InitVar ، سيتم تمرير قيمته كمعلمة لطريقة __post_init__ . لا يتم استخدام هذه الحقول في فئة البيانات بأي حال من الأحوال.


 @dataclass class Book: title: str author: str gen_desc: InitVar[bool] = True desc: str = None def __post_init__(self, gen_desc: str): if gen_desc and self.desc is None: self.desc = "`%s` by %s" % (self.title, self.author) 

 >>> Book("Fareneheit 481", "Bradbury") Book(title='Fareneheit 481', author='Bradbury', desc='`Fareneheit 481` by Bradbury') >>> Book("Fareneheit 481", "Bradbury", gen_desc=False) Book(title='Fareneheit 481', author='Bradbury', desc=None) 

الوراثة


عند استخدام الديكور @dataclass ، فإنه يمر عبر جميع الفئات الرئيسية بدءًا بالكائن ولكل فئة بيانات وجدت أنها تحفظ الحقول في قاموس منظم ، ثم تضيف خصائص الفصل الجاري معالجته. تستخدم جميع الأساليب التي تم إنشاؤها حقولًا من القاموس المرتبط الناتج.


ونتيجة لذلك ، إذا كان الفصل الأصل يحدد القيم الافتراضية ، فسيتعين عليك تحديد الحقول ذات القيم الافتراضية.


نظرًا لأن القاموس المنظم يقوم بتخزين القيم في ترتيب الإدراج للفئات التالية


 @dataclass class BaseBook: title: Any = None author: str = None @dataclass class Book(BaseBook): desc: str = None title: str = "Unknown" 

سيتم إنشاء طريقة __init__ مع هذا التوقيع:


 def __init__(self, title: str="Unknown", author: str=None, desc: str=None) 

Source: https://habr.com/ru/post/ar415829/


All Articles