6

I defined the following Enum in Python:

class Unit(Enum):
    GRAM = ("g")
    KILOGRAM = ("kg", GRAM, 1000.0)

    def __init__(self, symbol, base_unit = None, multiplier = 1.0):
        self.symbol = symbol
        self.multiplier = multiplier
        self.base_unit = self if base_unit is None else base_unit

I would expect that

print(Unit.GRAM.base_unit)
print(Unit.KILOGRAM.base_unit)

will return

Unit.GRAM
Unit.GRAM

However, what I get is quite confusing

Unit.GRAM
g

Why is it so?

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
dzieciou
  • 4,049
  • 8
  • 41
  • 85
  • Possible duplicate of [Python enum - getting value of enum on string conversion](https://stackoverflow.com/questions/24487405/python-enum-getting-value-of-enum-on-string-conversion)... also, take a look at how to represent the `str` unit in the other (unaccepted) answer – deadvoid Oct 14 '18 at 00:18
  • 1
    This is not a duplicate of that question. This question is asking how members can refer to each other before the class has been fully constructed. – Ethan Furman Jun 29 '19 at 04:48

2 Answers2

3

The way Python defines a class involves creating a new scope, processing a bunch of statements (variable assignments, function definitions, etc.), and then actually creating a class object based on the local variables which exist after all those statements have run. Nothing gets converted into Enum instances until that last step.

You could understand it somewhat like this:

def make_class_Unit():
  GRAM = ("g")
  KILOGRAM = ("kg", GRAM, 1000.0)

  def __init__(self, symbol, base_unit = None, multiplier = 1.0):
    self.symbol = symbol
    self.multiplier = multiplier
    self.base_unit = self if base_unit is None else base_unit
  return make_class(name='Unit', base=Enum, contents=locals())

Unit = make_class_Unit()

Looking at it this way, hopefully you can tell that at the time when KILOGRAM is defined, GRAM is really just a string. It doesn't become a Unit instance until the last stage, where I call the (imaginary) make_class() function.1


1Even though the make_class function I used above doesn't actually exist under that name, it's not too different from what Python really does, which is calling the constructor of type or a metaclass (which in this case is the metaclass for Enums).

David Z
  • 128,184
  • 27
  • 255
  • 279
1

DavidZ explained the problem well.

The last bit that you need to solve this problem is this: when the __init__ of each member is being run, the Enum has been created -- so you can call it:

self.base_unit = self if base_unit is None else self.__class__(base_unit)
Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
  • Interesting. I thought that, perhaps, `Unit.KILOGRAM.base_unit` and `Unit.GRAM` will refer to different objects, but `id(Unit.GRAM)`, `id(Unit.KILOGRAM.base_unit)`, `id(Unit.GRAM.base_unit)` all return same id. So there are no redundant instances. – dzieciou Jul 02 '19 at 13:48