enum discussion: can someone please summarize open issues?
Hi all, I've just read a few dozen enum-related emails, and there are so many more. I would like to form an opinion about the proposal(s), but I feel I don't know what the actual issues are anymore. In the past, somebody usually presented a summary of the issues so far, and that was a good point for late comers to get up to speed and weigh in. (It can be here or in the PEP.) It is also a good point to focus the discussion (which seems to have wandered quite far from sensible Pythonic design in places). Thanks in advance, Georg
Example enumeration: class Seasons(Enum): SPRING = 1 SUMMER = 2 AUTUMN = 3 WINTER = 4 days_in_year = 365 @property def avg_temp(self): return (75, 92, 66, 33)[int(self)+1] # enums are 1-based Definite Issues: - should enum items be of the type of the Enum class? (i.e. type(SPRING) is Seasons) - should an enum item be selectable via __call__ instead of __getitem__ (i.e. Seasons(3) is AUTUMN) - should days_in_year be enumerated? - should avg_temp be enumerated? - for the above two, how should they be included/excluded?
My opinions added On Sun, Apr 28, 2013 at 12:32 PM, Ethan Furman <ethan@stoneleaf.us> wrote:
Example enumeration:
class Seasons(Enum): SPRING = 1 SUMMER = 2 AUTUMN = 3 WINTER = 4
days_in_year = 365
@property def avg_temp(self): return (75, 92, 66, 33)[int(self)+1] # enums are 1-based
Definite Issues:
- should enum items be of the type of the Enum class? (i.e. type(SPRING) is Seasons)
IMO Yes.
- should an enum item be selectable via __call__ instead of __getitem__ (i.e. Seasons(3) is AUTUMN)
No opinion.
- should days_in_year be enumerated?
Yes. (If you don't want it to be, and it's not a method/descriptor, move it out of the class.)
- should avg_temp be enumerated?
IMO No.
- for the above two, how should they be included/excluded?
IMO Everything should be enumerated except (a) things with a __get__() method (i.e. descriptors) (b) __dunder__ names Also, I believe there's still an issue on the order in which items are returned by iter(Seasons), but I don't know which way this is heading. -- --Guido van Rossum (python.org/~guido)
On 04/28/2013 01:02 PM, Guido van Rossum wrote:
My opinions added
Mine also now added.
Example enumeration:
class Seasons(Enum): SPRING = 1 SUMMER = 2 AUTUMN = 3 WINTER = 4
days_in_year = 365
@property def avg_temp(self): return (75, 92, 66, 33)[int(self)+1] # enums are 1-based
Definite Issues:
- should enum items be of the type of the Enum class? (i.e. type(SPRING) is Seasons)
IMO Yes.
I agree.
- should an enum item be selectable via __call__ instead of __getitem__ (i.e. Seasons(3) is AUTUMN)
No opinion.
I think the callable syntax should be supported for database integration and consistency with every (?) other type in Python. No opinion about the __getitem__ portion.
- should days_in_year be enumerated?
Yes. (If you don't want it to be, and it's not a method/descriptor, move it out of the class.)
Making it a property to have it in the class certainly works for me.
- should avg_temp be enumerated?
IMO No.
I agree.
- for the above two, how should they be included/excluded?
IMO Everything should be enumerated except (a) things with a __get__() method (i.e. descriptors) (b) __dunder__ names
This also works for me.
Also, I believe there's still an issue on the order in which items are returned by iter(Seasons), but I don't know which way this is heading.
As somebody pointed out earlier, the only order which cannot be reconstructed after the fact is definition order (value order can be, lexical order can be, etc.). So my vote is to have the default iteration order be the original definition order, as any other desired order can be added to the class. -- ~Ethan~
Am 28.04.2013 22:36, schrieb Ethan Furman:
Example enumeration:
class Seasons(Enum): SPRING = 1 SUMMER = 2 AUTUMN = 3 WINTER = 4
days_in_year = 365
@property def avg_temp(self): return (75, 92, 66, 33)[int(self)+1] # enums are 1-based
Definite Issues:
- should enum items be of the type of the Enum class? (i.e. type(SPRING) is Seasons)
IMO Yes.
I agree.
- should an enum item be selectable via __call__ instead of __getitem__ (i.e. Seasons(3) is AUTUMN)
No opinion.
I think the callable syntax should be supported for database integration and consistency with every (?) other type in Python. No opinion about the __getitem__ portion.
- should days_in_year be enumerated?
Yes. (If you don't want it to be, and it's not a method/descriptor, move it out of the class.)
Making it a property to have it in the class certainly works for me.
- should avg_temp be enumerated?
IMO No.
I agree.
- for the above two, how should they be included/excluded?
IMO Everything should be enumerated except (a) things with a __get__() method (i.e. descriptors) (b) __dunder__ names
This also works for me.
Also, I believe there's still an issue on the order in which items are returned by iter(Seasons), but I don't know which way this is heading.
As somebody pointed out earlier, the only order which cannot be reconstructed after the fact is definition order (value order can be, lexical order can be, etc.). So my vote is to have the default iteration order be the original definition order, as any other desired order can be added to the class.
Thanks for the summary, that was very helpful. I find myself agreeing with every one of your opinions. Good job :) Georg
On Sun, 28 Apr 2013 13:02:11 -0700 Guido van Rossum <guido@python.org> wrote:
- for the above two, how should they be included/excluded?
IMO Everything should be enumerated except (a) things with a __get__() method (i.e. descriptors) (b) __dunder__ names
I think it would be nice to define regular methods on enums. Regards Antoine.
On 04/28/2013 02:29 PM, Antoine Pitrou wrote:
On Sun, 28 Apr 2013 13:02:11 -0700 Guido van Rossum <guido@python.org> wrote:
- for the above two, how should they be included/excluded?
IMO Everything should be enumerated except (a) things with a __get__() method (i.e. descriptors) (b) __dunder__ names
I think it would be nice to define regular methods on enums.
No worries -- regular methods have a __get__. -- ~Ethan~
On 29 Apr 2013 07:32, "Antoine Pitrou" <solipsis@pitrou.net> wrote:
On Sun, 28 Apr 2013 13:02:11 -0700 Guido van Rossum <guido@python.org> wrote:
- for the above two, how should they be included/excluded?
IMO Everything should be enumerated except (a) things with a __get__() method (i.e. descriptors) (b) __dunder__ names
I think it would be nice to define regular methods on enums.
Functions are descriptors, so this rule already covers ordinary methods. The slight concern I have with making the duck typed exclusion only descriptors (rather than descriptors and callables) is that it means things like functools.partial objects will be treated as enum values rather than as static methods. OTOH, explicitly wrapping such callables in staticmethod should still work, so the simpler rule is probably better. Cheers, Nick.
Regards
Antoine.
_______________________________________________ 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/ncoghlan%40gmail.com
On Mon, 29 Apr 2013 08:28:34 +1000 Nick Coghlan <ncoghlan@gmail.com> wrote:
On 29 Apr 2013 07:32, "Antoine Pitrou" <solipsis@pitrou.net> wrote:
On Sun, 28 Apr 2013 13:02:11 -0700 Guido van Rossum <guido@python.org> wrote:
- for the above two, how should they be included/excluded?
IMO Everything should be enumerated except (a) things with a __get__() method (i.e. descriptors) (b) __dunder__ names
I think it would be nice to define regular methods on enums.
Functions are descriptors, so this rule already covers ordinary methods.
Oh, I had missed that. Regards Antoine.
On Sun, Apr 28, 2013 at 3:28 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
Functions are descriptors, so this rule already covers ordinary methods. The slight concern I have with making the duck typed exclusion only descriptors (rather than descriptors and callables) is that it means things like functools.partial objects will be treated as enum values rather than as static methods. OTOH, explicitly wrapping such callables in staticmethod should still work, so the simpler rule is probably better.
I think the practice of using callables (other than possibly decorated functions) as global variables is confusing at best, even in a non-enum class, since few people will be able to predict whether they will get the instance passed or not. So I think the rule looking only for descriptors is superior. There has been a whole lot more discussion in this thread. I am now asking everyone to stop bikeshedding, even if you think *your* point isn't bikeshedding -- the cacophony makes it impossible to hear anyone. There are a few more issues on which I'd like to pronounce. 1. The order in which iter(Color) should produce the items. The argument has been made that definition order is the only order that is not easily reconstructed, and I agree. So let's use definition order, even if that means it can't be implemented in Python 2. 2. Whether Color(x) should be allowed, and what it should mean. Taking the example of bool, it should return an existing enum if the argument is either a Color or one of the values used for Color items (choosing one somehow if there are multiple with the same value -- it doesn't matter which one), and raise an exception if the argument is neither. E.g.: class Color(Enum): red = 1 white = 2 blue = 3 x = Color.red assert Color(x) is Color.red assert Color(1) is Color.red Color(42) # raises (2a. We could also allow Color('red') is Color.red, but that could be confusing, and we can already do that with getattr(Color, 'red'), and bool('False') doesn't return False anyway, so let's not do that.) (2b. Let's not hypergeneralize and try to make bool and enums completely alike. I see no advantage to adding bool.True and bool.False, nor in making enums "final" classes, or giving a meaning to iter(bool). Type bool is its own thing, but that doesn't mean enums can't emulate aspects of it.) Together with my pronouncements earlier in this thread I am hoping that Eli and Barry will update the PEP and implementation (let's give them a week) and everyone else will quiet down. I realize we're deviating further from flufl.enum than our initial hope, but I think the outcome is better. -- --Guido van Rossum (python.org/~guido)
On 4/28/2013 9:09 PM, Guido van Rossum wrote:
(2a. We could also allow Color('red') is Color.red, but that could be confusing, and we can already do that with getattr(Color, 'red'), and bool('False') doesn't return False anyway, so let's not do that.)
Glad you made this pronouncement in this way, because otherwise there would be ambiguity in the case: class Color(Enum): red = 'white' white = 'blue' blue = 'red' although that would no doubt drive the programmers batty anyway... but there may be instances where code is generated that could produce something ambiguous, even though this example is atrocious.
Guido van Rossum wrote:
(2a. We could also allow Color('red') is Color.red, but that could be confusing, and we can already do that with getattr(Color, 'red'),
That doesn't quite give you the same thing. Presumably Color('__str__') would be expected to raise a ValueError, for example. -- Greg
On Sun, Apr 28, 2013 at 9:09 PM, Guido van Rossum <guido@python.org> wrote:
On Sun, Apr 28, 2013 at 3:28 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
Functions are descriptors, so this rule already covers ordinary methods. The slight concern I have with making the duck typed exclusion only descriptors (rather than descriptors and callables) is that it means things like functools.partial objects will be treated as enum values rather than as static methods. OTOH, explicitly wrapping such callables in staticmethod should still work, so the simpler rule is probably better.
I think the practice of using callables (other than possibly decorated functions) as global variables is confusing at best, even in a non-enum class, since few people will be able to predict whether they will get the instance passed or not. So I think the rule looking only for descriptors is superior.
There has been a whole lot more discussion in this thread. I am now asking everyone to stop bikeshedding, even if you think *your* point isn't bikeshedding -- the cacophony makes it impossible to hear anyone.
There are a few more issues on which I'd like to pronounce.
1. The order in which iter(Color) should produce the items. The argument has been made that definition order is the only order that is not easily reconstructed, and I agree. So let's use definition order, even if that means it can't be implemented in Python 2.
2. Whether Color(x) should be allowed, and what it should mean. Taking the example of bool, it should return an existing enum if the argument is either a Color or one of the values used for Color items (choosing one somehow if there are multiple with the same value -- it doesn't matter which one), and raise an exception if the argument is neither. E.g.:
class Color(Enum): red = 1 white = 2 blue = 3
x = Color.red assert Color(x) is Color.red assert Color(1) is Color.red Color(42) # raises
(2a. We could also allow Color('red') is Color.red, but that could be confusing, and we can already do that with getattr(Color, 'red'), and bool('False') doesn't return False anyway, so let's not do that.)
(2b. Let's not hypergeneralize and try to make bool and enums completely alike. I see no advantage to adding bool.True and bool.False, nor in making enums "final" classes, or giving a meaning to iter(bool). Type bool is its own thing, but that doesn't mean enums can't emulate aspects of it.)
Together with my pronouncements earlier in this thread I am hoping that Eli and Barry will update the PEP and implementation (let's give them a week) and everyone else will quiet down. I realize we're deviating further from flufl.enum than our initial hope, but I think the outcome is better.
Sounds like a plan. Thanks for trying to converge things, Guido. Personally I don't have strong opinions about the changes inflicted by recent threads, but I may stumble into something as I dive deeper. I was away for a couple of days and a lot has changed - I'll need some time to catch up, but updating the PEP by the end of the week seems reasonable. Eli
On Sun, Apr 28, 2013 at 9:09 PM, Guido van Rossum <guido@python.org> wrote:
On Sun, Apr 28, 2013 at 3:28 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
Functions are descriptors, so this rule already covers ordinary methods. The slight concern I have with making the duck typed exclusion only descriptors (rather than descriptors and callables) is that it means things like functools.partial objects will be treated as enum values rather than as static methods. OTOH, explicitly wrapping such callables in staticmethod should still work, so the simpler rule is probably better.
I think the practice of using callables (other than possibly decorated functions) as global variables is confusing at best, even in a non-enum class, since few people will be able to predict whether they will get the instance passed or not. So I think the rule looking only for descriptors is superior.
There has been a whole lot more discussion in this thread. I am now asking everyone to stop bikeshedding, even if you think *your* point isn't bikeshedding -- the cacophony makes it impossible to hear anyone.
There are a few more issues on which I'd like to pronounce.
1. The order in which iter(Color) should produce the items. The argument has been made that definition order is the only order that is not easily reconstructed, and I agree. So let's use definition order, even if that means it can't be implemented in Python 2.
2. Whether Color(x) should be allowed, and what it should mean. Taking the example of bool, it should return an existing enum if the argument is either a Color or one of the values used for Color items (choosing one somehow if there are multiple with the same value -- it doesn't matter which one), and raise an exception if the argument is neither.
I don't feel strongly about allowing ()-lookup in addition to []-lookup, but in this paragraph the issue of multiple definitions has sneaked in :-) flufl.enum disallows this: class Color(Enum): red = 1 blue = 2 green = 1 # oops! Has it been decided that this is now allowed? If this is indeed the case, then Color(1) is a problem. The options are: A. Return either Color.red or Color.green B. Throwing an error Do we have a decision on this? Personally I think the latter is better; the former is error prone and doesn't seem to be useful too often. Eli [trying to tie loose ends for updating the PEP].
On 04/29/2013 06:51 AM, Eli Bendersky wrote:
flufl.enum disallows this:
class Color(Enum): red = 1 blue = 2 green = 1 # oops!
Has it been decided that this is now allowed? If this is indeed the case, then Color(1) is a problem. The options are:
A. Return either Color.red or Color.green B. Throwing an error
Do we have a decision on this? Personally I think the latter is better; the former is error prone and doesn't seem to be useful too often.
Eli [trying to tie loose ends for updating the PEP].
Would you disallow this too? class Color(Enum): red = 1 blue = 2 Color.green = Color.red IMO the more Pythonic behavior would be to allow multiple names for the same value, and your example should produce the same result as my example. I'm not convinced enums are special enough to break the rules. See also "consenting adults", //arry/
On 04/29/2013 09:30 AM, Larry Hastings wrote:
On 04/29/2013 06:51 AM, Eli Bendersky wrote:
flufl.enum disallows this:
class Color(Enum): red = 1 blue = 2 green = 1 # oops!
Has it been decided that this is now allowed? If this is indeed the case, then Color(1) is a problem. The options are:
At this point I think the best course is to not allow duplicates directly in the enum definition, but allow them after the fact: --> class Color(Enum): ... red = 1 ... green = 2 ... blue = 3 --> Color.grene = Color.green # stupid legacy typo This gives us both some protection against accidental duplicates, while still allowing them when necessary; also, no confusion about which is the canonical name. Guido? -- ~Ethan~
On Mon, Apr 29, 2013 at 9:47 AM, Ethan Furman <ethan@stoneleaf.us> wrote:
At this point I think the best course is to not allow duplicates directly in the enum definition, but allow them after the fact:
--> class Color(Enum): ... red = 1 ... green = 2 ... blue = 3
--> Color.grene = Color.green # stupid legacy typo
This gives us both some protection against accidental duplicates, while still allowing them when necessary; also, no confusion about which is the canonical name.
No. Poking a class has a code smell. It should be possible to define the aliases inside the enum class definition. The canonical one is the first one in definition order. -- --Guido van Rossum (python.org/~guido)
On Mon, Apr 29, 2013 at 6:51 AM, Eli Bendersky <eliben@gmail.com> wrote:
I don't feel strongly about allowing ()-lookup in addition to []-lookup, but in this paragraph the issue of multiple definitions has sneaked in :-) flufl.enum disallows this:
class Color(Enum): red = 1 blue = 2 green = 1 # oops!
Has it been decided that this is now allowed?
I don't recall if it was decided. I think it should be possible to create aliases like this. The main thing I care about is that Color.green == Color.red. I wouldn't mind if Color.green ends up as just a different name for the Color.red object. The most common use case is probably providing backwards compatibility with an old version of the enum when renaming something -- e.g. one could write class Color(Enum): ... turquoise = 42 aqua = turquoise # I can't tell them apart Here the metaclass would see two different names with the same value (42).
If this is indeed the case, then Color(1) is a problem. The options are:
A. Return either Color.red or Color.green B. Throwing an error
Do we have a decision on this? Personally I think the latter is better; the former is error prone and doesn't seem to be useful too often.
I think it should be A, and the choice should be the first one in definition order. -- --Guido van Rossum (python.org/~guido)
On 30/04/13 02:42, Guido van Rossum wrote:
On Mon, Apr 29, 2013 at 6:51 AM, Eli Bendersky <eliben@gmail.com> wrote:
I don't feel strongly about allowing ()-lookup in addition to []-lookup, but in this paragraph the issue of multiple definitions has sneaked in :-) flufl.enum disallows this:
class Color(Enum): red = 1 blue = 2 green = 1 # oops!
Has it been decided that this is now allowed?
I don't recall if it was decided. I think it should be possible to create aliases like this. The main thing I care about is that Color.green == Color.red.
I believe that Barry had decided that it should be prohibited. I objected, and Nick pointed out that although declaring two enums with the same value inside the class is prohibited, aliases are supported by adding them from the outside: class Color(Enum): red = 1 blue = 2 Color.green = Color.red which satisfies me. -- Steven
On 04/29/2013 10:35 AM, Steven D'Aprano wrote:
On 30/04/13 02:42, Guido van Rossum wrote:
On Mon, Apr 29, 2013 at 6:51 AM, Eli Bendersky <eliben@gmail.com> wrote:
I don't feel strongly about allowing ()-lookup in addition to []-lookup, but in this paragraph the issue of multiple definitions has sneaked in :-) flufl.enum disallows this:
class Color(Enum): red = 1 blue = 2 green = 1 # oops!
Has it been decided that this is now allowed?
I don't recall if it was decided. I think it should be possible to create aliases like this. The main thing I care about is that Color.green == Color.red.
I believe that Barry had decided that it should be prohibited. I objected, and Nick pointed out that although declaring two enums with the same value inside the class is prohibited, aliases are supported by adding them from the outside:
class Color(Enum): red = 1 blue = 2
Color.green = Color.red
which satisfies me.
Assuming that Color(1) always returns the same object, then we could also write this: class Color(Enum): red = 1 blue = 2 Color.green = Color(1) Which should be identical to class Color(Enum): red = 1 blue = 2 green = 1 To declare that my first example is okay but the second is not strikes me as awfully special. And I do mean awful. //arry/
On 29/04/13 06:02, Guido van Rossum wrote:
My opinions added
On Sun, Apr 28, 2013 at 12:32 PM, Ethan Furman <ethan@stoneleaf.us> wrote:
Definite Issues:
- should enum items be of the type of the Enum class? (i.e. type(SPRING) is Seasons)
IMO Yes.
+1
- should an enum item be selectable via __call__ instead of __getitem__ (i.e. Seasons(3) is AUTUMN)
No opinion.
Does anyone know why this is even an issue? Is this pure bike-shedding over the API, or are there technical reasons for choosing one over the other?
- should days_in_year be enumerated?
Yes. (If you don't want it to be, and it's not a method/descriptor, move it out of the class.)
+1, but see below.
- should avg_temp be enumerated?
IMO No.
- for the above two, how should they be included/excluded?
IMO Everything should be enumerated except (a) things with a __get__() method (i.e. descriptors) (b) __dunder__ names
+1 on excluding dunder names. +0 on excluding things with __get__. I have also suggested that that the enum package provide a decorator which can be used to explicitly flag values to *not* be turned into enum values. See here: http://mail.python.org/pipermail/python-dev/2013-April/125641.html Even if the Enum class doesn't support this feature, I ask that it be written in such a way that a subclass could add it (i.e. please expose a public method for deciding what to exclude). -- Steven
On Sun, Apr 28, 2013 at 7:37 PM, Steven D'Aprano <steve@pearwood.info> wrote:
I have also suggested that that the enum package provide a decorator which can be used to explicitly flag values to *not* be turned into enum values. See here:
http://mail.python.org/pipermail/python-dev/2013-April/125641.html
In that example, 'food = property(lambda:"skip")' would work in a pinch. (Granted, it wouldn't be a *class* attribute, but you can make a class attribute by assiging it after class creation is completed.) And if you want to make your enum instances callable, ISTM the right (or at least the One Obvious) way to do it is to add a __call__ method to the class.
Even if the Enum class doesn't support this feature, I ask that it be written in such a way that a subclass could add it (i.e. please expose a public method for deciding what to exclude).
Since you can exclude anything by it having a __get__ method, or include it by making it *not* have a __get__ method, I'm not sure what use case you're actually looking for.
On 4/28/2013 4:37 PM, Steven D'Aprano wrote:
I have also suggested that that the enum package provide a decorator which can be used to explicitly flag values to *not* be turned into enum values. See here:
http://mail.python.org/pipermail/python-dev/2013-April/125641.html
Even if the Enum class doesn't support this feature, I ask that it be written in such a way that a subclass could add it (i.e. please expose a public method for deciding what to exclude).
That last comment is very interesting to me... I think it would be good if the enum implementation doesn't prevent the creation of a subclass of integer enumerations which can be extended so that when arithmetic is done on them, that new enumeration values and names can be added to the collection (think flag fields). The add ones might have names that do not follow the rules for identifiers.
On 04/28/2013 04:37 PM, Steven D'Aprano wrote:
On Sun, Apr 28, 2013 at 12:32 PM, Ethan Furman <ethan@stoneleaf.us> wrote:
- should an enum item be selectable via __call__ instead of __getitem__ (i.e. Seasons(3) is AUTUMN)
Does anyone know why this is even an issue? Is this pure bike-shedding over the API, or are there technical reasons for choosing one over the other?
This is an issue because currently every other type* in Python creates (or selects ;) its instances via the call syntax: - bool(1) # True - int('11') # 11 - str(var) # whatever var had in it, now as a str But one of the latest changes to flufl.enum was to take out the call syntax, and have only getitem syntax - Season('AUTUMN') # raises an exception - Season['AUTUMN'] # Season.AUTUMN Not only is this inconsistent with the rest of Python*, but it's going to be a PITA for data storage/retrieval: datastore = dbf.Table('storage.dbf', 'event_name C(50); date D; season SEASON') def retrieve_record(...): result = [] for field_type, field_data in record: result.append(field_type(field_data)) vs. def retrieve_record(...): result = [] for field_type, field_data in record: if isinstance(field_type, Enum): result.append(field_type[field_data]) else: result.append(field_type(field_data)) Not being able to use call syntax on the Enum class unnecessarily complicates other code. -- ~Ethan~ * Please correct me if I'm wrong.
On 29/04/13 10:29, Ethan Furman wrote:
On 04/28/2013 04:37 PM, Steven D'Aprano wrote:
On Sun, Apr 28, 2013 at 12:32 PM, Ethan Furman <ethan@stoneleaf.us> wrote:
- should an enum item be selectable via __call__ instead of __getitem__ (i.e. Seasons(3) is AUTUMN)
Does anyone know why this is even an issue? Is this pure bike-shedding over the API, or are there technical reasons for choosing one over the other?
This is an issue because currently every other type* in Python creates (or selects ;) its instances via the call syntax:
- bool(1) # True - int('11') # 11 - str(var) # whatever var had in it, now as a str
I think that's a red herring, because you're comparing the use of the object constructor with look-up by name. Seasons[3] should not be considered as constructing an instance from argument 3, but a reverse lookup from raw value to enum value. Hence the use of __getitem__ rather than __call__.
But one of the latest changes to flufl.enum was to take out the call syntax, and have only getitem syntax
- Season('AUTUMN') # raises an exception - Season['AUTUMN'] # Season.AUTUMN
I'm not sure whether flufl.enums support creating additional instances after the event, but if it did, I would expect that I could say Season('WET') to get a new instance. I am indifferent to whether or not Season('AUTUMN') should return the existing AUTUMN enum value. I think that I lean very slightly to these rules: - Season(x) constructs new instances. If x is already a Season enum, it returns x; otherwise if x is a value already used for a Season enum, it raises. - Season[x] looks up existing instances, and raises if x is not already a Season value. but only very slightly.
Not only is this inconsistent with the rest of Python*, but it's going to be a PITA for data storage/retrieval:
datastore = dbf.Table('storage.dbf', 'event_name C(50); date D; season SEASON')
def retrieve_record(...): result = [] for field_type, field_data in record: result.append(field_type(field_data))
Instead of having field_type be Season, couldn't you make it Season.__getitem__ ? -- Steven
On 04/28/2013 06:52 PM, Steven D'Aprano wrote:
On 29/04/13 10:29, Ethan Furman wrote:
On 04/28/2013 04:37 PM, Steven D'Aprano wrote:
On Sun, Apr 28, 2013 at 12:32 PM, Ethan Furman <ethan@stoneleaf.us> wrote:
- should an enum item be selectable via __call__ instead of __getitem__ (i.e. Season(3) is AUTUMN)
Does anyone know why this is even an issue? Is this pure bike-shedding over the API, or are there technical reasons for choosing one over the other?
This is an issue because currently every other type* in Python creates (or selects ;) its instances via the call syntax:
- bool(1) # True - int('11') # 11 - str(var) # whatever var had in it, now as a str
I think that's a red herring, because you're comparing the use of the object constructor with look-up by name.
int, float, and bool all have object constructors that take the given string and return a matching instance; int /may/ return a pre-existing instance, bool /will/ return a pre-existing instance. As I've stated before, 'bool's are the closest existing data type to enums, in that bools use the object constructor to convert the incoming parameter to an existing bool instance, of which there will only ever be two. bool(9) is bool('maybe') is bool(True) is True and similarly, Enum behavior /should be/ (in my opinion ;) Season.AUTUMN is Season('AUTUMN') is Season(3) Like it or not, when you write `class Season(Enum)` a new *type* is created called Season, and Season(x) should either return a Season instance that matches x or raise. Having a match and raising anyway just doesn't seem to me to be the Python Way.
But one of the latest changes to flufl.enum was to take out the call syntax, and have only getitem syntax
- Season('AUTUMN') # raises an exception - Season['AUTUMN'] # Season.AUTUMN
I'm not sure whether flufl.enums support creating additional instances after the event, but if it did, I would expect that I could say Season('WET') to get a new instance. I am indifferent to whether or not Season('AUTUMN') should return the existing AUTUMN enum value.
Adding more enumerators after the fact should not be supported; there is subclassing for that. Not worth actively preventing, but not encouraged. -- ~Ethan~
On 28Apr2013 19:46, Ethan Furman <ethan@stoneleaf.us> wrote: | int, float, and bool all have object constructors that take the | given string and return a matching instance; int /may/ return a | pre-existing instance, bool /will/ return a pre-existing instance. I think Guido's already pointed out this:
bool('False') True
| As I've stated before, 'bool's are the closest existing data type to | enums, in that bools use the object constructor to convert the | incoming parameter to an existing bool instance, of which there will | only ever be two. | | bool(9) is bool('maybe') is bool(True) is True | | and similarly, Enum behavior /should be/ (in my opinion ;) | | Season.AUTUMN is Season('AUTUMN') is Season(3) I think that would be _efficient_, but as an outside user I wouldn't necessarily expect it unless I'd ready the spec very closely and the spec was explicit about it. Coming from (in the fairly distant past) a C background, I naively think of enums as nicely named ordinals of some kind and expect to compare them with ==, not "is". If you want to guarantee "is", be my guest though. I don't know how that might make things go with some hypothetical subclass with many many and extensible enum values. | Like it or not, when you write `class Season(Enum)` a new *type* is | created called Season, and Season(x) should either return a Season | instance that matches x or raise. Having a match and raising anyway | just doesn't seem to me to be the Python Way. I'm +1 on all of this. [...] | >I'm not sure whether flufl.enums support creating additional instances after the event, but if it did, I would expect | >that I could say Season('WET') to get a new instance. I am indifferent to whether or not Season('AUTUMN') should return | >the existing AUTUMN enum value. | | Adding more enumerators after the fact should not be supported; | there is subclassing for that. Not worth actively preventing, but | not encouraged. I'd go a bit further here: I'd take this final sentence as being -0 on preventing adding more enumerals(?), whereas I'm a solid -1 on preventing it. By all means not actively support it, but very against doing things that make it hard for a subclass to support it. Just 2c, -- Would you remember a one-line .sig? - Paul Thompson, thompson@apple.com
Cameron Simpson wrote:
I'd go a bit further here: I'd take this final sentence as being -0 on preventing adding more enumerals(?), whereas I'm a solid -1 on preventing it. By all means not actively support it, but very against doing things that make it hard for a subclass to support it.
I had another thought about subclassing. I don't think it would be a good idea to totally forbid subclassing classes derived from Enum, because you may want to define an Enum subclass for the purpose of creating a new *kind* of enum. For example, class FancyEnum(Enum): def fancy_str(self): return str(self) + " with bells on" If subclassing a subclass of Enum were prevented somehow, you would't be able to create any actual enums based on FancyEnum. -- Greg
On Apr 28, 2013, at 07:46 PM, Ethan Furman wrote:
and similarly, Enum behavior /should be/ (in my opinion ;)
Season.AUTUMN is Season('AUTUMN') is Season(3)
I think you'll have a problem with this. flufl.enum did this, but it has an inherent conflict, which is why we removed the getattr-like behavior. class Things(Enum): foo = 'bar' bar = 'foo' What does Things('foo') return? Note that it doesn't matter if that's spelled Things['foo']. Whether it's defined as lookup or instance "creation", you should only map values to items, and not attribute names to items, and definitely not both. Let getattr() do attribute name lookup just like normal. -Barry
On 4/30/2013 11:08 PM, Barry Warsaw wrote:
On Apr 28, 2013, at 07:46 PM, Ethan Furman wrote:
and similarly, Enum behavior /should be/ (in my opinion ;)
Season.AUTUMN is Season('AUTUMN') is Season(3) I think you'll have a problem with this. flufl.enum did this, but it has an inherent conflict, which is why we removed the getattr-like behavior.
class Things(Enum): foo = 'bar' bar = 'foo'
What does Things('foo') return?
Note that it doesn't matter if that's spelled Things['foo'].
Whether it's defined as lookup or instance "creation", you should only map values to items, and not attribute names to items, and definitely not both. Let getattr() do attribute name lookup just like normal.
I agree that it is confusing to be able to index by either the name of the enum or its value, in the same method. The current implementation prefers the names, but will check values if the name is not found, I discovered by experimentation, after reading the tests. But when there are conflicts (which would be confusing at best), the inability to look up some enumerations by value, because the one with that name is found first, would be even more confusing. Can Things('foo') lookup by name and Things['foo'] lookup by value? Or does that confuse things too?
On May 01, 2013, at 12:19 AM, Glenn Linderman wrote:
Can Things('foo') lookup by name and Things['foo'] lookup by value? Or does that confuse things too?
I think it confuses things too much. Why isn't getattr() for lookup by name good enough? It is for regular classes. -Barry
On May 02, 2013, at 11:44 AM, Greg Ewing wrote:
Barry Warsaw wrote:
Why isn't getattr() for lookup by name good enough?
Because it will find things that are not enum items, e.g. '__str__'.
Why does that matter? -Barry
On Thu, May 2, 2013 at 7:58 AM, Barry Warsaw <barry@python.org> wrote:
On May 02, 2013, at 11:44 AM, Greg Ewing wrote:
Barry Warsaw wrote:
Why isn't getattr() for lookup by name good enough?
Because it will find things that are not enum items, e.g. '__str__'.
Why does that matter?
I claim it doesn't. The name lookup is only relevant if you already know that you have a valid name of an enum in the class, e.g. if you know that a Color name was written earlier. If you don't, you should do some other check, e.g. "if x in Color:". (Note that from this you cannot derive that Color[x] should work.) -- --Guido van Rossum (python.org/~guido)
Guido van Rossum wrote:
you should do some other check, e.g. "if x in Color:".
So you don't think it's important to have an easy way to take user input that's supposed to be a Color name and either return a Color or raise a ValueError? -- Greg
On 05/02/2013 04:43 PM, Greg Ewing wrote:
Guido van Rossum wrote:
you should do some other check, e.g. "if x in Color:".
So you don't think it's important to have an easy way to take user input that's supposed to be a Color name and either return a Color or raise a ValueError?
I don't believe that's what he said:
The name lookup is only relevant if you already know that you have a valid name of an enum in the class [...]
User input should qualify, and using getattr(EnumClass, user_input) will get you an AttributeError instead of a ValueError if user_input is not valid, but surely you don't mind that small difference. ;) -- ~Ethan~
On Fri, May 3, 2013 at 9:57 AM, Ethan Furman <ethan@stoneleaf.us> wrote:
On 05/02/2013 04:43 PM, Greg Ewing wrote:
Guido van Rossum wrote:
you should do some other check, e.g. "if x in Color:".
So you don't think it's important to have an easy way to take user input that's supposed to be a Color name and either return a Color or raise a ValueError?
I don't believe that's what he said:
The name lookup is only relevant if you already know that you have a valid name of an enum in the class [...]
User input should qualify, and using getattr(EnumClass, user_input) will get you an AttributeError instead of a ValueError if user_input is not valid, but surely you don't mind that small difference. ;)
int(getattr(C(), "__str__")) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: int() argument must be a string or a number, not 'method-wrapper'
That's the problem Greg is complaining about: when you use getattr to do the name->enum member conversion, you have to do your own checking to exclude method names. This is part of why I think enums should offer an "as_dict()" method that returns an ordered dictionary. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
On May 03, 2013, at 11:06 AM, Nick Coghlan wrote:
User input should qualify, and using getattr(EnumClass, user_input) will get you an AttributeError instead of a ValueError if user_input is not valid, but surely you don't mind that small difference. ;)
int(getattr(C(), "__str__")) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: int() argument must be a string or a number, not 'method-wrapper'
That's the problem Greg is complaining about: when you use getattr to do the name->enum member conversion, you have to do your own checking to exclude method names.
This is part of why I think enums should offer an "as_dict()" method that returns an ordered dictionary.
Should this be allowed then? class Transformations(Enum): as_int = 1 as_dict = 2 as_tuple = 3 ? I still don't get it why this is an issue though, or at least why this is different than any other getattr on any other class, or even Enums. I mean, you could do a getattr on any other class or instance with any random user input and there's no guarantee you could pass it straight to int() or any other conversion type. So you pretty much have to be prepared to capture exceptions anyway. -Barry
Barry Warsaw wrote:
I still don't get it why this is an issue though, or at least why this is different than any other getattr on any other class,
It's not a problem that getattr() has this behaviour. What I'm questioning is the idea that getattr() should be the only provided way of doing a name->enum lookup, because that will require everyone to do extra checks to ensure safety. -- Greg
On Fri, May 3, 2013 at 6:34 AM, Greg Ewing <greg.ewing@canterbury.ac.nz>wrote:
Barry Warsaw wrote:
I still don't get it why this is an issue though, or at least why this is different than any other getattr on any other class,
It's not a problem that getattr() has this behaviour. What I'm questioning is the idea that getattr() should be the only provided way of doing a name->enum lookup, because that will require everyone to do extra checks to ensure safety.
I'm just curious what it is about enums that sets everyone on a "let's make things safer" path. Python is about duck typing, it's absolutely "unsafe" in the static typing sense, in the most fundamental ways imaginable. When programmatically invoking a method on a class (say some sort of RPC), we don't check that the class is of the correct type. We invoke a method, and if it quacks, that's a good enough duck. If it was actually the wrong class, something will break later. EAFP Is a central Python tenet, whether we like it or not. If one looks for static guarantees, Python surely shouldn't be the preferred language, no? And concretely, how is this case different from any programmatic attribute access in Python objects? You can pass dunders to getattr() and it probably wasn't what you meant, but Python does not do this type checking for you. Why is an Enum different than any other class? Eli
On Fri, May 3, 2013 at 7:14 AM, Eli Bendersky <eliben@gmail.com> wrote:
I'm just curious what it is about enums that sets everyone on a "let's make things safer" path. Python is about duck typing, it's absolutely "unsafe" in the static typing sense, in the most fundamental ways imaginable. When programmatically invoking a method on a class (say some sort of RPC), we don't check that the class is of the correct type. We invoke a method, and if it quacks, that's a good enough duck. If it was actually the wrong class, something will break later. EAFP Is a central Python tenet, whether we like it or not. If one looks for static guarantees, Python surely shouldn't be the preferred language, no?
And concretely, how is this case different from any programmatic attribute access in Python objects? You can pass dunders to getattr() and it probably wasn't what you meant, but Python does not do this type checking for you. Why is an Enum different than any other class?
Let's make that a topic for a separate, more philosophical thread, python-ideas. Back to this particular issue, I haven't seen code in the style that Greg proposes in decades, and I don't think it is an important enough use case to support more directly than through getattr() + isinstance(). -- --Guido van Rossum (python.org/~guido)
On Fri, May 3, 2013 at 4:08 PM, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Guido van Rossum wrote:
I haven't seen code in the style that Greg proposes in decades,
What style are you talking about here?
Code that wants to validate a string the user typed as input. Web forms just don't work that way. (Command-line flags are a special case, and there are a slew of specialized parsers for that case.) -- --Guido van Rossum (python.org/~guido)
On 4 May 2013 09:34, "Guido van Rossum" <guido@python.org> wrote:
On Fri, May 3, 2013 at 4:08 PM, Greg Ewing <greg.ewing@canterbury.ac.nz>
wrote:
Guido van Rossum wrote:
I haven't seen code in the style that Greg proposes in decades,
What style are you talking about here?
Code that wants to validate a string the user typed as input. Web forms just don't work that way. (Command-line flags are a special case, and there are a slew of specialized parsers for that case.)
And for code that really needs it, it is straightforward to use dir(MyEnum) and isinstance(obj, MyEnum) to get an exact mapping of names to values that also accounts for aliases. Cheers, Nick.
-- --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/ncoghlan%40gmail.com
Guido van Rossum wrote:
Code that wants to validate a string the user typed as input. Web forms just don't work that way.
Maybe "validation" was a misleading term to use. To be more precise, I'm talking about taking input to the program (it needn't come directly from a user, it could be read from a file or database) that is supposed to be the name of a Color, and turning it into a Color instance. For that purpose, it's convenient to have a function with only two possible outcomes: it either returns a Color instance, or raises a ValueError. The point is that you *shouldn't* have to perform a separate validation step. You should be able to use EAFP -- go ahead and perform the conversion, but be prepared to catch a ValueError at some level and report it to the user. -- Greg
Eli Bendersky wrote:
I'm just curious what it is about enums that sets everyone on a "let's make things safer" path. Python is about duck typing, it's absolutely "unsafe" in the static typing sense, in the most fundamental ways imaginable.
This isn't about catching bugs in the program, it's about validating user input. That's a common enough task that it deserves to have a convenient way to do it correctly. Imagine if int() had the property that, as well as accepting strings of decimal digits, it also accepted the string "guido" and returned his birthday as a DateTime object. When people complain, they're told it's okay, you only need to write if s != "guido": x = int(s) else: raise ValueError What would you think of that situation?
Why is an Enum different than any other class?
It's not, that's the whole point. IMO it deserves to have a convenient way of mapping a valid string representation -- and nothing else -- to a valid value, just as much as any other type does. -- Greg
On Sat, 04 May 2013 11:15:17 +1200 Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Eli Bendersky wrote:
I'm just curious what it is about enums that sets everyone on a "let's make things safer" path. Python is about duck typing, it's absolutely "unsafe" in the static typing sense, in the most fundamental ways imaginable.
This isn't about catching bugs in the program, it's about validating user input. That's a common enough task that it deserves to have a convenient way to do it correctly.
+1. An enum is basically a bidirectional mapping between some raw values and some "nice" instances, so it deserves a well-defined lookup operation in each direction. Regards Antoine.
Am 04.05.2013 01:22, schrieb Antoine Pitrou:
On Sat, 04 May 2013 11:15:17 +1200 Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Eli Bendersky wrote:
I'm just curious what it is about enums that sets everyone on a "let's make things safer" path. Python is about duck typing, it's absolutely "unsafe" in the static typing sense, in the most fundamental ways imaginable.
This isn't about catching bugs in the program, it's about validating user input. That's a common enough task that it deserves to have a convenient way to do it correctly.
+1. An enum is basically a bidirectional mapping between some raw values and some "nice" instances, so it deserves a well-defined lookup operation in each direction.
Agreed. Georg
On Sat, May 4, 2013 at 4:10 PM, Georg Brandl <g.brandl@gmx.net> wrote:
Am 04.05.2013 01:22, schrieb Antoine Pitrou:
On Sat, 04 May 2013 11:15:17 +1200 Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Eli Bendersky wrote:
I'm just curious what it is about enums that sets everyone on a "let's make things safer" path. Python is about duck typing, it's absolutely "unsafe" in the static typing sense, in the most fundamental ways imaginable.
This isn't about catching bugs in the program, it's about validating user input. That's a common enough task that it deserves to have a convenient way to do it correctly.
+1. An enum is basically a bidirectional mapping between some raw values and some "nice" instances, so it deserves a well-defined lookup operation in each direction.
As I see it, there are 3 possible ways forward here: 1. The current PEP, offering only "getattr(MyEnum, name)". If code needs to ensure non-enum values are detected immediately (such as during translation of user input entered at a command prompt), then they can either create a separate mapping using: lookup = {m.name, m for m in (getattr(MyEnum, name) for name in dir(MyEnum)) if isinstance(m, MyEnum)} or else create a lookup function: def getmember(enum, name): m = getattr(enum, name, None) if not isinstance(m, enum): raise KeyError("{!r} is not a member of {!r}".format(name, enum)) return m 2. We restore __getitem__ on EnumMetaclass *solely* for member lookup by name (the "getmember" functionality above). This would leave __call__ used for the reverse lookup (value to member and hence name) and __getitem__ for the forward lookup (name to member and hence value) (Note: given Ethan's comments about his current implementation, I believe this actually fits nicely with the way EnumMetaclass.__getattr__ is already implemented) 3. We offer my earlier suggestion of an "as_dict()" method on the metaclass, which implements the mapping calculation above. As others pointed out, this has the same name clash problem as offering additional non-special methods on namedtuple objects. I'm now -1 on my own as_dict() suggestion, due to the general name clash problem for arbitrary enums. Options 1 and 2 both sound reasonable to me, although I have a preference for 2 due to the ability to produce a more appropriate error message when the lookup fails. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
Nick Coghlan wrote:
1. The current PEP, offering only "getattr(MyEnum, name)".
2. We restore __getitem__ on EnumMetaclass *solely* for member lookup by name
3. Use keyword arguments to distinguish two different ways of calling the enum class: MyEnum(value = 1) --> lookup by value MyEnum(name = "foo") --> lookup by name MyEnum(1) could be made equivalent to MyEnum(value = 1) if it's thought that lookup by value will be the most common or natural case. Pros: Explicit is better than implicit. Cons: Not so convenient to get a type-conversion function to pass to other things. -- Greg
On 4 May 2013 07:42, "Nick Coghlan" <ncoghlan@gmail.com> wrote:
2. We restore __getitem__ on EnumMetaclass *solely* for member lookup by name (the "getmember" functionality above). This would leave __call__ used for the reverse lookup (value to member and hence name) and __getitem__ for the forward lookup (name to member and hence value) (Note: given Ethan's comments about his current implementation, I believe this actually fits nicely with the way EnumMetaclass.__getattr__ is already implemented)
This has the advantage of leaving one obvious way to do the 'reverse' lookup (namely __call__), rather than two redundant alternatives. Cheers, Phil
On Sat, 4 May 2013 16:42:08 +1000 Nick Coghlan <ncoghlan@gmail.com> wrote:
On Sat, May 4, 2013 at 4:10 PM, Georg Brandl <g.brandl@gmx.net> wrote:
Am 04.05.2013 01:22, schrieb Antoine Pitrou:
On Sat, 04 May 2013 11:15:17 +1200 Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Eli Bendersky wrote:
I'm just curious what it is about enums that sets everyone on a "let's make things safer" path. Python is about duck typing, it's absolutely "unsafe" in the static typing sense, in the most fundamental ways imaginable.
This isn't about catching bugs in the program, it's about validating user input. That's a common enough task that it deserves to have a convenient way to do it correctly.
+1. An enum is basically a bidirectional mapping between some raw values and some "nice" instances, so it deserves a well-defined lookup operation in each direction.
As I see it, there are 3 possible ways forward here:
4. Offer classmethods named Enum.by_name() and Enum.by_value(). Simple and explicit. Regards Antoine.
On 05/04/2013 04:33 AM, Antoine Pitrou wrote:
On Sat, 4 May 2013 16:42:08 +1000 Nick Coghlan <ncoghlan@gmail.com> wrote:
On Sat, May 4, 2013 at 4:10 PM, Georg Brandl <g.brandl@gmx.net> wrote:
Am 04.05.2013 01:22, schrieb Antoine Pitrou:
On Sat, 04 May 2013 11:15:17 +1200 Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Eli Bendersky wrote:
I'm just curious what it is about enums that sets everyone on a "let's make things safer" path. Python is about duck typing, it's absolutely "unsafe" in the static typing sense, in the most fundamental ways imaginable.
This isn't about catching bugs in the program, it's about validating user input. That's a common enough task that it deserves to have a convenient way to do it correctly.
+1. An enum is basically a bidirectional mapping between some raw values and some "nice" instances, so it deserves a well-defined lookup operation in each direction.
As I see it, there are 3 possible ways forward here:
4. Offer classmethods named Enum.by_name() and Enum.by_value(). Simple and explicit.
And then you can't have enum items named by_name and by_value. -- ~Ethan~
On Sat, 04 May 2013 06:37:23 -0700 Ethan Furman <ethan@stoneleaf.us> wrote:
+1. An enum is basically a bidirectional mapping between some raw values and some "nice" instances, so it deserves a well-defined lookup operation in each direction.
As I see it, there are 3 possible ways forward here:
4. Offer classmethods named Enum.by_name() and Enum.by_value(). Simple and explicit.
And then you can't have enum items named by_name and by_value.
You can. Normal shadowing rules apply. By the same token, you can't have enum items named __str__ or __init__. How is that a problem? Attribute resolution rules imply some restrictions, which are well-known to all Python programmers. But, really, you can decide on another name if you like: __byname__ or _byname, etc. My point is simply that lookup doesn't *have* to invoke operators, and explicitly named classmethods are less confusing than repurposed operators. Regards Antoine.
On 5/4/2013 2:42 AM, Nick Coghlan wrote:
On Sat, May 4, 2013 at 4:10 PM, Georg Brandl <g.brandl@gmx.net> wrote:
Am 04.05.2013 01:22, schrieb Antoine Pitrou:
On Sat, 04 May 2013 11:15:17 +1200 Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Eli Bendersky wrote:
I'm just curious what it is about enums that sets everyone on a "let's make things safer" path. Python is about duck typing, it's absolutely "unsafe" in the static typing sense, in the most fundamental ways imaginable.
This isn't about catching bugs in the program, it's about validating user input. That's a common enough task that it deserves to have a convenient way to do it correctly.
+1. An enum is basically a bidirectional mapping between some raw values and some "nice" instances, so it deserves a well-defined lookup operation in each direction.
As I see it, there are 3 possible ways forward here:
1. The current PEP, offering only "getattr(MyEnum, name)".
If code needs to ensure non-enum values are detected immediately (such as during translation of user input entered at a command prompt), then they can either create a separate mapping using:
lookup = {m.name, m for m in (getattr(MyEnum, name) for name in dir(MyEnum)) if isinstance(m, MyEnum)}
or else create a lookup function:
def getmember(enum, name): m = getattr(enum, name, None) if not isinstance(m, enum): raise KeyError("{!r} is not a member of {!r}".format(name, enum)) return m
2. We restore __getitem__ on EnumMetaclass *solely* for member lookup by name (the "getmember" functionality above). This would leave __call__ used for the reverse lookup (value to member and hence name) and __getitem__ for the forward lookup (name to member and hence value) (Note: given Ethan's comments about his current implementation, I believe this actually fits nicely with the way EnumMetaclass.__getattr__ is already implemented)
3. We offer my earlier suggestion of an "as_dict()" method on the metaclass, which implements the mapping calculation above. As others pointed out, this has the same name clash problem as offering additional non-special methods on namedtuple objects.
I'm now -1 on my own as_dict() suggestion, due to the general name clash problem for arbitrary enums.
To avoid the name collision, namedtuple calls this _asdict(). -- Eric.
On 05/04/2013 07:01 AM, Eric V. Smith wrote:
On 5/4/2013 2:42 AM, Nick Coghlan wrote:
I'm now -1 on my own as_dict() suggestion, due to the general name clash problem for arbitrary enums. To avoid the name collision, namedtuple calls this _asdict().
Although I recall Raymond told me he should have called it asdict_(), and reserved all identifiers with trailing underscores. //arry/
Hm. Trailing underscores look *really* weird to me. On Sat, May 4, 2013 at 3:41 PM, Larry Hastings <larry@hastings.org> wrote:
On 05/04/2013 07:01 AM, Eric V. Smith wrote:
On 5/4/2013 2:42 AM, Nick Coghlan wrote:
I'm now -1 on my own as_dict() suggestion, due to the general name clash problem for arbitrary enums.
To avoid the name collision, namedtuple calls this _asdict().
Although I recall Raymond told me he should have called it asdict_(), and reserved all identifiers with trailing underscores.
/arry
_______________________________________________ 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)
Just to stop the bikeshedding, let's do #2. Put back __getitem__ solely for lookup by name. Keep __call__ (really __new__) for lookup by value or "pass-through" for members. --Guido On Fri, May 3, 2013 at 11:42 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
On Sat, May 4, 2013 at 4:10 PM, Georg Brandl <g.brandl@gmx.net> wrote:
Am 04.05.2013 01:22, schrieb Antoine Pitrou:
On Sat, 04 May 2013 11:15:17 +1200 Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Eli Bendersky wrote:
I'm just curious what it is about enums that sets everyone on a "let's make things safer" path. Python is about duck typing, it's absolutely "unsafe" in the static typing sense, in the most fundamental ways imaginable.
This isn't about catching bugs in the program, it's about validating user input. That's a common enough task that it deserves to have a convenient way to do it correctly.
+1. An enum is basically a bidirectional mapping between some raw values and some "nice" instances, so it deserves a well-defined lookup operation in each direction.
As I see it, there are 3 possible ways forward here:
1. The current PEP, offering only "getattr(MyEnum, name)".
If code needs to ensure non-enum values are detected immediately (such as during translation of user input entered at a command prompt), then they can either create a separate mapping using:
lookup = {m.name, m for m in (getattr(MyEnum, name) for name in dir(MyEnum)) if isinstance(m, MyEnum)}
or else create a lookup function:
def getmember(enum, name): m = getattr(enum, name, None) if not isinstance(m, enum): raise KeyError("{!r} is not a member of {!r}".format(name, enum)) return m
2. We restore __getitem__ on EnumMetaclass *solely* for member lookup by name (the "getmember" functionality above). This would leave __call__ used for the reverse lookup (value to member and hence name) and __getitem__ for the forward lookup (name to member and hence value) (Note: given Ethan's comments about his current implementation, I believe this actually fits nicely with the way EnumMetaclass.__getattr__ is already implemented)
3. We offer my earlier suggestion of an "as_dict()" method on the metaclass, which implements the mapping calculation above. As others pointed out, this has the same name clash problem as offering additional non-special methods on namedtuple objects.
I'm now -1 on my own as_dict() suggestion, due to the general name clash problem for arbitrary enums.
Options 1 and 2 both sound reasonable to me, although I have a preference for 2 due to the ability to produce a more appropriate error message when the lookup fails.
Cheers, Nick.
-- Nick Coghlan | ncoghlan@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/guido%40python.org
-- --Guido van Rossum (python.org/~guido)
On 4 May 2013 00:17, "Eli Bendersky" <eliben@gmail.com> wrote:
On Fri, May 3, 2013 at 6:34 AM, Greg Ewing <greg.ewing@canterbury.ac.nz>
wrote:
Barry Warsaw wrote:
I still don't get it why this is an issue though, or at least why this
is
different than any other getattr on any other class,
It's not a problem that getattr() has this behaviour. What I'm questioning is the idea that getattr() should be the only provided way of doing a name->enum lookup, because that will require everyone to do extra checks to ensure safety.
I'm just curious what it is about enums that sets everyone on a "let's make things safer" path. Python is about duck typing, it's absolutely "unsafe" in the static typing sense, in the most fundamental ways imaginable. When programmatically invoking a method on a class (say some sort of RPC), we don't check that the class is of the correct type. We invoke a method, and if it quacks, that's a good enough duck. If it was actually the wrong class, something will break later. EAFP Is a central Python tenet, whether we like it or not. If one looks for static guarantees, Python surely shouldn't be the preferred language, no?
And concretely, how is this case different from any programmatic attribute access in Python objects? You can pass dunders to getattr() and it probably wasn't what you meant, but Python does not do this type checking for you. Why is an Enum different than any other class?
The only reason to use enums at all is to improve logging and error messages. Thus, designing the API and behaviour of an enum type is mostly a matter of asking "What mistakes are developers likely to make?" and "How can the enum design help guide them towards a suitable solution?". The answers are a combination of API design and providing appropriate details in error messages. If a developer doesn't care about those two questions then they would just use the raw underlying values. Cheers, Nick.
Eli
_______________________________________________ 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/ncoghlan%40gmail.com
Steven D'Aprano wrote:
On 29/04/13 10:29, Ethan Furman wrote:
- bool(1) # True - int('11') # 11 - str(var) # whatever var had in it, now as a str
I think that's a red herring, because you're comparing the use of the object constructor with look-up by name.
How does what bool() is doing differ from a lookup? It's not constructing a new instance. Neither is int() in the cases where the argument is in the range of values that it caches. More generally, the built-in types can be thought of as coercion functions -- they take an argument and return some related value from the type's repertoire. Whether they do that by constructing a new object or not is an implementation detail. -- Greg
On Sun, 28 Apr 2013 17:29:35 -0700 Ethan Furman <ethan@stoneleaf.us> wrote:
Not only is this inconsistent with the rest of Python*, but it's going to be a PITA for data storage/retrieval:
datastore = dbf.Table('storage.dbf', 'event_name C(50); date D; season SEASON')
def retrieve_record(...): result = [] for field_type, field_data in record: result.append(field_type(field_data))
I've never seen any kind of "data retrieval" which works like that. Would you care to explain us the context? Regards Antoine.
Steven D'Aprano writes:
- should an enum item be selectable via __call__ instead of __getitem__ (i.e. Seasons(3) is AUTUMN)
No opinion.
Does anyone know why this is even an issue? Is this pure bike-shedding over the API, or are there technical reasons for choosing one over the other?
Ethan thinks that "Seasons(3)" is a typecast, not an access into a mapping (which would be better expressed by "Seasons[3]"). Ie, the inverse of "int(AUTUMN)". This is consistent with the "AUTUMN is-a Seasons" position that Ethan and Guido take. It's inconsistent with the "AUTUMN is-a Seasons_VALUE" implementation of Flufl.Enum. @Ethan: I have real trouble sympathizing with your point of view because you consistently pluralize your Enum names. AUTUMN *is not* a SeasonZZ, it is an element of the *collection* Seasons. OTOH, AUTUMN *is* a Season (look Ma, no ZZ!) I wonder if you might not get more sympathy from Guido if you named your Enums with the singular form. Note that for some reason I don't have the same problem if Barry names an Enum "Season" (no ZZ!) I don't know why, maybe because the semantics of type is to define a collection (which in English is invariably denoted by the plural of the type name), so there's implicitly a plural there. But I'm not confident in that psychoanalysis.
On 04/28/2013 07:10 PM, Stephen J. Turnbull wrote:
@Ethan: I have real trouble sympathizing with your point of view because you consistently pluralize your Enum names. AUTUMN *is not* a SeasonZZ, it is an element of the *collection* Seasons. OTOH, AUTUMN *is* a Season (look Ma, no ZZ!)
I would hope that you would pay more attention to my arguments and rationale than to poorly chosen names. The way English does things is not the only way things are done. As it happens, I agree with you, and changed (and will change) all mention of Seasons to Season, and WeekDays to WeekDay, etc., etc., in my responses. -- ~Ethan~
Ethan Furman writes:
I would hope that you would pay more attention to my arguments and rationale than to poorly chosen names.
I do. Nevertheless, it requires conscious effort. It's quite appropriate for you to ask that of me, but ... do you think you're doing Python any good to ask more effort of Guido? (Assuming he needs it, of course -- I'm just conjecturing that the form of your examples may make the whole thing less persuasive for him. But of course he has advantages, being Dutch and all.... :-)
The way English does things is not the only way things are done.
I am nearly-native fluent in Japanese[1] -- I know better than most people just how differently things *can* be done. That doesn't help much with the effort gradient in English, though. Footnotes: [1] Even my 15-year-old daughter admits that, so it must be true.
On Apr 29, 2013, at 11:10 AM, Stephen J. Turnbull wrote:
Ethan thinks that "Seasons(3)" is a typecast, not an access into a mapping (which would be better expressed by "Seasons[3]"). Ie, the inverse of "int(AUTUMN)".
This is consistent with the "AUTUMN is-a Seasons" position that Ethan and Guido take. It's inconsistent with the "AUTUMN is-a Seasons_VALUE" implementation of Flufl.Enum.
I think this sums it up perfectly. I get that using class definition syntax is what sends people down Ethan's path. -Barry
On Sun, Apr 28, 2013 at 12:32 PM, Ethan Furman <ethan@stoneleaf.us> wrote:
Example enumeration:
class Seasons(Enum): SPRING = 1 SUMMER = 2 AUTUMN = 3 WINTER = 4
days_in_year = 365
@property def avg_temp(self): return (75, 92, 66, 33)[int(self)+1] # enums are 1-based
Definite Issues:
- should enum items be of the type of the Enum class? (i.e. type(SPRING) is Seasons)
- should an enum item be selectable via __call__ instead of __getitem__ (i.e. Seasons(3) is AUTUMN)
- should days_in_year be enumerated?
- should avg_temp be enumerated?
- for the above two, how should they be included/excluded?
Thanks for the summary. One issue I don't see addressed here is int-compatibility. Am I correct to assume that nothing changes w.r.t. that, and that an IntEnum subclass of Enum will be provided which is isinstance(integer)? Does that become straightforward by nature of enum values being the type of their enumerations? Eli
On Mon, Apr 29, 2013 at 5:24 AM, Eli Bendersky <eliben@gmail.com> wrote:
Thanks for the summary. One issue I don't see addressed here is int-compatibility. Am I correct to assume that nothing changes w.r.t. that, and that an IntEnum subclass of Enum will be provided which is isinstance(integer)? Does that become straightforward by nature of enum values being the type of their enumerations?
Correct, we'll still need an IntEnum subclass of Enum. I have no idea how this works out in the implementation, sorry. -- --Guido van Rossum (python.org/~guido)
On 4/29/2013 8:24 AM, Eli Bendersky wrote:
Thanks for the summary. One issue I don't see addressed here is int-compatibility. Am I correct to assume that nothing changes w.r.t. that, and that an IntEnum subclass of Enum will be provided which is isinstance(integer)? Does that become straightforward by nature of enum values being the type of their enumerations?
My only concern is that whatever you do does not break transitivity of ==. Aside from that, I will be happy with whatever you end up with and use it as appropriate.
On 04/29/2013 02:45 PM, Terry Jan Reedy wrote:
On 4/29/2013 8:24 AM, Eli Bendersky wrote:
Thanks for the summary. One issue I don't see addressed here is int-compatibility. Am I correct to assume that nothing changes w.r.t. that, and that an IntEnum subclass of Enum will be provided which is isinstance(integer)? Does that become straightforward by nature of enum values being the type of their enumerations?
My only concern is that whatever you do does not break transitivity of ==. Aside from that, I will be happy with whatever you end up with and use it as appropriate.
It will not be broken. -- ~Ethan~
On Mon, Apr 29, 2013 at 2:45 PM, Terry Jan Reedy <tjreedy@udel.edu> wrote:
On 4/29/2013 8:24 AM, Eli Bendersky wrote:
Thanks for the summary. One issue I don't see addressed here is int-compatibility. Am I correct to assume that nothing changes w.r.t. that, and that an IntEnum subclass of Enum will be provided which is isinstance(integer)? Does that become straightforward by nature of enum values being the type of their enumerations?
My only concern is that whatever you do does not break transitivity of ==. Aside from that, I will be happy with whatever you end up with and use it as appropriate.
This is a trick question though, isn't it? Example: class Color(Enum): red = 1 white = 2 blue = 3 class State(Enum): idle = 0 busy = 1 We would have State.busy == 1 and Color.red == 1, so now State.busy == Color.red. -- --Guido van Rossum (python.org/~guido)
On Mon, Apr 29, 2013 at 2:45 PM, Terry Jan Reedy <tjreedy@udel.edu> wrote:
On 4/29/2013 8:24 AM, Eli Bendersky wrote:
Thanks for the summary. One issue I don't see addressed here is
int-compatibility. Am I correct to assume that nothing changes w.r.t. that, and that an IntEnum subclass of Enum will be provided which is isinstance(integer)? Does that become straightforward by nature of enum values being the type of their enumerations?
My only concern is that whatever you do does not break transitivity of ==. Aside from that, I will be happy with whatever you end up with and use it as appropriate.
The transitivity of == is not broken by either proposal. In PEP 435, the split to Enum and IntEnum is made in part for this reason. Enum values don't compare equal even if they have the same underlying values. IntEnum values do compare equal to ints, and hence to each other. So: class Color(Enum): red = 1 blue = 2 class Animal(Enum): dog = 2 sheep = 7 Color.blue != Animal.dog, and neither compares to 2, of course. However, had both enums been IntEnum, we'd have: Color.blue == Animal.dog == 2 This is a werdness users of IntEnum have to live with, and that's why it's explicitly discouraged for 99% of the use-cases. Eli
participants (17)
-
Antoine Pitrou
-
Barry Warsaw
-
Cameron Simpson
-
Eli Bendersky
-
Eric V. Smith
-
Ethan Furman
-
Georg Brandl
-
Glenn Linderman
-
Greg Ewing
-
Guido van Rossum
-
Larry Hastings
-
Nick Coghlan
-
Phil Connell
-
PJ Eby
-
Stephen J. Turnbull
-
Steven D'Aprano
-
Terry Jan Reedy