'Using objects as the values of an Enum?

I'm currently trying to create an enum/constant-based system for defining colours in Python. I want to avoid having to use strings when attempting to use/access a colour.

In an ideal world, I want to be able to do:

>>> print(Colours.BLACK)
Black

>>> print(Colours.WHITE.hex)
ffffff

So far, I have come up with an enum and object based system like the following:

from enum import Enum

class Colour:

  def __init__(self, name, hex = "000000"):
    self.name = name
    self.hex = hex

  def __str__(self):
    return self.name


class Colours(Enum):
  WHITE = Colour("White", "ffffff")
  BLACK = Colour("Black", "000000")

print(Colours.BLACK.value)

print(Colours.WHITE.value.hex)

which prints:

Black
ffffff

However, something about assigning an object as the value of an enum feels like poor practice and I have not seen this done before elsewhere.

Is this considered bad? If so, why?

And is there a better way of achieving my desired outcome?

Edit:

I ended up going with this approach, using __hash__ as suggested by @MichealButscher and using __str__ and __repr__ to avoid using .value.

from enum import Enum

class Colour:

  def __init__(self, label, hex_value = "000000"):
    self.label = label
    self.hex_value = hex

  def __str__(self):
    return self.label

  def __hash__(self):
    hash(self.label)


class Colours(Enum):
  WHITE = Colour("White", "ffffff")
  BLACK = Colour("Black", "000000")

  def __repr__(self):
    return self.value

  def __str__(self):
    return str(self.value)


Solution 1:[1]

Everything is an object, so some object will be assigned. However, you can use just the color name and hex value in a tuple as the value, and have Colour.__init__ handle initializing each instance with the tuple.

class Colour(Enum):
    WHITE = ("White", "ffffff")
    BLACK = ("Black", "000000")

    def __init__(self, label, hexvalue):
        self.label = label
        self.hexvalue = hexvalue

(Warning: certain attribute names are already in use and cannot be overridden or even assigned to, as you'll see if you try to, for example, use name instead of label and value instead of hex value.)

Solution 2:[2]

Unless you have a need for a separate color class, just combine it all into the enum:

from enum import Enum

class Colour(Enum):
    WHITE = "ffffff"
    BLACK = "000000"
    #
    def __str__(self):
        return self.name

and in use:

>>> Colour.WHITE
<Colour.WHITE: 'ffffff'>

>>> str(Colour.WHITE)
'WHITE'

>>> Colour.WHITE.value
'ffffff'

@martineau, @chepner is saying the following will fail:

from enum import Enum

class Colour(Enum):
    WHITE = 'White', "ffffff"
    BLACK = 'black', "000000"
    #
    def __init__(self, name, hex):
        self.name = name              # uh-oh
        self.hex = hex
    #
    def __str__(self):
        return self.name

and indeed it does:

Traceback (most recent call last):
  ...
AttributeError: <enum 'Enum'> cannot set attribute 'name'

I think what you were saying is that the following will work:

class Example(Enum):
    name = 'a name member'
    value = 'a value member'

In use:

>>> list(Example)
[<Example.name: 'a name member'>, <Example.value: 'a value member'>]

>>> Example.name.name
'name'

If you weren't saying that, then you were wrong. :-/

As far as PEP 20 goes, commas are explicit.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Ethan Furman