4

Suppose I have a Python Enum were each instance of the Enum should reference another instance of the same enum. How do I do that?

When I try something like this:

    class Direction(Enum):
        NORTH = (1,0, Direction.EAST, Direction.WEST)
        SOUTH = (-1,0, Direction.WEST, Direction.EAST)
        EAST = (0,1, Direction.SOUTH, Direction.NORTH)
        WEST = (0, -1, Direction.NORTH, Direction.SOUTH)
        
        def __init__(self, y, x, r, l):
            self.y = y
            self.x = x
            self.r = r
            self.l = l

I get an error that looks a bit like this:

Traceback (most recent call last):
  File "lol.py", line 2, in <module>
    class Direction(Enum):
  File "lol.py", line 3, in Direction
    NORTH = (1,0, Direction.EAST, Direction.WEST)
NameError: name 'Direction' is not defined

I get the same issue when I replace "Direction" with "self" in the above example.

Is there a way to do this?

  • You are trying to define an enumeration value `NORTH` in terms of another value of the same enumeration `EAST`, which is in turn defined in terms of the value `NORTH` - such a circular definition can only lead to problems. What are you trying to achieve? Because there is probably a good, Pythonic solution to your XY problem here. – Grismar Sep 18 '20 at 05:45
  • I essentially want to be able to turn 'right' or 'left' by looking up the value of the direction we're turning to from the current direction. Ie: ```new_direction = old_direction.l ``` – Andrew Musselman Sep 18 '20 at 05:56

2 Answers2

2

If you don't mind reordering your Enum slightly, it's fairly easy:

class Directions(Enum):
    #
    NORTH = 1, 0
    EAST = 0, 1
    SOUTH = -1, 0
    WEST = 0, -1
    #
    def __init__(self, x, y):
        self.x = x
        self.y = y
        if len(self.__class__):
            # make links
            all = list(self.__class__)
            left, right = all[0], all[-1]
            self.left = left
            self.right = right
            left.right = self
            right.left = self

and in use:

>>> Directions.NORTH
<Directions.NORTH: (1, 0)>

>>> Directions.NORTH.left
<Directions.EAST: (0, 1)>

>>> Directions.NORTH.right
<Directions.WEST: (0, -1)>

>>> Directions.WEST.left
<Directions.NORTH: (1, 0)>

>>> Directions.WEST.right
<Directions.SOUTH: (-1, 0)>

Disclosure: I am the author of the Python stdlib Enum, the enum34 backport, and the Advanced Enumeration (aenum) library.

Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
  • Nice. I was also thinking about adding the point that it's easy to declare the values and then create the structure in a method .. but was thinking more still to populate a dict. Was thinking how enums are in other languages, I guess Java and C#. Though thinking that maybe it's somehow different in Python. Will check the docs out a bit, thanks! – antont Sep 23 '20 at 20:04
0

You can use @antont suggestion and use a dictionary, that's perfectly valid.

Another solution, close to the original:

from enum import Enum


class Direction(Enum):
    NORTH = 0
    SOUTH = 1
    EAST = 2
    WEST = 3

    @property
    def x(self):
        return 0 if self in [self.NORTH, self.SOUTH] else 1 if self == self.EAST else -1

    @property
    def y(self):
        return 0 if self in [self.EAST, self.WEST] else 1 if self == self.NORTH else -1

    def _turn(self, left=True):
        dirs = [self.NORTH, self.EAST, self.SOUTH, self.WEST]
        return dirs[(dirs.index(self) + (-1 if left else 1)) % 4]

    @property
    def left(self):
        return self._turn()

    @property
    def right(self):
        return self._turn(left=False)


direction = Direction.SOUTH
print(direction.y)
print(direction.left)

Output:

-1
Direction.EAST
Grismar
  • 27,561
  • 4
  • 31
  • 54