Re: [Python-Dev] PEP 435 -- Adding an Enum type to the Python standard library

2013-04-20 Thread Barry Warsaw
On Apr 13, 2013, at 08:37 AM, Tim Delaney wrote:

Just using definition order as the stable iteration order would do the
trick - no need for any comparisons at all. Subclasses (e.g. IntEnum) can
then override it.

I think this isn't possible if we want to keep backward compatibility with
earlier Pythons, which I want to do.  OTOH, we have another natural sorting
order for base Enums sitting right in front of us: the attribute name.  These
have to be unique and ordered, so why not use this for both the __repr__() and
the base Enum __iter__()?  IntEnum can override __iter__() to iterate over
item values, which also must be ordered.

I just made this change to flufl.enum and it seems to work well.

 from flufl.enum import Enum
 A = Enum('A', 'a b c')
 A
A {a: 1, b: 2, c: 3}
 for item in A: print(item)
... 
A.a
A.b
A.c
 B = Enum('B', 'c b a')
 B
B {a: 3, b: 2, c: 1}
 for item in B: print(item)
... 
B.a
B.b
B.c
 from flufl.enum import IntEnum
 C = IntEnum('C', 'c b a')
 C
C {a: 3, b: 2, c: 1}
 for item in C: print(item)
... 
C.c
C.b
C.a

-Barry
___
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com


Re: [Python-Dev] PEP 435 -- Adding an Enum type to the Python standard library

2013-04-20 Thread R. David Murray
On Sat, 20 Apr 2013 14:10:32 -0400, Barry Warsaw ba...@python.org wrote:
 On Apr 13, 2013, at 08:37 AM, Tim Delaney wrote:
 
 Just using definition order as the stable iteration order would do the
 trick - no need for any comparisons at all. Subclasses (e.g. IntEnum) can
 then override it.
 
 I think this isn't possible if we want to keep backward compatibility with
 earlier Pythons, which I want to do.  OTOH, we have another natural sorting
 order for base Enums sitting right in front of us: the attribute name.  These
 have to be unique and ordered, so why not use this for both the __repr__() and
 the base Enum __iter__()?  IntEnum can override __iter__() to iterate over
 item values, which also must be ordered.
 
 I just made this change to flufl.enum and it seems to work well.
 
  from flufl.enum import Enum
  A = Enum('A', 'a b c')
  A
 A {a: 1, b: 2, c: 3}
  for item in A: print(item)
 ... 
 A.a
 A.b
 A.c
  B = Enum('B', 'c b a')
  B
 B {a: 3, b: 2, c: 1}
  for item in B: print(item)
 ... 
 B.a
 B.b
 B.c
  from flufl.enum import IntEnum
  C = IntEnum('C', 'c b a')
  C
 C {a: 3, b: 2, c: 1}
  for item in C: print(item)
 ... 
 C.c
 C.b
 C.a

I think definition order would be much better, but if we can't have that,
this is probably better than value order for non-int.

--David
___
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com


Re: [Python-Dev] PEP 435 -- Adding an Enum type to the Python standard library

2013-04-20 Thread Barry Warsaw
On Apr 13, 2013, at 11:31 AM, Serhiy Storchaka wrote:

On 12.04.13 15:55, Eli Bendersky wrote:
 The enumeration value names are available through the class members::

   for member in Colors.__members__:
  ... print(member)
  red
  green
  blue

This is unnecessary because enumerations are iterable. Colors.__members__ is
equal to [v.name for v in Colors] and the latter looks more preferable,
because it does not use the magic method.

__members__ was really a holdover from earlier versions.  I've removed this,
but I've added an __dir__().

 The str and repr of the enumeration class also provides useful information::

   print(Colors)
  Colors {red: 1, green: 2, blue: 3}
   print(repr(Colors))
  Colors {red: 1, green: 2, blue: 3}

Does the enumeration's repr() use str() or repr() for the enumeration values?

No, enumeration values have different reprs and strs.

And same question for the enumeration's str().

Enumerations share a repr and str (well, technically, Enums don't define a
__str__()).

 To programmatically access enumeration values, use ``getattr``::

   getattr(Colors, 'red')
  EnumValue: Colors.red [value=1]

How to get the enumeration value by its value?

Use getitem syntax:

 from flufl.enum import Enum
 A = Enum('A', 'a b c')
 A[2]
EnumValue: A.b [value=2]

 Ordered comparisons between enumeration values are *not* supported.  Enums
 are not integers (but see `IntEnum`_ below)::

