Enum: determining if a value is valid
A question that comes up quite a bit on Stackoverflow is how to test to see if a value will result in an Enum member, preferably without having to go through the whole try/except machinery. A couple versions ago one could use a containment check: if 1 in Color: but than was removed as Enums are considered containers of members, not containers of the member values. It was also possible to define one's own `_missing_` method and have it return None or the value passed in, but that has also been locked down to either return a member or raise an exception. At this point I see three options: 1) add a `get(value, default=None)` to EnumMeta (similar to `dict.get()` 2) add a recipe to the docs 3) do nothing Thoughts? -- ~Ethan~
On Fri, Mar 12, 2021 at 4:52 PM Ethan Furman <ethan@stoneleaf.us> wrote:
A question that comes up quite a bit on Stackoverflow is how to test to see if a value will result in an Enum member, preferably without having to go through the whole try/except machinery.
A couple versions ago one could use a containment check:
if 1 in Color:
but than was removed as Enums are considered containers of members, not containers of the member values. It was also possible to define one's own `_missing_` method and have it return None or the value passed in, but that has also been locked down to either return a member or raise an exception.
At this point I see three options:
1) add a `get(value, default=None)` to EnumMeta (similar to `dict.get()`
2) add a recipe to the docs
3) do nothing
Thoughts?
-- ~Ethan~
Could this be an instance where match-case might become the canonical solution? I'm probably getting the syntax wrong, but maybe it would be something like: match value: case MyEnum(): assert isinstance(value, MyEnum) case _: assert not isinstance(value, MyEnum) --- Ricky. "I've never met a Kentucky man who wasn't either thinking about going home or actually going home." - Happy Chandler
On 3/12/21 2:49 PM, Ricky Teachey wrote:
On Fri, Mar 12, 2021 at 4:52 PM Ethan Furman wrote:
A question that comes up quite a bit on Stackoverflow is how to test to see if a value will result in an Enum member, preferably without having to go through the whole try/except machinery.
Could this be an instance where match-case might become the canonical solution?
I'm probably getting the syntax wrong, but maybe it would be something like:
match value: case MyEnum(): assert isinstance(value, MyEnum) case _: assert not isinstance(value, MyEnum)
The use case is when you have an unknown value that may or may not convert into an Enum member. So a three-member Enum would look something like: ```python match value: case MyEnum(): pass case 1|2|3: value = MyEnum(value) case _: handle_error_or_use_default() ``` Seven lines of code. try/except would be something like: ```python try: value = MyEnum(value) except ValueError: handle_error_or_use_default() ``` vs what I'm envisioning: ```python value = MyEnum.get(value, some_default) ``` or maybe ```python value = MyEnum.get(value) if value is None: handle_error() ``` -- ~Ethan~
On Fri, Mar 12, 2021 at 1:52 PM Ethan Furman <ethan@stoneleaf.us> wrote:
A question that comes up quite a bit on Stackoverflow is how to test to see if a value will result in an Enum member, preferably without having to go through the whole try/except machinery.
A couple versions ago one could use a containment check:
if 1 in Color:
but than was removed as Enums are considered containers of members, not containers of the member values.
Maybe you were a bit too quick in deleting it. Was there a serious bug that led to the removal? Could it be restored?
It was also possible to define one's own `_missing_` method and have it return None or the value passed in, but that has also been locked down to either return a member or raise an exception.
At this point I see three options:
1) add a `get(value, default=None)` to EnumMeta (similar to `dict.get()`
But the way to convert a raw value to an enum value is Color(1), not Color[1], so Color.get(1) seems inconsistent. Maybe you can just change the constructor so you can spell this as Color(1, default=None) (and then check whether that's None)?
2) add a recipe to the docs
But what would the recipe say? Apparently you're looking for a one-liner, since you reject the try/except solution.
3) do nothing
Always a good option. :-) Where's that StackOverflow item? How many upvotes does it have? -- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/>
On 3/12/21 5:28 PM, Guido van Rossum wrote:
On Fri, Mar 12, 2021 at 1:52 PM Ethan Furman wrote:
A question that comes up quite a bit on Stackoverflow is how to test to see if a value will result in an Enum member, preferably without having to go through the whole try/except machinery.
A couple versions ago one could use a containment check:
if 1 in Color:
but than was removed as Enums are considered containers of members, not containers of the member values.
Maybe you were a bit too quick in deleting it. Was there a serious bug that led to the removal? Could it be restored?
Part of the reason is that there are really two ways to identify an enum -- by name, and by value -- which should `__contains__` work with?
At this point I see three options:
1) add a `get(value, default=None)` to EnumMeta (similar to `dict.get()`
But the way to convert a raw value to an enum value is Color(1), not Color[1], so Color.get(1) seems inconsistent.
Very good point.
Maybe you can just change the constructor so you can spell this as Color(1, default=None) (and then check whether that's None)?
An interesting idea.
2) add a recipe to the docs
But what would the recipe say? Apparently you're looking for a one-liner, since you reject the try/except solution.
The recipe would be for a method that could be added to an Enum, such as: @classmethod def get_by_value(cls, value, default=None): for member in cls: if member.value == value: return member return default
3) do nothing
Always a good option. :-)
Yes, but not always a satisfying one. :)
Where's that StackOverflow item? How many upvotes does it have?
93 - How do I test if int value exists in Python Enum without using try/catch? https://stackoverflow.com/q/43634618/208880 25 - How to test if an Enum member with a certain name exists? https://stackoverflow.com/q/29795488/208880 3 - Validate value is in Python Enum values https://stackoverflow.com/q/54126570/208880 2 - How to check if string exists in Enum of strings? https://stackoverflow.com/q/63335753/208880 I think I like your constructor change idea, with a small twist: Color(value=<sentinel>, name=<sentinel>, default=<sentinal>) This would make it possible to search for an enum by value or by name, and also specify a default return value (raising an exception if the default is not set and a member cannot be found). -- ~Ethan~
On Mon, Mar 15, 2021 at 10:53 AM Ethan Furman <ethan@stoneleaf.us> wrote:
On 3/12/21 5:28 PM, Guido van Rossum wrote:
On Fri, Mar 12, 2021 at 1:52 PM Ethan Furman wrote:
A question that comes up quite a bit on Stackoverflow is how to test to see if a value will result in an Enum member, preferably without having to go through the whole try/except machinery.
A couple versions ago one could use a containment check:
if 1 in Color:
but than was removed as Enums are considered containers of members, not containers of the member values.
Maybe you were a bit too quick in deleting it. Was there a serious bug that led to the removal? Could it be restored?
Part of the reason is that there are really two ways to identify an enum -- by name, and by value -- which should `__contains__` work with?
The two sets don't overlap, so we could allow both. (Funny interpretations of `__contains__` are not unusual, e.g. substring checks are spelled 'abc' in 'fooabcbar'.)
At this point I see three options:
1) add a `get(value, default=None)` to EnumMeta (similar to `dict.get()`
But the way to convert a raw value to an enum value is Color(1), not Color[1], so Color.get(1) seems inconsistent.
Very good point.
Maybe you can just change the constructor so you can spell this as Color(1, default=None) (and then check whether that's None)?
An interesting idea.
2) add a recipe to the docs
But what would the recipe say? Apparently you're looking for a one-liner, since you reject the try/except solution.
The recipe would be for a method that could be added to an Enum, such as:
@classmethod def get_by_value(cls, value, default=None): for member in cls: if member.value == value: return member return default
But that's a non-solution -- people can figure out how to write such a helper just fine (although probably using try/except) but they don't want to -- they have *one* line where they want to do this check and so they're going for a local solution -- probably the try/except.
3) do nothing
Always a good option. :-)
Yes, but not always a satisfying one. :)
Where's that StackOverflow item? How many upvotes does it have?
93 - How do I test if int value exists in Python Enum without using try/catch? https://stackoverflow.com/q/43634618/208880
25 - How to test if an Enum member with a certain name exists? https://stackoverflow.com/q/29795488/208880
3 - Validate value is in Python Enum values https://stackoverflow.com/q/54126570/208880
2 - How to check if string exists in Enum of strings? https://stackoverflow.com/q/63335753/208880
I think I like your constructor change idea, with a small twist:
Color(value=<sentinel>, name=<sentinel>, default=<sentinal>)
This would make it possible to search for an enum by value or by name, and also specify a default return value (raising an exception if the default is not set and a member cannot be found).
So specifically this would allow (hope my shorthand is clear): ``` Color['RED'] --> Color.RED or raises Color(1) -> Color.RED or raises Color(1, default=None) -> Color.RED or None Color(name='RED', default=None) -> Color.RED or None ``` This seems superficially reasonable. I'm not sure what Color(value=1, name='RED') would do -- insist that both value and name match? Would that have a use case? My remaining concern is that it's fairly verbose -- assuming we don't really need the name argument, it would be attractive if we could write Color(1, None) instead of Color(1, default=None). Note that instead of Color(name='RED') we can already write this: ``` getattr(Color, 'RED') -> Color.RED or raises getattr(Color, 'RED', None) -> Color.RED or None ``` -- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/>
On Mon, Mar 15, 2021 at 2:28 PM Guido van Rossum <guido@python.org> wrote:
...
I think I like your constructor change idea, with a small twist:
Color(value=<sentinel>, name=<sentinel>, default=<sentinal>)
This would make it possible to search for an enum by value or by name, and also specify a default return value (raising an exception if the default is not set and a member cannot be found).
So specifically this would allow (hope my shorthand is clear): ``` Color['RED'] --> Color.RED or raises Color(1) -> Color.RED or raises Color(1, default=None) -> Color.RED or None Color(name='RED', default=None) -> Color.RED or None ```
Additional possibility (just raising it; neither for nor against) with PEP 637: Color['RED', default=None] --> Color.RED or None --- Ricky. "I've never met a Kentucky man who wasn't either thinking about going home or actually going home." - Happy Chandler
On 3/15/21 11:27 AM, Guido van Rossum wrote:
On Mon, Mar 15, 2021 at 10:53 AM Ethan Furman wrote:
Part of the reason is that there are really two ways to identify an enum -- by name, and by value -- which should `__contains__` work with?
The two sets don't overlap, so we could allow both. (Funny interpretations of `__contains__` are not unusual, e.g. substring checks are spelled 'abc' in 'fooabcbar'.)
They could overlap if the Enum is a `str`-subclass -- although having the name of one member match the value of a different member seems odd.
I think I like your constructor change idea, with a small twist:
Color(value=<sentinel>, name=<sentinel>, default=<sentinal>)
This would make it possible to search for an enum by value or by name, and also specify a default return value (raising an exception if the default is not set and a member cannot be found).
So specifically this would allow (hope my shorthand is clear): ``` Color['RED'] --> Color.RED or raises Color(1) -> Color.RED or raises Color(1, default=None) -> Color.RED or None Color(name='RED', default=None) -> Color.RED or None ``` This seems superficially reasonable. I'm not sure what Color(value=1, name='RED') would do -- insist that both value and name match? Would that have a use case?
I would enforce that both match, or raise. Also not sure what the use-case would be.
My remaining concern is that it's fairly verbose -- assuming we don't really need the name argument, it would be attractive if we could write Color(1, None) instead of Color(1, default=None).
Note that instead of Color(name='RED') we can already write this: ``` getattr(Color, 'RED') -> Color.RED or raises getattr(Color, 'RED', None) -> Color.RED or None
Very good points. Everything considered, I think I like allowing `__contains__` to verify both names and values, adding `default=<sentinel>` to the constructor for the value-based "gimme an Enum or None" case, and recommending `getattr` for the name-based "gimme an Enum or None" case. -- ~Ethan~
+1 On Mon, Mar 15, 2021 at 12:48 PM Ethan Furman <ethan@stoneleaf.us> wrote:
On 3/15/21 11:27 AM, Guido van Rossum wrote:
On Mon, Mar 15, 2021 at 10:53 AM Ethan Furman wrote:
Part of the reason is that there are really two ways to identify an enum -- by name, and by value -- which should `__contains__` work with?
The two sets don't overlap, so we could allow both. (Funny interpretations of `__contains__` are not unusual, e.g. substring checks are spelled 'abc' in 'fooabcbar'.)
They could overlap if the Enum is a `str`-subclass -- although having the name of one member match the value of a different member seems odd.
I think I like your constructor change idea, with a small twist:
Color(value=<sentinel>, name=<sentinel>, default=<sentinal>)
This would make it possible to search for an enum by value or by name, and also specify a default return value (raising an exception if the default is not set and a member cannot be found).
So specifically this would allow (hope my shorthand is clear): ``` Color['RED'] --> Color.RED or raises Color(1) -> Color.RED or raises Color(1, default=None) -> Color.RED or None Color(name='RED', default=None) -> Color.RED or None ``` This seems superficially reasonable. I'm not sure what Color(value=1, name='RED') would do -- insist that both value and name match? Would that have a use case?
I would enforce that both match, or raise. Also not sure what the use-case would be.
My remaining concern is that it's fairly verbose -- assuming we don't really need the name argument, it would be attractive if we could write Color(1, None) instead of Color(1, default=None).
Note that instead of Color(name='RED') we can already write this: ``` getattr(Color, 'RED') -> Color.RED or raises getattr(Color, 'RED', None) -> Color.RED or None
Very good points.
Everything considered, I think I like allowing `__contains__` to verify both names and values, adding `default=<sentinel>` to the constructor for the value-based "gimme an Enum or None" case, and recommending `getattr` for the name-based "gimme an Enum or None" case.
-- ~Ethan~ _______________________________________________ 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/UQBSDZ... Code of Conduct: http://python.org/psf/codeofconduct/
-- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/>
I find the idea of having the constructor potentially return something other than an instance of the class to be very... off-putting. Maybe it's the best option, but my first impression of it isn't favorable, and I can't think of any similar case that exists in the stdlib today off the top of my head. It seems like we should be able to do better. If I might propose an alternative before this gets set in stone: what if `Enum` provided classmethods `from_value` and `from_name`, each with a `default=<sentinel>`, so that you could do: Color.from_value(1) # returns Color.RED Color.from_value(-1) # raises ValueError Color.from_value(-1, None) # returns None Color.from_name("RED") # returns Color.RED Color.from_name("BLURPLE") # raises ValueError Color.from_name("BLURPLE", None) # returns None That still allows each concept to be expressed in a single line, and remains explicit about whether the lookup is happening by name or by value. It allows spelling `default=None` as just `None`, as we desire. And instead of being a `__contains__` with unusual semantics coupled with a constructor with unusual semantics, it's a pair of class methods that each have fairly unsurprising semantics. ~Matt On Mon, Mar 15, 2021 at 3:55 PM Guido van Rossum <guido@python.org> wrote:
+1
On Mon, Mar 15, 2021 at 12:48 PM Ethan Furman <ethan@stoneleaf.us> wrote:
On 3/15/21 11:27 AM, Guido van Rossum wrote:
On Mon, Mar 15, 2021 at 10:53 AM Ethan Furman wrote:
Part of the reason is that there are really two ways to identify an enum -- by name, and by value -- which should `__contains__` work with?
The two sets don't overlap, so we could allow both. (Funny interpretations of `__contains__` are not unusual, e.g. substring checks are spelled 'abc' in 'fooabcbar'.)
They could overlap if the Enum is a `str`-subclass -- although having the name of one member match the value of a different member seems odd.
I think I like your constructor change idea, with a small twist:
Color(value=<sentinel>, name=<sentinel>, default=<sentinal>)
This would make it possible to search for an enum by value or by name, and also specify a default return value (raising an exception if the default is not set and a member cannot be found).
So specifically this would allow (hope my shorthand is clear): ``` Color['RED'] --> Color.RED or raises Color(1) -> Color.RED or raises Color(1, default=None) -> Color.RED or None Color(name='RED', default=None) -> Color.RED or None ``` This seems superficially reasonable. I'm not sure what Color(value=1, name='RED') would do -- insist that both value and name match? Would that have a use case?
I would enforce that both match, or raise. Also not sure what the use-case would be.
My remaining concern is that it's fairly verbose -- assuming we don't really need the name argument, it would be attractive if we could write Color(1, None) instead of Color(1, default=None).
Note that instead of Color(name='RED') we can already write this: ``` getattr(Color, 'RED') -> Color.RED or raises getattr(Color, 'RED', None) -> Color.RED or None
Very good points.
Everything considered, I think I like allowing `__contains__` to verify both names and values, adding `default=<sentinel>` to the constructor for the value-based "gimme an Enum or None" case, and recommending `getattr` for the name-based "gimme an Enum or None" case.
-- ~Ethan~ _______________________________________________ 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/UQBSDZ... Code of Conduct: http://python.org/psf/codeofconduct/
-- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/> _______________________________________________ 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/ZK7KKA... Code of Conduct: http://python.org/psf/codeofconduct/
You have a good point (and as static typing proponent I should have thought of that). Maybe there is not actually a use case for passing an arbitrary default? Then maybe overloading __contains__ (‘in’) might be better? The ergonomics of that seem better for the dominant use case (“is this a valid value for that enum?”). On Mon, Mar 15, 2021 at 21:37 Matt Wozniski <godlygeek@gmail.com> wrote:
I find the idea of having the constructor potentially return something other than an instance of the class to be very... off-putting. Maybe it's the best option, but my first impression of it isn't favorable, and I can't think of any similar case that exists in the stdlib today off the top of my head. It seems like we should be able to do better.
If I might propose an alternative before this gets set in stone: what if `Enum` provided classmethods `from_value` and `from_name`, each with a `default=<sentinel>`, so that you could do:
Color.from_value(1) # returns Color.RED Color.from_value(-1) # raises ValueError Color.from_value(-1, None) # returns None
Color.from_name("RED") # returns Color.RED Color.from_name("BLURPLE") # raises ValueError Color.from_name("BLURPLE", None) # returns None
That still allows each concept to be expressed in a single line, and remains explicit about whether the lookup is happening by name or by value. It allows spelling `default=None` as just `None`, as we desire. And instead of being a `__contains__` with unusual semantics coupled with a constructor with unusual semantics, it's a pair of class methods that each have fairly unsurprising semantics.
~Matt
On Mon, Mar 15, 2021 at 3:55 PM Guido van Rossum <guido@python.org> wrote:
+1
On Mon, Mar 15, 2021 at 12:48 PM Ethan Furman <ethan@stoneleaf.us> wrote:
On 3/15/21 11:27 AM, Guido van Rossum wrote:
On Mon, Mar 15, 2021 at 10:53 AM Ethan Furman wrote:
Part of the reason is that there are really two ways to identify an enum -- by name, and by value -- which should `__contains__` work with?
The two sets don't overlap, so we could allow both. (Funny interpretations of `__contains__` are not unusual, e.g. substring checks are spelled 'abc' in 'fooabcbar'.)
They could overlap if the Enum is a `str`-subclass -- although having the name of one member match the value of a different member seems odd.
I think I like your constructor change idea, with a small twist:
Color(value=<sentinel>, name=<sentinel>, default=<sentinal>)
This would make it possible to search for an enum by value or by name, and also specify a default return value (raising an exception if the default is not set and a member cannot be found).
So specifically this would allow (hope my shorthand is clear): ``` Color['RED'] --> Color.RED or raises Color(1) -> Color.RED or raises Color(1, default=None) -> Color.RED or None Color(name='RED', default=None) -> Color.RED or None ``` This seems superficially reasonable. I'm not sure what Color(value=1, name='RED') would do -- insist that both value and name match? Would that have a use case?
I would enforce that both match, or raise. Also not sure what the use-case would be.
My remaining concern is that it's fairly verbose -- assuming we don't really need the name argument, it would be attractive if we could write Color(1, None) instead of Color(1, default=None).
Note that instead of Color(name='RED') we can already write this: ``` getattr(Color, 'RED') -> Color.RED or raises getattr(Color, 'RED', None) -> Color.RED or None
Very good points.
Everything considered, I think I like allowing `__contains__` to verify both names and values, adding `default=<sentinel>` to the constructor for the value-based "gimme an Enum or None" case, and recommending `getattr` for the name-based "gimme an Enum or None" case.
-- ~Ethan~ _______________________________________________ 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/UQBSDZ... Code of Conduct: http://python.org/psf/codeofconduct/
-- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/> _______________________________________________ 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/ZK7KKA...
Code of Conduct: http://python.org/psf/codeofconduct/
-- --Guido (mobile)
That's a problem with any attempt to find an enum member by value, since values aren't guaranteed to be unique. With either proposal, we'd just need to pick one - probably the one that appears first in the class dict. On Tue, Mar 16, 2021, 2:39 PM Marco Sulla <Marco.Sulla.Python@gmail.com> wrote:
On Tue, 16 Mar 2021 at 05:38, Matt Wozniski <godlygeek@gmail.com> wrote:
Color.from_value(1) # returns Color.RED
What if I have an alias? _______________________________________________ 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/NQ535P... Code of Conduct: http://python.org/psf/codeofconduct/
On 3/16/21 11:43 AM, Matt Wozniski wrote:
On Tue, Mar 16, 2021, 2:39 PM Marco Sulla wrote:
On Tue, 16 Mar 2021 at 05:38, Matt Wozniski wrote:
Color.from_value(1) # returns Color.RED
What if I have an alias?
Aliases are different names for a single Enum member, so a by-value search is unaffected by them.
That's a problem with any attempt to find an enum member by value, since values aren't guaranteed to be unique. With either proposal, we'd just need to pick one - probably the one that appears first in the class dict.
This is incorrect. Enum values are unique -- there is only one member that will be returned for any given value. Aliases are additional names for that one member: from enum import Enum class Color(Enum): RED = 1 GREEN = 2 BLUE = 3 REDD = 1 # to support a silly misspelling >>> Color.RED <Color.RED: 1> >>> Color(1) <Color.RED: 1> >>> Color['RED'] <Color.RED: 1> >>> Color['REDD'] <Color.RED: 1> Notice that 'REDD' returns the RED member. There is no member named REDD in Color. -- ~Ethan~
On Mon, 15 Mar 2021 at 20:49, Ethan Furman <ethan@stoneleaf.us> wrote:
Everything considered, I think I like allowing `__contains__` to verify both names and values
What about Enum.values()?
adding `default=<sentinel>` to the constructor for the value-based "gimme an Enum or None" case
What's the use case, apart checking if the value is a "member" of the enum?
12.03.21 23:48, Ethan Furman пише:
A question that comes up quite a bit on Stackoverflow is how to test to see if a value will result in an Enum member, preferably without having to go through the whole try/except machinery.
A couple versions ago one could use a containment check:
if 1 in Color:
but than was removed as Enums are considered containers of members, not containers of the member values. It was also possible to define one's own `_missing_` method and have it return None or the value passed in, but that has also been locked down to either return a member or raise an exception.
At this point I see three options:
1) add a `get(value, default=None)` to EnumMeta (similar to `dict.get()`
2) add a recipe to the docs
3) do nothing
Thoughts?
The Enum class is already too overloaded. I sometimes think about adding SimpleEnum class with minimal simple functionality which would allow to use enums in more modules sensitive to import time. As for solving your problem, try/except looks the best solution to me. try: Color(1) except ValueError: ... # invalid color else: ... # valid color If you don't like try/except, the second best solution is to add a module level helper in the enum module: def find_by_value(cls, value, default=None): try: return cls(value) except ValueError: return default You can add also find_all_by_value(), get_aliases(), etc. It is important that they are module-level function, so they do not spoil the namespace of the Enum class.
On Wed, Mar 17, 2021, 5:34 AM Serhiy Storchaka <storchaka@gmail.com> wrote:
12.03.21 23:48, Ethan Furman пише:
A question that comes up quite a bit on Stackoverflow is how to test to see if a value will result in an Enum member, preferably without having to go through the whole try/except machinery.
...
Thoughts?
The Enum class is already too overloaded. I sometimes think about adding SimpleEnum class with minimal simple functionality which would allow to use enums in more modules sensitive to import time.
As for solving your problem, try/except looks the best solution to me.
try: Color(1) except ValueError: ... # invalid color else: ... # valid color
If you don't like try/except, the second best solution is to add a module level helper in the enum module:
def find_by_value(cls, value, default=None): try: return cls(value) except ValueError: return default
You can add also find_all_by_value(), get_aliases(), etc. It is important that they are module-level function, so they do not spoil the namespace of the Enum class.
+1 on a module level helper. The preponderance of stackoverflow questions seem convincing enough that people want to do this and it seems like a reasonable request. And I have wanted to do something like this myself, so I'd probably use it. I see it as analogous to situations when you want to use dict.get(key) instead of dict[key]. But adding a non-dunder method to the Enum class namespace seems more suboptimal to me compared to a module level helper, because of the namespace spoiling/crowding issue. No matter what method name were to be chosen, someone at some point would want to use it as an Enum member name (unless of course it's a reserved dunder method).
participants (6)
-
Ethan Furman
-
Guido van Rossum
-
Marco Sulla
-
Matt Wozniski
-
Ricky Teachey
-
Serhiy Storchaka