راهنمای عملی Enum

یک Enum مجموعه‌ای از نام‌های نمادین است که به مقادیر یکتا متصل شده‌اند. آن‌ها شبیه متغیرهای سراسری هستند، اما repr() مفیدتری، گروه‌بندی، ایمنی نوع و چند قابلیت دیگر ارائه می‌دهند.

آن‌ها زمانی بیشترین کاربرد را دارند که متغیری دارید که می‌تواند یکی از مقادیر یک مجموعه محدود را بگیرد. برای مثال، روزهای هفته:

>>> from enum import Enum
>>> class Weekday(Enum):
...     MONDAY = 1
...     TUESDAY = 2
...     WEDNESDAY = 3
...     THURSDAY = 4
...     FRIDAY = 5
...     SATURDAY = 6
...     SUNDAY = 7

یا شاید رنگ‌های اصلی RGB:

>>> from enum import Enum
>>> class Color(Enum):
...     RED = 1
...     GREEN = 2
...     BLUE = 3

همان‌طور که می‌بینید، ایجاد یک Enum به‌سادگی نوشتن کلاسی است که از خود Enum به ارث می‌برد.

توجه

بزرگی و کوچکی حروف اعضای Enum

از آن‌جا که Enumها برای نمایش ثابت‌ها استفاده می‌شوند، و برای کمک به جلوگیری از مشکلات ناشی از تداخل نام‌ها میان متدها/ویژگی‌های کلاس‌های mixin و نام‌های enum، اکیداً توصیه می‌کنیم برای اعضا از نام‌های UPPER_CASE استفاده کنید، و در مثال‌های خود نیز از همین سبک استفاده خواهیم کرد.

بسته به ماهیت enum، مقدار یک عضو ممکن است مهم باشد یا نباشد، اما در هر صورت می‌توان از آن مقدار برای دریافت عضو متناظر استفاده کرد:

>>> Weekday(3)
<Weekday.WEDNESDAY: 3>

همان‌طور که می‌بینید، repr() یک عضو، نام enum، نام عضو و مقدار را نشان می‌دهد. str() یک عضو فقط نام enum و نام عضو را نشان می‌دهد:

>>> print(Weekday.THURSDAY)
Weekday.THURSDAY

نوع یک عضو شمارش، همان enum است که به آن تعلق دارد:

>>> type(Weekday.MONDAY)
<enum 'Weekday'>
>>> isinstance(Weekday.FRIDAY, Weekday)
True

اعضای Enum دارای ویژگی‌ای هستند که فقط شامل name آن‌ها می‌شود:

>>> print(Weekday.TUESDAY.name)
TUESDAY

به همین ترتیب، آن‌ها یک ویژگی برای value خود دارند:

>>> Weekday.WEDNESDAY.value
3

برخلاف بسیاری از زبان‌ها که شمارش‌ها را صرفاً به‌عنوان جفت‌های نام/مقدار در نظر می‌گیرند، می‌توان به شمارش‌های پایتون رفتار اضافه کرد. برای مثال، datetime.date دو متد برای برگرداندن روز هفته دارد: weekday() و isoweekday(). تفاوت این است که یکی از آن‌ها از ۰ تا ۶ و دیگری از ۱ تا ۷ می‌شمارد. به‌جای این‌که خودمان این موضوع را پیگیری کنیم، می‌توانیم متدی به شمارش Weekday اضافه کنیم تا روز را از نمونه date استخراج کند و عضو متناظر شمارش را برگرداند:

@classmethod
def from_date(cls, date):
    return cls(date.isoweekday())

شمارش کامل Weekday اکنون به این شکل است:

>>> class Weekday(Enum):
...     MONDAY = 1
...     TUESDAY = 2
...     WEDNESDAY = 3
...     THURSDAY = 4
...     FRIDAY = 5
...     SATURDAY = 6
...     SUNDAY = 7
...     #
...     @classmethod
...     def from_date(cls, date):
...         return cls(date.isoweekday())

اکنون می‌توانیم بفهمیم امروز چه روزی است! مشاهده کنید:

>>> import datetime as dt
>>> Weekday.from_date(dt.date.today())
<Weekday.TUESDAY: 2>

البته، اگر این متن را در روز دیگری می‌خوانید، آن روز را خواهید دید.

این شمارش Weekday زمانی عالی است که متغیر ما فقط به یک روز نیاز داشته باشد، اما اگر به چندین روز نیاز داشته باشیم چطور؟ شاید در حال نوشتن تابعی برای رسم کارها در طول هفته باشیم و نمی‌خواهیم از list استفاده کنیم -- می‌توانیم از نوع متفاوتی از Enum استفاده کنیم:

>>> from enum import Flag
>>> class Weekday(Flag):
...     MONDAY = 1
...     TUESDAY = 2
...     WEDNESDAY = 4
...     THURSDAY = 8
...     FRIDAY = 16
...     SATURDAY = 32
...     SUNDAY = 64

ما دو چیز را تغییر داده‌ایم: از Flag ارث می‌بریم، و مقدارها همگی توان‌های ۲ هستند.

درست مانند enum اصلی Weekday در بالا، می‌توانیم یک انتخاب واحد داشته باشیم:

>>> first_week_day = Weekday.MONDAY
>>> first_week_day
<Weekday.MONDAY: 1>

اما Flag همچنین به ما اجازه می‌دهد چند عضو را در یک متغیر ترکیب کنیم:

>>> weekend = Weekday.SATURDAY | Weekday.SUNDAY
>>> weekend
<Weekday.SATURDAY|SUNDAY: 96>

شما حتی می‌توانید یک متغیر Flag را پیمایش کنید:

>>> for day in weekend:
...     print(day)
Weekday.SATURDAY
Weekday.SUNDAY

بسیار خوب، بیایید چند کار را راه‌اندازی کنیم:

