PEP 354: Enumerations in Python

D

Dan Sommers

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

Not really: In some parts of the world, calendar weeks begin on Monday
and end on Sunday, and in other parts of the world, work weeks begin on
Sunday and end on Thursday. ISTM that if you want to do anything with
the value of a given enumeration tag (including make greater/less than
comparisons, sort, etc.), then a dictionary might be better than an
enum. Also, enumeration tags like "end_of_work_week" should be assigned
at run-time based on locale and/or preferences settings (not that you
couldn't have stuck the code above into a preferences file...).
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...

I don't know: If you're using enums as names for otherwise meaningful
values, then you're right. But if you're using enums as meaningful
names for pairwise distinct concepts, then I think otherwise. IMO, if
your program breaks if someone re-assigns the values randomly, then a
better solution than an enum exists. (Or maybe that's because I think
of the tags as nothing more than keywords that work like distinct
values.)

Regards,
Dan
 
T

Terry Reedy

Steven D'Aprano said:
A list of X is like a box containing X,

and in another post
A list is a container.

I think it is misleading, if not wrong, to refer to Python collections as
'containers', 'boxes', or similar. A object in a box cannot be in another
disjoint box. A object in a box cannot be in the box a second time. But
both 'cannot's are 'can's for Python objects in respect to Python
collections. So, bad metaphor.

(Yes, CPython implements the association of list positions with objects by
putting an instance/copy of the id/address of the implemented object 'in' a
particular position in a block of memory, but even then, the object itself
in not 'in' the list block. And that is so because of the need for
multiple associations for each object. Beside which, we are discussing the
abstract notion of the empty enumeration.)
You can take the elements away and still have the container left.

I would say instead that you have an empty roster ;-)
I suspect that the notion of empty set was once controversial.
It certainly gives some set theory beginners a pause.
But an enum is not a container.

But neither, I claim, is a list. So to me, you have not drawn a
distrinction, and therefore no justification for different treatment.

To me, the enum proposal is for an immutable ordered set (rather than
multiset) with a friendly interface. So, like other posters, I have no
problem with one that is empty. I also expect enum() to return such
because that is GvR policy for builtin type constructors.

To me, 'I can't think of a use for X' is insufficient reason to prohibit X
in Python. You also need an 'X is tricky/difficult to implement' or 'X is
harmful' claim.

Terry Jan Reedy
 
I

I V

Paul said:
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.

I think it does, doesn't it? From the PEP:

"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." (my emphasis)
 
P

Paul Rubin

I V said:
I think it does, doesn't it? From the PEP:

"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." (my emphasis)

Oh ok, thanks. I didn't understand this before. The link from the
footnote explains it.
 
B

Ben Finney

Giovanni Bajo said:
Agreed. Allowing subclassing of an existing enum is a strong plus to me.

Having classes as enumerations seems to me to complicate the
conceptual model. As I see it, an enumeration is a container
object. For a container object to be subclassed has no obvious
semantic meaning.

If an enumeration object were to be derived from, I would think it
just as likely to want to have *fewer* values in the derived
enumeration. Subclassing would not appear to offer a simple way to do
that.

To preserve the promise that every enumeration's values are unique,
even the values that were inherited would need to be different in each
enumeration. This, again, doesn't fit well with the concept of
subclassing.

I can see value in wanting to derive a new enumeration from an
existing one; I just don't think subclassing makes sense for that
operation.

How would you specify the interface for deriving one enumeration from
another?
 
B

Ben Finney

Paul Rubin 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.

By way of explanation, my earlier implementations of this didn't have
enumerations as sequences. Now that the design has converged more on a
sequence and container idiom, I agree with you.

I'll amend the specification so empty enumerations are not an
error. (This will also remove the last remaining exception class from
the specification, making it cleaner still. Good.)
 
B

Ben Finney

Paul Rubin said:
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.

The PEP text specifies::

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

The Python parser knows what to do when a comparison returns
NotImplemented.
 
B

Ben Finney

Terry Reedy said:
I think it is misleading, if not wrong, to refer to Python
collections as 'containers', 'boxes', or similar. A object in a box
cannot be in another disjoint box. A object in a box cannot be in
the box a second time. But both 'cannot's are 'can's for Python
objects in respect to Python collections. So, bad metaphor.

Thanks. Earlier today I referred to my conceptual model for
enumerations as "container". I agree with you that should be amended
to "collection".

Since an empty enumeration is consistent with the "collection" idiom,
and has obvious behaviour, I see no problem changing the specification
to remove that restriction.
To me, 'I can't think of a use for X' is insufficient reason to
prohibit X in Python.

I'm finding that most of the changes I made to the specification
before submitting the initial draft were removing such
restrictions. I'm glad to see that continues :)
 
M

Mikalai

Steven said:
Paul Rubin wrote:

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.

