[Python-Dev] [Python-checkins] peps: Pre-alpha draft for PEP 435 (enum). The name is not important at the moment, as

Eric Snow ericsnowcurrently at gmail.com
Thu Feb 28 10:56:07 CET 2013


Having had some time to think about this problem space, here's my take on it:

===============================

The problem-space can be broken down into four layers:

1. the items
2. interaction between grouped items
3. the grouping itself
4. conversion from a class to a group

Here are *potential* characteristics at each layer:

items
-----

* immutable
* unique
* proxies or otherwise implicitly exposes underlying value
* or only explicitly exposes value
* or *is* the value with extra attributes added on
* int-like
* immutable underlying values
* hashable
* repr shows group name, item name, and underlying value
* str shows just item name
* value-less
* a class that subclasses the group

items and item interaction
--------------------------

* equality comparible
* richly comparable
* comparison by identity only
* interaction restricted to within group
* flag-like (bitwise operations, etc.)

groups
------

* iterable
* sized
* ordered (sortable)
* inheritance to extend the group
* a class rather than an instance of an enum type
* query: name -> value
* immutable
* immutable underlying collection of items
* allow aliases for actual items
* group itself is named
* has __qualname__
* repr shows items
* no duplicates
* a sequence without semantically meaningful values
* unserialize (value -> item) using call syntax on group

conversion
----------

* inherit a base enum type
* use a class decorator
* use a factory function, perhaps not even on a class (for more dynamic
  enum creation)
* __getitem__() trick for defining value-less items
* auto-numbering
* explicit values
* implicit values using ...
* filter out "private" names
* use only upper-case names

There is a danger in trying to make an "enum" that is capable of doing
everything.  People need a simple common ground.  When an object is an
enum, a developer should be able to know immediately how to interact
with it and that interaction should have a small cross-section.

===============================

With that in mind and considering a lot of the discussion that has
gone on, here's an API that tries to find a middle ground:

"items"

NamedValue  #Nick's recipe
NamedItem  #an opaque named wrapper around a value
    group  # the "enum" to which the item belongs
    value  # the wrapped value
    __qualname__  # group qualname + item name
    __repr__ -> class + qualname + value
    __str__ -> item name
    __eq__ -> NotImplemented
    expose(ns)  # export the value out to any namespace
OrderedItem(NamedItem)  # a totally ordered item
    __eq__ -> other is self
    __lt__ -> ask self.group to decide
FlagItem(NamedItem)  # a bitwise/set operation compatible item
    __and__  -> ask self.group to do it
    ...
FlagsItem(FlagItem)  # the result of FlagItem bitwise operations
    __repr__ -> shows the the items or'ed together
    items  # the items that were joined together by the operation
IntItem(NamedItem)  # for those that really need a named item to be a
little less opaque
    __int__
    __index__
    __bool__

" groups"

Group
    __name__
    __qualname__
    __items__  # the underlying collection of items (tuple or frozenset)
    _ns  # a GroupValues instance for the group
    __repr__ -> group name + items
    __call__(value) -> item  # essentially the unserializer for an item
    _expose(ns)  # calls expose() on each item in the group
OrderedGroup  # corresponds to OrderedItem
FlagGroup  # corresponds to FlagItem
GroupValues  # a Mapping proxy around a group's (name -> value)

"conversion"

named_values_from_class()  -> {name: value}  # simply extract the
values from the class
auto_int_from_class()  # a classic enum
auto_bin_from_class()  # similar but steps in powers of 2
as_enum(*, ordered=False, flagged=False, converter=..., strict=True,
**kwargs)  # the main class decorator factory

Examples:

@as_enum()
class Spam:
    A = ...
    B = ...
    C = ...

Spam.A._expose(globals())

@as_enum(converter=auto_bin_from_class, flagged=True)
class Ham:
    A = 1
    B = ...
    C = ...
    D = 32

Ham.A == 1  # False
Ham.B | Ham.C  # a FlagsItem containing the two
iter(Ham)  # TypeError
iter(Ham.__items__)  # the items
iter(Ham._ns)  # the names
iter(Ham._ns.values())  # the values

Key points:

* uses a class decorator rather than inheritance to convert a class into an enum
* the objects make use of __qualname__ as much as is reasonable
* the various Item classes are meant to be compatible (e.g. multiple
inheritance)
* groups and items are immutable
* if order matters, using a class decorator requires that classes use
OrderedDict by default for their namespace,
this is a feasible change once we have an OrderedDict written in C
(issue #16991)
* value-less NamedItems are totally valid and the use of ... in
converters would support their creation
* the Group API is very simple (what you are using over and over)
* the definition/conversion API is a little thicker with the basic
case simplified
* NamedItems are simply opaque wrappers (with the mild exception of IntItem)
* Nick's NamedValue is utterly transparent and yet compatible with use
in a Group

In the interest of having something more concrete, a very raw and
still relatively non-functional implementation of the above API can be
found at:

https://bitbucket.org/ericsnowcurrently/odds_and_ends/src/default/enum.py

Finally, like I said before, the smaller the API the better.
Obviously what I've got here could be distilled.  However, it does
capture the way I see the separate layers here.

-eric


More information about the Python-Dev mailing list