PEP 354: Enumerations in Python

B

Ben Finney

Howdy all,

PEP 354: Enumerations in Python has been accepted as a draft PEP. The
current version can be viewed online:

<URL:http://www.python.org/peps/pep-0354.html>

Here is the reStructuredText source as it is today. Please discuss it
here so I can see what issues people may have.


PEP: 354
Title: Enumerations in Python
Version: $Revision: 42186 $
Last-Modified: $Date: 2006-01-26 11:55:20 +1100 (Thu, 26 Jan 2006) $
Author: Ben Finney <[email protected]>
Status: Draft
Type: Standards Track
Content-Type: text/x-rst
Created: 20-Dec-2005
Python-Version: 2.6
Post-History: 20-Dec-2005


Abstract
========

This PEP specifies an enumeration data type for Python.

An enumeration is an exclusive set of symbolic names bound to
arbitrary unique values. Values within an enumeration can be iterated
and compared, but the values have no inherent relationship to values
outside the enumeration.


Motivation
==========

The properties of an enumeration are useful for defining an immutable,
related set of constant values that have a defined sequence but no
inherent semantic meaning. Classic examples are days of the week
(Sunday through Saturday) and school assessment grades ('A' through
'D', and 'F'). Other examples include error status values and states
within a defined process.

It is possible to simply define a sequence of values of some other
basic type, such as ``int`` or ``str``, to represent discrete
arbitrary values. However, an enumeration ensures that such values
are distinct from any others, and that operations without meaning
("Wednesday times two") are not defined for these values.


Specification
=============

An enumerated type is created from a sequence of arguments to the
type's constructor::

Enumerations with no values are meaningless. The exception
``EnumEmptyError`` is raised if the constructor is called with no
value arguments.

The values are bound to attributes of the new enumeration object::

The values can be compared::
... print "Get ready for the weekend"