It's unexpected if values of the enumeration values have the natural
order. And values of the enumeration values *should be* comparable
(Iteration is defined as the sorted order of the item values).

This is one reason why Enums are not comparable except by equality.  While I
think it's not a good idea to mix the types of Enum item values, it *is*
possible, and I don't think we should add strict checks to enforce this for
the base Enum.  Thus we cannot guarantee that , , =, or = will not throw a
TypeError.

IntEnums do define these because they can be guaranteed to succeed, since
their enumeration item values are guaranteed to be integers.

There is some ambiguity in the term enumeration values. On the one hand,
it's the singleton instances of the enumeration class (Colors.red,
Colors.gree, Colors.blue), and on the other hand it is their values (1, 2,
3).

I've just made sure that the flufl.enum using.rst document is consistent here.
The terms I'm using are enumeration item to define things like Colors.red
and enumeration item value (or sometimes just enumeration value) to define
the value of the enumeration item, e.g. 2, and it's available on the .value
attribute of the item.  Enumeration item name is essentially the attribute
name, and is available on the .name attribute of the item.

 But if the value *is* important,  enumerations can have arbitrary values.

Should enumeration values be hashable?

At least they should be comparable (Iteration is defined as the sorted order
of the item values).

Given my previous responses, these questions should be already answered.

 ``IntEnum`` values behave like integers in other ways you'd expect::

   int(Shape.circle)
  1
   ['a', 'b', 'c'][Shape.circle]
  'b'
   [i for i in range(Shape.square)]
  [0, 1]

What is ``isinstance(Shape.circle, int)``? Does PyLong_Check() return true
for ``IntEnum`` values?

True.  Yes, because IntEnumValues inherit from int.

Why the enumeration starts from 1? It is not consistent with namedtuple, in
which indices are zero-based, and I believe that in most practical cases the
enumeration integer values are zero-based.

There are several reasons:

* It's been that way since day 1 wink of the package
* Zero is special in a way; it's the only false integer value
* This is very easy to override
* If you're using the auto-numbering convenience API, then you don't care
  about the values anyway

The Python standard library has many places where named integer constants
used as bitmasks (i.e. os.O_CREAT | os.O_WRONLY | os.O_TRUNC, select.POLLIN |
select.POLLPRI, re.IGNORECASE | re.ASCII). The proposed PEP is not applicable
to these cases. Whether it is planned expansion of Enum or additional EnumSet
class to aid in these cases?

IntEnums work fine for these cases, but it's true that the result of a logical
operation is an int and not a subclass with a nice repr.  Contributions are
welcome.

-Barry
___
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com


Re: [Python-Dev] PEP 435 -- Adding an Enum type to the Python standard library

2013-04-20 Thread Guido van Rossum
Can we separate the iteration order and the comparison order? For
iteration order, I think by definition order or by attribute name are
both great, and better than by value. But for comparing values using
, ==, , I strongly feel we should defer to the underlying values --
if those cannot be compared, then the enums can't either, but the
iteration order is still defined.

On Sat, Apr 20, 2013 at 11:26 AM, R. David Murray rdmur...@bitdance.com wrote:
 On Sat, 20 Apr 2013 14:10:32 -0400, Barry Warsaw ba...@python.org wrote:
 On Apr 13, 2013, at 08:37 AM, Tim Delaney wrote:

 Just using definition order as the stable iteration order would do the
 trick - no need for any comparisons at all. Subclasses (e.g. IntEnum) can
 then override it.

 I think this isn't possible if we want to keep backward compatibility with
 earlier Pythons, which I want to do.  OTOH, we have another natural sorting
 order for base Enums sitting right in front of us: the attribute name.  These
 have to be unique and ordered, so why not use this for both the __repr__() 
 and
 the base Enum __iter__()?  IntEnum can override __iter__() to iterate over
 item values, which also must be ordered.

 I just made this change to flufl.enum and it seems to work well.

  from flufl.enum import Enum
  A = Enum('A', 'a b c')
  A
 A {a: 1, b: 2, c: 3}
  for item in A: print(item)
 ...
 A.a
 A.b
 A.c
  B = Enum('B', 'c b a')
  B
 B {a: 3, b: 2, c: 1}
  for item in B: print(item)
 ...
 B.a
 B.b
 B.c
  from flufl.enum import IntEnum
  C = IntEnum('C', 'c b a')
  C
 C {a: 3, b: 2, c: 1}
  for item in C: print(item)
 ...
 C.c
 C.b
 C.a

 I think definition order would be much better, but if we can't have that,
 this is probably better than value order for non-int.

 --David
 ___
 Python-Dev mailing list
 Python-Dev@python.org
 http://mail.python.org/mailman/listinfo/python-dev
 Unsubscribe: 
 http://mail.python.org/mailman/options/python-dev/guido%40python.org