>>> chores_for_ethan = {
...     'feed the cat': Weekday.MONDAY | Weekday.WEDNESDAY | Weekday.FRIDAY,
...     'do the dishes': Weekday.TUESDAY | Weekday.THURSDAY,
...     'answer SO questions': Weekday.SATURDAY,
...     }

و تابعی برای نمایش کارهای یک روز مشخص:

>>> def show_chores(chores, day):
...     for chore, days in chores.items():
...         if day in days:
...             print(chore)
...
>>> show_chores(chores_for_ethan, Weekday.SATURDAY)
answer SO questions

در مواردی که مقادیر واقعی اعضا اهمیت ندارند، می‌توانید کمی کار خود را ساده‌تر کنید و برای مقادیر از auto() استفاده کنید:

>>> from enum import auto
>>> class Weekday(Flag):
...     MONDAY = auto()
...     TUESDAY = auto()
...     WEDNESDAY = auto()
...     THURSDAY = auto()
...     FRIDAY = auto()
...     SATURDAY = auto()
...     SUNDAY = auto()
...     WEEKEND = SATURDAY | SUNDAY

دسترسی برنامه‌ای به اعضای شمارش و ویژگی‌های آن‌ها

گاهی دسترسی به اعضای شمارش‌ها به‌صورت برنامه‌ای مفید است (یعنی شرایطی که Color.RED کارساز نیست، زیرا رنگ دقیق در زمان نوشتن برنامه مشخص نیست). Enum چنین دسترسی‌ای را فراهم می‌کند:

>>> Color(1)
<Color.RED: 1>
>>> Color(3)
<Color.BLUE: 3>

اگر می‌خواهید به اعضای enum با نام دسترسی پیدا کنید، از دسترسی آیتمی استفاده کنید:

>>> Color['RED']
<Color.RED: 1>
>>> Color['GREEN']
<Color.GREEN: 2>

اگر یک عضو enum دارید و به name یا value آن نیاز دارید:

>>> member = Color.RED
>>> member.name
'RED'
>>> member.value
1

تکرار اعضای enum و مقدارهای آن

داشتن دو عضو enum با نام یکسان نامعتبر است:

>>> class Shape(Enum):
...     SQUARE = 2
...     SQUARE = 3
...
Traceback (most recent call last):
...
TypeError: 'SQUARE' already defined as 2

با این حال، یک عضو enum می‌تواند نام‌های دیگری نیز داشته باشد که به آن مرتبط هستند. با فرض دو ورودی A و B با مقدار یکسان (و A ابتدا تعریف شده باشد)، B نام مستعاری برای عضو A است. جست‌وجوی بر اساس مقدار برای مقدار A، عضو A را برمی‌گرداند. جست‌وجوی بر اساس نام برای A، عضو 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>

توجه

تلاش برای ایجاد عضوی هم‌نام با یک ویژگی از پیش تعریف‌شده (عضوی دیگر، یک متد و غیره) یا تلاش برای ایجاد ویژگی هم‌نام با یک عضو مجاز نیست.

اطمینان از یکتایی مقادیر شمارش

به‌طور پیش‌فرض، شمارش‌ها اجازه می‌دهند چندین نام به‌عنوان نام‌های مستعار برای یک مقدار یکسان استفاده شوند. هنگامی که این رفتار مطلوب نباشد، می‌توانید از دکوراتور @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()
...
>>> [member.value for member in Color]
[1, 2, 3]

مقادیر توسط _generate_next_value_() انتخاب می‌شوند، که می‌توان آن را بازنویسی کرد:

>>> class AutoName(Enum):
...     @staticmethod
...     def _generate_next_value_(name, start, count, last_values):
...         return name
...
>>> class Ordinal(AutoName):
...     NORTH = auto()
...     SOUTH = auto()
...     EAST = auto()
...     WEST = auto()
...
>>> [member.value for member in Ordinal]
['NORTH', 'SOUTH', 'EAST', 'WEST']

توجه

متد _generate_next_value_() باید پیش از هر عضوی تعریف شود.

تکرار

پیمایش اعضای یک enum، نام‌های مستعار را ارائه نمی‌دهد:

>>> list(Shape)
[<Shape.SQUARE: 2>, <Shape.DIAMOND: 1>, <Shape.CIRCLE: 3>]
>>> list(Weekday)
[<Weekday.MONDAY: 1>, <Weekday.TUESDAY: 2>, <Weekday.WEDNESDAY: 4>, <Weekday.THURSDAY: 8>, <Weekday.FRIDAY: 16>, <Weekday.SATURDAY: 32>, <Weekday.SUNDAY: 64>]

توجه داشته باشید که نام‌های مستعار Shape.ALIAS_FOR_SQUARE و Weekday.WEEKEND نمایش داده نمی‌شوند.

ویژگی خاص __members__ یک نگاشت مرتب فقط‌خواندنی از نام‌ها به اعضا است. این ویژگی شامل تمام نام‌های تعریف‌شده در شمارش، از جمله نام‌های مستعار است:

>>> 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__ برای دسترسی برنامه‌ای با جزئیات به اعضای شمارش استفاده کرد. برای مثال، یافتن همه‌ی نام‌های مستعار:

>>> [name for name, member in Shape.__members__.items() if member.name != name]
['ALIAS_FOR_SQUARE']

توجه

نام‌های مستعار پرچم‌ها شامل مقادیری با چند پرچم فعال، مانند 3، و بدون پرچم فعال، یعنی 0 می‌شوند.

مقایسه‌ها

اعضای شمارش بر اساس هویت مقایسه می‌شوند:

>>> Color.RED is Color.RED
True
>>> Color.RED is Color.BLUE
False
>>> Color.RED is not Color.BLUE
True

مقایسه‌های ترتیبی بین مقادیر enum پشتیبانی نمی‌شوند. اعضای Enum عدد صحیح نیستند (اما در ادامه IntEnum را ببینید):

>>> 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