Values within an enumeration cannot be meaningfully compared except
with values from the same enumeration. The comparison operation
functions return ``NotImplemented`` [#CMP-NOTIMPLEMENTED]_ when a
value from an enumeration is compared against any value not from the
same enumeration or of a different type::
NotImplemented

This allows the operation to succeed, evaluating to a boolean value::
True

Coercing a value from an enumeration to a ``str`` results in the
string that was specified for that value when constructing the
enumeration::
'wed'

The sequence index of each value from an enumeration is exported as an
integer via that value's ``index`` attribute::
3

An enumeration can be iterated, returning its values in the sequence
they were specified when the enumeration was created::
>>> print [str(day) for day in Weekdays]
['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']

Values from an enumeration are hashable, and can be used as dict
keys::
>>> plans = {}
>>> plans[Weekdays.sat] = "Feed the horse"

The normal usage of enumerations is to provide a set of possible
values for a data type, which can then be used to map to other
information about the values::
... report_students[report_grade] = \
... [s for s in students if students.grade == report_grade]


Rationale -- Other designs considered
=====================================

All in one class
----------------

Some implementations have the enumeration and its values all as
attributes of a single object or class.

This PEP specifies a design where the enumeration is a container, and
the values are simple comparables. It was felt that attempting to
place all the properties of enumeration within a single class
complicates the design without apparent benefit.


Metaclass for creating enumeration classes
------------------------------------------

The enumerations specified in this PEP are instances of an ``enum``
type. Some alternative designs implement each enumeration as its own
class, and a metaclass to define common properties of all
enumerations.

One motivation for having a class (rather than an instance) for each
enumeration is to allow subclasses of enumerations, extending and
altering an existing enumeration. A class, though, implies that
instances of that class will be created; it is difficult to imagine
what it means to have separate instances of a "days of the week"
class, where each instance contains all days. This usually leads to
having each class follow the Singleton pattern, further complicating
the design.

In contrast, this PEP specifies enumerations that are not expected to
be extended or modified. It is, of course, possible to create a new
enumeration from the string values of an existing one, or even
subclass the ``enum`` type if desired.


Values related to other types
-----------------------------

Some designs express a strong relationship to some other value, such
as a particular integer or string, for each enumerated value.

This results in using such values in contexts where the enumeration
has no meaning, and unnecessarily complicates the design. The
enumerated values specified in this PEP export the values used to
create them, and can be compared for equality with any other value,
but sequence comparison with values outside the enumeration is
explicitly not implemented.


Hiding attributes of enumerated values
--------------------------------------

A previous design had the enumerated values hiding as much as possible
about their implementation, to the point of not exporting the string
key and sequence index.

The design in this PEP acknowledges that programs will often find it
convenient to know the enumerated value's enumeration type, sequence
index, and string key specified for the value. These are exported by
the enumerated value as attributes.


Implementation
==============

This design is based partly on a recipe [#ENUM-RECIPE]_ from the
Python Cookbook.

The PyPI package ``enum`` [#ENUM-PACKAGE]_ provides a Python
implementation of the data types described in this PEP.


References and Footnotes
========================

... [#CMP-NOTIMPLEMENTED]
The ``NotImplemented`` return value from comparison operations
signals the Python interpreter to attempt alternative comparisons
or other fallbacks.
<http://docs.python.org/ref/types.html#l2h-29>

... [#ENUM-RECIPE]
"First Class Enums in Python", Zoran Isailovski,
Python Cookbook recipe 413486
<http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/413486>

... [#ENUM-PACKAGE]
Python Package Index, package ``enum``
<http://cheeseshop.python.org/pypi/enum/>


Copyright
=========

This document has been placed in the public domain.


...
Local Variables:
mode: indented-text
indent-tabs-mode: nil
sentence-end-double-space: t
fill-column: 70
End:
 
K

Kay Schluehr

Ben said:
Metaclass for creating enumeration classes
------------------------------------------

The enumerations specified in this PEP are instances of an ``enum``
type. Some alternative designs implement each enumeration as its own
class, and a metaclass to define common properties of all
enumerations.

One motivation for having a class (rather than an instance) for each
enumeration is to allow subclasses of enumerations, extending and
altering an existing enumeration. A class, though, implies that
instances of that class will be created; it is difficult to imagine
what it means to have separate instances of a "days of the week"
class, where each instance contains all days. This usually leads to
having each class follow the Singleton pattern, further complicating
the design.

In contrast, this PEP specifies enumerations that are not expected to
be extended or modified. It is, of course, possible to create a new
enumeration from the string values of an existing one, or even
subclass the ``enum`` type if desired.

Maybe a metaclass implementation complicates design, but usage is quite
simple and flexible. The classics is already referenced by the Python
docs:

http://www.python.org/doc/essays/metaclasses/Enum.py

I'm -0 on the PEP.

Kay
 
P

Paul Rubin

Ben Finney said:
Enumerations with no values are meaningless. The exception
``EnumEmptyError`` is raised if the constructor is called with no
value arguments.

Why are empty enumerations not allowed? Empty sets, empty lists,
empty dictionaries are all allowed. I don't see any obvious benefits
to not allowing empty enumerations.
 
G

Giovanni Bajo

Ben said:
Values within an enumeration cannot be meaningfully compared except
with values from the same enumeration. The comparison operation
functions return ``NotImplemented`` [#CMP-NOTIMPLEMENTED]_ when a
value from an enumeration is compared against any value not from the
same enumeration or of a different type::
[...]
This allows the operation to succeed, evaluating to a boolean value::

Given that this is going to change in Python 3.0 for builtin types, I'm not
sure there's much reason to keep it this way. I'd rather a comparison operation
between different enum types to raise an exception. This would be a very
compelling feature to use enum (and in fact, the implementation I chose in
Cookbook for my programs have this feature).
Coercing a value from an enumeration to a ``str`` results in the
string that was specified for that value when constructing the
enumeration::

'wed'

What's the repr of an enumeration value? OTOH, it should be something like
"Weekdays.wed", so that eval(repr()) holds true. Also, it'd be very useful in
debug dumps, tracebacks and whatnot.
 
C

Crutcher

This seems great, except why can't I compare strings? It seems too
useful when dealing with user input, or parsing messages or config
files.
0

Additionaly, perhaps the call method of the enumeration object should
construct a value from strings?
 
S

Steven D'Aprano

Paul said:
Why are empty enumerations not allowed? Empty sets, empty lists,
empty dictionaries are all allowed. I don't see any obvious benefits
to not allowing empty enumerations.

What is an empty enum? How and when would you use it?

The best I can come up with is that an empty enum would
be the enumerated values you have when you don't
actually have any enumerated values. This is not to be
confused with an empty list: an empty list is a
well-defined concept. It is just a list (a container)
with nothing in it. A list of X is like a box
containing X, and like boxes, an empty list (or set, or
dict) is meaningful. An enum of X is just the Xs
themselves. If there is no X, there is nothing there.

But if you have a good usage case for an empty enum,
please feel free to tell us.
 
T

Tim Chase

Why are empty enumerations not allowed? Empty sets, empty lists,
What is an empty enum? How and when would you use it?

It's a Zen thing :) (agh! not the zen thread!)
.... if emptyness != enum():
.... throw UnthrowableKoan
....

(okay...maybe it's a little too early on a weekdays.mon morning)

-tkc
 
T

Tim Chase

Just a couple thoughts:
An enumeration is an exclusive set of symbolic names bound
to arbitrary unique values.

Uniqueness imposes an odd constraint that you can't have
synonyms in the set:

Not a bad thing, as it would then have interesting results
upon iterating (would the above example have three or four
passes through the iteration loop?), but there could be some
handy uses for enums containing duplicates.

This produces the counter-intuitive result of

>>> Grades.A > Grades.B
False

Sure, one can bung with it and create the set of grades
backwards, but then iteration becomes weird. Perhaps the
ability to override the comparitor?

-tkc
 
P

Paul Rubin

Steven D'Aprano said:
What is an empty enum?

An empty enum is an enum with no identifiers, just like an empty list
is a list with no elements.
How and when would you use it?
The best I can come up with is that an empty enum would be the
enumerated values you have when you don't actually have any enumerated
values. This is not to be confused with an empty list: an empty list
is a well-defined concept. It is just a list (a container) with
nothing in it. A list of X is like a box containing X, and like boxes,
an empty list (or set, or dict) is meaningful. An enum of X is just
the Xs themselves. If there is no X, there is nothing there.

An enum X is a datatype and if the enum is empty then there are no
values for that type. There's nothing wrong with that.

def print_members(header, e): # print header, then members of enum e
print header
for m in e:
print '\t', str(m)

months_longer_than_february = enum('jan', 'mar', 'apr', ) # etc
months_shorter_than_february = enum()

print_members('shorter:', months_shorter_than_february)
print_members('longer:', months_longer_than_february)

should do the obvious thing.

Hmm, I also see the PEP doesn't specify what's supposed to happen with

Weekdays = enum('sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat')
Solar_system = enum('sun', 'mercury', 'venus', 'earth',) # etc.
print Weekdays.sun == Solar_system.sun

so that's another shortcoming with the PEP.
But if you have a good usage case for an empty enum, please feel free
to tell us.

Do you have a good usage case for the number
647574296340241173406516439806634217274336603815968998799147348150763731 ?

If not, maybe we should modify Python so that all arithmetic operations
throw exceptions if they ever encounter that number. It's unlikely to
ever happen (that number came from os.urandom).

The issue is that Python operations should work uniformly on all values
that make sense. Set operations should work on the empty set. Addition
should work on zero. Enums with no members are similarly perfectly valid
and should work. Kernighan and Plauger in "Software Tools" explained it,
"Make sure your code 'does nothing' gracefully".
 
T

Tim Chase

Uniqueness imposes an odd constraint that you can't have
synonyms in the set:

Blast, I hate responding to my own posts, but as soon as I
hit Send, I noticed the syntax here was biffed. Should have
been something like
("gray",50), ("black", 50))

Same could go for days of the week:
("start_of_work_week", 1), ... ("friday", 5),
("end_of_work_week", 5)...)

Given more little grey cells (or gray cells?), I'm sure I
could come up with more examples of when it's handy to have
duplicate values in an enum...

-tkc
 
F

Felipe Almeida Lessa

Em Seg, 2006-02-27 às 00:43 -0800, Paul Rubin escreveu:
def print_members(header, e): # print header, then members of enum e
print header
for m in e:
print '\t', str(m)

months_longer_than_february = enum('jan', 'mar', 'apr', ) # etc
months_shorter_than_february = enum()

print_members('shorter:', months_shorter_than_february)
print_members('longer:', months_longer_than_february)

IMHO, you should be using sets, not enums. Something like:

def print_members(header, e):
print header
from m in e:
print '\t', str(e)

months = enum('jan', 'fev', 'mar' ...)
months_longer_than_february = frozenset(months.jan, months.mar,
months.apr ...)
months_shorter_than_february = frozenset()

print_members('shorter:', months_shorter_than_february)
print_members('longer:', months_longer_than_february)

--
"Quem excele em empregar a força militar subjulga os exércitos dos
outros povos sem travar batalha, toma cidades fortificadas dos outros
povos sem as atacar e destrói os estados dos outros povos sem lutas
prolongadas. Deve lutar sob o Céu com o propósito primordial da
'preservação'. Desse modo suas armas não se embotarão, e os ganhos
poderão ser preservados. Essa é a estratégia para planejar ofensivas."

-- Sun Tzu, em "A arte da guerra"
 
P

Paul Rubin

Felipe Almeida Lessa said:
IMHO, you should be using sets, not enums. Something like:

If enums aren't supposed to work in that construction then the PEP
shouldn't specify that they work that way.
 
F

Felipe Almeida Lessa

Em Seg, 2006-02-27 às 02:42 -0800, Paul Rubin escreveu:
If enums aren't supposed to work in that construction then the PEP
shouldn't specify that they work that way.

Sorry, but where do they say that?

--
"Quem excele em empregar a força militar subjulga os exércitos dos
outros povos sem travar batalha, toma cidades fortificadas dos outros
povos sem as atacar e destrói os estados dos outros povos sem lutas
prolongadas. Deve lutar sob o Céu com o propósito primordial da
'preservação'. Desse modo suas armas não se embotarão, e os ganhos
poderão ser preservados. Essa é a estratégia para planejar ofensivas."

-- Sun Tzu, em "A arte da guerra"
 
S

Steven D'Aprano

An empty enum is an enum with no identifiers, just like an empty list
is a list with no elements.

No, I don't think that is the case. A list is a container. You can take
the elements away and still have the container left. But an enum is not a
container. Take the enumerated values away, and you don't have an empty
enum left, you have nothing left.

All of the machinery of the enum class created by Ben Finney is just to
make sure that red, green and blue behave correctly, without extraneous
string-like or number-like methods. Of course Ben could modify his code so
that enum() returned an object. But to my mind, that would be like
creating an empty integer. Not zero -- zero is a perfectly good integer.
An empty integer, one with no bits. Yes, you could create a wrapper class
that did that. But why would you want to, and even if you did, in what
sense is the result still an integer?


An enum X is a datatype and if the enum is empty then there are no
values for that type. There's nothing wrong with that.

Then you would be happy with empty longints that are distinct from zero?
An empty enum() is like a bitless integer.

def print_members(header, e): # print header, then members of enum e
print header
for m in e:
print '\t', str(m)

months_longer_than_february = enum('jan', 'mar', 'apr', ) # etc
months_shorter_than_february = enum()

print_members('shorter:', months_shorter_than_february)
print_members('longer:', months_longer_than_february)

should do the obvious thing.

Obvious, but meaningless.

Sure, if we were talking about the LIST of months shorter than February,
or the SET of cows with feathers, I would absolutely agree with you. But
think about how enums are handled in other languages, e.g. Pascal and C:

type:
fruit = (banana, apple, orange);

enum { banana, apple, orange } fruit;

As far as I know, neither Pascal nor C allow empty enums. And even if they
did, what and how would you use them for?

type
shortmonth = (); {Months shorter than February}
var
x: shortmonth;
begin
x := ; {empty right hand side of the expression}


Think about the Python usage:
fruit = Enum('apple', 'banana', 'orange')
snack = fruit[2]

fruit is just a holder, if you will, allowing us to access the enums.
True

fruit just exists because of limitations of Python's syntax. What we'd
really like to do is this:
True

but that is too hard to do in Python. Hence we need some sort of
higher-order object to carry the enums around. But that higher order
object is not an enum, despite the name. The enums are attributes of the
higher order object: apple, banana, orange. fruit is just the scaffolding.


Hmm, I also see the PEP doesn't specify what's supposed to happen with

Weekdays = enum('sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat')
Solar_system = enum('sun', 'mercury', 'venus', 'earth',) # etc.
print Weekdays.sun == Solar_system.sun

so that's another shortcoming with the PEP.


If I recall earlier posts from Ben, enums with the same label from
different enumerations should compare as different.

Do you have a good usage case for the number
647574296340241173406516439806634217274336603815968998799147348150763731 ?

Of course. If you have a book with
647574296340241173406516439806634217274336603815968998799147348150763732
pages, then the last-but-one page will be numbered with the number you are
asking about.

Your example demonstrates that you don't understand my point. I'm not
objecting to any specific group of enums. I'm saying that a group of
enumerated values without any enumerated values is not anything at all.


If not, maybe we should modify Python so that all arithmetic operations
throw exceptions if they ever encounter that number. It's unlikely to
ever happen (that number came from os.urandom).

The issue is that Python operations should work uniformly on all values
that make sense.

Absolutely.
Set operations should work on the empty set.
Correct.

Addition should work on zero.

Agreed 100%.
Enums with no members are similarly perfectly valid
and should work.

Nope, not at all. Enums with no members aren't anything at all. They are
like a book with no pages and no cover, a boiled egg without the egg. You
can't shell an egg that isn't there.

Kernighan and Plauger in "Software Tools" explained it,
"Make sure your code 'does nothing' gracefully".

Which is excellent advice, for code for which doing nothing is a
meaningful operation. Enums is not one of those cases.
 
P

Paul Rubin

Felipe Almeida Lessa said:
Sorry, but where do they say that?

The PEP gives an example of iterating through the members of an enum.
I'm not sure if that's what you're asking, but if you meant something
else, it went past me.
 
P

Paul Rubin

Steven D'Aprano said:
All of the machinery of the enum class created by Ben Finney is just to
make sure that red, green and blue behave correctly, without extraneous
string-like or number-like methods. Of course Ben could modify his code so
that enum() returned an object.

From the PEP:
...
The values are bound to attributes of the new enumeration object::
Looks to me like he already has enum returning an object and he
describes it as an "enumeration object". The values are attributes on
the object. An empty enum simply doesn't have any of those attributes.
But to my mind, that would be like creating an empty integer. Not
zero -- zero is a perfectly good integer. An empty integer, one
with no bits. Yes, you could create a wrapper class that did
that. But why would you want to, and even if you did, in what sense
is the result still an integer?

It's more like a set than an integer. You can't iterate through an
integer, but according to the PEP, you can iterate through enums.
Then you would be happy with empty longints that are distinct from zero?
An empty enum() is like a bitless integer.

No, I'd say enums are like types. The integer 0 is different from 0.0
which is different from 0+0j and your program can tell them apart.
Similarly you can make e1, e2, and e3 be distinct empty enums.
As far as I know, neither Pascal nor C allow empty enums. And even if they
did, what and how would you use them for?

I don't use Pascal. C formerly didn't allow zero-length arrays (I
don't know about now) and that was a design error; a GCC extension
fixes it. Python doesn't make that error: it supports zero-length
arrays (empty lists). If Python fixes C's error about empty arrays it
can also fix C's error about empty enums.

Anyway, Ben's enums already differ from C and Pascal enums in two ways:

1) they are first-class objects, not syntatic sugar
2) You can iterate through them.

That's enough to say their best semantics are probably different from
C's or Pascal's.
Think about the Python usage:
fruit = Enum('apple', 'banana', 'orange')
snack = fruit[2]

The PEP doesn't have Python enums working like that. Have you
actually read it?
 
A

Antoon Pardon

Op 2006-02-27 said:
What is an empty enum? How and when would you use it?

It could come up in a tool where the tool constructs
an identifier list somehow and then an EnumType from
this list.

IMO this is just as usefull as the possibility to do
the following:

for a in []:
...


One could argue that there is no need to loop over an
empty list, because looping over an empty list is a
noop. But it would needlessly complicate code if every
time we wanted to loop over a list we had to code like
this.

if lst:
for a in lst:

Sure as long as we look at Enum as something that will
be something literally provided by the programmer, there
is little use for an Empty Enum. Just as there is little
use for looping over an empty list if you literally
provide that empty list, but maybe somewhere in the future
some automation tool will produce scripts that use Enums.

Why should we possibly make the future production of such
a tool more difficult because we can't imagine the use of
Empty Enums now?
 
R

Roy Smith

Steven D'Aprano said:
But if you have a good usage case for an empty enum,
please feel free to tell us.

I could see empty enums being useful in machine-generated code, perhaps
when generating wrappers around C library APIs. There might be some reason
to exclude certain values that exist in the C API from the Python wrapper.
It's easier to just generate an empty enum than to notice that it's empty
and do something else.

Yeah, I know this is not a very strong argument, but I also can't see any
strong reason to outlaw empty enums either. You can have empty tuples,
lists, sets, and dictionaries. If you count "pass", you can also have
empty code blocks and classes. Why not empty enums? What harm does it do
to allow them?
 
E

Eric Nieuwland

Blast, I hate responding to my own posts, but as soon as I
hit Send, I noticed the syntax here was biffed. Should have
been something like

("gray",50), ("black", 50))

Same could go for days of the week:

("start_of_work_week", 1), ... ("friday", 5),
("end_of_work_week", 5)...)

if enum would handle a dict as an input parameter by replacing it with
the dict's iteritems() this is equivalent.

--eric
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,066
Latest member
VytoKetoReviews

Latest Threads

Top