-- 
--Guido van Rossum (python.org/~guido)
___
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com


Re: [Python-Dev] PEP 435 -- Adding an Enum type to the Python standard library

2013-04-20 Thread Barry Warsaw
On Apr 13, 2013, at 08:33 AM, Guido van Rossum wrote:

(And yes, I am now +1 on documenting this mechanism.)

Here's what I've added to the flufl.enum documentation:

Customization protocol
==

You can define your own enumeration value types by using the
``__value_factory__`` protocol.  This is how the ``IntEnum`` type is
defined.  As an example, let's say you want to define a new type of
enumeration where the values were subclasses of ``str``.  First, define your
enumeration value subclass.

 from flufl.enum import EnumValue
 class StrEnumValue(str, EnumValue):
... def __new__(cls, enum, value, attr):
... return super(StrEnumValue, cls).__new__(cls, value)

And then define your enumeration class.  You must set the class attribute
``__value_factory__`` to the class of the values you want to create.

 class StrEnum(Enum):
... __value_factory__ = StrEnumValue

Now, when you define your enumerations, the values will be ``str`` subclasses.
::

 class Noises(StrEnum):
... dog = 'bark'
... cat = 'meow'
... cow = 'moo'

 isinstance(Noises.cow, str)
True

-Barry
___
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com


Re: [Python-Dev] PEP 435 -- Adding an Enum type to the Python standard library

2013-04-20 Thread Barry Warsaw
On Apr 13, 2013, at 12:51 PM, Steven D'Aprano wrote:

I think that's too strong a restriction. I would expect to be able to do this:

class Insect(Enum):
 wsap = 1  # Oops, needed for backward compatibility, do not remove.
 wasp = 1  # Preferred. Use this in new code.
 bee = 2
 ant = 3


Or at the very least:

class Insect(Enum):
 wasp = wsap = 1
 bee = 2
 ant = 3

What's the justification for this restriction? I have looked in the PEP, and
didn't see one.

If you allowed this, there would be no way to look up an enumeration item by
value.  This is necessary for e.g. storing the value in a database.  If you
know that the insect column is an INTEGER that represents an enumeration
item of Insect, then you can just store the int value in the column.  To
reconstitute the actual enumeration item when you read the column back from
the database, you need to be able to look up the item by value.

Currently, you do this:

 my_insect = Insect[database_value]

but if the values are not unique, you have no way to reliably do it.  I don't
much like APIs which return sequences (either always or on demand) or rely
on definition order or some other arbitrary discrimination because I don't
think any of those are practical in the real world.

I also recall that duplication was a specific anti-feature in the previous
python-ideas discussion.  It was thought that this would allow for typos in
the set of enumeration items to creep in.

I don't see how you can reconcile these issues to allow for duplicate values.

-Barry
___
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com


Re: [Python-Dev] PEP 435 -- Adding an Enum type to the Python standard library

2013-04-20 Thread Tim Delaney
On 21 April 2013 04:10, Barry Warsaw ba...@python.org wrote:

 On Apr 13, 2013, at 08:37 AM, Tim Delaney wrote:

 Just using definition order as the stable iteration order would do the
 trick - no need for any comparisons at all. Subclasses (e.g. IntEnum) can
 then override it.

 I think this isn't possible if we want to keep backward compatibility with
 earlier Pythons, which I want to do.


Do you want it compatible with Python 2.x? In that case I don't see a way
to do it - getting definition order relies on __prepare__ returning an
ordered dict, and __prepare__ of course is only available in 3.x.

Tim Delaney
___
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com


Re: [Python-Dev] PEP 435 -- Adding an Enum type to the Python standard library

2013-04-20 Thread R. David Murray
On Sun, 21 Apr 2013 08:34:39 +1000, Tim Delaney timothy.c.dela...@gmail.com 
wrote:
 On 21 April 2013 04:10, Barry Warsaw ba...@python.org wrote:
 
  On Apr 13, 2013, at 08:37 AM, Tim Delaney wrote:
 
  Just using definition order as the stable iteration order would do the
  trick - no need for any comparisons at all. Subclasses (e.g. IntEnum) can
  then override it.
 
  I think this isn't possible if we want to keep backward compatibility with
  earlier Pythons, which I want to do.
 
 Do you want it compatible with Python 2.x? In that case I don't see a way
 to do it - getting definition order relies on __prepare__ returning an
 ordered dict, and __prepare__ of course is only available in 3.x.