مقایسه‌ها با مقادیر غیرشمارشی همیشه نابرابر ارزیابی می‌شوند (باز هم، IntEnum به‌صراحت برای رفتار متفاوتی طراحی شده است، در زیر ببینید):

>>> Color.BLUE == 2
False

هشدار

بارگذاری مجدد ماژول‌ها امکان‌پذیر است — اگر ماژولی که دوباره بارگذاری‌شده است شامل enumها (enums) باشد، آن‌ها دوباره ایجاد می‌شوند و ممکن است اعضای جدید در مقایسه با اعضای اصلی یکسان/برابر نباشند.

اعضا و ویژگی‌های مجاز شمارش‌ها

بیشتر مثال‌های بالا از اعداد صحیح برای مقادیر شمارش استفاده می‌کنند. استفاده از اعداد صحیح کوتاه و راحت است (و به‌طور پیش‌فرض توسط Functional API فراهم می‌شود)، اما به‌طور اکید اعمال نمی‌شود. در اکثر قریب به اتفاق موارد استفاده، اهمیت ندارد که مقدار واقعی یک شمارش چیست. اما اگر مقدار مهم باشد، شمارش‌ها می‌توانند مقادیر دلخواهی داشته باشند.

شمارش‌ها کلاس‌های پایتون هستند و می‌توانند مانند معمول متدها و متدهای ویژه داشته باشند. اگر این شمارش را داشته باشیم:

>>> 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 رزرو شده‌اند و نمی‌توان از آن‌ها استفاده کرد؛ تمام ویژگی‌های دیگری که در یک شمارش تعریف می‌شوند، به اعضای این شمارش تبدیل خواهند شد، به استثنای متدهای ویژه (__str__()، __add__() و غیره)، توصیف‌گرها (متدها نیز توصیف‌گر هستند) و نام متغیرهای فهرست‌شده در _ignore_.

توجه: اگر شمارش شما __new__() و/یا __init__() را تعریف کند، هر مقدار(هایی) که به عضو شمارش داده شود، به آن متدها پاس داده می‌شود. برای یک مثال Planet را ببینید.

توجه

متد __new__()، در صورت تعریف شدن، در هنگام ایجاد اعضای Enum استفاده می‌شود؛ سپس با متد __new__() خود Enum جایگزین می‌شود که پس از ایجاد کلاس برای جست‌وجوی اعضای موجود استفاده می‌شود. برای جزئیات بیشتر چه زمانی از __new__() در مقابل __init__() استفاده کنیم را ببینید.

زیرکلاس‌سازی محدود Enum

یک کلاس Enum جدید باید یک کلاس enum پایه، حداکثر یک نوع داده مشخص، و هر تعداد کلاس میکس‌این مبتنی بر object که لازم باشد، داشته باشد. ترتیب این کلاس‌های پایه به این صورت است:

class EnumName([mix-in, ...,] [data-type,] base-enum):
    pass

همچنین، زیرکلاس‌سازی از یک شمارش تنها در صورتی مجاز است که آن شمارش هیچ عضوی را تعریف نکرده باشد. بنابراین این کار ممنوع است:

>>> class MoreColor(Color):
...     PINK = 17
...
Traceback (most recent call last):
...
TypeError: <enum 'MoreColor'> cannot extend <enum 'Color'>

اما این مجاز است:

>>> class Foo(Enum):
...     def some_behavior(self):
...         pass
...
>>> class Bar(Foo):
...     HAPPY = 1
...     SAD = 2
...

مجاز کردن زیرکلاس‌سازی از enumهایی که اعضا را تعریف می‌کنند، منجر به نقض برخی از ناورداهای مهم انواع و نمونه‌ها می‌شود. از سوی دیگر، منطقی است که اجازه داده شود برخی رفتارهای مشترک میان گروهی از enumها به اشتراک گذاشته شود. (برای نمونه OrderedEnum را ببینید.)

پشتیبانی از دیتاکلاس

هنگام ارث‌بردن از یک dataclass، متد __repr__() نام کلاس به‌ارث‌برده‌شده را حذف می‌کند. برای مثال:

>>> from dataclasses import dataclass, field
>>> @dataclass
... class CreatureDataMixin:
...     size: str
...     legs: int
...     tail: bool = field(repr=False, default=True)
...
>>> class Creature(CreatureDataMixin, Enum):
...     BEETLE = 'small', 6
...     DOG = 'medium', 4
...
>>> Creature.DOG
<Creature.DOG: size='medium', legs=4>

برای استفاده از repr() استاندارد، از آرگومان repr=False در dataclass() استفاده کنید.

تغییر یافته در نسخه‌ی 3.12: تنها فیلدهای دیتاکلاس در ناحیه‌ی مقدار نمایش داده می‌شوند، نه نام دیتاکلاس.

توجه

افزودن دکوراتور @~dataclasses.dataclass به Enum و زیرکلاس‌های آن پشتیبانی نمی‌شود. این کار هیچ خطایی را پرتاب نمی‌کند، اما نتایج بسیار عجیبی در ران‌تایم تولید می‌کند، مانند برابر بودن اعضا با یکدیگر:

>>> @dataclass               # don't do this: it does not make any sense
... class Color(Enum):
...    RED = 1
...    BLUE = 2
...
>>> Color.RED is Color.BLUE
False
>>> Color.RED == Color.BLUE  # problem is here: they should not be equal
True

پیکل‌کردن

می‌توان شمارش‌ها را پیکل و پیکل‌گشایی (unpickle) کرد:

>>> from test.test_enum import Fruit
>>> from pickle import dumps, loads
>>> Fruit.TOMATO is loads(dumps(Fruit.TOMATO))
True

محدودیت‌های معمول برای پیکل‌کردن اعمال می‌شوند: enumهای قابل پیکل‌کردن باید در بالاترین سطح یک ماژول تعریف شوند، زیرا برای پیکل‌گشایی لازم است که بتوان آن‌ها را از آن ماژول ایمپورت کرد.

توجه

