MtotheM
MtotheM

Reputation: 73

Is there a way to instantiating a class directly from an Enum?

I recently found out you can use enums in Python, but I'm not happy with how my code is working and I'm wondering if there is a cleaner way to implement it.

from enum import Enum

class Hex:
    def __init__(self, hex_code: str):
        self.code = hex_code

class Rgb:
    def __init__(self, R: int, G: int, B: int):
        self.r = R
        self.g = G
        self.b = B

class Color(Enum):
    HEX = Hex
    RGB = Rgb

def main():
    hex_color = Color.HEX.value('#00FF00')
    rgb_color = Color.RGB.value(255, 255, 255)

if __name__ == "__main__":
    main()

In this example I have to instantiate by calling the .value() enum method. but when you instantiate a class normally, all you do is Class(value). Would it be possible to implement something similar to enum variants that holds a class? For example:

Color.HEX('#00FF00')
# Instead of:
Color.HEX.value('#00FF00')

Upvotes: 4

Views: 2705

Answers (2)

Ethan Furman
Ethan Furman

Reputation: 69288

I see nothing in your question that requires, or is benefited by, the use of Enum.

Check this for guidelines on using Enum.

Enum does offer easy membership testing, so you could do:

hex_value is rgb_value

or

hex_value in Color

Using the aenum1 library, your code would look like this:

from aenum import Enum, extend_enum

class Color(Enum):
    #
    def __new__(cls, value):
        member = object.__new__(cls)
        member._value_ = value
        member.hex = hex(value)[2:]
        r, value = divmod(value, 1 << 16)
        g, value = divmod(value, 1 << 8)
        b = value
        member.red = r
        member.green = g
        member.blue = b
        member.rgb = r, g, b
        return member
    #
    @classmethod
    def _missing_(cls, value):
        # r, g, b = value
        name = 'rgb:%r' % (value, )
        # name = 'rgb:%r' % ((r << 16) + (g << 8) + b, )
        extend_enum(cls, name, value)
        return cls[name]
    #
    @classmethod
    def from_hex(cls, value):
        # on leading #
        red = int(value[:2], 16)
        green = int(value[2:4], 16)
        blue = int(value[4:], 16)
        value = (red << 16) + (green << 8) + blue
        return cls(value)
    #
    @classmethod
    def from_rgb(cls, red, green, blue):
        value = (red << 16) + (green << 8) + blue
        return cls(value)
    #
    RED = 255 << 16
    GREEN = 255 << 8
    BLUE = 255

and in use:

>>> list(Color)
[<Color.RED: 16711680>, <Color.GREEN: 65280>, <Color.BLUE: 255>]

>>> Color.from_rgb(255, 0, 0)
<Color.RED: 16711680>

>>> Color.from_hex('00FF00')
<Color.GREEN: 65280>

>>> Color.from_hex('15A97F')
<Color.rgb:1419647: 1419647>

>>> Color.from_rgb(21, 169, 127)
<Color.rgb:1419647: 1419647>

>>> Color.from_hex('15A97F') is Color.from_rgb(21, 169, 127)
True

You can, of course, change the details of the repr(), the stored attributes (red, green, blue, hex, rgb, etc).


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

Upvotes: 1

chepner
chepner

Reputation: 532418

HEX and RGB are not classes; they are instances of Color. (enum uses a metaclass that abuses the class statement quite a bit.) You use the value attribute of those instances to get the value you "assigned" to those names.

In order to make Color.HEX('#00ff00') return an instance of the class Hex, you need to define Color.__call__. (As an aside, note that you can simply define the two classes inside the class statement that defines Color, rather than defining them externally. A class statement is just a fancy assignment statement at heart.)

from enum import Enum

class Color(Enum):
    class HEX:
        def __init__(self, hex_code: str):
            self.code = hex_code

    class RGB:
        def __init__(self, R: int, G: int, B: int):
            self.r = R
            self.g = G
            self.b = B

    def __call__(self, *args):
        return self.value(*args)

Then

>>> Color.HEX('#00ff00')
<__main__.Color.HEX object at 0x108740f28>

There's no inherited value of __call__ being overriden, so there's no immediate need to use anything like super().__call__(*args) in its definition. That might change if you think you'll need to support multiple inheritance, but given the use of a custom metaclass by Enum, I'm going to declare handling that beyond the scope of this question.

Upvotes: 3

Related Questions