4

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')
Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
MtotheM
  • 73
  • 5
  • 1
    no, there isn't without fundamentally changing how `enum` works. What is your use-case for enum here? why not just use the classes directly? In any case, `value` isn't a method, it is simply the attribute on the enum object which holds the *value* you gave to that enum member. – juanpa.arrivillaga Jul 23 '19 at 19:56
  • @chepner I'm not sure the OP is trying to dynamically generate a class. – juanpa.arrivillaga Jul 23 '19 at 19:58
  • @chepner but to me it seems like the issue is that the OP wants to be able to call `Color.HEX()` to instantiate an instance, rather than `Color.HEX.value()`. Honestly, to do what you're trying to do i would just use the class definition statement and simply `del` the names, if that was bothering me. Of course, I simply think that this isn't a use-case for `enum` – juanpa.arrivillaga Jul 23 '19 at 20:00
  • Actually, I forgot that since a `class` statement is just a fancy assignment statement, you can just nest the definitions of `Hex` and `Rgb` directly in `Color`, so that eliminates the need for the `type` hackery. True, it doesn't eliminate the issue of having to use `value` to get back the actual type instance. – chepner Jul 23 '19 at 20:03
  • I ran your code, unfortunately IDLE's subprocess did not make connection – Ṃųỻịgǻňạcểơửṩ Jul 23 '19 at 20:03
  • I mostly want to learn more in depth how i can use enums. cause in other languages I've been programming in, they have been very useful. That being said. my main goal with trying this approach were to make an easy API for grouping types together so i can access them with a deterministic path. while being able to assign shared behavior. `Color.HEX('#00ff00')` Instead of the following and not knowing what it belongs to. `Hex('#00ff00')` But after closer inspection i agree with the comments in here. that it makes more sense to use modules for pathing. – MtotheM Jul 23 '19 at 23:09

2 Answers2

3

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.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • 1
    This is cool, but then why bring `Enum` into the picture at all? Just use `Color(object)` and give it class attributes? – Brad Solomon Jul 23 '19 at 20:34
  • That's a question you could ask about just about *any* use of `Enum`. – chepner Jul 23 '19 at 20:47
  • There are good use-cases for using `Enum` -- the OP's is not one of them. – Ethan Furman Jul 23 '19 at 22:34
  • I'm not a fan of nesting the classes inside. cause it makes them unavailable outside. doing this approach i would rather just have the enum point at the outer class definitions. not having them defined inside. – MtotheM Jul 23 '19 at 23:12
  • You weren't using them outside the class in the first place; the whole point of the question was how to write `Color.HEX(...)` instead of `Hex(...)`. – chepner Jul 24 '19 at 01:12
1

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.

Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
  • I came to the realization that my way of using the Enum here isn't very pythonic. i'm used to languages that doesn't have inheritance. were grouping types in an Enum is very useful. but in this case it doesn't offer any benefits apart from the pathing. which can be achived by nesting modules in folders using normal inheritance. – MtotheM Jul 23 '19 at 23:23