با نسخه‌ی ۴ پروتکل پیکل، می‌توان enumهای تودرتو در کلاس‌های دیگر را به‌سادگی پیکل کرد.

می‌توان با تعریف __reduce_ex__() در کلاس enum، نحوه‌ی پیکل‌کردن/از پیکل خارج‌شدن اعضای enum را تغییر داد. روش پیش‌فرض بر اساس مقدار (by-value) است، اما enumهایی با مقادیر پیچیده ممکن است بخواهند از روش نام‌محور (by-name) استفاده کنند:

>>> import enum
>>> class MyEnum(enum.Enum):
...     __reduce_ex__ = enum.pickle_by_enum_name

توجه

استفاده از روش نام‌محور (by-name) برای پرچم‌ها توصیه نمی‌شود، زیرا نام‌های مستعار بی‌نام پیکل‌گشایی (unpickle) نخواهند شد.

API تابعی

کلاس Enum فراخوانی‌پذیر است و API تابعی زیر را فراهم می‌کند:

>>> Animal = Enum('Animal', 'ANT BEE CAT DOG')
>>> Animal
<enum 'Animal'>
>>> Animal.ANT
<Animal.ANT: 1>
>>> list(Animal)
[<Animal.ANT: 1>, <Animal.BEE: 2>, <Animal.CAT: 3>, <Animal.DOG: 4>]

معناشناسی این API به namedtuple شبیه است. اولین آرگومان فراخوانی Enum نام شمارش است.

آرگومان دوم، منبع نام‌های اعضای شمارش است. این آرگومان می‌تواند رشته‌ای از نام‌ها که با فضای خالی از هم جدا شده‌اند، دنباله‌ای از نام‌ها، دنباله‌ای از تاپل‌های دوتایی با جفت‌های کلید/مقدار، یا نگاشتی (مثلاً دیکشنری) از نام‌ها به مقادیر باشد. دو گزینه آخر امکان اختصاص مقادیر دلخواه به شمارش‌ها را فراهم می‌کنند؛ سایر گزینه‌ها به‌طور خودکار اعداد صحیح افزایشی را از ۱ اختصاص می‌دهند (برای تعیین مقدار شروع متفاوت، از پارامتر start استفاده کنید). کلاس جدیدی مشتق‌شده از Enum بازگردانده می‌شود. به عبارت دیگر، انتساب بالا به Animal معادل است با:

>>> class Animal(Enum):
...     ANT = 1
...     BEE = 2
...     CAT = 3
...     DOG = 4
...

دلیل پیش‌فرض قرار دادن 1 به‌عنوان عدد آغازین و نه 0 این است که 0 از نظر بولی False است، اما به‌طور پیش‌فرض همه اعضای enum به True ارزیابی می‌شوند.

پیکل‌کردن enumهای ایجادشده با API تابعی ممکن است دشوار باشد، زیرا جزئیات پیاده‌سازی پشته‌ی فریم‌ها برای تلاش و تشخیص اینکه enum در کدام ماژول ایجاد می‌شود به کار می‌روند (برای مثال، اگر از یک تابع کمکی در یک ماژول جداگانه استفاده کنید، این کار شکست می‌خورد و همچنین ممکن است روی IronPython یا Jython کار نکند). راه‌حل این است که نام ماژول را به‌صورت صریح به شرح زیر مشخص کنید:

>>> Animal = Enum('Animal', 'ANT BEE CAT DOG', module=__name__)

هشدار

اگر module ارائه‌نشده باشد و Enum نتواند تشخیص دهد که چیست، اعضای جدید Enum غیرقابل پیکل (unpicklable) نخواهند بود؛ برای نزدیک‌تر نگه‌داشتن خطاها به منبع، پیکل‌کردن غیرفعال خواهد شد.

پروتکل ۴ جدید pickle همچنین، در برخی شرایط، به این موضوع متکی است که __qualname__ روی مکانی تنظیم شده باشد که pickle بتواند کلاس را بیابد. برای مثال، اگر کلاس در کلاس 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. این می‌تواند رشته‌ای جداشده با فضای خالی یا ویرگول باشد (مقادیر از ۱ شروع خواهند شد مگر اینکه خلاف آن مشخص شده باشد):

    'RED GREEN BLUE' | 'RED,GREEN,BLUE' | 'RED, GREEN, BLUE'
    

    یا یک پیمایش‌گر از نام‌ها:

    ['RED', 'GREEN', 'BLUE']
    

    یا پیمایش‌گری از جفت‌های (نام، مقدار):

    [('CYAN', 4), ('MAGENTA', 5), ('YELLOW', 6)]
    

    یا یک نگاشت:

    {'CHARTREUSE': 7, 'SEA_GREEN': 11, 'ROSEMARY': 42}
    
  • module: نام ماژولی که کلاس enum جدید در آن یافت می‌شود.

  • qualname: جایگاهی در ماژول که کلاس enum جدید در آن یافت می‌شود.

  • type: نوعی که باید در کلاس enum جدید ادغامشود.

  • start: عددی که اگر فقط نام‌ها ارسال شده باشند، شمارش از آن آغاز می‌شود.

تغییر یافته در نسخه‌ی 3.5: پارامتر start اضافه شد.

شمارش‌های مشتق‌شده

IntEnum

اولین گونه از Enum که ارائه شده است، همچنین زیرکلاسی از int است. اعضای یک IntEnum می‌توانند با اعداد صحیح مقایسه شوند؛ به‌تبع، شمارش‌های عدد صحیح از انواع مختلف نیز می‌توانند با یکدیگر مقایسه شوند:

>>> 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

با این حال، همچنان نمی‌توان آن‌ها را با شمارش‌های استاندارد 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]

StrEnum

دومین گونه‌ی ارائه‌شده از Enum نیز زیرکلاسی از str است. می‌توان اعضای یک StrEnum را با رشته‌ها مقایسه کرد؛ به‌تبع آن، شمارش‌های رشته‌ای از انواع مختلف نیز می‌توانند با یکدیگر مقایسه شوند.

