Column address: a Python module per week

Meanwhile, welcome to follow my wechat official account AlwaysBeta, more exciting content waiting for you.

An enumeration type can be thought of as a label or a set of constants, often used to represent some particular finite set, such as week, month, status, and so on. There are no enumerated types in Python’s built-in types, but there are many ways to implement them, such as dictionaries, classes, etc. :

WEEKDAY = {
    'MON': 1.'TUS': 2.'WEN': 3.'THU': 4.'FRI': 5
}
class Color:
    RED   = 0
    GREEN = 1
    BLUE  = 2
Copy the code

The above two methods can be thought of as simple implementations of enum types. Using such enum variables only locally is fine. The problem is that they are mutable, meaning that they can be modified elsewhere to affect their normal use:

WEEKDAY['MON'] = WEEKDAY['FRI']
print(WEEKDAY) # {'FRI': 5, 'TUS': 2, 'MON': 5, 'WEN': 3, 'THU': 4}
Copy the code

Enumerations defined by a class can even be instantiated to become nondescribable:

c = Color()
print(c.RED)	# 0
Color.RED = 2
print(c.RED)	# 2
Copy the code

It is possible to use immutable types, such as tuples, but this defeats the purpose of enumerated types and reduces labels to meaningless variables:

COLOR = ('R'.'G'.'B')
print(COLOR[0], COLOR[1], COLOR[2])	# R G B
Copy the code

To provide a better solution, Python has added the enum standard library in version 3.4 through PEP 435, and compatible supported libraries can also be downloaded through PIP Install Enum prior to version 3.4. Enum provides enum /IntEnum/unique tools, which can be used to define enum types by inheriting enum /IntEnum. IntEnum defines enumerators that must be (or can be converted to) integers. The unique method can be used as a decorator to limit the value of an enumerator to non-repetition.

Create an enumeration

Enumerations are defined by subclassing the enum class as follows:

import enum


class BugStatus(enum.Enum):

    new = 7
    incomplete = 6
    invalid = 5
    wont_fix = 4
    in_progress = 3
    fix_committed = 2
    fix_released = 1


print('\nMember name: {}'.format(BugStatus.wont_fix.name))
print('Member value: {}'.format(BugStatus.wont_fix.value))

# output
# Member name: wont_fix
# Member value: 4
Copy the code

When an Enum class is parsed, each member is converted into an instance, each of which has name and value attributes that correspond to the name and value of the member, respectively.

Iterative enumeration

Look directly at the code:

import enum


class BugStatus(enum.Enum):

    new = 7
    incomplete = 6
    invalid = 5
    wont_fix = 4
    in_progress = 3
    fix_committed = 2
    fix_released = 1


for status in BugStatus:
    print('{: 15} = {}'.format(status.name, status.value))
    
# output
# new = 7
# incomplete = 6
# invalid = 5
# wont_fix = 4
# in_progress = 3
# fix_committed = 2
# fix_released = 1
Copy the code

Members are generated in the order they are defined in the class.

Compare the enumeration

Because enumerators are not sorted, they only support comparisons through is and ==.

import enum


class BugStatus(enum.Enum):

    new = 7
    incomplete = 6
    invalid = 5
    wont_fix = 4
    in_progress = 3
    fix_committed = 2
    fix_released = 1


actual_state = BugStatus.wont_fix
desired_state = BugStatus.fix_released

print('Equality:',
      actual_state == desired_state,
      actual_state == BugStatus.wont_fix)
print('Identity:',
      actual_state is desired_state,
      actual_state is BugStatus.wont_fix)
print('Ordered by value:')
try:
    print('\n'.join(' ' + s.name for s in sorted(BugStatus)))
except TypeError as err:
    print(' Cannot sort: {}'.format(err))
    
# output
# Equality: False True
# Identity: False True
# Ordered by value:
# Cannot sort: '<' not supported between instances of 'BugStatus' and 'BugStatus'
Copy the code

The size comparison raises TypeError.

An enumeration class that inherits the IntEnum class and supports size comparisons among its members.

import enum


class BugStatus(enum.IntEnum):

    new = 7
    incomplete = 6
    invalid = 5
    wont_fix = 4
    in_progress = 3
    fix_committed = 2
    fix_released = 1


print('Ordered by value:')
print('\n'.join(' ' + s.name for s in sorted(BugStatus)))

# output
# Ordered by value:
# fix_released
# fix_committed
# in_progress
# wont_fix
# invalid
# incomplete
# new
Copy the code

Unique enumerated value

Enumeration members with the same value will be referred to as aliases to the same member object and will not be printed during iteration.

import enum


class BugStatus(enum.Enum):

    new = 7
    incomplete = 6
    invalid = 5
    wont_fix = 4
    in_progress = 3
    fix_committed = 2
    fix_released = 1

    by_design = 4
    closed = 1


for status in BugStatus:
    print('{: 15} = {}'.format(status.name, status.value))

print('\nSame: by_design is wont_fix: ',
      BugStatus.by_design is BugStatus.wont_fix)
print('Same: closed is fix_released: ',
      BugStatus.closed is BugStatus.fix_released)

# output
# new = 7
# incomplete = 6
# invalid = 5
# wont_fix = 4
# in_progress = 3
# fix_committed = 2
# fix_released = 1
# 
# Same: by_design is wont_fix: True
# Same: closed is fix_released: True
Copy the code

