Re: [Python-Dev] PEP 435 -- Adding an Enum type to the Python standard library
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
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
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
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
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
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
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
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
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
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
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
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
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