اضافه شده در نسخه‌ی 3.11.

IntFlag

گونه‌ی بعدی ارائه‌شده از Enum، یعنی IntFlag، نیز بر پایه‌ی int است. تفاوت این است که اعضای IntFlag را می‌توان با عملگرهای بیتی (&, |, ^, ~) ترکیب کرد و نتیجه در صورت امکان همچنان عضوی از IntFlag است. مانند IntEnum، اعضای IntFlag نیز اعداد صحیح هستند و می‌توانند در هر جایی که از int استفاده می‌شود، به کار بروند.

توجه

هر عملیاتی بر روی یک عضو IntFlag، به‌جز عملیات بیتی، منجر به از دست رفتن عضویت IntFlag می‌شود.

عملیات‌های بیتی که به مقدارهای نامعتبر IntFlag منجر شوند، عضویت در IntFlag را از دست می‌دهند. برای جزئیات، FlagBoundary را ببینید.

اضافه شده در نسخه‌ی 3.6.

تغییر یافته در نسخه‌ی 3.11.

نمونه کلاس 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

همچنین می‌توان ترکیب‌ها را نام‌گذاری کرد:

>>> class Perm(IntFlag):
...     R = 4
...     W = 2
...     X = 1
...     RWX = 7
...
>>> Perm.RWX
<Perm.RWX: 7>
>>> ~Perm.RWX
<Perm: 0>
>>> Perm(7)
<Perm.RWX: 7>

توجه

ترکیب‌های نام‌دار به‌عنوان نام مستعار در نظر گرفته می‌شوند. نام‌های مستعار در حین پیمایش نمایش داده نمی‌شوند، اما می‌توانند از طریق جستجوهای بر اساس مقدار برگردانده شوند.

تغییر یافته در نسخه‌ی 3.11.

تفاوت مهم دیگر میان IntFlag و Enum این است که اگر هیچ پرچمی تنظیم نشده باشد (مقدار ۰ باشد)، ارزیابی بولی آن False است:

>>> Perm.R & Perm.X
<Perm: 0>
>>> bool(Perm.R & Perm.X)
False

