greenland-base documentation¶
Module greenland.base.enums¶
Provides an enumeration (Enum) type as python objects.
The way programming languages handle “enums” — a small set of discrete objects belonging together, like primary colors or days of the week – is often influenced by the capabilities of the programming language whithin which such a facility exists. Very often the emphasis is on providing names (identifiers) for integers as if the central semantics of e.g. a weekday is it being a number. Enums in C certainly work like this.
One unfortunate consequence of these types of design decisions is, that it is difficult to express in type annotations that a certain parameter should only take members of a specific enum. The conclusion we must draw is, that it is desirable that enum types correspond to classes and that the enum members are instances of those classes, so it is possible to express the following:
def foo(day: Weekday):
...
The built-in enums of Python work like this already. The author of this package also became (admittedly) aware of the built-in enums too late, so went down a slightly different (but possibly simpler) path.
This package provides enums as a type where the enumeration type is a class (almost like any other) which only has a finite, explicitely specified number of instances (the members) and where the enum member can be enriched with almost any kind of additional behaviour.
Constructing enum types with Enum
¶
- class Enum(name_or_ord: str | int, ord: int | None = None)¶
The super class of all enum types (= classes).
Enum types are created by deriving from
Enum
. Members are added by calling the constructor and assigning to an identifier in the same namespace as the class definition before callingfinalize()
.After
finalize()
, the constructor will just either return a previously existing member of the enum type or raise aParseError
.- Parameters:
name_or_ord (str | int) – Before finalizing the enum type only the name of the enum member — a
str
– is allowed here. After finalization both astr
and aint
here are allowed and do not construct a new member, but rather retrieve a member of the give name or ord respectively. AParseError
will be raised if such a member does not exist.ord (int | None) – Before finalizing the the enum type an optional
ord
integer for the member. After finalization this argument must not be given.
from greenland.base.enums import Enum
class Direction(Enum):
pass
NORTH = Direction('NORTH')
EAST = Direction('EAST')
SOUTH = Direction('SOUTH')
WEST = Direction('WEST')
Direction.finalize()
finalize()
will actually do two things:
Lock the type so that further calls to the constructor result either in retrieval of an already defined member or in raising a
ParseError
if no such member exists.Check if all members have been bound to names in the same namespace where the enum class has been defined.
assert Direction('SOUTH') == SOUTH
with pytest.raises(AssertionError):
_ = Direction('FOO')
Typically enums are global types. I cannot currently see (except for testing purposes) much of an application for defining an enum in a local namespace.
Regardless, if one desires to do so, this is possible, but
finalize()
needs to be called with locals()
:
class Turn(Enum):
pass
LEFT = Turn('LEFT')
RIGHT = Turn('RIGHT')
Turn.finalize(locals())
Membership¶
Membership (if a value is a member of an enum) can be tested using the operator in or using isinstance.
assert SOUTH in Direction
assert isinstance(SOUTH, Direction)
thing = object()
assert thing not in Direction
Iteration¶
The members of an enum type can be iterated over with the operator in. The order in which the members are provided by the iterator is the order of definition.
members = []
for member in Direction:
members.append(member)
assert members == [NORTH, EAST, SOUTH, WEST]
An iterator can also be explicitely obtained using the property
member
.
members = []
for member in Direction.members:
members.append(member)
assert members == [NORTH, EAST, SOUTH, WEST]
Sort order¶
assert NORTH < SOUTH
assert not NORTH > SOUTH
assert EAST > NORTH
assert not EAST < NORTH
assert EAST != NORTH
Conversion to and from numbers¶
assert SOUTH.ord == 2
assert int(SOUTH) == 2
assert Direction(2) is SOUTH
assert Direction[2] is SOUTH
Conversion to and from strings¶
assert str(SOUTH) == 'SOUTH'
assert repr(SOUTH) == 'SOUTH'
assert Direction('SOUTH') is SOUTH
assert Direction['SOUTH'] is SOUTH
Explicitely specifying ord¶
ord()
can be overridden in the default constructor with the
keyword argument ord
. It is possible to provide an own
constructor, but it then should be considered whether and how to pass
on ord
to the super class Enum
.
class Quartett(Enum):
pass
ONE = Quartett('ONE') # automatic ord = 0
TWO = Quartett('TWO', ord = 100)
THREE = Quartett('THREE') # automatic ord = 101
FOUR = Quartett('FOUR', ord = 50)
Quartett.finalize(locals())
Those members where ord
is specified get the desired
ord
, but will raise an assertion if the ord value already
exists. Where no ord
is specified, an ord
value (one)
larger than all ord
values of the members existing so far
is chosen automatically. This algorithm has the advantage of
preserving the definition order in the sort order as far as possible,
but avoids unwelcome surprises.
assert ONE.ord == 0
assert TWO.ord == 100
assert THREE.ord == 101
assert FOUR.ord == 50
The sort order is defined by the ord
attribute of the enum
members.
assert FOUR < THREE
assert FOUR < TWO
Whereas the iteration order is always the order in which the members where defined.
assert list(Quartett.members) == [
ONE, TWO, THREE, FOUR
]
Attributes¶
(TBD)
Typing¶
(TBD)
def print_direction(d: Direction) -> None:
print(d)
print_direction(NORTH)
Example(s)¶
The complete code examples can be found in the file enums-example.py
in the doc distribution archive or in doc/_build/tangle
after
building the documentation with make tangle
.
Comparison to built-in enum¶
TBD: More complicated
TBD: Only a single construction parameter
Design decisions and alternatives¶
(TBD)