Should nested classes in an Enum be Enum members?

Consider the following Enum definition: class Color(Enum): RED = 1 GREEN = 2 BLUE = 3 @property def lower(self): return self.name.lower() def spam(self): return "I like %s eggs and spam!" % self.lower class SomeClass: pass Which of the above Color attributes are enums, and which aren't? . . . Answer: - RED, GREEN, and BLUE are members - lower and spam() are not - SomeClass /is/ a member (but not its instances) Question: Should `SomeClass` be an enum member? When would it be useful to have an embedded class in an Enum be an enum member? The only example I have seen so far of nested classes in an Enum is when folks want to make an Enum of Enums, and the nested Enum should not itself be an enum member. Since the counter-example already works I haven't seen any requests for it. ;) So I'm asking the community: What real-world examples can you offer for either behavior? Cases where nested classes should be enum members, and cases where nested classes should not be members. Thanks! -- ~Ethan~

Sounds to me really strange that the nested class would become a member. Probably because everything becomes a member unless it's a function (maybe decorated)? On Wed, Jun 27, 2018 at 7:47 AM Ethan Furman <ethan@stoneleaf.us> wrote:
-- --Guido van Rossum (python.org/~guido)

People working with sum types might expect the instances of the nested class to be instances of the enclosing class. So if the nested class is a namedtuple, you get a sum type. The only problem is that there's no way to express this subtype relationship in code. Elazar בתאריך יום ד׳, 27 ביוני 2018, 11:59, מאת Guido van Rossum <guido@python.org
:

Yes, Ryan. I mean a way to express something like sealed classes in scala/kotlin. The Enum class defines a finite region in which subclasses can be defined, thus allows verifying that "elif" cases are exhaustive, for exampe. It mostly helpful for static type checking, but it also helps readability and is a natural way to describe ASTs. class Expr(Enum): class BinOp(NamedTuple): # ideally should subclass Expr left: Expr right: Expr op: str class UnOp(NamedTuple): operator: Expr operand: str ... It's one of the (rejected) ideas here: https://github.com/python/mypy/issues/2464 Not the best link out there but it explains: https://antonioleiva.com/sealed-classes-kotlin/ Elazar On Wed, Jun 27, 2018 at 12:49 PM Ryan Gonzalez <rymg19@gmail.com> wrote:

On 06/27/2018 11:52 AM, Guido van Rossum wrote:
Sounds to me really strange that the nested class would become a member. Probably because everything becomes a member unless it's a function (maybe decorated)?
Pretty much. __dunders__, _sunders_, and descriptors do not get transformed. Everything else does. -- ~Ethan~

Guido van Rossum wrote:
Maybe it would have been better if Enums got told what type their members are supposed to be, an only decorated things of that type. class Color(Enum): __type__ = int RED = 1 GREEN = 2 BLUE = 3 i_get_left_alone = 4.2 Or perhaps this could be made to work somehow: class Color(Enum(int)): RED = 1 GREEN = 2 BLUE = 3 i_get_left_alone = 4.2 -- Greg

