Reputation: 31
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)
Upvotes: 3
Views: 3376
Reputation: 7756
@ethan-furman @martineau
You can overcome the issue with the pre-used name attribute by using property
decorators and storing the name to a different attribute under the hood:
from enum import Enum
class Colour(Enum):
WHITE = 'White', "ffffff"
BLACK = 'black', "000000"
def __init__(self, name, hex):
self._name = name # no uh-oh anymore
self.hex = hex
@property
def name(self):
return self._name
@property
def label(self):
return self._name_
>>> Colour.WHITE
<Colour.WHITE: ('White', 'ffffff')>
>>> Colour.WHITE.name
'White'
>>> Colour.WHITE.label
'WHITE'
Upvotes: 1
Reputation: 69288
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.
Upvotes: 0
Reputation: 532418
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
.)
Upvotes: 1