It seems strange to limit a new Python3 feature to the Python2 feature
set.  Just saying :)

--David
___
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com


Re: [Python-Dev] PEP 435 -- Adding an Enum type to the Python standard library

2013-04-20 Thread Steven D'Aprano

On 21/04/13 05:42, Barry Warsaw wrote:

On Apr 13, 2013, at 12:51 PM, Steven D'Aprano wrote:


I think that's too strong a restriction. I would expect to be able to do this:

class Insect(Enum):
 wsap = 1  # Oops, needed for backward compatibility, do not remove.
 wasp = 1  # Preferred. Use this in new code.
 bee = 2
 ant = 3


Or at the very least:

class Insect(Enum):
 wasp = wsap = 1
 bee = 2
 ant = 3

What's the justification for this restriction? I have looked in the PEP, and
didn't see one.


If you allowed this, there would be no way to look up an enumeration item by
value.  This is necessary for e.g. storing the value in a database.  If you
know that the insect column is an INTEGER that represents an enumeration
item of Insect, then you can just store the int value in the column.  To
reconstitute the actual enumeration item when you read the column back from
the database, you need to be able to look up the item by value.


I agree that's a good example of a situation where the user might want unique
values. But I don't agree that this is the responsibility of the Enum type
itself. Enums are a mapping from name to value, just like dicts:

d = {'wasp': 1, 'bee': 2, 'ant': 3}

There are use-cases where we might want dicts to be 1:1 too, but we don't force
that restriction on all dicts. Even if I want to reconstruct the key from the
value, doesn't mean that everybody who uses dicts must be prohibited from using
duplicate values. We don't even offer a guaranteed 1:1 mapping type in the
standard library.

Actual real world enums can and do frequently contain duplicate values, I've
previously given examples of such. The ability to have two enums with the same
value is in my opinion not just a Nice To Have but is a Must Have.

With the ability to have duplicate values, enums are the One Obvious Way to
do it. Without it, the decision process becomes more complex: does my
application now, or will it ever, need duplicate values? If there's even a tiny
chance it might need them in the future, I cannot risk getting stuck with an
enum type that prohibits that.

I would argue that it is the responsibility of enums to start with the least
restrictions as is reasonable, and leave additional restrictions up to
subclasses, rather than the other way around. (I'll just quietly mention the
Liskov Substitution Principle here...) If some people need enums to have unique
values, then enforcing that should be their responsibility, and not forced on
all users whether they need that restriction or not.

If there is enough demand for that, then perhaps the enum module could provide a
standard mechanism for enforcing unique values, via a flag, or a subclass, say.
I like the idea of a mixin:

class Insect(UniqueValues, Enum):
wasp = 1
bee = 2
ant = 3


But it should be off by default.



Currently, you do this:

  my_insect = Insect[database_value]

but if the values are not unique, you have no way to reliably do it.  I don't
much like APIs which return sequences (either always or on demand) or rely
on definition order or some other arbitrary discrimination because I don't
think any of those are practical in the real world.


Neither do I. I would be perfectly happy for enums to raise ValueError in that
case, and leave it up to the caller to either prevent duplicate values from
occurring, or to deal with the exception in whichever way makes sense for their
application.




I also recall that duplication was a specific anti-feature in the previous
python-ideas discussion.  It was thought that this would allow for typos in
the set of enumeration items to creep in.


Typos occur whether enums allow duplicate values or not. How you deal with
such typos depends on whether you are forced to keep it forever, or can define
a second enum with the same value and deprecate the typo.




--
Steven
___
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com


Re: [Python-Dev] PEP 435 -- Adding an Enum type to the Python standard library

2013-04-20 Thread Rurpy
On 04/20/2013 01:42 PM, Barry Warsaw wrote: On Apr 13, 2013, at 12:51 PM, 
Steven D'Aprano wrote:
[...]
What's the justification for this [unique values] restriction? I have
looked in the PEP, and didn't see one.
 
 If you allowed this, there would be no way to look up an enumeration item by
 value.  This is necessary for e.g. storing the value in a database.  If you
 know that the insect column is an INTEGER that represents an enumeration
 item of Insect, then you can just store the int value in the column.  To
 reconstitute the actual enumeration item when you read the column back from
 the database, you need to be able to look up the item by value.
[...] 