Enum already is callable - it creates Enum subclasses. e.g. Color = Enum("Color", ("RED", "GREEN", "BLUE")) (or something similar, I didn't check the docs.) In general, it sounds to me like you already can avoid them becoming members if you want - simply by making them a property. Therefore, letting them be members by default doesn't sound weird to me. As for embedded classes - well, the cannot be subclasses because the outer class is not instantiated. You could perhaps build a class decorator that injects the outer class in the inner class' MRO, but that doesn't sound like code I'd want to debug.

27.06.18 17:46, Ethan Furman пише:
What would be a benefit of making a class nested if it not be an enum member? Nested functions become methods, but there are no relations between a nested class or its instances and the outer class or its instances. The current behavior looks understandable to me. Functions are descriptors, but classes are not. Making a nested class a member you don't lost anything, because you always can make it not-nested if you don't want it be a member. But when a nested class is not a member, you would lost the possibility of making it a member (and this may break existing code).

On Thu, Jun 28, 2018 at 06:57:45AM +0300, Serhiy Storchaka wrote:
You lose the ability to have Colors.RED.NestedClass() # returns something useful # similar to Colors.RED.method() for the (dubious?) advantage of having Colors.NestedClass treated as a colour enum.
I must admit I'm still perplexed why I might want NestedClass to be an enum member. -- Steve

29.06.18 03:25, Steven D'Aprano пише:
Since NestedClass is an enum member, you should use Colors.RED.NestedClass.value() or just Colors.NestedClass.value() for calling its value. If you don't want it be a member, just don't initialize it in the enum body. class Color(Enum): RED = 1 class NestedClass: pass Color.RED.NestedClass = NestedClass Now Color.RED.NestedClass() can return something useful.

Replying to the list this time. On 27 June 2018 at 15:46, Ethan Furman <ethan@stoneleaf.us> wrote:
I wanted few times to make an enum of enums. For example: class Method(Enum): Powell = 1 Newton_CG = 2 <few more> class Trust(Enum): Constr = 3 Exact = 4 <few more> So that one can write: minimize(..., method=Method.Powell) minimize(..., method=Method.Trust.Exact) # this currently fails -- Ivan

On 1 July 2018 at 20:47, Ethan Furman <ethan@stoneleaf.us> wrote:
I am fine with what `list(Method)` does currently: [<Method.Powell: 1>, <Method.Newton_CG: 2>, <Method.Trust: <enum 'Trust'>>] I think it is intuitive that the nested enums are _not_ automatically flattened. Also it is easy to write a helper that manually flattens nested enums, but it would be tedious to group them back if they are already flattened. The only thing that I find a bit unintuitive is that one needs to write `Method.Trust.value.Exact` instead of just `Method.Trust.Exact`. Maybe his can be allowed by adding a `__getattr__` to `Method` (by the metaclass) that will check if a value is an enum and delegate the attribute access. -- Ivan

Sounds to me really strange that the nested class would become a member. Probably because everything becomes a member unless it's a function (maybe decorated)? On Wed, Jun 27, 2018 at 7:47 AM Ethan Furman <ethan@stoneleaf.us> wrote:
-- --Guido van Rossum (python.org/~guido)

People working with sum types might expect the instances of the nested class to be instances of the enclosing class. So if the nested class is a namedtuple, you get a sum type. The only problem is that there's no way to express this subtype relationship in code. Elazar בתאריך יום ד׳, 27 ביוני 2018, 11:59, מאת Guido van Rossum <guido@python.org
:

Yes, Ryan. I mean a way to express something like sealed classes in scala/kotlin. The Enum class defines a finite region in which subclasses can be defined, thus allows verifying that "elif" cases are exhaustive, for exampe. It mostly helpful for static type checking, but it also helps readability and is a natural way to describe ASTs. class Expr(Enum): class BinOp(NamedTuple): # ideally should subclass Expr left: Expr right: Expr op: str class UnOp(NamedTuple): operator: Expr operand: str ... It's one of the (rejected) ideas here: https://github.com/python/mypy/issues/2464 Not the best link out there but it explains: https://antonioleiva.com/sealed-classes-kotlin/ Elazar On Wed, Jun 27, 2018 at 12:49 PM Ryan Gonzalez <rymg19@gmail.com> wrote:

On 06/27/2018 11:52 AM, Guido van Rossum wrote:
Sounds to me really strange that the nested class would become a member. Probably because everything becomes a member unless it's a function (maybe decorated)?
Pretty much. __dunders__, _sunders_, and descriptors do not get transformed. Everything else does. -- ~Ethan~

Guido van Rossum wrote:
Maybe it would have been better if Enums got told what type their members are supposed to be, an only decorated things of that type. class Color(Enum): __type__ = int RED = 1 GREEN = 2 BLUE = 3 i_get_left_alone = 4.2 Or perhaps this could be made to work somehow: class Color(Enum(int)): RED = 1 GREEN = 2 BLUE = 3 i_get_left_alone = 4.2 -- Greg

Enum already is callable - it creates Enum subclasses. e.g. Color = Enum("Color", ("RED", "GREEN", "BLUE")) (or something similar, I didn't check the docs.) In general, it sounds to me like you already can avoid them becoming members if you want - simply by making them a property. Therefore, letting them be members by default doesn't sound weird to me. As for embedded classes - well, the cannot be subclasses because the outer class is not instantiated. You could perhaps build a class decorator that injects the outer class in the inner class' MRO, but that doesn't sound like code I'd want to debug.

27.06.18 17:46, Ethan Furman пише:
What would be a benefit of making a class nested if it not be an enum member? Nested functions become methods, but there are no relations between a nested class or its instances and the outer class or its instances. The current behavior looks understandable to me. Functions are descriptors, but classes are not. Making a nested class a member you don't lost anything, because you always can make it not-nested if you don't want it be a member. But when a nested class is not a member, you would lost the possibility of making it a member (and this may break existing code).

On Thu, Jun 28, 2018 at 06:57:45AM +0300, Serhiy Storchaka wrote:
You lose the ability to have Colors.RED.NestedClass() # returns something useful # similar to Colors.RED.method() for the (dubious?) advantage of having Colors.NestedClass treated as a colour enum.
I must admit I'm still perplexed why I might want NestedClass to be an enum member. -- Steve

29.06.18 03:25, Steven D'Aprano пише:
Since NestedClass is an enum member, you should use Colors.RED.NestedClass.value() or just Colors.NestedClass.value() for calling its value. If you don't want it be a member, just don't initialize it in the enum body. class Color(Enum): RED = 1 class NestedClass: pass Color.RED.NestedClass = NestedClass Now Color.RED.NestedClass() can return something useful.

Replying to the list this time. On 27 June 2018 at 15:46, Ethan Furman <ethan@stoneleaf.us> wrote:
I wanted few times to make an enum of enums. For example: class Method(Enum): Powell = 1 Newton_CG = 2 <few more> class Trust(Enum): Constr = 3 Exact = 4 <few more> So that one can write: minimize(..., method=Method.Powell) minimize(..., method=Method.Trust.Exact) # this currently fails -- Ivan

On 1 July 2018 at 20:47, Ethan Furman <ethan@stoneleaf.us> wrote:
I am fine with what `list(Method)` does currently: [<Method.Powell: 1>, <Method.Newton_CG: 2>, <Method.Trust: <enum 'Trust'>>] I think it is intuitive that the nested enums are _not_ automatically flattened. Also it is easy to write a helper that manually flattens nested enums, but it would be tedious to group them back if they are already flattened. The only thing that I find a bit unintuitive is that one needs to write `Method.Trust.value.Exact` instead of just `Method.Trust.Exact`. Maybe his can be allowed by adding a `__getattr__` to `Method` (by the metaclass) that will check if a value is an enum and delegate the attribute access. -- Ivan
participants (10)
-
Elazar
-
Ethan Furman
-
Greg Ewing
-
Guido van Rossum
-
Ivan Levkivskyi
-
Jacco van Dorp
-
Random832
-
Ryan Gonzalez
-
Serhiy Storchaka
-
Steven D'Aprano