في بايثون هي سمة خاصة في الفئات والمثيلات، تعمل كمساحة اسم، تربط أسماء السمات بقيمها المقابلة. يمكنك استخدام .__dict__.__dict__ لفحص السمات، أو تعديلها، أو إضافتها، أو حذفها ديناميكيًا، مما يجعلها أداة متعددة الاستخدامات للبرمجة الوصفية وتصحيح الأخطاء.
في هذا الدرس، ستتعلم كيفية استخدام .__dict__ في سياقات متنوعة، بما في ذلك الفئات والمثيلات والدوال. ستستكشف أيضًا دوره في الوراثة من خلال أمثلة عملية ومقارنات مع أدوات أخرى لمعالجة السمات.
على الرغم من أن هذا البرنامج التعليمي يوفر رؤى تفصيلية حول استخدام .__dict__ بشكل فعال، فإن الحصول على فهم قوي لقواميس بايثون وكيفية استخدامها في الكود الخاص بك سيساعدك على تحقيق أقصى استفادة منها.
التعرف على السمة .__dict__ في بايثون
يدعم بايثون نموذج البرمجة كائنية التوجه (OOP) من خلال فئات تُغلّف البيانات (السمات) والسلوكيات (التوابع) في كيان واحد. ويستفيد بايثون، من حيث المبدأ، من القواميس للتعامل مع هذه السمات والتوابع.
لماذا القواميس؟ لأنها تُطبّق كجداول تجزئة، تربط المفاتيح بالقيم، مما يجعل عمليات البحث سريعة وفعّالة.
بشكل عام، يستخدم بايثون قاموسًا خاصًا يُسمى .__dict__ للحفاظ على مراجع السمات والتوابع القابلة للكتابة في فئة أو مثيل بايثون. عمليًا، تُعدّ السمة.__dict__ مساحة أسماء تربط أسماء السمات بالقيم، وأسماء التوابع بكائنات التوابع.
السمة __dict__. أساسية لنموذج بيانات بايثون. يتعرف عليها المفسّر ويستخدمها داخليًا لمعالجة الفئات والكائنات. تتيح هذه السمة الوصول الديناميكي إلى السمات، وإضافتها، وإزالتها، ومعالجتها. ستتعلم كيفية إجراء هذه العمليات بعد قليل. لكن أولًا، ستتعرف على الاختلافات بين الفئة .__dict__ والمثيل .__dict__.
سمة الفئة __dict__.
للبدء في التعرف على __dict__. في فئة Python، ستستخدم فئة العرض التوضيحي التالية، والتي تحتوي على سمات و توابع:
class DemoClass:
class_attr = "This is a class attribute"
def __init__(self):
self.instance_attr = "This is an instance attribute"
def method(self):
return "This is a method"
في هذه الفئة، لديك سمة فئة، وتابعان، وسمة مثيل. الآن، ابدأ جلسة REPL في بايثون وشغّل الكود التالي:
>>> from demo import DemoClass
>>> print(DemoClass.__dict__)
{
'__module__': 'demo',
'__firstlineno__': 1,
'class_attr': 'This is a class attribute',
'__init__': <function DemoClass.__init__ at 0x102bcd120>,
'method': <function DemoClass.method at 0x102bcd260>,
'__static_attributes__': ('instance_attr',),
'__dict__': <attribute '__dict__' of 'DemoClass' objects>,
'__weakref__': <attribute '__weakref__' of 'DemoClass' objects>,
'__doc__': None
}
يعرض استدعاء دالة ()print قاموسًا يربط الأسماء بالكائنات. أولًا، لديك المفتاح ‘__module__‘، الذي يربط بخاصية خاصة تحدد مكان تعريف الفئة. في هذه الحالة، توجد الفئة في وحدة العرض التوضيحي. ثم لديك المفتاح ‘__firstlineno__‘، الذي يحمل رقم السطر الأول من تعريف الفئة، بما في ذلك المُزخرفات. بعد ذلك، لديك المفتاح ‘class_attr’ وقيمته المقابلة.
ملاحظة: عند الوصول إلى سمة __dict__. في فئة، ستحصل على كائن mappingproxy. يُنشئ هذا النوع من الكائنات عرضًا للقراءة فقط للقاموس.
مفتاحا ‘__init__‘ و’method’ مرتبطان بكائني التابع .__init__() و.method(). بعد ذلك، لديك مفتاح ‘__dict__‘ مرتبط بالخاصية __dict__. في كائنات DemoClass. ستستكشف هذه الخاصية بمزيد من التفصيل لاحقًا.
المفتاح ‘__static_attributes__‘ عبارة عن مجموعة تحتوي على أسماء السمات التي تقوم بتعيينها من خلال self.attribute = value من أي تابع في نص الفصل.
يمثل المفتاح ‘__weakref__‘ سمة خاصة تمكنك من الإشارة إلى الكائنات دون منع تجميعها في سلة المهملات.
أخيرًا، لديك المفتاح ‘__doc__‘، الذي يُطابق سلسلة توثيق الصف. إذا لم يكن للصف سلسلة توثيق، فسيتم تعيينه افتراضيًا على ” None“.
هل لاحظتَ أن اسم .instance_attr لا يحتوي على مفتاح في سمة .__dict__؟ ستكتشف مكان إخفائه في القسم التالي.
سمة مثيل .__dict__
في القسم السابق، تعلمت أن سمة .__dict__ للفئة تحتوي على مفتاح يُسمى ‘__dict__‘. يُطابق هذا المفتاح سمة __dict__. في أي مثيل أو كائن من الفئة:
>>> DemoClass.__dict__["__dict__"]
<attribute '__dict__' of 'DemoClass' objects>
قم بتشغيل الكود التالي لإنشاء مثيل ملموس من DemoClass والوصول إلى محتوى السمة __dict__. الخاصة به:
>>> demo_object = DemoClass()
>>> demo_object.__dict__
{'instance_attr': 'This is an instance attribute'}
عند الوصول إلى السمة __dict__. على كائن، ستحصل على قاموس يحتوي فقط على أزواج القيمة الرئيسية التي تمثل سمات المثيل وقيمها.
الآن، ماذا لو كانت لديك سمة مثيل تُخفي سمة فئة؟ خُذ الفئة التالية في الاعتبار:
>>> class Number:
... value = 42
...
>>> Number.__dict__
mappingproxy({
'__module__': '__main__',
'__firstlineno__': 1,
'value': 42,
...
})
>>> number = Number()
>>> number.__dict__
{}
>>> number.value
42
عند الوصول إلى __dict__. على المتغير Number، يتضمن الناتج ‘value’ كسمة للفئة. عند الوصول إلى __dict__. على المتغير Number، ستحصل على قاموس فارغ. وأخيرًا، عند استخدام تدوين النقاط للوصول إلى value.، ستحصل على قيمة سمة الفئة.
قم بالمضي قدمًا وإضافة سمة مثيل تسمى value.:
>>> class Number:
... value = 42
... def __init__(self):
... self.value = 7
...
>>> Number.__dict__
mappingproxy({
'__module__': '__main__',
'__firstlineno__': 1,
'value': 42,
...
})
>>> number = Number()
>>> number.__dict__
{'value': 7}
>>> number.value
7
الآن، عند استخدامك لرمز النقطة للبحث عن value.، ستحصل على قيمة سمة المثيل. وذلك لأن بايثون يبحث عن value. في number.__dict__ أولًا. إذا لم يجدها، فسيبحث عنها في Number.__dict__.
السمة .__dict__ في الدوال
بالإضافة إلى الفئات والمثيلات، تحتوي الدوال أيضًا على سمة خاصة .__dict__. عادةً ما تكون هذه السمة قاموسًا فارغًا:
>>> def greet(name):
... print(f"Hello, {name}!")
...
>>> greet.__dict__
{}
في هذا المثال، عند الوصول إلى السمة __dict__. في كائن دالة، ستحصل على قاموس فارغ لأن الدوال لا تحتوي على أي سمات مرفقة بشكل افتراضي.
في الممارسة العملية، يوفر لك ملف .__dict__ الخاص بالدالة مساحة اسم لإرفاق البيانات الوصفية بالدالة، والتي يمكن أن تكون مفيدة للتخزين المؤقت واستكشاف الأخطاء وإصلاحها والتأمل.
على سبيل المثال، لنفترض أنك تريد كتابة دالة قادرة على تتبع عدد الاستدعاءات. يمكنك استخدام الدالة __dict__. لذلك:
>>> def track_calls():
... track_calls.__dict__["calls"] = track_calls.__dict__.get("calls", 0) + 1
... print(f"Calls: {track_calls.calls}")
...
>>> track_calls()
Calls: 1
>>> track_calls()
Calls: 2
>>> track_calls()
Calls: 3
>>> track_calls.calls
3
في السطر الثاني، أضف سمة جديدة باسم calls. إلى دالتك عن طريق تعيين مفتاح قاموس. يحصل التعبير الموجود على اليمين على مفتاح “calls” من .__dict__ باستخدام .get() ويزيد قيمته بمقدار واحد.
السمة .__dict__ في الكائنات الأخرى
في بايثون، كل شيء كائن. قد يوحي هذا بأن كل شيء في بايثون يمتلك الخاصية .__dict__. لكن هذا غير صحيح.
على سبيل المثال، لا تحتوي الدوال المضمنة على واحدة:
>>> abs.__dict__
Traceback (most recent call last):
...
AttributeError: 'builtin_function_or_method' object has no attribute '__dict__'.
Did you mean: '__dir__'?
>>> print.__dict__
Traceback (most recent call last):
...
AttributeError: 'builtin_function_or_method' object has no attribute '__dict__'.
Did you mean: '__dir__'?
عند محاولة الوصول إلى __dict__. على كائن دالة مضمن مثل ()abs أو ()print، ستحصل على استثناء AttributeError مع رسالة خطأ واضحة.
تحتوي الكائنات الأخرى مثل أنواع البيانات المضمنة على سمة __dict__.:
>>> int.__dict__
mappingproxy({
'__new__': <built-in method __new__ of type object at 0x1010adb00>,
'__repr__': <slot wrapper '__repr__' of 'int' objects>,
...
})
>>> list.__dict__
mappingproxy({
'__new__': <built-in method __new__ of type object at 0x1010ad238>,
'__repr__': <slot wrapper '__repr__' of 'list' objects>,
...
})
>>> dict.__dict__
mappingproxy({
'__new__': <built-in method __new__ of type object at 0x1030da9a8>,
'__repr__': <slot wrapper '__repr__' of 'dict' objects>,
...
})
في هذا المثال، يمكنك أن ترى أن أنواع البيانات int وlist وdict تحتوي على سمة __dict__. مع سلسلة كبيرة من أزواج المفتاح والقيمة.
فيما يلي ملخص لأنواع مختلفة من الكائنات وما إذا كانت تحتوي على سمة .__dict__:
| الكائن | لديها __dict__.؟ |
|---|---|
| الفئات المحددة من قبل المستخدم ومثيلاتها | ✅ |
| الوحدات | ✅ |
| الوظائف والتوابع المحددة من قبل المستخدم | ✅ |
| الاستثناءات المضمنة ومثيلاتها | ✅ |
| الفئات التي تم إنشاؤها باستخدام الدالة ()type المضمنة | ✅ |
| أنواع البيانات المضمنة | ✅ |
| حالات أنواع البيانات المضمنة | ❌ |
| الدوال المضمنة | ❌ |
الكائنات ذات السمة .__slots__ | ❌ |
لا تحتوي نماذج أنواع البيانات المضمنة، مثل int وfloat وstr وlist وdict وset، على سمة __dict__ لأنها مُنفَّذة بلغة C ومُحسَّنة لتحسين كفاءة الذاكرة والأداء. كما تُنفَّذ الدوال المضمنة بلغة C ولا تحتوي على سمة __dict__. أيضًا.
تم تصميم السمة __slots__. لتكون بديلاً غير قابل للتغيير وموفرًا للذاكرة لـ __dict__. ويمكنك استخدامه عندما تحتاج إلى إنشاء العديد من الحالات لكائن معين.
فحص .__dict__ باستخدام الدالة ()vars المضمنة
تعيد الدالة ()vars السمة __dict__. للوحدة أو الفئة أو المثيل أو أي كائن آخر يحتوي على هذه السمة:
>>> vars(list)
mappingproxy({
'__new__': <built-in method __new__ of type object at 0x1010ad238>,
'__repr__': <slot wrapper '__repr__' of 'list' objects>,
...
})
>>> class DemoClass:
... class_attr = "This is a class attribute"
... def __init__(self):
... self.instance_attr = "This is an instance attribute"
... def method(self):
... return "This is a method"
...
>>> vars(DemoClass)
mappingproxy({
'__module__': '__main__',
...
'class_attr': 'This is a class attribute',
...
})
>>> vars(abs)
Traceback (most recent call last):
...
TypeError: vars() argument must have __dict__ attribute
إذا كنت بحاجة إلى فحص سريع لخاصية __dict__. لكائن، فيمكنك استخدام دالة ()vars. لاحظ أنه عندما لا تحتوي وسيطة ()vars على .__dict__، ستحصل على استثناء TypeError.
أخيرًا، من المهم ملاحظة أن استخدام ()vars للتأمل واستكشاف الأخطاء وإصلاحها أكثر شبهًا بلغة Python من استخدام السمة __dict__.مباشرةً.
التلاعب بالسمات باستخدام .__dict__
تتيح لك السمة __dict__. الوصول المباشر إلى مساحة اسم الكائن، مما يسمح لك بفحص السمات وتعديلها وإضافتها وحذفها ديناميكيًا. هذا يجعلها أداة فعّالة للبرمجة الوصفية، وتصحيح الأخطاء، وتوسيع نطاق السلوكيات أثناء التشغيل.
في الأقسام التالية، ستكتشف كيفية العمل مع __dict__. للوصول إلى السمات الموجودة وتعديلها وإدخال سمات جديدة بشكل ديناميكي وإزالتها عند الحاجة إليها.
الوصول إلى السمات الموجودة
يمكنك استخدام __dict__. للوصول إلى سمات الكائن. ولأن __dict__. كائن يشبه القاموس، يمكنك استخدامه بطرق مختلفة وبأقل جهد، لأنه يمتلك نفس واجهة القواميس.
على سبيل المثال، ضع في اعتبارك فئة Person التالية حيث تستخدم __dict__. لتنفيذ بعض التوابع:
class Person:
def __init__(self, first_name, last_name, age):
self.first_name = first_name
self.last_name = last_name
self.age = age
def __str__(self):
return "{first_name} {last_name} is {age} years old".format(
**self.__dict__
)
def __repr__(self):
return "{cls}('{first_name}', '{last_name}', {age})".format(
cls=type(self).__name__,
**self.__dict__,
)
def as_dict(self):
return self.__dict__
def as_tuple(self):
return tuple(self.__dict__.values())
في هذا المثال، ستستخدم أولاً __dict__. لملء سمات المثيل في السلسلة الناتجة في .__str__() و .__repr__(). توفر هذه التوابع تمثيلًا سلسًا وسهل الاستخدام للمطورين لكائنات Person.
يمكنك بعد ذلك استخدام التابع .format() لاستيفاء أزواج القيمة الرئيسية من __dict__.، والتي تقوم بفك ضغطها باستخدام عامل فك ضغط القاموس (**).
بعد ذلك، استخدم __dict__. لتنفيذ دالة تُسمى .as_dict()، والتي تتيح لك التعبير عن مثيل Person كقاموس. وأخيرًا، لديك دالة مشابهة تُسمى .as_tuple() تُرجع مجموعة من قيم السمات. للقيام بذلك، تستخدم هذه الدالة __dict__. ودالة .values() التابعة لها.
إليك كيفية عمل هذه الفئة:
>>> from person import Person
>>> john = Person("John", "Doe", 30)
>>> john
Person('John', 'Doe', 30)
>>> print(john)
John Doe is 30 years old
>>> john.as_dict()
{'first_name': 'John', 'last_name': 'Doe', 'age': 30}
>>> john.as_tuple()
('John', 'Doe', 30)
كما يمكنك الاستنتاج، يُعدّ __dict__. أداة فعّالة للوصول السريع إلى سمات المثيلات ضمن تعريف الفئة. وعند دمجه مع عامل فكّ الحزم **، يُمكن أن يكون مفيدًا جدًا.
تعديل السمات الموجودة
يُعد استخدام .__dict__ لتعديل السمات الحالية استراتيجيةً مفيدةً أيضًا. خاصةً عند الحاجة إلى تغيير عدة سمات في وقت واحد. على سبيل المثال، لننظر إلى فئة التكوين التالية التي تُمكّنك من إدارة إعدادات تطبيق افتراضي:
class Config:
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
update = __init__
def __str__(self):
return str(self.__dict__)
في هذا المثال، تأخذ دالة .__init__() عددًا غير محدد من وسيطات الكلمات المفتاحية التي تمثل أزواجًا من معلمات الإعداد (مفتاح-قيمة). داخل هذه الدالة، يمكنك استدعاء دالة .update() على .dict لإضافة معلمات الإدخال وقيمها إلى المثيل.
يوفر التعيين update = __init____ طريقة سريعة لتعريف دالة مثيل .update() بنفس تنفيذ __init__().. هذه حيلة مفيدة يجب أن تكون لديك.
أخيرًا، عرّف التابع __str__(). لتوفير تمثيل سلس للفئة. في هذه الطريقة، لا تُجري أي تعديلات على السمات.
وهنا كيفية عمل الفئة في الممارسة العملية:
>>> from config_v1 import Config
>>> config = Config(theme="light", font_size=12, language="English")
>>> config
{'theme': 'light', 'font_size': 12, 'language': 'English'}
>>> user_conf = {"theme": "dark", "font_size": 14, "language": "Spanish"}
>>> config.update(**user_conf)
>>> config
{'theme': 'dark', 'font_size': 14, 'language': 'Spanish'}
في هذا المثال، تُنشئ أولاً مثيلاً من Config بسلسلة من معلمات التكوين الأولية وقيمها. ثم تستخدم دالة .update() لتغيير القيم بعد اختيار المستخدم.
إضافة سمات جديدة بشكل ديناميكي
يمكنك إضافة سمات ديناميكيًا إلى كائن يحتوي على سم .__dict__. وبالمثل، يمكنك إضافة طرق ديناميكيًا إلى فئة تحتوي على سمة __dict__..
خذ بعين الاعتبار الفئة التالية، والتي تهدف إلى تخزين صف من البيانات من جدول قاعدة بيانات أو ملف CSV:
>>> class Record:
... """Hold a record of data."""
...
لا تحتوي هذه الفئة على أي سمات حتى الآن، لأنك لا تعرف البيانات التي ستخزنها. لحسن الحظ، يمكنك إضافة سمات، وحتى توابع، إليها ديناميكيًا.
على سبيل المثال، لنفترض أنك قرأت صفًا من البيانات من ملف employees.csv باستخدام csv.DictReader. تقرأ هذه الفئة البيانات وتُرجعها في كائن يشبه القاموس. لنفترض الآن أن لديك قاموس البيانات التالي:
>>> john = {
... "name": "John Doe",
... "position": "Python Developer",
... "department": "Engineering",
... "salary": 80000,
... "hire_date": "2020-01-01",
... "is_manager": False,
... }
بعد ذلك، قد ترغب في إضافة هذه البيانات إلى مثيل لفئة السجل الخاصة بك، وتحتاج إلى تمثيل كل حقل بيانات كسمة مثيل.
إليك كيفية القيام بذلك باستخدام السمة __dict__.:
>>> john_record = Record()
>>> john_record.__dict__.update(john)
>>> john_record.name
'John Doe'
>>> john_record.department
'Engineering'
>>> john_record.__dict__
{
'name': 'John Doe',
'position': 'Python Developer',
'department': 'Engineering',
'salary': 80000,
'hire_date': '2020-01-01',
'is_manager': False
}
في هذا المقطع البرمجي، تُنشئ أولاً مثيلاً من السجل باسم john_record. ثم تستخدم دالة .update() على المثيل __dict__. لإضافة كل حقل كسمة إلى john_record. عند فحص john_record، ستلاحظ أنه يُخزّن جميع البيانات الأصلية كسمات.
يمكنك أيضًا إضافة سمات فردية باستخدام تدوين النقاط وتعيين مثل الموجود في john_record.insurance = 1234.
يعمل الكود في المثال أعلاه على المثيل __dict__.، مما يسمح لك بإضافة سمات المثيل. لإضافة توابع وسمات فئوية، استخدم تدوين النقاط كما يلي:
>>> def as_dict(self):
... return self.__dict__
...
>>> Record.as_dict = as_dict
>>> Record.__dict__
mappingproxy({
'__module__': '__main__',
...
'as_dict': <function as_dict at 0x1026cff60>
})
في هذا المثال، تُعرّف أولاً دالة ()as_dict خارج الفئة. ثم تُضيفها كتابع في السجل باستخدام تدوين النقاط وتعيين. يُمكنك إجراء هذه الإضافة الديناميكية لأن الفئة تحتوي على السمة .__dict__ مع ذلك، لا يُمكنك القيام بما يلي:
>>> Record.__dict__["as_dict"] = as_dict
Traceback (most recent call last):
...
TypeError: 'mappingproxy' object does not support item assignment
على مستوى الفئة، يُرجع الوصول إلى __dict__. كائن mappingproxy، وهو كائن للقراءة فقط، يشبه القاموس. لذا، بينما يمكنك إضافة توابع وسمات الفئة ديناميكيًا باستخدام تدوين النقاط، لا يمكنك تعديل __dict__. مباشرةً.
حذف السمات
تتيح لك السمة __dict__. أيضًا حذف السمات من فئة أو مثيل ديناميكيًا باستخدام عبارة del، أو باستخدام دوال القاموس مثل .pop() و .clear(). لإزالة سمة مثيل، يمكنك اتباع الخطوات التالية:
class Config:
def __init__(self, name):
self.name = name
def set_option(self, key, value):
self.__dict__[key] = value
def get_option(self, key):
return self.__dict__.get(key, None)
def remove_option(self, key):
if key in self.__dict__:
del self.__dict__[key]
# self.__dict__.pop(key)
print(f"'{key}' removed!")
else:
print(f"'{key}' does not exist.")
def clear(self):
self.__dict__.clear()
print("All options removed!")
في هذا المثال، توضح الأسطر المميزة كيفية استخدام عبارة del وطريقتي .pop() و.clear() لإزالة أزواج المفتاح-القيمة من .__dict__. يؤدي هذا إلى إزالة السمات من المثيل الحالي، self.
إليك كيفية عمل فئة Config:
>>> from config_v2 import Config
>>> conf = Config("GUI App")
>>> conf.set_option("theme", "dark")
>>> conf.set_option("size", "200x400")
>>> conf.__dict__
{'name': 'GUI App', 'theme': 'dark', 'size': '200x400'}
>>> conf.remove_option("size")
'size' removed!
>>> conf.__dict__
{'name': 'GUI App', 'theme': 'dark'}
>>> conf.remove_option("autosave")
'autosave' does not exist.
>>> conf.clear()
All options removed!
>>> conf.__dict__
{}
في هذا المثال، ستُنشئ أولًا مثيلًا من Config باسم “GUI App”. ثم تستخدم الدالة .set_option() لإضافة سمتين مع القيم المقابلة لهما. عند تحديد __dict__.، ستظهر جميع سمات المثيل مُدرجة.
بعد ذلك، يمكنك إزالة سمة
بعد ذلك، يمكنك إزالة سمة size. باستخدام دالة .remove_option(). تعمل هذه الإزالة كما هو متوقع. يمكنك استخدام هذه الطريقة لإزالة سمة غير موجودة في مثيل التكوين، وستتلقى رسالةً بناءً على ذلك. وأخيرًا، يمكنك إزالة جميع خيارات التكوين باستخدام دالة .clear().
مقارنة __dict__. مع أدوات معالجة السمات الأخرى
تحتوي بايثون على بعض الدوال المُدمجة التي تُتيح لك التحكم بالسمات في فئاتك ومثيلاتك. إليك قائمة بهذه الدوال ووظائفها:
- تقوم getattr(object, name) بإرجاع قيمة سمة الاسم على الكائن.
- setattr(object, name, value) يقوم بتعيين قيمة سمة الاسم على الكائن.
- يقوم delattr(object, name) بإزالة سمة الاسم من الكائن.
- hasattr(object, name) يتحقق ما إذا كان الكائن يحتوي على سمة اسم.
تُعد هذه الدوال الخيار الأمثل للتعامل مع السمات والتوابع بشكل فردي. فهي تتيح لك الوصول إلى السمات بأمان وتوفر قيمًا افتراضية للسمات المفقودة. كما أنها تُتيح لك التعامل مع الفئات التي تحتوي على سمة __slots__. بدلاً من __dict__..
مع ذلك، في بعض الحالات، قد تكون السمة __dict__. هي الطريقة المناسبة للوصول إلى السمات وتعديلها. بل قد تكون الطريقة الوحيدة عند مراجعة الكود أو تصحيح أخطائه. كما أنها حل جيد عند الحاجة إلى إضافة أو تحديث سمات متعددة برمجيًا أو عند العمل مع كائنات مُنشأة ديناميكيًا.
في حالات الاستخدام المتقدمة، مثل استخدام الوصافات، قد يكون .dict هو الطريقة الوحيدة لتجنب مشاكل مثل استثناءات RecursionError. ستتعلم المزيد عن هذا في قسم كتابة الوصافات.
لتوضيح كيفية استخدام هذه الدوال المضمنة للتعامل مع سماتك، إليك تنفيذ لفئة Config التي تستخدمها:
class Config:
def __init__(self, name):
self.name = name
def set_option(self, key, value):
setattr(self, key, value)
def get_option(self, key):
return getattr(self, key, None)
def remove_option(self, key):
if hasattr(self, key):
delattr(self, key)
print(f"'{key}' removed!")
else:
print(f"'{key}' does not exist.")
def clear(self):
for key in list(self.__dict__.keys()):
delattr(self, key)
print("All options removed!")
في هذا التنفيذ الجديد لـ Config، يمكنك استخدام ()setattr و()getattr و()delattr للتعامل مع السمات بدلاً من استخدام __dict__..
استكشاف .__dict__ في الميراث
عند العمل مع الميراث في Python، فإن فهم كيفية سلوك __dict__.في هذا السياق يساعدك على توضيح مكان تخزين السمات، وكيفية الوصول إليها، وكيف تؤثر التعديلات على فئات الوالدين والأبناء.
في الأقسام التالية، ستستكشف الفروق بين فئة __dict__. ومثيلها __dict__. في تسلسلات الوراثة. سيساعدك هذا على فهم كيفية حل السمات، وتجاوز الدالة، وغيرها من المشاكل المحتملة.
الفئة .__dict__ في الميراث
لتصور كيفية سلوك سمة الفئة __dict__. في شجرة الميراث، راجع المثال التالي:
>>> class Parent:
... parent_attr = "parent"
...
>>> class Child(Parent):
... child_attr = "child"
...
>>> Parent.__dict__
mappingproxy({
'__module__': '__main__',
'__firstlineno__': 1,
'parent_attr': 'parent',
'__static_attributes__': (),
'__dict__': <attribute '__dict__' of 'Parent' objects>,
'__weakref__': <attribute '__weakref__' of 'Parent' objects>,
'__doc__': None
})
>>> Child.__dict__
mappingproxy({
'__module__': '__main__',
'__firstlineno__': 1,
'child_attr': 'child',
'__static_attributes__': (),
'__doc__': None
})
>>> Child.parent_attr
'parent'
عند الوصول إلى __dict__. في فئة Child، يتضمن الناتج child_attr. وليس parent_attr. مع ذلك، يمكنك الوصول إلى parent_attr. في فئة Child. ذلك لأن بايثون يبحث عن parent_attr . في فئة Child.__dict__ ولا يجده. ثم يبحث عن parent_attr. في فئة Parent.__dict__ ويجده هناك.
مثيل .__dict__ في الميراث
ماذا عن سمة __dict__.؟ كيف تعمل في أشجار الوراثة؟ انظر المثال التالي:
>>> class Parent:
... def __init__(self):
... self.parent_attr = "parent"
...
>>> class Child(Parent):
... def __init__(self):
... self.child_attr = "child"
...
>>> parent = Parent()
>>> parent.__dict__
{'parent_attr': 'parent'}
>>> child = Child()
>>> child.__dict__
{'child_attr': 'child'}
كما ترى، عند الوصول إلى __dict__. على مثيل من Child، ستحصل على سمات هذا المثيل وليس سمات مثيل الأصل. إذا أردت أن يحمل child.__dict__ سمات مثيل الأصل، يمكنك إضافة استدعاء )super إلى دالة Child.__init__():
>>> class Child(Parent):
... def __init__(self):
... super().__init__()
... self.child_attr = "child"
...
>>> child = Child()
>>> child.__dict__
{'parent_attr': 'parent', 'child_attr': 'child'}
يؤدي استدعاء super().__init__() إلى تهيئة المثيل الضمني لفئة الأصل. الآن، أصبحت السمتان .parent_attr و.child_attr موجودتين في child.__dict__.
استخدام .__dict__ في الممارسة العملية
تتيح لك سمة __dict__. الوصول المباشر إلى مساحة اسم الكائن، مما يجعلها أداة فعّالة لإدارة السمات الديناميكية. بالإضافة إلى فحص الكائن، تتيح لك سمة __dict__. تطبيق تقنيات متقدمة، مثل الحفظ المؤقت، والتسلسل، ومعالجة السمات المخصصة، وتعديل الكائن أثناء التشغيل.
في الأقسام التالية، ستستكشف أمثلة عملية لاستخدام __dict__. لكل من هذه التقنيات.
حفظ البيانات في الدوال
عند كتابة دوال في بايثون، قد تجد أن حفظ القيم المحسوبة مسبقًا وتخزينها مؤقتًا يُحسّن من وقت تنفيذ بعض الدوال. على سبيل المثال، لنفترض أن لديك الدالة التالية التي تحسب أرقام فيبوناتشي بشكل متكرر:
>>> def fibonacci_of(n):
... if n < 2:
... return n
... return fibonacci_of(n - 1) + fibonacci_of(n - 2)
...
>>> fibonacci_of(35)
9227465
هذه الدالة تعمل. تُمرّر عددًا صحيحًا كمُعامل، وتحسب الدالة رقم فيبوناتشي المُقابل. مع ذلك، هذا التنفيذ غير فعّال:
>>> import time
>>> start = time.perf_counter(); fibonacci_of(35); end = time.perf_counter()
>>> print(f"{end - start:.3f} seconds")
1.153 seconds
لحساب عدد فيبوناتشي في الموضع 35 من المتتالية، تستغرق دالتك ثانية واحدة. هذا لأن الدالة تحسب أرقام فيبوناتشي المحسوبة مسبقًا بشكل متكرر. يمكنك تجنب هذا التكرار باستخدام الحفظ.
التنفيذ التالي لدالة ()fibonacci_of أسرع بكثير من التنفيذ السابق:
>>> def fibonacci_of(n):
... cache = fibonacci_of.__dict__.setdefault("cache", {})
... if n not in cache:
... cache[n] = n if n < 2 else fibonacci_of(n - 1) + fibonacci_of(n - 2)
... return cache[n]
...
>>> import time
>>> start = time.perf_counter(); fibonacci_of(35); end = time.perf_counter()
>>> print(f"{end - start:.3f} seconds")
0.024 seconds
هذه المرة، لا يستغرق تشغيل الدالة سوى جزء من الثانية. يكمن سر هذه السرعة في استخدامك __dict__. لإرفاق سمة cache. بدالك. وكما يوحي اسمها، تعمل هذه السمة كذاكرة تخزين مؤقت لأرقام فيبوناتشي المحسوبة مسبقًا.
في هذا التطبيق، تُهيئ قاموس cache. في fibonacci_of.__dict__ باستخدام دالة .setdefault() . تضمن هذه الخاصية تخزينًا مؤقتًا مستمرًا عند استدعاء الدالة. إذا لم تكن قيمة فيبوناتشي لـ n موجودة في ذاكرة التخزين المؤقت، تحسب الدالة الرقم بشكل متكرر وتخزن النتيجة في cache[n]. وأخيرًا، تُعاد النتيجة من ذاكرة التخزين المؤقت.
فحص الكود وتصحيح أخطائه
تُعد سمة __dict__. مفيدة جدًا أيضًا عند مراجعة الكود ومعرفة قيمة سمة معينة في مرحلة معينة. وهذا مفيد بشكل خاص عند تصحيح أخطاء الكود أثناء التطوير.
على سبيل المثال، افترض أن لديك الفئة التالية وعندما تستخدمها، فإن النتيجة ليست ما تتوقعه:
>>> class Employee:
... def __init__(self, name, department, salary):
... self.name = name
... self.department = department
... self.salary = salary
... def give_raise(self, amount):
... self.salery = self.salary + amount # Typo here: self.salery
...
>>> john = Employee("John", "Engineering", 70000)
>>> john.give_raise(5000)
>>> john.salary
70000
بعد زيادة راتب جون، كنت تتوقع أن يكون راتبه 75,000. لكنك حصلت على 70,000. لم يتم تحديث الراتب بسبب خطأ مطبعي: self.salery بدلاً من self.salary. إذا استخدمتَ سمة __dict__. في حساب جون، فستجد أن لديك سمة غير متوقعة:
>>> john.__dict__
{
'name': 'John',
'department': 'Engineering',
'salary': 70000,
'salery': 75000
}
يكشف محتوى __dict__. أن لديك الآن .salery بقيمة 75000. بمجرد ملاحظة هذه المشكلة، يمكنك إصلاح الكود عن طريق تصحيح الخطأ المطبعي.
من التفاصيل المثيرة للاهتمام حول استخدام __dict__. للتحليل الذاتي وتصحيح الأخطاء أن السمات المشوهة تظهر أيضًا في المخرجات. لننظر إلى الفئة التالية:
>>> class DemoClass:
... def __init__(self):
... self.__attr = "This is a mangled name"
...
>>> demo_object = DemoClass()
>>> demo_object.__attr
Traceback (most recent call last):
...
AttributeError: 'DemoClass' object has no attribute '__attr'
>>> demo_object.__dict__
{'_DemoClass__attr': 'This is a mangled name'}
في هذا المثال، يبدأ __attr. بعلامة سفلية مزدوجة. تُفعّل هذه التسمية تشويه الأسماء في بايثون. لاحظ أن تشويه الأسماء يُخفي الاسم، فلا يمكنك الوصول إليه مباشرةً. تكشف السمة .__dict__ عن سرّ تشويه الأسماء في بايثون. يتغير اسم __attr. داخليًا إلى _DemoClass__attr.
تسلسل البيانات إلى تنسيق JSON
في بايثون، تسلسل البيانات هو عملية تحويل الكائنات إلى صيغة يمكن تخزينها في نظام ملفات أو نقلها عبر شبكة. ومن الأمثلة الشائعة على ذلك تسلسل كائنات بايثون إلى JSON. تتوفر وحدة json في المكتبة القياسية لهذه المهمة.
لا يمكن تسلسل مثيلات الفئات المُعرّفة من قِبل المستخدم مباشرةً باستخدام أدوات مثل ()json.dumps لأنها ليست أنواع بيانات أساسية يدعمها JSON. مع ذلك، بالوصول إلى سمة __dict__. للكائن، يمكنك استرجاع سمات مثيلاته كقاموس يُمكنك تسلسله إلى صيغة JSON.
في المثال أدناه، يمكنك استخدام __dict__. لتحويل كائن Person إلى سلسلة JSON:
>>> class Person:
... def __init__(self, first_name, last_name, age):
... self.first_name = first_name
... self.last_name = last_name
... self.age = age
...
>>> jane = Person("Jane", "Doe", 25)
>>> import json
>>> jane_as_json = json.dumps(jane.__dict__)
>>> jane_as_json
'{"first_name": "Jane", "last_name": "Doe", "age": 25}'
تحتوي فئة الشخص على ثلاث سمات: .first_name و.last_name و.age. يمكنك إنشاء مثيل لفئة الشخص باستخدام القيم “Jane” و”Doe” و25 للسمات المقابلة.
بعد ذلك، استخدم السمة __dict__. كوسيطة لدالة ()json.dumps، التي تحوّل الكائن إلى سلسلة نصية JSON. يمكنك الآن تخزين محتوى JSON هذا في ملف أو إرساله عبر شبكة.
لقد تعلمتَ الكثير عن سمة __dict__. في بايثون، وهي أداة فعّالة لإدارة السمات في الفئات والمثيلات، سواءً على مستوى منخفض أو ديناميكي. تعلمتَ أن __dict__. عبارة عن قاموس يُطابق أسماء السمات مع قيمها. كما تعمقتَ في كيفية عمل __dict__. في الفئات والمثيلات والدوال والكائنات الأخرى.
يعد فهم __dict__. أمرًا أساسيًا لمطوري Python، لأنه مفيد بشكل خاص للمهام مثل تصحيح الأخطاء، وتسلسل البيانات، والتذكير، والبرمجة الوصفية.
الآن وقد اكتسبتَ فهمًا متعمقًا لـ __dict__.، يمكنك الاستفادة من هذه المعرفة لجعل شيفرة بايثون الخاصة بك أكثر ديناميكية ومرونة وكفاءة. سواءً كنتَ تُنقِّح الأخطاء، أو تُنشئ كائنات ديناميكية، أو تُنفِّذ سلوكيات متقدمة، فإن __dict__. أداة قيّمة في مجموعة أدوات بايثون الخاصة بك.
اكتشاف المزيد من بايثون العربي
اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.