Access to member name/names via context during Enum instance initialization
I brought this up before, but I had a kind of weak understanding of `enum.Enum` at the time, so my expression of the issue and proposal for a solution were pretty awkward. Basically, with the current implementation of `Enum` and supporting classes, the only way that a member can have access to its own name during or prior to its initialization is if/when its value is auto-generated, so if you want to specify a value rather than having it auto-generated (e.g. to generate a label attribute) then there's no convenient way to do it. One can work around that by writing the code to make use of the `name` property after installation, but why should we force the developer to write code in an awkward manner when we could simply make the name available to the `__new__ method. It might also be nice to supply the list of all names (in case the value is assigned to multiple names) as well as the primary name. The idea that comes to mind is to pass a `context` object argument to `__new__` with `name` and `names` attributes. To preserve backward compatibility, there could be have a class attribute named something like `_use_context_` (`False` by default) to enable or disable passing that parameter. Going forward, additional attributes could be added to the context without additional backward compatibility concerns. Of course, the exact mechanism could be something completely different, but I'd like to see this or some other mechanism that addresses the concern.
On Sat, Nov 30, 2019 at 08:24:39AM -0000, Steve Jorgensen wrote:
Basically, with the current implementation of `Enum` and supporting classes, the only way that a member can have access to its own name during or prior to its initialization is if/when its value is auto-generated, so if you want to specify a value rather than having it auto-generated (e.g. to generate a label attribute) then there's no convenient way to do it.
Code speaks louder than words. Can you give an example of what you are trying to do? If I'm reading you correctly, we can already specify values: py> class Colours(Enum): ... red = auto() ... green = 999 ... py> Colours.red, Colours.green (<Colours.red: 1>, <Colours.green: 999>) so I'm not really sure what you want or how you are doing it. Can we pretend you are reporting a bug, and ask what did you do, what did you expect, and what happened instead?
One can work around that by writing the code to make use of the `name` property after installation, but why should we force the developer to write code in an awkward manner when we could simply make the name available to the `__new__ method.
Whose `__new__` method? [...]
The idea that comes to mind is to pass a `context` object argument to `__new__` with `name` and `names` attributes. To preserve backward compatibility, there could be have a class attribute named something like `_use_context_` (`False` by default) to enable or disable passing that parameter.
That sounds convoluted, complicated and confusing. Are there existing examples where we do that? I would think if we need an backwards-incompatible breaking change, there are three better ways: - a subclass of Enum that does what you want; - following a period of deprecation, have a flag-day change to the new behaviour (3.9 is probably too soon, but 3.10 might be possible); - a __future__ import (although that generally only applies to syntax. especially the first. -- Steven
Steven D'Aprano wrote:
Basically, with the current implementation of Enum and supporting classes, the only way that a member can have access to its own name during or prior to its initialization is if/when its value is auto-generated, so if you want to specify a value rather than having it auto-generated (e.g. to generate a label attribute) then there's no convenient way to do it. Code speaks louder than words. Can you give an example of what you are
On Sat, Nov 30, 2019 at 08:24:39AM -0000, Steve Jorgensen wrote: trying to do? If I'm reading you correctly, we can already specify values: py> class Colours(Enum): ... red = auto() ... green = 999 ... py> Colours.red, Colours.green (<Colours.red: 1>, <Colours.green: 999>)
One can work around that by writing the code to make use of the name property after installation, but why should we force the developer to write code in an awkward manner when we could simply make the name available to the `__new__ method. Whose __new__ method? [...] The idea that comes to mind is to pass a context object argument to __new__ with name and names attributes. To preserve backward compatibility, there could be have a class attribute named something like _use_context_ (False by default) to enable or disable passing that parameter. That sounds convoluted, complicated and confusing. Are there existing examples where we do that? I would think if we need an backwards-incompatible breaking change,
so I'm not really sure what you want or how you are doing it. Can we pretend you are reporting a bug, and ask what did you do, what did you expect, and what happened instead? there are three better ways:
a subclass of Enum that does what you want;
following a period of deprecation, have a flag-day change to the new behaviour (3.9 is probably too soon, but 3.10 might be possible);
a __future__ import (although that generally only applies to syntax.
especially the first.
Per the `AutoName` example at https://docs.python.org/3/library/enum.html#using-automatic-values, one can easily use `_generate_next_value_` to derive a member's value from its name, but that is only invoked when the object's value is an instance of `auto()`, so if you want to give an enum member an explicit value _and_ have an attribute such as `label` derived from its name, that's a hard nut to crack. An example usage of what I'm suggesting would be something like… class ChoiceEnum(Enum): _use_context_ = True def __new__(cls, value, context): obj = object.__new__(cls) obj._value_ = value obj.label = context.name.capitalize() class Color(ChoiceEnum): SMALL = 'S' MEDIUM = 'M' LARGE = 'L' print(Color.SMALL.label) # Prints 'Small'
Per the AutoName example at https://docs.python.org/3/library/enum.html#using-automatic-values, one can easily use _generate_next_value_ to derive a member's value from its name, but that is only invoked when the object's value is an instance of auto(), so if you want to give an enum member an explicit value _and_ have an attribute such as label derived from its name, that's a hard nut to crack. An example usage of what I'm suggesting would be something like… class ChoiceEnum(Enum): _use_context_ = True
def __new__(cls, value, context): obj = object.__new__(cls) obj._value_ = value obj.label = context.name.capitalize()
class Color(ChoiceEnum): SMALL = 'S' MEDIUM = 'M' LARGE = 'L'
print(Color.SMALL.label) # Prints 'Small'
My though about `_use_context_ = True` is not _only_ about backward compatibility. It is also about not making simpler cases deal with the fact that the `context` argument will be passed to `__new__` though I guess one could argue that cases where `__new__` is used are already not simple.
Peeking at the source for Enum, the __new__ method is explicitly called, thereby deferring the call to __init__ until it, too, is explicitly called, but only after member's _name_ attribute has been assigned. So, adapting your example: class ChoiceEnum(Enum): def __init__(self, value): self.label = self.name.capitalize() class Color(ChoiceEnum): SMALL = 'S' MEDIUM = 'M' LARGE = 'L' print(Color.SMALL.label) # Prints 'Small' Side note: I was surprised to find the docs lacking a clear example to explain this behaviour, the DuplicateFreeEnum recipe does show that members' name/_name_ attributes can be accessed, via the __init__, but that info could have more attention drawn to it rather than being somewhat buried in an example where it isn't necessarily immediately obvious. This is especially true considering the specific "When to use __new__() vs. __init__()" section, which reads:
__new__() must be used whenever you want to customize the actual value of the Enum member. Any other modifications may go in either __new__() or __init__(), with __init__() being preferred.
The second sentence, as per this thread, is demonstrably untrue. Modifications that are reliant upon the name of the member *must* go in __init__ (in cases where _generate_next_value_ & auto() are inappropriate).
Adam Johnson wrote:
Peeking at the source for Enum, the __new__ method is explicitly called, thereby deferring the call to __init__ until it, too, is explicitly called, but only after member's _name_ attribute has been assigned. So, adapting your example: class ChoiceEnum(Enum): def __init__(self, value): self.label = self.name.capitalize() class Color(ChoiceEnum): SMALL = 'S' MEDIUM = 'M' LARGE = 'L' print(Color.SMALL.label) # Prints 'Small' Side note: I was surprised to find the docs lacking a clear example to explain this behaviour, the DuplicateFreeEnum recipe does show that members' name/_name_ attributes can be accessed, via the __init__, but that info could have more attention drawn to it rather than being somewhat buried in an example where it isn't necessarily immediately obvious. This is especially true considering the specific "When to use __new__() vs. __init__()" section, which reads:
__new__() must be used whenever you want to customize the actual value of the Enum member. Any other modifications may go in either __new__() or __init__(), with __init__() being preferred. The second sentence, as per this thread, is demonstrably untrue. Modifications that are reliant upon the name of the member must go in __init__ (in cases where _generate_next_value_ & auto() are inappropriate).
Thanks for this. It comes close to answering my concern. There's one case that's very useful (IMO) that it still doesn't handle though. There's still no way to have the value and the label each be _optionally_ based on the name. Using `_generate_next_value_`, both the value and the label could be based on the name, but then any means of trying to override the label would mean not using `auto()` and therefore not having access to name `__new__`, and we're not allowed to modify the value in `__init__` where the name _is_ available.
Steve Jorgensen wrote:
I brought this up before, but I had a kind of weak understanding of enum.Enum at the time, so my expression of the issue and proposal for a solution were pretty awkward. Basically, with the current implementation of Enum and supporting classes, the only way that a member can have access to its own name during or prior to its initialization is if/when its value is auto-generated, so if you want to specify a value rather than having it auto-generated (e.g. to generate a label attribute) then there's no convenient way to do it. One can work around that by writing the code to make use of the name property after installation, but why should we force the developer to write code in an awkward manner when we could simply make the name available to the `__new__ method. It might also be nice to supply the list of all names (in case the value is assigned to multiple names) as well as the primary name. The idea that comes to mind is to pass a context object argument to __new__ with name and names attributes. To preserve backward compatibility, there could be have a class attribute named something like _use_context_ (False by default) to enable or disable passing that parameter. Going forward, additional attributes could be added to the context without additional backward compatibility concerns. Of course, the exact mechanism could be something completely different, but I'd like to see this or some other mechanism that addresses the concern.
I accidentally omitted some text, leaving that a little unclear. "…so if you want to specify a value rather than having it auto-generated (e.g. to generate a label attribute) then there's no convenient way to do it." should read "…so if you want to specify a value rather than having it auto-generated _and_ make use of the name (e.g. to generate a label attribute) then there's no convenient way to do it."
Steve, It looks like what you're trying to achieve is an Enum with more than a singleton `.value` exposed on each sub-item. I was able to achieve something that *looks* like your example using an Enum mixin with a custom class:
from enum import Enum class Labeler: @property def label(self): return self.name.capitalize()
class Size(Labeler, Enum): SMALL = "S" MEDIUM = "M" LARGE = "L" Size.LARGE.label 'Large' Size.LARGE.value 'L' More custom values could be added by expanding `Labeler`. FWIW, I also got a mixin with namedtuple to work:
from collections import namedtuple as nt Key = nt('Key', ('label', 'value')) class Foo(Key, Enum) Bar = Key(label='Cat', value=5) Baz = Key(label='Dog', value=10)
Foo.Bar.label 'Cat' These both were with Python 3.7. -Brian
Brian Skinn wrote:
Steve, It looks like what you're trying to achieve is an Enum with more than a singleton .value exposed on each sub-item. I was able to achieve something that looks like your example using an Enum mixin with a custom class:
from enum import Enum class Labeler: @property def label(self): return self.name.capitalize() class Size(Labeler, Enum): SMALL = "S" MEDIUM = "M" LARGE = "L" Size.LARGE.label 'Large' Size.LARGE.value 'L' More custom values could be added by expanding Labeler. FWIW, I also got a mixin with namedtuple to work: from collections import namedtuple as nt Key = nt('Key', ('label', 'value')) class Foo(Key, Enum) Bar = Key(label='Cat', value=5) Baz = Key(label='Dog', value=10) Foo.Bar.label 'Cat' These both were with Python 3.7. -Brian
Yes. I thought of that approach, and it does work, but it still forces developers to do unnecessary extra work and potentially have to compromise what they're actually trying to do. One thing this will specifically not support is having the name and the value each be optionally based on the name. If providing an explicit value for the label but not the name (maybe like `FOO = (auto(), 'Fooo')`, then since the value is a `tuple` (in spite of containing an `auto`) `_generate_next_value_` will not be invoked, so the next time we can get the name is during `_init_ which is too late to set `_value_`. Maybe a simpler solution than what I proposed would be if we could wait until after `__init__` is called before locking down `_value_`, but I assume that would already be allowed if there weren't some problem with trying to allow it.
Maybe the right way to think about Enum is as only for the most trivial use cases. At work we almost never use Enum because it's too limited and we instead reach for our own tri.declarative which scales up to very complex use cases. Adding a lot of features to Enum would make it less of an enum and more like the static tables we are aiming for with tri.declarative. I'm assuming there are performance and memory implications to this.
On 30 Nov 2019, at 09:25, Steve Jorgensen <stevej@stevej.name> wrote:
I brought this up before, but I had a kind of weak understanding of `enum.Enum` at the time, so my expression of the issue and proposal for a solution were pretty awkward.
Basically, with the current implementation of `Enum` and supporting classes, the only way that a member can have access to its own name during or prior to its initialization is if/when its value is auto-generated, so if you want to specify a value rather than having it auto-generated (e.g. to generate a label attribute) then there's no convenient way to do it.
One can work around that by writing the code to make use of the `name` property after installation, but why should we force the developer to write code in an awkward manner when we could simply make the name available to the `__new__ method. It might also be nice to supply the list of all names (in case the value is assigned to multiple names) as well as the primary name.
The idea that comes to mind is to pass a `context` object argument to `__new__` with `name` and `names` attributes. To preserve backward compatibility, there could be have a class attribute named something like `_use_context_` (`False` by default) to enable or disable passing that parameter. Going forward, additional attributes could be added to the context without additional backward compatibility concerns.
Of course, the exact mechanism could be something completely different, but I'd like to see this or some other mechanism that addresses the concern. _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/2RCMJ2... Code of Conduct: http://python.org/psf/codeofconduct/
Anders Hovmöller wrote:
Maybe the right way to think about Enum is as only for the most trivial use cases. At work we almost never use Enum because it's too limited and we instead reach for our own tri.declarative which scales up to very complex use cases. Adding a lot of features to Enum would make it less of an enum and more like the static tables we are aiming for with tri.declarative. I'm assuming there are performance and memory implications to this.
On 30 Nov 2019, at 09:25, Steve Jorgensen stevej@stevej.name wrote: I brought this up before, but I had a kind of weak understanding of enum.Enum at the time, so my expression of the issue and proposal for a solution were pretty awkward. Basically, with the current implementation of Enum and supporting classes, the only way that a member can have access to its own name during or prior to its initialization is if/when its value is auto-generated, so if you want to specify a value rather than having it auto-generated (e.g. to generate a label attribute) then there's no convenient way to do it. One can work around that by writing the code to make use of the name property after installation, but why should we force the developer to write code in an awkward manner when we could simply make the name available to the `__new__ method. It might also be nice to supply the list of all names (in case the value is assigned to multiple names) as well as the primary name. The idea that comes to mind is to pass a context object argument to __new__ with name and names attributes. To preserve backward compatibility, there could be have a class attribute named something like _use_context_ (False by default) to enable or disable passing that parameter. Going forward, additional attributes could be added to the context without additional backward compatibility concerns. Of course, the exact mechanism could be something completely different, but I'd like to see this or some other mechanism that addresses the concern.
Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/2RCMJ2... Code of Conduct: http://python.org/psf/codeofconduct/
I get what you're saying, and thanks for letting me know about `tri.declarative`. I'll read up on that. I feel like what I'm asking for is a small change to eliminate a frustrating, unnecessary restriction in `Enum` though, and not something to make it handle a higher order of complex situation.
On 30 Nov 2019, at 23:30, Steve Jorgensen <stevej@stevej.name> wrote:
Anders Hovmöller wrote:
Maybe the right way to think about Enum is as only for the most trivial use cases. At work we almost never use Enum because it's too limited and we instead reach for our own tri.declarative which scales up to very complex use cases. Adding a lot of features to Enum would make it less of an enum and more like the static tables we are aiming for with tri.declarative. I'm assuming there are performance and memory implications to this.
On 30 Nov 2019, at 09:25, Steve Jorgensen stevej@stevej.name wrote: I brought this up before, but I had a kind of weak understanding of enum.Enum at the time, so my expression of the issue and proposal for a solution were pretty awkward. Basically, with the current implementation of Enum and supporting classes, the only way that a member can have access to its own name during or prior to its initialization is if/when its value is auto-generated, so if you want to specify a value rather than having it auto-generated (e.g. to generate a label attribute) then there's no convenient way to do it. One can work around that by writing the code to make use of the name property after installation, but why should we force the developer to write code in an awkward manner when we could simply make the name available to the `__new__ method. It might also be nice to supply the list of all names (in case the value is assigned to multiple names) as well as the primary name. The idea that comes to mind is to pass a context object argument to __new__ with name and names attributes. To preserve backward compatibility, there could be have a class attribute named something like _use_context_ (False by default) to enable or disable passing that parameter. Going forward, additional attributes could be added to the context without additional backward compatibility concerns. Of course, the exact mechanism could be something completely different, but I'd like to see this or some other mechanism that addresses the concern.
Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/2RCMJ2... Code of Conduct: http://python.org/psf/codeofconduct/
I get what you're saying, and thanks for letting me know about `tri.declarative`. I'll read up on that.
Oops! I meant tri.token actually! Which is based on tri.declarative.
participants (5)
-
Adam Johnson
-
Anders Hovmöller
-
Brian Skinn
-
Steve Jorgensen
-
Steven D'Aprano