Composite keys have been part of relational databases from
their inception.  If you want to store an enumeration value
in a database when non-unique values are possible, you can 
do so simply by storing the name, value pair; i.e. use two 
columns instead of one.  Of course this does not preclude 
storing just the value when you know they will be unique.
But it is not true that unique values are *required* for 
storing enumeration values in a database.

___
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com


Re: [Python-Dev] PEP 435 -- Adding an Enum type to the Python standard library

2013-04-20 Thread Nick Coghlan
On Sun, Apr 21, 2013 at 9:10 AM, R. David Murray rdmur...@bitdance.com wrote:
 On Sun, 21 Apr 2013 08:34:39 +1000, Tim Delaney timothy.c.dela...@gmail.com 
 wrote:
 On 21 April 2013 04:10, Barry Warsaw ba...@python.org wrote:

  On Apr 13, 2013, at 08:37 AM, Tim Delaney wrote:
 
  Just using definition order as the stable iteration order would do the
  trick - no need for any comparisons at all. Subclasses (e.g. IntEnum) can
  then override it.
 
  I think this isn't possible if we want to keep backward compatibility with
  earlier Pythons, which I want to do.

 Do you want it compatible with Python 2.x? In that case I don't see a way
 to do it - getting definition order relies on __prepare__ returning an
 ordered dict, and __prepare__ of course is only available in 3.x.

 It seems strange to limit a new Python3 feature to the Python2 feature
 set.  Just saying :)

Agreed. I think the stdlib enum library should use __prepare__ and
iterate in definition order (since 2.x compatibility isn't of any
concern), while flufl.enum can use sorted by name as the iteration
order.

An order_by_name keyword argument to __prepare__ in the stdlib
version could then allow the user to opt in to the flufl.enum
behaviour, while still using definition order by default.

As in:

class DefinitionOrder(enum.Enum):
first = 1
second = 2
third = 3

list(DefinitionOrder) - [DefinitionOrder.first,
DefinitionOrder.second, DefinitionOrder.third]

And:

class NameOrder(enum.Enum, order_by_name=True):
a = 1
c = 2
b = 3

list(NameOrder) - [NameOrder.a, NameOrder.b, NameOrder.c]

flufl.enum could also offer the order_by_name flag on 3.x, but set
it to True by default.

Cheers,
Nick.

--
Nick Coghlan   |   ncogh...@gmail.com   |   Brisbane, Australia
___
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com


Re: [Python-Dev] PEP 435 -- Adding an Enum type to the Python standard library

2013-04-20 Thread Nick Coghlan
On Sun, Apr 21, 2013 at 11:29 AM, Steven D'Aprano st...@pearwood.info wrote:
 I would argue that it is the responsibility of enums to start with the least
 restrictions as is reasonable, and leave additional restrictions up to
 subclasses, rather than the other way around. (I'll just quietly mention the
 Liskov Substitution Principle here...) If some people need enums to have
 unique
 values, then enforcing that should be their responsibility, and not forced
 on
 all users whether they need that restriction or not.

 If there is enough demand for that, then perhaps the enum module could
 provide a
 standard mechanism for enforcing unique values, via a flag, or a subclass,
 say.

The PEP is fine, as it already allows duplicate names without encouraging them:

class Insect(Enum):
wasp = 1  # Preferred. Use this in new code.
bee = 2
ant = 3
# Backwards compatibility aliases
Insect.wsap = Insect.wasp

If you have a lot of such aliases:

   aliases = {
   wasp: [wsap],
   ...
   }
   for attr, names in aliases.items():
   for name in names:
   setattr(Insect, name, getattr(Insect, attr))

A more concise syntax for handling duplicates may prove desirable at
some point in the future, but this is a case where encouraging
correctness by default is a good idea.

Cheers,
Nick.

--
Nick Coghlan   |   ncogh...@gmail.com   |   Brisbane, Australia
___
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com


Re: [Python-Dev] PEP 435 -- Adding an Enum type to the Python standard library

2013-04-20 Thread Rurpy
On 04/20/2013 10:55 PM, Rurpy wrote:
[...]
 But it is not true that unique values are *required* for 
 storing enumeration values in a database.

I should have added that allowing mixed types for values (e.g.
as discussed in 
 http://mail.python.org/pipermail/python-dev/2013-April/125322.html)
is far more problematic for database storage than non-unique
values are.  Nearly all databases (Sqlite being an exception)
don't allow different types in a column.  (Not sure if mixed
types is still an open issue or not...)

___
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com