از آن‌جا که اعضای IntFlag نیز زیرکلاس‌هایی از int هستند، می‌توانند با آن‌ها ترکیب شوند (اما ممکن است عضویت در IntFlag را از دست بدهند:

>>> Perm.X | 4
<Perm.R|X: 5>

>>> Perm.X + 8
9

توجه

عملگر نقض، ~، همیشه یک عضو IntFlag با مقدار مثبت برمی‌گرداند:

>>> (~Perm.X).value == (Perm.R|Perm.W).value == 6
True

همچنین می‌توان اعضای IntFlag را نیز پیمایش کرد:

>>> list(RW)
[<Perm.R: 4>, <Perm.W: 2>]

اضافه شده در نسخه‌ی 3.11.

پرچم

آخرین گونه Flag است. مانند IntFlag، می‌توان اعضای Flag را با استفاده از عملگرهای بیتی (&, |, ^, ~) ترکیب کرد. برخلاف IntFlag، نمی‌توان آن‌ها را با هیچ شمارش 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>
>>> bool(Color.RED & Color.GREEN)
False

پرچم‌های منفرد باید مقادیری داشته باشند که توان‌هایی از دو هستند (۱، ۲، ۴، ۸، ...)، در حالی که ترکیب‌هایی از پرچم‌ها این‌گونه نخواهند بود:

>>> class Color(Flag):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...     WHITE = RED | BLUE | GREEN
...
>>> Color.WHITE
<Color.WHITE: 7>

نام‌گذاری وضعیت «هیچ پرچمی تنظیم نشده است» مقدار بولی آن را تغییر نمی‌دهد:

>>> class Color(Flag):
...     BLACK = 0
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> Color.BLACK
<Color.BLACK: 0>
>>> bool(Color.BLACK)
False

می‌توان روی اعضای Flag نیز تکرار کرد:

>>> purple = Color.RED | Color.BLUE
>>> list(purple)
[<Color.RED: 1>, <Color.BLUE: 2>]

اضافه شده در نسخه‌ی 3.11.

توجه

برای بیشتر کدهای جدید، استفاده از Enum و Flag به‌شدت توصیه می‌شود، زیرا IntEnum و IntFlag برخی وعده‌های معنایی یک شمارش (enumeration) را نقض می‌کنند (با قابل مقایسه بودن با اعداد صحیح، و در نتیجه قابل مقایسه بودن با شمارش‌های غیرمرتبط دیگر از طریق تعدی). IntEnum و IntFlag باید فقط در مواردی استفاده شوند که Enum و Flag کارساز نباشند؛ برای مثال، وقتی ثابت‌های عدد صحیح با شمارش‌ها جایگزین می‌شوند، یا برای تعامل‌پذیری با سیستم‌های دیگر.

سایر

اگرچه IntEnum بخشی از ماژول enum است، پیاده‌سازی مستقل آن بسیار ساده است:

class IntEnum(int, ReprEnum):   # or Enum instead of ReprEnum
    pass

این نشان می‌دهد که چگونه می‌توان شمارش‌های مشتق‌شده مشابه را تعریف کرد؛ برای مثال، یک FloatEnum که float را به‌جای int به‌صورت میکس‌این به‌کار می‌گیرد.

برخی قوانین:

  1. هنگام زیرکلاس‌سازی از Enum، انواع میکس‌اینباید در دنباله‌ی پایه‌ها پیش از خود کلاس Enum قرار بگیرند، همان‌طور که در مثال IntEnum بالا آمده است.

  2. انواع میکس‌اینباید قابل زیرکلاس‌سازی باشند. برای مثال، bool و range قابل زیرکلاس‌سازی نیستند و در صورت استفاده به‌عنوان نوع میکس‌این، هنگام ایجاد Enum خطایی پرتاب می‌کنند.

  3. در حالی که Enum می‌تواند اعضایی از هر نوعی داشته باشد، هنگامی که یک نوع اضافی را به‌صورت میکس‌ایناضافه می‌کنید، همه اعضا باید مقادیری از آن نوع داشته باشند، برای مثال int در بالا. این محدودیت در مورد کلاس‌های میکس‌اینکه فقط متدها را اضافه می‌کنند و نوع دیگری را مشخص نمی‌کنند، اعمال نمی‌شود.

  4. هنگامی که نوع داده دیگری در آن آمیخته شود، ویژگی value با خود عضو enum یکسان نیست، هرچند معادل آن است و در مقایسه برابر خواهد بود.

  5. یک data type یک میکس‌این است که __new__() را تعریف می‌کند، یا یک dataclass است

  6. قالب‌بندی %-style: %s و %r به‌ترتیب __str__() و __repr__() کلاس Enum را فراخوانی می‌کنند؛ سایر کدها (مانند %i یا %h برای IntEnum) با عضو enum به‌عنوان نوع آمیخته‌ی آن (mixed-in type) رفتار می‌کنند.

  7. رشته‌های قالب‌بندی‌شده، str.format() و format() از متد __str__() enum استفاده خواهند کرد.

توجه

از آنجا که IntEnum، IntFlag و StrEnum به‌عنوان جایگزین‌های مستقیم برای ثابت‌های موجود طراحی شده‌اند، متد __str__() آن‌ها به متد __str__() انواع داده‌ی آن‌ها بازنشانی شده است.

چه زمانی از __new__() در مقابل __init__() استفاده کنیم

__new__() باید هرگاه بخواهید مقدار واقعی عضو Enum را سفارشی کنید، استفاده شود. هرگونه تغییر دیگر را می‌توان در __new__() یا __init__() انجام داد، که __init__() ترجیح داده می‌شود.

برای مثال، اگر می‌خواهید چند آیتم را به سازنده ارسال کنید، اما فقط می‌خواهید یکی از آن‌ها مقدار باشد:

>>> class Coordinate(bytes, Enum):
...     """
...     Coordinate with binary codes that can be indexed by the int code.
...     """
...     def __new__(cls, value, label, unit):
...         obj = bytes.__new__(cls, [value])
...         obj._value_ = value
...         obj.label = label
...         obj.unit = unit
...         return obj
...     PX = (0, 'P.X', 'km')
...     PY = (1, 'P.Y', 'km')
...     VX = (2, 'V.X', 'km/s')
...     VY = (3, 'V.Y', 'km/s')
...

>>> print(Coordinate['PY'])
Coordinate.PY

>>> print(Coordinate(3))
Coordinate.VY

هشدار

super().__new__() را فراخوانی نکنید، زیرا __new__ که فقط جستجو (lookup-only) است، همان چیزی است که یافت می‌شود؛ در عوض، مستقیماً از نوع داده استفاده کنید.

نکات دقیق‌تر

نام‌های __dunder__ و _sunder_ پشتیبانی‌شده

نام‌های __dunder__ و _sunder_ پشتیبانی‌شده را می‌توانید در مستندات API Enum بیابید.

_Private__names

نام‌های خصوصی به اعضای enum تبدیل نمی‌شوند، بلکه به‌عنوان ویژگی‌های عادی باقی می‌مانند.

تغییر یافته در نسخه‌ی 3.11.

نوع عضو Enum

اعضای Enum، نمونه‌هایی از کلاس Enum خود هستند و دسترسی به آن‌ها معمولاً به‌صورت EnumClass.member انجام می‌شود. در شرایط خاص، مانند نوشتن رفتار سفارشی برای Enum، امکان دسترسی مستقیم به یک عضو از یک عضو دیگر مفید است و پشتیبانی می‌شود؛ با این حال، برای جلوگیری از تداخل نام بین نام‌های اعضا و ویژگی‌ها/متدهای کلاس‌های میکس‌این (mixed-in)، استفاده از نام‌های با حروف بزرگ به‌شدت توصیه می‌شود.

تغییر یافته در نسخه‌ی 3.5.

ایجاد اعضایی که با انواع داده دیگر آمیخته شده‌اند

هنگام زیرکلاس‌سازی از سایر انواع داده، مانند int یا str، به‌همراه یک Enum، همه‌ی مقادیر بعد از = به سازنده‌ی آن نوع داده فرستاده می‌شوند. برای مثال:

>>> class MyEnum(IntEnum):      # help(int) -> int(x, base=10) -> integer
...     example = '11', 16      # so x='11' and base=16
...
>>> MyEnum.example.value        # and hex(11) is...
17

مقدار بولی کلاس‌ها و اعضای Enum

کلاس‌های Enum که با انواعی غیر از Enum (مانند int، str و غیره) آمیخته شده‌اند، بر اساس قواعد نوع افزوده‌شده ارزیابی می‌شوند؛ در غیر این صورت، تمام اعضا به‌عنوان True ارزیابی می‌شوند. برای اینکه ارزیابی بولی enum خودتان به مقدار عضو وابسته باشد، موارد زیر را به کلاس خود اضافه کنید:

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__', 'mass', 'name', 'radius', 'surface_gravity', 'value']

ترکیب اعضای Flag

پیمایش روی ترکیبی از اعضای Flag فقط اعضایی را برمی‌گرداند که از یک بیت تشکیل‌شده‌اند:

>>> class Color(Flag):
...     RED = auto()
...     GREEN = auto()
...     BLUE = auto()
...     MAGENTA = RED | BLUE
...     YELLOW = RED | GREEN
...     CYAN = GREEN | BLUE
...
>>> Color(3)  # named combination
<Color.YELLOW: 3>
>>> Color(7)      # not named combination
<Color.RED|GREEN|BLUE: 7>

ریزجزئیات Flag و IntFlag

از قطعه‌کد زیر برای مثال‌هایمان استفاده می‌کنیم:

>>> class Color(IntFlag):
...     BLACK = 0
...     RED = 1
...     GREEN = 2
...     BLUE = 4
...     PURPLE = RED | BLUE
...     WHITE = RED | GREEN | BLUE
...

موارد زیر برقرار است:

  • پرچم‌های تک‌بیتی کانونیکال هستند

  • پرچم‌های چندبیتی و صفربیتی نام‌های مستعار هستند

  • در حین تکرار، تنها پرچم‌های کانونیکال بازگردانده می‌شوند:

    >>> list(Color.WHITE)
    [<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 4>]
    
  • منفی کردن یک پرچم یا مجموعه پرچم، یک پرچم/مجموعه پرچم جدید با مقدار عدد صحیح مثبت متناظر برمی‌گرداند:

    >>> Color.BLUE
    <Color.BLUE: 4>
    
    >>> ~Color.BLUE
    <Color.RED|GREEN: 3>
    
  • نام‌های شبه‌پرچم‌ها (pseudo-flags) از نام اعضای آن‌ها ساخته می‌شوند:

    >>> (Color.RED | Color.GREEN).name
    'RED|GREEN'
    
    >>> class Perm(IntFlag):
    ...     R = 4
    ...     W = 2
    ...     X = 1
    ...
    >>> (Perm.R & Perm.W).name is None  # effectively Perm(0)
    True
    
  • پرچم‌های چندبیتی، که به نام‌های مستعار نیز شناخته می‌شوند، می‌توانند از عملیات برگردانده شوند:

    >>> Color.RED | Color.BLUE
    <Color.PURPLE: 5>
    
    >>> Color(7)  # or Color(-1)
    <Color.WHITE: 7>
    
    >>> Color(0)
    <Color.BLACK: 0>
    
  • بررسی عضویت / شامل بودن: پرچم‌های با مقدار صفر همیشه به‌عنوان شامل‌شده در نظر گرفته می‌شوند:

    >>> Color.BLACK in Color.WHITE
    True
    

    در غیر این صورت، تنها در صورتی True برگردانده می‌شود که تمام بیت‌های یک پرچم در پرچم دیگر وجود داشته باشند:

    >>> Color.PURPLE in Color.WHITE
    True
    
    >>> Color.GREEN in Color.PURPLE
    False
    

یک سازوکار مرزی جدید وجود دارد که نحوه‌ی مدیریت بیت‌های خارج از محدوده / نامعتبر را کنترل می‌کند: STRICT، CONFORM، EJECT و KEEP:

  • STRICT --> در صورت ارائه مقادیر نامعتبر، یک استثنا پرتاب می‌کند

  • CONFORM --> هر بیت نامعتبر را دور می‌اندازد

  • EJECT --> وضعیت Flag را از دست می‌دهد و به یک int معمولی با مقدار داده‌شده تبدیل می‌شود

  • KEEP --> بیت‌های اضافی را حفظ کنید

    • وضعیت Flag و بیت‌های اضافی را حفظ می‌کند

    • بیت‌های اضافی در تکرار ظاهر نمی‌شوند

    • بیت‌های اضافی در repr() و str() نمایش داده می‌شوند.

پیش‌فرض برای Flag برابر STRICT است، پیش‌فرض برای IntFlag برابر EJECT است، و پیش‌فرض برای _convert_ برابر KEEP است (برای نمونه‌ای از زمانی که به KEEP نیاز است، ssl.Options را ببینید).

Enumها و Flagها چه تفاوتی با هم دارند؟

Enumها یک فراکلاس سفارشی دارند که بر جنبه‌های زیادی هم از کلاس‌های مشتق‌شده از Enum و هم از نمونه‌های آن‌ها (اعضا) تأثیر می‌گذارد.

کلاس‌های Enum

فراکلاس EnumType مسئول فراهم کردن __contains__()، __dir__()، __iter__() و سایر متدهایی است که به شما اجازه می‌دهند با یک کلاس Enum کارهایی را انجام دهید که در یک کلاس معمولی با شکست مواجه می‌شوند، مانند list(Color) یا some_enum_var in Color. EnumType مسئول اطمینان از صحیح بودن سایر متدهای گوناگون کلاس Enum نهایی است (مانند __new__()، __getnewargs__()، __str__() و __repr__()).

کلاس‌های Flag

پرچم‌ها نگاه گسترده‌تری به نام‌مستعارسازی دارند: برای اینکه یک پرچم کانونیکال باشد، مقدار آن باید یک مقدار توانی از دو باشد و نام آن تکراری نباشد. بنابراین، علاوه بر تعریف نام‌مستعار در Enum، پرچمی بدون مقدار (یعنی 0) یا دارای بیش از یک مقدار توانی از دو (مثلاً 3) یک نام‌مستعار محسوب می‌شود.

اعضای Enum (معروف به نمونه‌ها)

جالب‌ترین نکته در مورد اعضای enum این است که آن‌ها تک‌نمونه هستند. EnumType تمام آن‌ها را در حین ایجاد خود کلاس enum می‌سازد و سپس یک __new__() سفارشی را جایگزین می‌کند تا با بازگرداندن تنها نمونه‌های موجودِ اعضا، اطمینان حاصل شود که هرگز نمونه‌های جدیدی ایجاد نمی‌شوند.

اعضای Flag

می‌توان اعضای Flag را درست مانند کلاس Flag پیمایش کرد، و فقط اعضای کانونیکال برگردانده می‌شوند. برای مثال:

>>> list(Color)
[<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 4>]

(توجه داشته باشید که BLACK، PURPLE و WHITE نمایش داده نمی‌شوند.)

وارونه کردن یک عضو پرچم، مقدار مثبت متناظر را برمی‌گرداند، نه یک مقدار منفی --- برای مثال:

>>> ~Color.RED
<Color.GREEN|BLUE: 6>

طول اعضای Flag با تعداد مقادیر توان ۲ موجود در آن‌ها متناظر است. برای مثال:

>>> len(Color.PURPLE)
2

کتاب آشپزی Enum

اگرچه انتظار می‌رود Enum، IntEnum، StrEnum، Flag و IntFlag اکثر موارد استفاده را پوشش دهند، اما نمی‌توانند همه‌ی آن‌ها را پوشش دهند. در اینجا دستورالعمل‌هایی برای چند نوع مختلف از شمارش‌ها آورده شده است که می‌توانند مستقیماً استفاده شوند، یا به‌عنوان نمونه‌هایی برای ایجاد شمارش‌های خودتان به کار روند.

حذف مقادیر

در بسیاری از موارد استفاده، اهمیت ندارد که مقدار واقعی یک شمارش چه باشد. چندین روش برای تعریف این نوع از شمارش ساده وجود دارد:

  • از نمونه‌های auto برای مقدار استفاده کنید

  • از نمونه‌های object به‌عنوان مقدار استفاده کنید

  • از یک رشته توصیفی به‌عنوان مقدار استفاده کنید

  • از یک تاپل به‌عنوان مقدار و از یک __new__() سفارشی برای جایگزینی تاپل با یک مقدار int استفاده کنید

استفاده از هر یک از این متدها به کاربر نشان می‌دهد که این مقادیر اهمیت ندارند، و همچنین به شما امکان می‌دهد که اعضا را بدون نیاز به شماره‌گذاری مجدد اعضای باقی‌مانده، اضافه، حذف یا مرتب‌سازی مجدد کنید.

استفاده از auto

استفاده از auto به این شکل خواهد بود:

>>> class Color(Enum):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> Color.GREEN
<Color.GREEN: 3>

استفاده از object

استفاده از object به این شکل خواهد بود:

>>> class Color(Enum):
...     RED = object()
...     GREEN = object()
...     BLUE = object()
...
>>> Color.GREEN
<Color.GREEN: <object object at 0x...>>

این نیز مثال خوبی است از اینکه چرا ممکن است بخواهید __repr__() خودتان را بنویسید:

>>> class Color(Enum):
...     RED = object()
...     GREEN = object()
...     BLUE = object()
...     def __repr__(self):
...         return "<%s.%s>" % (self.__class__.__name__, self._name_)
...
>>> Color.GREEN
<Color.GREEN>

استفاده از یک رشته توصیفی

استفاده از یک رشته به‌عنوان مقدار به این شکل خواهد بود:

>>> class Color(Enum):
...     RED = 'stop'
...     GREEN = 'go'
...     BLUE = 'too fast!'
...
>>> Color.GREEN
<Color.GREEN: 'go'>

استفاده از __new__() سفارشی

استفاده از یک __new__() با شماره‌گذاری خودکار به این شکل خواهد بود:

>>> class AutoNumber(Enum):
...     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: 2>

برای ساخت یک AutoNumber با کاربرد عمومی‌تر، *args را به امضا اضافه کنید:

>>> class AutoNumber(Enum):
...     def __new__(cls, *args):      # this is the only change from above
...         value = len(cls.__members__) + 1
...         obj = object.__new__(cls)
...         obj._value_ = value
...         return obj
...

سپس وقتی از AutoNumber ارث می‌برید، می‌توانید __init__ خودتان را بنویسید تا آرگومان‌های اضافی را مدیریت کند:

>>> class Swatch(AutoNumber):
...     def __init__(self, pantone='unknown'):
...         self.pantone = pantone
...     AUBURN = '3497'
...     SEA_GREEN = '1246'
...     BLEACHED_CORAL = () # New color, no Pantone code yet!
...
>>> Swatch.SEA_GREEN
<Swatch.SEA_GREEN: 2>
>>> Swatch.SEA_GREEN.pantone
'1246'
>>> Swatch.BLEACHED_CORAL.pantone
'unknown'

توجه

متد __new__()، در صورت تعریف شدن، در هنگام ایجاد اعضای Enum استفاده می‌شود؛ سپس با __new__() مربوط به Enum جایگزین می‌شود که پس از ایجاد کلاس برای جست‌وجوی اعضای موجود استفاده می‌شود.

هشدار

super().__new__() را فراخوانی نکنید، زیرا __new__فقط برای جستجو همان چیزی است که پیدا می‌شود؛ در عوض، مستقیماً از نوع داده استفاده کنید -- مثلاً:

obj = int.__new__(cls, value)

OrderedEnum

یک شمارش ترتیب‌دار که مبتنی بر IntEnum نیست و بنابراین ویژگی‌های ناورداهای معمول Enum را حفظ می‌کند (مانند قابل‌مقایسه نبودن با سایر شمارش‌ها):

>>> 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

DuplicateFreeEnum

در صورت یافتن مقدار تکراری برای یک عضو، به‌جای ایجاد نام مستعار، یک خطا پرتاب می‌کند:

>>> 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() استفاده کرد.

MultiValueEnum

از داشتن بیش از یک مقدار برای هر عضو پشتیبانی می‌کند:

>>> class MultiValueEnum(Enum):
...     def __new__(cls, value, *values):
...         self = object.__new__(cls)
...         self._value_ = value
...         for v in values:
...             self._add_value_alias_(v)
...         return self
...
>>> class DType(MultiValueEnum):
...     float32 = 'f', 8
...     double64 = 'd', 9
...
>>> DType('f')
<DType.float32: 'f'>
>>> DType(9)
<DType.double64: 'd'>

سیاره

اگر __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_ در حال استفاده:

>>> import datetime as dt
>>> class Period(dt.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)>]

زیرکلاس‌سازی از EnumType

اگرچه بیشتر نیازهای مربوط به enum را می‌توان با سفارشی‌سازی زیرکلاس‌های Enum، چه با دکوراتورهای کلاس و چه با توابع سفارشی، برطرف کرد، می‌توان از EnumType نیز زیرکلاس ساخت تا تجربه‌ای متفاوت از Enum ارائه شود.