الوحدة enum في بايثون
enumeration هو مجموعة من الأسماء الرمزية (العناصر) المرتبطة بقيم ثابتة وفريدة. يمكن مقارنة عناصر enumeration عن طريق هويتها، ويمكن المرور على عناصر enumeration بواسطة حلقة تكرارية.
وحدة enum
تقدّم وحدة enum
أربعة أصناف enumeration يمكن استخدامها لتعريف مجموعة فريدة من الأسماء والقيم، وهذه الأصناف هي: Enum
و IntEnum
و Flag
و IntFlag
. وإلى جانب ما سبق تقدّم الوحدة مزخرفًا واحدًا هو unique()
، وصنفًا مساعدًا واحدًا هو auto
.
الصنف enum.Enum
هو الصنف الأساسي والذي يستخدم لإنشاء ثوابت معدّدة enumerated constants. راجع قسم الواجهة البرمجية الوظيفية للاطلاع على الصيغة البديلة.
الصنف enum.IntEnum
الصنف الأساسي الذي يستخدم لإنشاء ثوابت معدّدة تكون كذلك أصنافًا فرعية للصنف int
.
الصنف enum.IntFlag
الصنف الأساسي الذي يستخدم لإنشاء ثوابت معدّدة يمكن دمجها مع بعضها البعض باستخدم عامل bitwise دون التأثير على كونها عناصر للصنف IntFlag
. وتكون عناصر IntFlag
أصنافًا فرعية من الصنف int
أيضًا.
الصنف enum.Flag
الصنف الرئيسي الذي يستخدم لإنشاء ثوابت معدّدة يمكن دمجها مع بعضها البعض دون التأثير على كونها من نوع Flag
.
المزخرف enum.unique()
مزخرف الصنف Enum والذي يضمن ارتباط اسم واحد بقيمة واحدة فقط.
الصنف المساعد enum.auto
عند استخدام هذا الصنف تُستبدل نسخ الصنف Enum بقيم ملائمة.
ملاحظة: الأصناف Flag
و IntFlag
و auto
جديدة في الإصدار 3.6 من بايثون.
إنشاء 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.RED لأنّه اللون المطلوب قد يكون غير معروف أثناء كتابة البرنامج) ويمكن القيام بذلك كما يلي:
>>> 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) (التوابع واصفات أيضًا).
التفريع المقيد لأصناف enum
تسمح اللغة فقط بإنشاء أصناف فرعية للـ enumeration التي لا تعرّف أي عناصر، ولهذا تعدّ الشيفرة التالية خاطئة:
>>> class MoreColor(Color):
... PINK = 17
...
Traceback (most recent call last):
...
TypeError: Cannot extend enumerations
أما الشيفرة التالية فصحيحة:
>>> class Foo(Enum):
... def some_behavior(self):
... pass
...
>>> class Bar(Foo):
... HAPPY = 1
... SAD = 2
يؤدي السماح بإنشاء أصناف فرعية للصنف enum الذي يمتلك عددًا من العناصر إلى مخالفة بعض الثوابت المهمّة في الأنواع ونسخ الأصناف، ولكن من جانب آخر من المعقول أن يُسمح بمشاركة بعض الأمور المشتركة بين مجموعة من الـ enumeration.
(راجع OrderedEnum
للاطلاع على الأمثلة).
عملية Pickling
يمكن إجراء عمليتي pickling
و unpickling
على enumerations:
>>> from test.test_enum import Fruit
>>> from pickle import dumps, loads
>>> Fruit.TOMATO is loads(dumps(Fruit.TOMATO))
True
وتنطبق هنا جميع القيود المعروفة لعملية pickling: حيث يجب تعريف الـ enums في المستوى الأعلى من الوحدة؛ وذلك لأنّ عملية unpickling تتطلب كون هذه الـ enums قابلة للاستيراد من تلك الوحدة.
ملاحظة: من الممكن بالاعتماد على الإصدار الرابع من بروتوكول عملية pickling إجراء هذه العملية على enums متداخلة في أصناف أخرى.
من الممكن تعديل طريقة إجراء عمليتي pickling و unpickling على عناصر الصنف Enum وذلك بتعريف التابع __reduce_ex__()
في صنف enumeration.
الواجهة البرمجية الوظيفية Functional API
الصنف Enum قابل للاستدعاء، ويقدّم الواجهة البرمجية الوظيفية التالية:
>>> Animal = Enum('Animal', 'ANT BEE CAT DOG')
>>> Animal
<enum 'Animal'>
>>> Animal.ANT
<Animal.ANT: 1>
>>> Animal.ANT.value
1
>>> list(Animal)
[<Animal.ANT: 1>, <Animal.BEE: 2>, <Animal.CAT: 3>, <Animal.DOG: 4>]
هذه الواجهة البرمجية مشابهة للصفوف المسمّاة، ويكون المعامل الأول في عملية استدعاء الصنف Enum هو اسم enumeration.
أما المعامل الثاني source
فهو مصدر أسماء عناصر enumeration. يمكن لهذا المعامل أن يكون سلسلة نصية تتضمن أسماء مفصولة عن بعضها البعض بمسافات بيضاء، أو تسلسل من الأسماء، أو تسلسل من صفوف مزدوجة تتضمن زوج مفتاح/قيمة، أو ربط mapping (مثل: القاموس) أسماء بقيم.
يتيح الخياران الأخيران إسناد قيم متنوعة إلى enumerations، في حين تسند الخيارات الأخرى وبصورة تلقائية أعدادًا صحيحة تبدأ من الرقم 1
(يمكن استخدام المعامل start
لتحديد قيمة ابتدائية مختلفة)، ويعاد صنف جديد مشتّق من الصنف Enum.
وبعبارة أخرى فإنّ عملية الإسناد في المثال السابقة إلى الصنف Animal
تكون مكافئة لما يلي:
>>> class Animal(Enum):
... ANT = 1
... BEE = 2
... CAT = 3
... DOG = 4
...
إن السبب الذي يدعو إلى البدء بالعدد 1
وليس 0
هو أنّ العدد 0
يمثّل القيمة False
في السياقات البوليانية، لكنّ جميع عناصر enum
تُعالج إلى القيمة True
.
إجراء عملية Pickling على enums أنشئت بواسطة الواجهة البرمجية الوظيفية قد يكون صعبًا في بعض الأحيان، وذلك لأنّ frame stack implementation details تستخدم لمعرفة الوحدة التي أنشئ فيها enumeration (على سبيل المثال ستفشل العملية عند استخدام دالة مساعدة في وحدة أخرى، وقد لا تتم العملية أيضًا على IronPython أو Jython).
وحل هذه المشكلة هو تحديد اسم الوحدة وعلى النحو التالي:
>>> Animal = Enum('Animal', 'ANT BEE CAT DOG', module=__name__)
تحذير:
إن لم تُعرّف الوحدة المستخدمة، ولم يتمكن الصنف Enum من تحديدها، فبالإمكان إجراء عملية unpickling على عناصر Enum، ولكن لتقريب الأخطاء إلى مصادرها، فإنّ عملية pickling هي التي ستعطل.
كذلك يعتمد البروتوكول الرابع الجديد الخاص بعملية pickling -في بعض الحالات- على التابع __qualname__
والذي يُعيّن الموقع الذي تكون فيه عملية pickling قادرة على إيجاد الصنف. فمثلًا، إن كان الصنف موجودًا في صنف SomeData
ضمن النطاق العام:
>>> Animal = Enum('Animal', 'ANT BEE CAT DOG', qualname='SomeData.Animal')
والتوقيع الكامل هو:
Enum(value='NewEnumName', names=<...>, *, module='...', qualname='...', type=<mixed-in class>, start=1)
value
|
القيمة التي سيسجّلها الصنف Enum كاسم له. |
names
|
عناصر الصنف Enum. يمكن أن يكون سلسلة نصية تتضمن أسماء مفصولة بمسافات بيضاء أو بفواصل (القيم تبدأ بالرقم 1 إلا إذا حُدّد عدد آخر كعدد ابتدائي):
أو كقائمة من الأسماء:
أو كقائمة من أزواج (اسم، قيمة):
أو كقاموس:
|
module
|
اسم الوحدة التي يمكن إيجاد الصنف Enum الجديد فيها. |
qualname
|
المكان الذي يمكن إيجاد الصنف Enum الجديد فيه ضمن الوحدة. |
type
|
النوع المطلوب دمجه مع صنف Enum الجديد. |
start
|
العدد الذي يبدأ منه العدّ عند تمرير الأسماء فقط. |
ملاحظة: أضيف المعامل start في الإصدار 3.5 من بايثون.
الـ Enumerations المشتقّة
الصنف IntEnum
أول صنف مشتّق من Enum وهو صنف مشتّق كذلك من int
. يمكن مقارنة عناصر الصنف IntEnum
مع الأعداد الصحيحة، ويمكن كذلك مقارنة الـ enumerations العددية ذات الأنواع المختلفة مع بعضها بعضًا:
>>> from enum import IntEnum
>>> class Shape(IntEnum):
... CIRCLE = 1
... SQUARE = 2
...
>>> class Request(IntEnum):
... POST = 1
... GET = 2
...
>>> Shape == 1
False
>>> Shape.CIRCLE == 1
True
>>> Shape.CIRCLE == Request.POST
True
ولكن لا يمكن مقارنتها مع enumerations من نوع Enum الاعتيادية:
>>> class Shape(IntEnum):
... CIRCLE = 1
... SQUARE = 2
...
>>> class Color(Enum):
... RED = 1
... GREEN = 2
...
>>> Shape.CIRCLE == Color.RED
False
تسلك قيم IntEnum
سلوك الأعداد الصحيحة وحسب ما هو متوقع:
>>> int(Shape.CIRCLE)
1
>>> ['a', 'b', 'c'][Shape.CIRCLE]
'b'
>>> [i for i in range(Shape.SQUARE)]
[0, 1]
الصنف IntFlag
يستند هذا النوع من أصناف Enum على الصنف int
أيضًا، ويمتاز هذا الصنف بقدرة عناصره على الاندماج باستخدام عوامل bitwise (&
و |
و ^
و ~
) وتبقى النتيجة عنصر IntFlag
. ولكن، وكما يوحي إليه الاسم، فإنّ عناصر IntFlag
هي أصناف فرعية من int
أيضًا، ويمكن استخدامها في أي مكان يمكن استخدام الأعداد الصحيحة فيه، ولكن يؤدي إجراء أي عملية على عناصر IntFlag
باستثناء عمليات bitwise إلى خروج العنصر من IntFlag
.
ملاحظة: هذا الصنف جديد في الإصدار 3.6 من بايثون.
يعرض المثال التالي نموذجًا لصنف IntFlag
:
>>> from enum import IntFlag
>>> class Perm(IntFlag):
... R = 4
... W = 2
... X = 1
...
>>> Perm.R | Perm.W
<Perm.R|W: 6>
>>> Perm.R + Perm.W
6
>>> RW = Perm.R | Perm.W
>>> Perm.R in RW
True
يمكن كذلك تسمية التركيبات combinations:
>>> class Perm(IntFlag):
... R = 4
... W = 2
... X = 1
... RWX = 7
>>> Perm.RWX
<Perm.RWX: 7>
>>> ~Perm.RWX
<Perm.-8: -8>
هناك فارق آخر بين الصنفين IntFlag
و Enum، وهو أنّه في حال عدم تعيين أية رايات (أي كانت القيمة هي 0
) فإنّ IntFlag
يعالج في السياقات البوليانية إلى القيمة False
:
>>> Perm.R & Perm.X
<Perm.0: 0>
>>> bool(Perm.R & Perm.X)
False
لمّا كانت عناصر IntFlag
أصنافًا فرعية من الصنف int
، فمن الممكن إذًا دمج الصنفين بعضهما ببعض:
>>> Perm.X | 8
<Perm.8|X: 9>
الصنف Flag
النوع الأخير من أصناف Enum هو الصنف Flag
. كما هو الحال مع الصنف IntFlag
، فبالإمكان دمج عناصر الصنف Flag
باستخدام معاملات bitwise (&
و |
و ^
و ~
). ولكن بخلاف IntFlag
، لا يمكن دمج عناصر الصنف Flag
أو مقارنتها مع أي صنف Flag
أو int
.
على الرغم من إمكانية تحديد القيم بصورة مباشرة، إلا أنّه ينصح باستخدام auto
كقيمة والسماح للصنف Flag
باختيار القيمة المناسبة.
ملاحظة: هذا الصنف جديد في الإصدار 3.6.
كما هو الحال مع الصنف IntFlag
، إن لم تعين مجموعة من عناصر Flag
أيّة راية، فإنّ الصنف يعالج إلى القيمة False
في السياقات البوليانية:
>>> from enum import Flag, auto
>>> class Color(Flag):
... RED = auto()
... BLUE = auto()
... GREEN = auto()
...
>>> Color.RED & Color.GREEN
<Color.0: 0>
>>> bool(Color.RED & Color.GREEN)
False
يجب أن تحمل الرايات المفردة قيمًا تكون قوىً للعدد 2 (1، 2، 4، 8، ...)، أما مجموعات الرايات فلا:
>>> class Color(Flag):
... RED = auto()
... BLUE = auto()
... GREEN = auto()
... WHITE = RED | BLUE | GREEN
...
>>> Color.WHITE
<Color.WHITE: 7>
لا يؤدي إعطاء اسم إلى الشرط "no flags set
" إلى تغيير قيمته البوليانية:
>>> class Color(Flag):
... BLACK = 0
... RED = auto()
... BLUE = auto()
... GREEN = auto()
...
>>> Color.BLACK
<Color.BLACK: 0>
>>> bool(Color.BLACK)
False
ملاحظة:
ينصح بشدة استخدام الصنفين Enum و Flag
في الشيفرات الجديدة، وذلك لأنّ الصنفين IntEnum
و IntFlag
يكسران بعض القواعد الخاصة بالـ enumeration (مثل إمكانية المقارنة مع الأعداد الصحيحة، الأمر الذي يؤدي إلى الانتقال إلى enumerations أخرى غير ذات علاقة).
يجب استخدام الصنفين IntEnum
و IntFlags
فقط في الحالات التي يكون فيها استخدام الصنفين Enum و Flag
غير مجدٍ، كأن يجري استبدال ثوابت الأعداد الصحيحة بالـ enumerations، أو للحاجة إلى التوافق مع الأنظمة الأخرى.
أصناف أخرى
على الرغم من كون الصنف IntEnum
جزءًا من الوحدة enum
، إلا أنّه يمكن وبكل سهولة استخدامه على نحو منعزل:
class IntEnum(int, Enum):
pass
يبين المثال السابق كيف أنه يمكن تعريف enumerations مشتّقة متنوعة، فعلى سبيل المثال يمكن إنشاء StrEnum
والذي يدمج بين Enum والسلاسل النصية بدلًا من الأعداد الصحيحة.
ولكن هناك بعض القواعد:
- عند تفريع الصنف Enum، يجب أن يظهر النوع المراد دمجه قبل Enum في تسلسل الأصناف الأساسية، كما هو مبين في المثال السابق.
- يمكن للصنف Enum أن يمتلك عناصر ذات أنواع مختلفة، ولكن عند دمج هذا الصنف مع نوع آخر يصبح من الواجب أن تمتلك عناصر الصنف Enum قيمًا من ذلك النوع حصرًا (النوع
int
في المثال السابق). هذا القيد لا ينطبق على النوع الممزوج مع الصنف Enum، حيث تؤدي عملية المزج إلى إضافة توابع النوع الممزوج فقط، ولا تحدد أي نوع آخر من البيانات بخصوص الأنواع مثل int أو str. - عند مزج نوع آخر من البيانات، لا تكون الخاصية
value
مطابقة لعنصر enum نفسه، على الرغم من أنّهما متساويان. - التنسيق بنمط
%
: يستدعي الرمزان %s
و%r
التابعين __str__()
و__repr__()
على التوالي من الصنف Enum، أما الرموز الباقية (مثل%i
أو%h
لأجلIntNum
) فتعامل عنصر enum معاملة النوع الممزوج مع الصنفEnum
. - تستخدم سلاسل التنسيق النصية والتابع
str.format()
والدالةformat()
، التابع __format()__
الخاصّ بالنوع الممزوج. إن كان المطلوب الوصول إلى التابعstr()
أوrepr()
في الصنف Enum فيمكن استخدام علامة التعجب!
(!s
أو!r
).
بعض الأمثلة المفيدة
صحيح أنّ الأصناف Enum و IntEnum
و IntFlag
و Flag
تغطي الغالبية العظمى من الحالات، ولكنّها لن تستطيع القيام بكلّ شيء. يتضمّن هذا القسم بعض الأنواع المختلفة من enumerations والتي يمكن استخدامها على نحو مباشرة، أو الاستفادة منها في إنشاء أنواع خاصّة بالمستخدم.
حذف القيم
في الكثير من الحالات لا تكون لقيمة enumeration أي أهمّية، وهناك الكثير من الطرق التي يمكن من خلالها تعريف هذا مثل هذا النوع من الـ enumeration البسيط:
- استخدام نسخ من الصنف
auto
كقيمة.
- استخدم نسخ من كائن كقيمة.
- استخدم سلسلة نصية واصفة كقيمة.
- استخدام صف كقيمة، وتابع
__new__()
خاصّ لاستبدال الصف بقيمة من نوعint
.
استخدام أي طريقة من الطرق السابقة يعني أنّ هذه القيمة ليست مهمّة، إلى جانب أنّها تسمح بإضافة وحذف وإعادة ترتيب العناصر دون الحاجة إلى إعادة ترقيم العناصر الباقية.
ومهما كانت الطريقة المستخدمة، يجب توفير دالة repr()
والتي تخفي كذلك القيم (غير المهمّة):
>>> class NoValue(Enum):
... def __repr__(self):
... return '<%s.%s>' % (self.__class__.__name__, self.name)
...
استخدام auto
يمكن استخدام auto
بالطريقة التالية:
>>> class Color(NoValue):
... RED = auto()
... BLUE = auto()
... GREEN = auto()
...
>>> Color.GREEN
<Color.GREEN>
استخدام كائن
يمكن استخدام الكائن بالطريقة التالية:
>>> class Color(NoValue):
... RED = object()
... GREEN = object()
... BLUE = object()
...
>>> Color.GREEN
<Color.GREEN>
استخدام سلسلة نصية واصفة
يمكن استخدام السلسلة النصية الواصفة بالطريقة التالية:
>>> class Color(NoValue):
... RED = 'stop'
... GREEN = 'go'
... BLUE = 'too fast!'
...
>>> Color.GREEN
<Color.GREEN>
>>> Color.GREEN.value
'go'
استخدام تابع __new__()
خاص
يمكن استخدام تابع __new__()
لترقيم العناصر تلقائيًا وبالطريقة التالية:
>>> class AutoNumber(NoValue):
... def __new__(cls):
... value = len(cls.__members__) + 1
... obj = object.__new__(cls)
... obj._value_ = value
... return obj
...
>>> class Color(AutoNumber):
... RED = ()
... GREEN = ()
... BLUE = ()
...
>>> Color.GREEN
<Color.GREEN>
>>> Color.GREEN.value
2
ملاحظة:
يستخدم التابع __new__()
(في حال تعريفه) أثناء عملية إنشاء عناصر Enum
، ويُستبدل بعد ذلك بتابع __new__()
الخاصّ بالصنف Enum والذي يستخدم بعد إنشاء الصنف للبحث عن العناصر الموجودة في الصنف Enum.
الـ Enumerations المرتبة OrderdEnum
الـ Enumeration المرتّب هو ذلك الذي لا يستند إلى الصنف IntEnum
وبذلك يحافظ على خصائص Enum الاعتيادية (مثل عدم كونه قابلًا للمقارنة مع enumerations أخرى):
>>> class OrderedEnum(Enum):
... def __ge__(self, other):
... if self.__class__ is other.__class__:
... return self.value >= other.value
... return NotImplemented
... def __gt__(self, other):
... if self.__class__ is other.__class__:
... return self.value > other.value
... return NotImplemented
... def __le__(self, other):
... if self.__class__ is other.__class__:
... return self.value <= other.value
... return NotImplemented
... def __lt__(self, other):
... if self.__class__ is other.__class__:
... return self.value < other.value
... return NotImplemented
...
>>> class Grade(OrderedEnum):
... A = 5
... B = 4
... C = 3
... D = 2
... F = 1
...
>>> Grade.C < Grade.A
True
الـ Enumeration المضاعف الحرّ DuplicateFreeEnum
يطلق هذا الـ enumeration خطأً في حال وجود عناصر ذات أسماء مكرّرة، عوضًا عن إنشاء مختصر لها:
>>> class DuplicateFreeEnum(Enum):
... def __init__(self, *args):
... cls = self.__class__
... if any(self.value == e.value for e in cls):
... a = self.name
... e = cls(self.value).name
... raise ValueError(
... "aliases not allowed in DuplicateFreeEnum: %r --> %r"
... % (a, e))
...
>>> class Color(DuplicateFreeEnum):
... RED = 1
... GREEN = 2
... BLUE = 3
... GRENE = 2
...
Traceback (most recent call last):
...
ValueError: aliases not allowed in DuplicateFreeEnum: 'GRENE' --> 'GREEN'
ملاحظة:
هذا مثال جيّد على تفريع الصنف Enum
لإضافة أو تعديل خصائص أخرى، إلى جانب عدم السماح بإنشاء الاختصارات. إن كان المطلوب هو عدم السماح بإنشاء الاختصارات فقط، فإنّ المزخرف unique()
يقوم بهذا العمل.
مثال: صنف الكوكب Planet
إن جرى تعريف التابع __new__()
أو __init__()
، فإن قيمة عنصر enum ستُمرّر إلى هذين التابعين:
>>> class Planet(Enum):
... MERCURY = (3.303e+23, 2.4397e6)
... VENUS = (4.869e+24, 6.0518e6)
... EARTH = (5.976e+24, 6.37814e6)
... MARS = (6.421e+23, 3.3972e6)
... JUPITER = (1.9e+27, 7.1492e7)
... SATURN = (5.688e+26, 6.0268e7)
... URANUS = (8.686e+25, 2.5559e7)
... NEPTUNE = (1.024e+26, 2.4746e7)
... def __init__(self, mass, radius):
... self.mass = mass # in kilograms
... self.radius = radius # in meters
... @property
... def surface_gravity(self):
... # universal gravitational constant (m3 kg-1 s-2)
... G = 6.67300E-11
... return G * self.mass / (self.radius * self.radius)
...
>>> Planet.EARTH.value
(5.976e+24, 6378140.0)
>>> Planet.EARTH.surface_gravity
9.802652743337129
مثال: صنف الفترة الزمنية TimePeriod
يعرض المثال التالي طريقة استخدام الخاصّية _ignore_
:
>>> from datetime import timedelta
>>> class Period(timedelta, Enum):
... "different lengths of time"
... _ignore_ = 'Period i'
... Period = vars()
... for i in range(367):
... Period['day_%d' % i] = i
...
>>> list(Period)[:2]
[<Period.day_0: datetime.timedelta(0)>, <Period.day_1: datetime.timedelta(days=1)>]
>>> list(Period)[-2:]
[<Period.day_365: datetime.timedelta(days=365)>, <Period.day_366: datetime.timedelta(days=366)>]
ما الذي يميّز Enums
تمتلك Enums صنفًا عاليًا خاصًّا metaclass
له القدرة على التأثير على العديد من الجوانب المرتبطة بأصناف Enum المشتّقة وبنسخها كذلك (عناصرها).
أصناف Enum
يقدم الصنف العالي EnumMeta التوابع __contains__()
و __dir__()
و __iter__()
وتوابع أخرى تسمح بأداء بعض الوظائف التي لا يمكن أداؤها مع الأصناف الاعتيادية، مثل الحصول على قائمة بالعناصر list(Color)
أو المرور على العناصر some_enumv_var in Color
. إلى جانب ما سبق، يضمن الصنف EnumMeta
صحّة توابع أخرى متعدّدة في صنف Enum
النهائية (مثل __new__()
و __getnewargs__()
و __str__()
و __repr__()
).
عناصر الصنف Enum (نُسخ الصنف)
إن أكثر ما يميّز عناصر الصنف Enum أنّها منفردة singletons، حيث يتولّى الصنف EnumMeta
مهمّة إنشاء هذه العناصر أثناء إنشاء الصنف Enum، ثم يضعها في تابع __new__()
الخاص وفي مكانها الصحيح لضمان عدم إنشاء نسخ من العناصر الجديدة باستخدام العناصر الموجودة فعلًا.
أسماء __dunder__ المدعومة
__members__
هو قاموس مرتّب OrderdDict
من عناصر تأخذ الهيئة member_name:member
، وهو متوفّر في الصنف فقط.
يجب على التابع __new__()
، في حال استخدامه، أن ينشئ ويعيد عناصر enum، ويُنصح كذلك استخدام قيم _value_
مناسبة لكل عنصر. لن يُستخدم التابع __new__()
بعد إتمام علمية إنشاء جميع العناصر.
أسماء _sunder_ المدعومة
_name_
: اسم العنصر.
_value_
: قيمة العنصر، يمكن تعيينها أو تعديلها في __new__
_missing_
: دالة باحثة تستخدم عند عدم العثور على القيمة، ويمكن إعادة تعريفها override.
_ignore_
: قائمة الأسماء، إما كقائمة list()
أو كسلسلة نصية str()، التي لن تحوّل إلى عناصر، والتي تُحذف من الصنف النهائي.
_order_
: يستخدم في شيفرات الإصدارين 2 و 3 من بايثون لضمان تراتبية العناصر (خواصّ الصنف، يحذف أثناء عملية إنشاء الصنف).
_generate_next_value_
: يُستخدم من طرف الواجهة البرمجية الوظيفية والصنف auto
للحصول على القيمة المناسبة لعنصر enum، ويمكن إعادة تعريفه.
جديد في الإصدار 3.6: _missing_
و _order_
و _generate_next_value_
جديد في الإصدار 3.7: _ignore_
يمكن تعريف الترتيب المطلوب باستخدام _order_
وذلك لضمان توافق شيفرات بايثون في الإصدارين 2 و 3، حيث ستجري مقارنة الترتيب الفعلي للـ enumeration مع الترتيب المقدّم، ويطلق الخطأ في حال عدم وجود التطابق:
>>> class Color(Enum):
... _order_ = 'RED GREEN BLUE'
... RED = 1
... BLUE = 3
... GREEN = 2
...
Traceback (most recent call last):
...
TypeError: member order does not match _order_
ملاحظة:
في الإصدار الثاني من بايثون تكون الخاصية _order_
مطلوبة لتحديد ترتيب عناصر enum، وذلك لأنّ ترتيب العناصر يُفقد قبل تسجيله.
أنواع عناصر Enum
عناصر الصنف Enum هي نسخ من الصنف Enum الذي تتبعه، ويمكن الوصول إليها عادة باستخدام الصيغة EnumClass.member
. ولكن في بعض الحالات يمكن الوصول إليها باستخدام الصيغة EnumClass.member.member
ولكن يجب عدم استخدام هذه الصيغة لأنّها قد تفشل في العثور على العنصر المطلوب، والأسوء من ذلك هو أنّه قد تعيد هذه الصيغة شيئًا آخر إلى جانب عنصر Enum المطلوب (وهذا سبب آخر لاستخدام الحروف الكبيرة في تسمية العناصر):
>>> class FieldTypes(Enum):
... name = 0
... value = 1
... size = 2
...
>>> FieldTypes.value.size
<FieldTypes.size: 2>
>>> FieldTypes.size.value
2
القيم المنطقية لأصناف Enum وعناصرها
تعالج اللغة عناصر Enum المدمجة مع أنواع أخرى (مثل int
و str
وغيرها) بحسب طريقة معالجة تلك الأنواع، وباستثناء ذلك، فإنّ جميع العناصر تعالج إلى القيمة True
. يمكن استخدام الشيفرة التالية لجعل عملية المعالجة تعتمد على قيمة العناصر:
def __bool__(self):
return bool(self.value)
أما أصناف Enum فتعالج دائمًا إلى القيمة True
.
أصناف Enum مع التوابع
عند إضافة توابع جديدة إلى صنف Enum الفرعي (مثل الصنف Planet في أعلاه) فإنّ التوابع المضافة تظهر عند استخدام الدالة dir()
مع العناصر وليس مع الصنف:
>>> dir(Planet)
['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__members__', '__module__']
>>> dir(Planet.EARTH)
['__class__', '__doc__', '__module__', 'name', 'surface_gravity', 'value']
دمج عناصر الصنف Flag
في حال عدم تسمية مجموعة من عناصر الصنف Flag
، فإنّ الدالة repr()
ستتضمن جميع الرايات وجميع مجموعات الرايات المسمّاة:
>>> class Color(Flag):
... RED = auto()
... GREEN = auto()
... BLUE = auto()
... MAGENTA = RED | BLUE
... YELLOW = RED | GREEN
... CYAN = GREEN | BLUE
...
>>> Color(3) # مجموعة مسماة
<Color.YELLOW: 3>
>>> Color(7) # مجموعة غير مسمّاة
انظر أيضًا
مصادر
- صفحة Support for enumerations في توثيق بايثون الرسمي.