[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