"Nothing" in python is None. If empty enum is None, then it is the same
as empty list in Lisp is nil, i.e. type-of(nil)=='list. Giving a type
to nothing, isn't really useful. Thus, empty enum is needed.

Programaticaly or, say, in implementation, enum is some structure that
holds some stuff. Just like tuples and lists are structures to hold
something. As a structure, it can be empty.
 
P

Paul Rubin

Ben Finney said:
If an enumeration object were to be derived from, I would think it
just as likely to want to have *fewer* values in the derived
enumeration. Subclassing would not appear to offer a simple way to do
that.

pentium_instructions = enum('add', 'sub', 'mul', ) # etc

athlon64_instructions = enum('add64', 'sub64', # etc
base_enum=pentium_instructions)

# 386 has no floating point unit
i386_instructions = enum(base_enum=pentium_instructions,
remove=('addf', 'subf', 'mulf',)) # etc
 
B

Ben Finney

Giovanni Bajo said:
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.

What is it that will change? Can you point to something we should read
about this?
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).

A previous implementation raised exceptions from failed comparisons.
Here is the discussion that convinced me that was not the right thing
to do::

said:
What's the repr of an enumeration value?

Hmm, the PEP doesn't specify. I was leaving it up to the
implementation.
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.

An enumeration value object knows the enumeration object that created
it, but that enumeration doesn't know its own name; objects don't know
their own name, since they could have many, or none.

Even if an enumeration value were to know the name of the enumeration
that created it, you still have this problem::
Colours.red

Is this desirable behaviour? I don't think so.
 
B

Ben Finney

Tim Chase said:
Just a couple thoughts:
Appreciated.


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

I think a dict is best used for this purpose. An enumeration is for
when you want all the values in the collection to be unique, but don't
care particularly what those values *are*.
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?

Again, if you actually want specific values associated with each
member of the collection, I think a dict is best.

Note that the enumeration values *can* be dict keys, so you could
associate the values with other objects that way.
 
B

Ben Finney

[fixing References field. Please reply in the original thread so I can
find you easily.]

Roy Smith said:
If I have an enum, how can I verify that it's a legal value? Can I
do:

Weekdays = enum('sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat')

def foo (day):
if day not in Weekdays:
raise ValueError

The current PEP text doesn't specify; that's an omission on my
part. The answer should be "yes, an enum can be tested for
membership".

I'll add that to the specification.
Also, do enums have __dict__ slots?

Not specified as having one, no.
Can I do something like:

day = 'sun'
print Weekdays.__dict__[day]

You can do the same thing more elegantly by explicitly querying the
attributes of the enumeration::
sun
 
P

Paul Rubin

Ben Finney said:
By way of explanation, my earlier implementations of this didn't have
enumerations as sequences. Now that the design has converged more on a
sequence and container idiom, I agree with you.

say that
Weekdays = enum('mon', 'tue', ...)

What is the type of Weekdays.mon supposed to be?

Do you anticipate having parameters like socket.AF_INET that are
currently integers, become enumeration members in future releases?
 
R

Roy Smith

Ben Finney said:
I'll amend the specification so empty enumerations are not an
error. (This will also remove the last remaining exception class from
the specification, making it cleaner still. Good.)

And once you've done that, we can build an enum of all the exceptions which
enums can raise :)
 
R

Roy Smith

A few random questions:

a = enum ('foo', 'bar', 'baz')
b = enum ('foo', 'bar', 'baz')

what's the value of the following:

a == b
a is b
a.foo == b.foo
a.foo is b.foo
len (a)
str (a)
repr (a)
hash (a)
type (a)

Can you make an enum from a sequence?

syllables = ['foo', 'bar', 'baz']
c = enum (syllables)

You imply that it works from "An enumerated type is created from a sequence
of arguments to the type's constructor", but I suspect that's not what you
intended.

BTW, I think this is a great proposal; enums are a badly needed part of the
language. There's been a number of threads recently where people called
regex methods with flags (i.e. re.I) when integers were expected, with
bizarre results. Making the flags into an enum would solve the problem
while retaining backwards compatibility. You would just have to put in the
re module something like:

flags = enum ('I', 'L', 'M', 'S', 'U', 'X')
I = flags.I
L = flags.L
M = flags.M
S = flags.S
U = flags.U
X = flags.X

then the methods which expect a flag can do:

if flag is not in re.flags:
raise ValueError ("not a valid flag")

and the ones which expect an integer would (if nothing better), raise
NotImplemented when they tried to use the value as an integer if you passed
it a flag by mistake.
 
A

Alex Martelli

Terry Reedy said:
I suspect that the notion of empty set was once controversial.

