How to make nested enum also have value

Question:

Consider the following code example:

from enum import Enum

class Location(Enum):
    Outside = 'outside'
    Inside = 'inside' 
    class Inside(Enum): # TypeError for conflicting names
        Downstairs = 'downstairs'
        Upstairs = 'upstairs'

How do I make Inside have the value ‘inside’ whilst also being a nested enum for accessing Downstairs and Upstairs?

Desired input:

print(Location.Inside)
print(Location.Inside.value)
print(Location.Inside.Downstairs)
print(Location.Inside.Downstairs.value)

Desired output:

Location.Inside
inside
Location.Inside.Downstairs
downstairs

UPDATE 1:

Some more context to my specific problem:

class Location(Enum):
    Outside = 'outside'
    Inside = 'inside' 
    class Inside(Enum): # TypeError for conflicting names
        Downstairs = 'downstairs'
        Upstairs = 'upstairs'

class Human:
    def __init__(self, location):
        self.location = location

def getLocationFromAPI():
    # this function returns either 'inside' or 'outside'
    # make calls to external API  
    return location # return location from api in str

def whereInside(human):
    if human.location != Location.Inside:
        return None
    # here goes logic that determines if human is downstairs or upstairs
    return locationInside # return either Location.Downstairs or Location.Upstairs


location_str = getLocationFromAPI() # will return 'inside' or 'outside'
location = Location(location_str) # make Enum
human = Human(location) # create human with basic location
if human.location == Location.Inside:
    where_inside = whereInside(human)
    human.location = where_inside # update location to be more precise

The problem is when I create the Human object I only know of a basic location, as in ‘inside’ or ‘outside’. Only after that can I update the location to be more precise.

Asked By: Dr. Alban

||

Answers:

You can accomplish this by embedding an enum.Enum inside another like so: (just watch out for names conflicting)

from enum import Enum

class _Inside(Enum):
    Downstairs = 'downstairs'
    Upstairs = 'upstairs'

class Location(Enum):
    Outside = 'outside'
    Inside = _Inside 

print(Location.Inside.value.Downstairs.value)
downstairs
Answered By: Jab

it may be a bit late and the one who asked the question is no longer necessary, but I leave it here in case someone wants to take a look at it, and even if it has already been validated as one, although the same comment that it is not completely complete .

But I have been thinking about it and in the end I have solved it by looking at the same documentation XD.

You cannot extend classes of Enums, but you can extend methods, I have followed this way and the only thing I have done has been to override the new and init methods, the use case can be modified, this is only to nest enumerators.

from enum import Enum

class SuperNestedEnum(Enum):
    def __new__(cls, *args):
        obj = object.__new__(cls)
        value = None
        # Normal Enumerator definition
        if len(args) == 1:
            value = args[0]

        # Have a tuple of values, first de value and next the nested enum (I will set in __init__ method)
        if len(args) == 2:
            value = args[0]

        if value:
            obj._value_ = value

        return obj

    def __init__(self, name, nested=None):
        # At this point you can set any attribute what you want
        if nested:
            # Check if is an Enumerator you can comment this if. if you want another object
            if isinstance(nested, EnumMeta):
                for enm in nested:
                    self.__setattr__(enm.name, enm)


class Homework(Enum):
    Task = "5"

class Subjects(SuperNestedEnum):
    Maths = "maths"
    English = "english"
    Physics = "nested", Homework

class School(SuperNestedEnum):
    Name = "2"
    Subjects = "subjects", Subjects

Ignore the use case because it doesn’t make sense, it’s just an example

>>> School.Name
<School.Name: '2'>

>>> School.Subjects
<School.Subjects: 'subjects'>

>>> School.Subjects.value
'subjects'

>>> School.Subjects.Maths
<Subjects.Maths: 'maths'>

>>> School.Subjects.Physics.value
'nested'

>>> School.Subjects.Physics.Task
<Homework.Task: '5'>

>>> School.Subjects.Physics.Task.value
'5'
Answered By: Víctor Quilón

If anyone has similar issues and just wants a simple solution for the topic without patching any functions or additional imports for enums containing strings, follow these steps:

  1. Create the value enums, in your lower hierarchy, like:

    class __private_enum1__(str, enum.Enum):
        VAL11 = "abc"
        VAL12 = "def"
    
    class enum2(str, enum.Enum):
        VAL21 = "123"
        VAL22 = "456"
    
  2. Create a base class (a container) for these enums. Where you can either import the enums classes or simply directly acccess the enums.

     class myValues:
         VAL11 = __private_enum1__.VAL11
         VAL12 = __private_enum1__.VAL12
         VALS2X = enum2
    

Then you can access your values by:

print(myValues.VAL11.value)
print(myValues.VAL2X.VAL21.value)

.value is not necessary here but it shows that you both access the string inside the enum for passing it to other functions but also the enum itself, which is pretty neat. So basically, first create the values, then the structure. That way you have a class but it provides you the basic functionality of enums and you can nest them as deep as you want to without further imports.

Answered By: MichaelJanz

Maybe not an answer to the question, but still on the same topic. I was looking for some kind of nested Enum but it ended up using just a regular class with class-variables and StrEnum such as:

from enum import StrEnum


class _Inside(StrEnum):
    Downstairs = "downstairs"
    Upstairs = "upstairs"


class Location:
    Outside = "outside"
    Inside = _Inside


print(Location.Inside.Downstairs)

It also works good with IntEnum, but if you are using Enum, you would have to also call the .value-property to get the actual value as:

print(Location.Inside.Downstairs.value)
Answered By: Robert Nyström
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.