Python/enum

من موسوعة حسوب
< Python
مراجعة 20:05، 10 مارس 2018 بواسطة Mohammed Taher (نقاش | مساهمات) (أنشأ الصفحة ب'enumeration هو مجموعة من الأسماء الرمزية (العناصر) المرتبطة بقيم ثابتة وفريدة. يمكن مقارنة عناصر enu...')
(فرق) → مراجعة أقدم | المراجعة الحالية (فرق) | مراجعة أحدث ← (فرق)
اذهب إلى التنقل اذهب إلى البحث

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

يوضح المثال التالي كيفية استخدام هذا المزخرف:

>>> 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) (التوابع واصفات أيضًا).

مصادر