Yep: Reverend Dodgson (best known by his pen name of Lewis Carroll, and
as the author of the Alice novels, but a logician and mathematician IRL)
fought long and hard against Cantor's set theory, focusing on "the empty
set" (singular: in Cantor's theory there can be only one) but not
managing to build an actual contradiction around it. The poor man died
just before his compatriot Bertrand Russell found "Russell's Paradox"
(which destroyed Frege's logic -- but is quite applicable to destroying
a foundation stone of Cantor's set theory as well).

I personally believe that Dodgson was reaching towards what today we
call modal logic (particularly intensional logic), though he could
hardly get there with such encumbrances as his fiction writing, his
photography, his heavy smoking of cannabis, etc, etc. But, that's just
me, and I can't claim to be an expert at modern set theory (though I do
know enough of it to see that it's quite different from Cantor's "naive"
version that's still taught in most schools...!-), much less of the
subtleties of modal logic. Still, if you give a set interpretation of
modal logic, there isn't ONE empty set: the crucial point of modal
logic, from my semi-learned POV, is that it distinguishes what JUST
HAPPENS to be false, from what MUST INTRINSICALLY be false (and ditto
for true). In set terms, say, "all integers x such that x>x" would be
an *intrinsically* empty set, while "all dogs that are in this house
right now" would be a set which *just happens* to be empty -- they
aren't "one and the same, the sole empty set" any more than they are in
commonsense (the notion Dodgson fought against).


Alex
 
A

Alex Martelli

Ben Finney said:
The Python parser knows what to do when a comparison returns
NotImplemented.

The parser has nothing to do with it, but the bytecode interpreter sure
does;-).


Alex
 
C

Carl Banks

Ben said:
This PEP specifies an enumeration data type for Python.

-1 for this particular proposal as a builtin. I feel it's not a great
design of something that isn't terribly useful to begin with. +0 for
the standard library, only after a couple changes. As I see it, this
whole proposal is a minor improvement over doing something like the
following, but too minor an improvement to add to the builtin
namespace:

MON = 'mon'
TUE = 'tue'
etc.

DAYS_OF_WEEK = (MON,TUE,etc.)


[snip]
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.

Here's why I think it's not too useful to begin with: the benefits of
the enum you describe here are pretty weak.

It's a pretty weak case to have a dedicated builtin to prevent
duplicates in something that changes maybe once a month, as enums tend
to change rather slowly. (At least, that's the way enums in other
languages are used, and the design you present here seems to suggest
you intend to use them that way as well.) And frankly, a unit test or
assertion could check this.

As for preventing nonsensical operations on enum constants: good idea,
but let's face it: a decent programmer knows whether he or she's
dealing with a enum value or a piece of string data. This is why I
said it's not terribly useful (as opposed to not useful at all).
Unfortunately, this proposal fails to prevent a nonsensical operation
in the case where it would be most useful to do so.


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

No strong feeling on this issue. Probably no use for an empty enum,
but no harm either.


[snip]
This allows the operation to succeed, evaluating to a boolean value::

True

The nonsensical comparisions should throw value errors. You say that
you want to get rid of operations that don't make sense, but it seems
misguided that we would get rid of multiplying by a integer (something
that's not likely to happen much at all) but retain comparison with
other types (something that's going to be much more common).

I realize that this is a thoughtful decision on your part, that you
have some reason for it and were not just blindly following the example
of other Python builtins. However, IMHO, this design decision forsakes
the single most useful feature of an enum, and replaces it with
confusion.


[snip examples of use]

One thing I'd add is something like Weekdays.get_constant("wed"). I
realize that you can do this with getattr, but you can also do
getattr(Weekdays,"__dict__") with getattr, and you ought to have a
method that thows and exception if you try to access the __dict__
constant.

Also, it blesses the operation, so that people who are trying to access
the constant after reading its name from the database don't have to
feel guilty about using getattr.


[snip]
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;

How so? This is true of classes with metaclass type, but using a
different metaclass suggests maybe it isn't like a regular class. In
particular, constructing an instance could be a non-op; instead the
class creates it's own instances at definition time. I don't see
anything that implies that a class can't do that.
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.

I don't understand what you're saying here. It doesn't seem to me hard
to imagine that a class could create its own instances, but I'm not
sure if that's what you're talking about.
This usually leads to
having each class follow the Singleton pattern, further complicating
the design.

Meh. I don't feel too strongly about this, but I'd think an enum as a
class is better, because you can't (or, rather, oughtn't) compare
constants from different enums. That's a logical demarcation of
classes.

Sorry if I was a little blunt here. In the end, I really don't see the
problems that this enum addresses being all that common; it seems a lot
of work for minor benefit.


Carl Banks
 
M

Magnus Lycka

Tim said:
... throw UnthrowableKoan
...

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

Probably, since a SyntaxError slipped in. Throw is C++ Tim.
It's "raise UnRaisableKoan".
 

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

Forum statistics

Threads
473,776
Messages
2,569,603
Members
45,194
Latest member
KarriWhitt

Latest Threads

Top