Because by_design and CLOSED are aliases for other members, they are not printed. In enumerations, the first value that occurs is valid.

If you want each member to have a unique value, you can use the @unique decorator.

import enum


@enum.unique
class BugStatus(enum.Enum):

    new = 7
    incomplete = 6
    invalid = 5
    wont_fix = 4
    in_progress = 3
    fix_committed = 2
    fix_released = 1

    # This will trigger an error with unique applied.
    by_design = 4
    closed = 1
    
# output
# Traceback (most recent call last):
# File "enum_unique_enforce.py", line 11, in 
      
# class BugStatus(enum.Enum):
# File "... /lib/python3.6/enum. Py ", line 834, in unique
# (enumeration, alias_details))
# ValueError: duplicate values found in <enum 'BugStatus'>:
# by_design -> wont_fix, closed -> fix_released
Copy the code

If there are duplicate values in the member, ValueError is raised.

Create enumerations programmatically

In some cases, it is more convenient to create enumerations programmatically than to hardcode them directly into a class. If you do this, you can also pass the member’s name and value to the class’s constructor.

import enum


BugStatus = enum.Enum(
    value='BugStatus',
    names=('fix_released fix_committed in_progress '
           'wont_fix invalid incomplete new'),
)

print('Member: {}'.format(BugStatus.new))

print('\nAll members:')
for status in BugStatus:
    print('{: 15} = {}'.format(status.name, status.value))
    
# output
# Member: BugStatus.new
# 
# All members:
# fix_released = 1
# fix_committed = 2
# in_progress = 3
# wont_fix = 4
# invalid = 5
# incomplete = 6
# new = 7
Copy the code

The value argument represents the name of the enumeration, and names represents the members. If the argument passed to name is a string, the string is split from Spaces and commas, the single string is assigned as the name of the member, and so on, starting at 1.

For better control over the values associated with members, names can be replaced with strings using tuples or dictionaries that map names to values. What does this mean? Look at the following code:

import enum


BugStatus = enum.Enum(
    value='BugStatus',
    names=[
        ('new'.7),
        ('incomplete'.6),
        ('invalid'.5),
        ('wont_fix'.4),
        ('in_progress'.3),
        ('fix_committed'.2),
        ('fix_released'.1),
    ],
)

print('All members:')
for status in BugStatus:
    print('{: 15} = {}'.format(status.name, status.value))
    
# output
# All members:
# new = 7
# incomplete = 6
# invalid = 5
# wont_fix = 4
# in_progress = 3
# fix_committed = 2
# fix_released = 1
Copy the code

In this example, names is a list, and the elements in the list are tuples.

Value of a non-integer member

Enumerator values are not limited to integers. In fact, any type of object can be used as an enumerated value. If the value is a tuple, the member is passed as a separate argument to __init__().

import enum


class BugStatus(enum.Enum):

    new = (7['incomplete'.'invalid'.'wont_fix'.'in_progress'])
    incomplete = (6['new'.'wont_fix'])
    invalid = (5['new'])
    wont_fix = (4['new'])
    in_progress = (3['new'.'fix_committed'])
    fix_committed = (2['in_progress'.'fix_released'])
    fix_released = (1['new'])

    def __init__(self, num, transitions):
        self.num = num
        self.transitions = transitions

    def can_transition(self, new_state):
        return new_state.name in self.transitions


print('Name:', BugStatus.in_progress)
print('Value:', BugStatus.in_progress.value)
print('Custom attribute:', BugStatus.in_progress.transitions)
print('Using attribute:', BugStatus.in_progress.can_transition(BugStatus.new))

# output
# Name: BugStatus.in_progress
# Value: (3, ['new', 'fix_committed'])
# Custom attribute: ['new', 'fix_committed']
# Using attribute: True
Copy the code

In this example, each member value is a tuple containing a number and a list.

For more complex cases, tuples may not be so convenient. Because member values can be any type of object, dictionaries come in handy if you have a large number of enumerated value scenarios that require key-value pairs of data structures.

import enum


class BugStatus(enum.Enum):

    new = {
        'num': 7.'transitions': [
            'incomplete'.'invalid'.'wont_fix'.'in_progress',
        ],
    }
    incomplete = {
        'num': 6.'transitions': ['new'.'wont_fix'],
    }
    invalid = {
        'num': 5.'transitions': ['new'],
    }
    wont_fix = {
        'num': 4.'transitions': ['new'],
    }
    in_progress = {
        'num': 3.'transitions': ['new'.'fix_committed'],
    }
    fix_committed = {
        'num': 2.'transitions': ['in_progress'.'fix_released'],
    }
    fix_released = {
        'num': 1.'transitions': ['new'],}def __init__(self, vals):
        self.num = vals['num']
        self.transitions = vals['transitions']

    def can_transition(self, new_state):
        return new_state.name in self.transitions


print('Name:', BugStatus.in_progress)
print('Value:', BugStatus.in_progress.value)
print('Custom attribute:', BugStatus.in_progress.transitions)
print('Using attribute:', BugStatus.in_progress.can_transition(BugStatus.new))

# output
# Name: BugStatus.in_progress
# Value: (3, ['new', 'fix_committed'])
# Custom attribute: ['new', 'fix_committed']
# Using attribute: True
Copy the code

This example is equivalent to using tuples above. Related documents:

Pymotw.com/3/enum/inde…

python.jobbole.com/84112/