
يتناول هذا المقال أحد أفضل اختراعات بايثون: namedtuple. سننظر في ميزاته الممتعة ، من المعروف إلى غير الواضح. سيزداد مستوى الانغماس في الموضوع تدريجياً ، لذلك آمل أن يجد الجميع شيئًا مثيرًا للاهتمام لأنفسهم. دعنا نذهب!
مقدمة
من المؤكد أنك تواجه موقفًا تحتاج فيه إلى نقل العديد من خصائص الكائن في قطعة واحدة. على سبيل المثال ، معلومات حول حيوان أليف: النوع واللقب والعمر.
غالبًا ما يكون كسولًا جدًا لإنشاء فصل منفصل لهذا الشيء ، ويتم استخدام tuples:
("pigeon", "", 3) ("fox", "", 7) ("parrot", "", 1)
لمزيد من الوضوح ، فإن tuple - collections.namedtuple
مناسب:
from collections import namedtuple Pet = namedtuple("Pet", "type name age") frank = Pet(type="pigeon", name="", age=3) >>> frank.age 3
يعلم الجميع هذا here وإليك بعض الميزات الأقل شهرة:
حقول التغيير السريع
ماذا لو كان أحد الخصائص يحتاج إلى تغيير؟ صراخ الشيخوخة ، والموكب غير قابل للتغيير. لكي لا _replace()
بالكامل ، توصلنا إلى طريقة _replace()
:
>>> frank._replace(age=4) Pet(type='pigeon', name='', age=4)
وإذا كنت ترغب في جعل البنية بأكملها قابلة للتغيير - _asdict()
:
>>> frank._asdict() OrderedDict([('type', 'pigeon'), ('name', ''), ('age', 3)])
تغيير العنوان التلقائي
افترض أنك تقوم باستيراد البيانات من ملف CSV وقم بتحويل كل سطر إلى مجموعة. تم أخذ أسماء الحقول من رأس ملف CSV. لكن هناك خطأ ما:
الحل هو rename=True
وسيطة rename=True
في المُنشئ:
تمت إعادة تسمية الأسماء "غير الناجحة" وفقًا للأرقام التسلسلية.
القيم الافتراضية
إذا كان لدى المجموعة مجموعة من الحقول الاختيارية ، فلا يزال يتعين عليك إدراجها في كل مرة تنشئ فيها كائنًا:
Pet = namedtuple("Pet", "type name alt_name") >>> Pet("pigeon", "") TypeError: __new__() missing 1 required positional argument: 'alt_name' >>> Pet("pigeon", "", None) Pet(type='pigeon', name='', alt_name=None)
لتجنب ذلك ، حدد defaults
في المُنشئ:
Pet = namedtuple("Pet", "type name alt_name", defaults=("",)) >>> Pet("pigeon", "") Pet(type='pigeon', name='', alt_name='')
defaults
تعين القيم الافتراضية من الذيل. يعمل في الثعبان 3.7+
بالنسبة للإصدارات الأقدم ، يمكنك تحقيق نفس النتيجة بشكل خرقاء من خلال النموذج الأولي:
Pet = namedtuple("Pet", "type name alt_name") default_pet = Pet(None, None, "") >>> default_pet._replace(type="pigeon", name="") Pet(type='pigeon', name='', alt_name='') >>> default_pet._replace(type="fox", name="") Pet(type='fox', name='', alt_name='')
لكن مع defaults
، بالطبع ، أجمل بكثير.
خفة استثنائية
واحدة من فوائد tuple اسمه هو الخفة. جيش من مائة ألف حمام سيستغرق 10 ميغابايت فقط:
from collections import namedtuple import objsize
للمقارنة ، إذا جعلت من Pet فئة عادية ، فستشغل قائمة مماثلة بالفعل 19 ميغابايت.
يحدث هذا لأن الكائنات العادية في بيثون تحمل متجهم ثقيلًا ، والذي يحتوي على أسماء وقيم جميع سمات الكائن:
class PetObj: def __init__(self, type, name, age): self.type = type self.name = name self.age = age frank_obj = PetObj(type="pigeon", name="", age=3) >>> frank_obj.__dict__ {'type': 'pigeon', 'name': '', 'age': 3}
الكائنات المسماة - خالية من هذا القاموس ، وبالتالي تستهلك ذاكرة أقل:
frank = Pet(type="pigeon", name="", age=3) >>> frank.__dict__ AttributeError: 'Pet' object has no attribute '__dict__' >>> objsize.get_deep_size(frank_obj) 335 >>> objsize.get_deep_size(frank) 239
لكن كيف تخلصت __dict__
من __dict__
؟ اقرأ على ツ
العالم الداخلي الغني
إذا كنت تعمل مع الثعبان لفترة طويلة ، فمن المحتمل أن تعرف: يمكن إنشاء كائن خفيف الوزن من خلال __slots__:
class PetSlots: __slots__ = ("type", "name", "age") def __init__(self, type, name, age): self.type = type self.name = name self.age = age frank_slots = PetSlots(type="pigeon", name="", age=3)
لا تحتوي كائنات "Slot" على قاموس به سمات ، لذا فهي لا تشغل سوى القليل من الذاكرة. "فرانك على الفتحات" خفيف مثل "فرانك على الموكب" ، انظر:
>>> objsize.get_deep_size(frank) 239 >>> objsize.get_deep_size(frank_slots) 231
إذا قررت أن اسمه الذي يستخدم أيضًا فتحات ، فهذا ليس بعيدًا عن الحقيقة. كما تتذكر ، يتم الإعلان عن فئات tuple معينة بشكل حيوي:
Pet = namedtuple("Pet", "type name age")
يستخدم مُنشئ اسمه المسمى السحري الداكن مختلفًا ويولد شيئًا مثل هذه الفئة (تبسيطًا كبيرًا):
class Pet(tuple): __slots__ = () type = property(operator.itemgetter(0)) name = property(operator.itemgetter(1)) age = property(operator.itemgetter(2)) def __new__(cls, type, name, age): return tuple.__new__(cls, (type, name, age))
وهذا يعني أن tuple
الأليف هو عبارة عن tuple
عادية ، حيث تم تثبيت ثلاثة أساليب للملكية بالأظافر:
type
إرجاع العنصر الفارغ من tuplename
- العنصر الأول من tupleage
- العنصر الثاني من tuple
__slots__
حاجة إلى __slots__
فقط لإضاءة الكائنات. نتيجة لذلك ، يحتل حيوان أليف مساحة صغيرة ويمكن استخدامه كدورة عادية:
>>> frank.index("") 1 >>> type, _, _ = frank >>> type 'pigeon'
اخترع بمكر ، هاه؟
ليس أقل شأنا من فئات البيانات
لأننا نتحدث عن توليد الشفرة. في بيثون 3.7 ، ظهر رمز مولد uber ، الذي لا يوجد لديه مساوي - dataclasses.
عندما ترى فئة بيانات لأول مرة ، فأنت تريد التبديل إلى إصدار جديد من اللغة لمجرد:
from dataclasses import dataclass @dataclass class PetData: type: str name: str age: int
معجزة جيدة جدا! ولكن هناك فارق بسيط - إنه سمين:
frank_data = PetData(type="pigeon", name="", age=3) >>> objsize.get_deep_size(frank_data) 335 >>> objsize.get_deep_size(frank) 239
تقوم فئة البيانات بإنشاء فئة بيثون عادية ، يتم استنفاد كائناتها تحت وطأة __dict__
. لذلك إذا كنت تقرأ أسطرًا من القاعدة وتحولها إلى كائنات ، فإن فئات البيانات ليست هي الخيار الأفضل.
ولكن مهلا ، يمكنك تجميد فئة البيانات مثل tuple. ربما بعد ذلك سوف تصبح أسهل؟
@dataclass(frozen=True) class PetFrozen: type: str name: str age: int frank_frozen = PetFrozen(type="pigeon", name="", age=3) >>> objsize.get_deep_size(frank_frozen) 335
للأسف حتى المجمدة ، فقد بقي كائن عادي الوزن مع قاموس من السمات. لذلك إذا كنت بحاجة إلى أشياء ثابتة غير قابلة للتغيير (والتي يمكن استخدامها أيضًا كأنواع عادية) - فلا يزال اسمه هو الخيار الأفضل.
⌘ ⌘ ⌘
أنا حقا أحب tuple اسمه:
- صادقة iterable ،
- إعلان نوع ديناميكي
- الوصول إلى السمة المسماة
- خفيفة الوزن وغير قابلة للتغيير.
وفي الوقت نفسه يتم تنفيذه في 150 سطور من التعليمات البرمجية. ماذا هناك حاجة للسعادة ツ
إذا كنت تريد معرفة المزيد عن مكتبة Python القياسية ، فقم بالاشتراك في قناة ohmypy