enum في بايثون
enumeration هو مجموعة من الأسماء الرمزية (العناصر) المرتبطة بقيم ثابتة وفريدة. يمكن مقارنة عناصر enumeration عن طريق هويتها، ويمكن المرور على عناصر enumeration بواسطة حلقة تكرارية.
إنشاء Enum
يمكن إنشاء Enumeration باستخدام الصيغة الخاصّة بإنشاء الأصناف في بايثون، الأمر الذي يسهّل قراءة وكتابة هذا النوع من البيانات، ويوضح المثال التالي طريقة تعريف enumeration:
>>> from enum import Enum
>>> class Color(Enum):
... RED = 1
... GREEN = 2
... BLUE = 3
ملاحظة: قيم عناصر Enumeration
يمكن استخدام أي نوع من البيانات (الأعداد الصحيحة، والسلاسل النصية) كقيمة لعناصر enumeration. إن كانت القيمة المضبوطة غير مهمّة فيمكن استخدام نسخ auto
وستختار اللغة القيمة الملائمة، ولكن يجب الانتباه عند استخدام auto
مع قيم أخرى.
ملاحظة: التسمية
- الصنف
Color
هو enumeration (أو enum)
- الخاصيات
Color.RED، Color.GREEN
وغيرها هي عناصر enumeration (أو عناصر enum) وهي قيم ثابتة.
- تمتلك عناصر enum أسماء وقيم (اسم
Color.RED
هوRED
، وقيمةColor.BLUE
هي3
).
ملاحظة:
صحيح أن تعريف Enumerations يكون باستخدام صيغة تعريف الأصناف، إلا أنّ Enums لا تعدّ أصنافًا اعتيادية في بايثون.
تمتلك عناصر Enumeration تمثيلًا نصّيًا قابلًا للقراءة:
>>> print(Color.RED)
Color.RED
في حين تعطي الدالة repr()
معلومات أكثر:
>>> print(repr(Color.RED))
<Color.RED: 1>
أما نوع العنصر فهو الـ enumeration الذي ينتمي إليه:
>>> type(Color.RED)
<enum 'Color'>
>>> isinstance(Color.GREEN, Color)
True
>>>
تمتلك عناصر Enum خاصّية تحمل اسم العنصر فقط:
>>> print(Color.RED.name)
RED
تدعم Enumeration المرور على العناصر بحسب ترتيب تعريفها:
>>> class Shake(Enum):
... VANILLA = 7
... CHOCOLATE = 4
... COOKIES = 9
... MINT = 3
...
>>> for shake in Shake:
... print(shake)
...
Shake.VANILLA
Shake.CHOCOLATE
Shake.COOKIES
Shake.MINT
عناصر Enumeration قابلة للتقطيع (hashable)؛ لذا يمكن استخدامها في القواميس والمجموعات:
>>> apples = {}
>>> apples[Color.RED] = 'red delicious'
>>> apples[Color.GREEN] = 'granny smith'
>>> apples == {Color.RED: 'red delicious', Color.GREEN: 'granny smith'}
True
الوصول إلى عناصر Enumeration وخواصّها برمجيًا
من المفيد في بعض الأحيان الوصول إلى عناصر Enumeration برمجيًّا، ويمكن القيام بذلك كما يلي:
>>> Color(1)
<Color.RED: 1>
>>> Color(3)
<Color.BLUE: 3>
ويمكن الوصول إلى عناصر enum بواسطة أسمائها كما يلي:
>>> Color['RED']
<Color.RED: 1>
>>> Color['GREEN']
<Color.GREEN: 2>
للحصول على اسم أو قيمة عنصر من عناصر enum:
>>> member = Color.RED
>>> member.name
'RED'
>>> member.value
1
مضاعفة عناصر وقيم enum
لا تسمح اللغة بوجود عنصري enum يحملان نفس الاسم:
>>> class Shape(Enum):
... SQUARE = 2
... SQUARE = 3
...
Traceback (most recent call last):
...
TypeError: Attempted to reuse key: 'SQUARE'
ولكن يمكن لعنصري enum أن يمتلكا القيمة ذاتها. لو فرضنا وجود عنصرين A
و B
يحملان القيمة ذاتها (وكان A
معرّفًا قبل B
) فإن العنصر B
سيكون اختصارًا (alias، أو اسمًا بديلًا) للعنصر A
. إن جرى الاستعلام عن قيمة A
و B
فإنّ النتيجة ستكون A
، وإن جرى الاستعلام عن اسم B
فإن النتيجة ستكون A
أيضًا:
>>> class Shape(Enum):
... SQUARE = 2
... DIAMOND = 1
... CIRCLE = 3
... ALIAS_FOR_SQUARE = 2
...
>>> Shape.SQUARE
<Shape.SQUARE: 2>
>>> Shape.ALIAS_FOR_SQUARE
<Shape.SQUARE: 2>
>>> Shape(2)
<Shape.SQUARE: 2>
ملاحظة: لا يمكن إنشاء عنصر يحمل اسم خاصّية معرّفة مسبقًا (عنصر آخر، أو تابع ...إلخ.) كما لا يمكن إنشاء خاصّية تحمل نفس اسم عنصر معرّف مسبقًا.
ضمان فرادة قيم enumeration
تسمح enumerations - افتراضيًّا - باستخدام أسماء متعددة كمختصرات للقيمة نفسها. إن كان هذا الأسلوب غير مرغوب به فيمكن استخدام المزخرف التالي لضمان استخدام كلّ قيمة في الـ enumeration لمرة واحدة فقط:
@enum.unique
يوضح المثال التالي كيفية استخدام هذا المزخرف (decorator):
>>> from enum import Enum, unique
>>> @unique
... class Mistake(Enum):
... ONE = 1
... TWO = 2
... THREE = 3
... FOUR = 3
...
Traceback (most recent call last):
...
ValueError: duplicate values found in <enum 'Mistake'>: FOUR -> THREE
استخدام القيم التلقائية
إن كانت القيمة الدقيقة غير مهمّة فيمكن استخدام auto
محلّها:
>>> from enum import Enum, auto
>>> class Color(Enum):
... RED = auto()
... BLUE = auto()
... GREEN = auto()
...
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]
تختار اللغة القيم عن طريق التابع _generate_next_value_()
والذي يمكن تجاوزه (override):
>>> class AutoName(Enum):
... def _generate_next_value_(name, start, count, last_values):
... return name
...
>>> class Ordinal(AutoName):
... NORTH = auto()
... SOUTH = auto()
... EAST = auto()
... WEST = auto()
...
>>> list(Ordinal)
[<Ordinal.NORTH: 'NORTH'>, <Ordinal.SOUTH: 'SOUTH'>, <Ordinal.EAST: 'EAST'>, <Ordinal.WEST: 'WEST'>]
المرور على عناصر enumeration
لا تقدم عملية المرور على عناصر enumeration الاختصارات الموجودة فيه:
>>> list(Shape)
[<Shape.SQUARE: 2>, <Shape.DIAMOND: 1>, <Shape.CIRCLE: 3>]
الخاصّية الخاصة __members__
هي قاموس مرتّب يربط الأسماء بالعناصر، ويتضمن جميع الأسماء المعرّفة في enumeration وبضمنها الاختصارات:
>>> for name, member in Shape.__members__.items():
... name, member
...
('SQUARE', <Shape.SQUARE: 2>)
('DIAMOND', <Shape.DIAMOND: 1>)
('CIRCLE', <Shape.CIRCLE: 3>)
('ALIAS_FOR_SQUARE', <Shape.SQUARE: 2>)
يمكن الاستفادة من الخاصية __members__
للوصول إلى عناصر enumeration برمجيًّا. فعلى سبيل المثال يمكن العثور على جميع الاختصارات بالطريقة التالية:
>>> [name for name, member in Shape.__members__.items() if member.name != name]
['ALIAS_FOR_SQUARE']
مقارنة العناصر
يمكن عقد المقارنات بين عناصر enumeration باستخدام هوية العنصر:
>>> Color.RED is Color.RED
True
>>> Color.RED is Color.BLUE
False
>>> Color.RED is not Color.BLUE
True
لا تدعم اللغة المقارنات المرتّبة بين قيم enumeration، إلى جانب أنّ عناصر Enum ليست أعدادًا صحيحة.
>>> Color.RED < Color.BLUE
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'Color' and 'Color'
ولكن يمكن اختبار المساواة:
>>> Color.BLUE == Color.RED
False
>>> Color.BLUE != Color.RED
True
>>> Color.BLUE == Color.BLUE
True
عقد المقارنة مع قيم ليست ضمن enumeration تكون نتيجتها دومًا عدم المساواة:
>>> Color.BLUE == 2
False
العناصر والخصائص المسموح بها في enumeration
في الأمثلة السابقة كانت قيم enumeration أعدادًا صحيحة. بالرغم من أن استخدام الأعداد الصحيحة مختصر وسريع، ولكنّه ليس إلزاميًا. في أغلب الحالات ليس من المهمّ معرفة القيمة الحقيقية للـ enumeration، ولكن إن كانت القيمة مهمّة، فيمكن للـ enumerations أن تمتلك أيّ قيمة ممكنة.
Enumerations هي أصناف بايثون؛ لذا يمكن أن تمتلك توابع وتوابع خاصّة كما هو الحال مع الأصناف، فلو كان لدينا الـ enumeration التالي:
>>> class Mood(Enum):
... FUNKY = 1
... HAPPY = 3
...
... def describe(self):
... # self is the member here
... return self.name, self.value
...
... def __str__(self):
... return 'my custom str! {0}'.format(self.value)
...
... @classmethod
... def favorite_mood(cls):
... # cls here is the enumeration
... return cls.HAPPY
...
فيمكن حينئذ كتابة الشيفرة التالية:
>>> Mood.favorite_mood()
<Mood.HAPPY: 3>
>>> Mood.HAPPY.describe()
('HAPPY', 3)
>>> str(Mood.FUNKY)
'my custom str! 1'
تتبع عملية التسمية القواعد التالية:
- تكون الأسماء التي تبدأ وتنتهي بشرطة سفلية واحدة محجوزة من طرف enum ولا يمكن استخدامها.
- تصبح جميع الخاصيات الأخرى المعرّفة ضمن enumeration عناصر له؛ باستثناء التوابع الخاصة (
__str__()، __add__()
، وغيرها) والواصفات (descriptors) (التوابع واصفات أيضًا).
انظر أيضًا
مصادر
- صفحة Support for enumerations في توثيق بايثون الرسمي.