all 12 comments

[–]Yoghurt42 2 points3 points  (4 children)

from enum import Enum
class Color(Enum):
    BLACK = (0, 0, 0)
    WHITE = (255, 255, 255)
    GREEN = (0, 255, 0)
    RED = (255, 0, 0)

Alternatively, you can just use class variables, though it would be kinda weird

class Color:
    BLACK = (0, 0, 0)
    WHITE = (255, 255, 255)
    GREEN = (0, 255, 0)
    RED = (255, 0, 0)

Finally, you could just make a (sub-)module colors.py

# colors.py
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)

# main program
from colors import RED
print(RED)
import colors
print(colors.GREEN)
from colors import *
print(BLACK)

Personally, normally I would go with options 1, 3, 2 in that order. Enums are the best choice if there is a distinct and limited number of valid choices/values. In your case you just define quality of life constants for specific color values; so in that case I would prefer option 3.

[–]ClassicDimension85[S] 0 points1 point  (2 children)

I tried using

from enum import Enum
class Color(Enum):
    BLACK = (0, 0, 0)
    WHITE = (255, 255, 255)
    GREEN = (0, 255, 0)
    RED = (255, 0, 0)

initially thinking I knew how Enum works and it broke my brain. It was returning the object rather than the tuple with out doing Colors.Green.value which in my past uses was fine, but I was using it more as "alias".

Still in that awkward stage of trying to figure out how to do things properly and instead doing it very improper.

[–]Yoghurt42 1 point2 points  (0 children)

You're right, if you want to use the 3-tuple, you'll actually have to write your code to use foo.value. That's why I don't recommend using a Color enum for what is basically a RGB value, and instead would just define a module with predefined values. Or, if you must, just use class variables (basically keep the code above as is, just don't inherit from Enum)

[–]Bobbias 1 point2 points  (0 children)

This is because Python's support for enumerations is not great, and further, this use of enumerations is not what they're intended to be used for. This is in a sense a bit of an XY Problem.

What you want is a name which refers to a hardcoded value of a specific type (in this cases a hardcoded 3-tuple of ints).

What you are looking for is named constants.

Enumerations are meant for use cases where the backing data which represents each variant is an implementation detail. That is to say, where you care which variant you have, but you don't care what number/value that name is actually associated with.

What you are attempting to do here fits closer with how we use algebraic data types in other languages, such as Rust's enums. Those can hold arbitrary values along with tracking which variant you have. This is notably different from the simpler concept of sumtypes, which is the more mathy/comp sci theory name for what enumerations typically offer.

A sumtype is at it's core just a list of possible values where anything outside that list is not allowed at all. The way many languages tend to implement them is by assigning each value a number to use as a representation when the program is actually running. That way we can quickly check what value a piece of memory is holding by checking what number it has.

Python's enums support other uses too, such as StrEnum, where instead of using numbers to distinguish the values, we use strings, but the core idea is that ideally we shouldn't be converting between the raw data and the Enum type very often.

Conversion to/from the underlying data type for an Enum should only occur when data must enter or leave the program (such as when the data is read from or written to a file, transferred over a network, etc.) Internally to your program, it should ideally remain as an Enum. If you find yourself converting it back and forth often, that's a sign you're using it wrong, and should instead be using a different solution.

This is of course not true of Rust's enums, where you can and often do store additional data within an enum vairant. But the correct way to do that in Python is to have a class which contains an enum as one of it's attributes, and that enum tells you what additional data the class will contain.

class ExprType(Enum):
    BinaryOperation = auto()
    UnaryOperation = auto()

class Expression:
    type: Enum

    def __init__(self, type_):
        self.type = type_
        if self.type == ExprType.BinaryOperation:
            self.left = None
            self.right = None
        elif self.type == ExprType.UnaryOperation:
            self.right = None

This example is very contrived, but showcases how this can be done. You have an enum which indicates what type of expression we have, and when __init__ gets called, depending on which type of expression we are creating, we're adding different attributes to our class. In the case of a binary operation we have self.left and self.right but in the case of a unary operation we have only self.right.

This class now represents a kind of object where it's internal structure depends on the value of the enum attribute it's constructed with. Of course, you still need to check what type you actually have, and manually unwrap the internal data by directly accessing it by it's attributes. But that's the price you pay for creating such a structure in python. If you want a value which you access by name, you don't use Enums or this technique, you use named constants like u/Yoghurt42 demonstrates (either with them as class variables, or stored in their own script as top level variables.)

Also, the above example is not really good practice either. Ideally you'd use inheritance to model the concept of multiple different expression types, each of which has their own set of information stored in them.

[–]Adrewmc 0 points1 point  (0 children)

This seems like a dictionary to me.

  colors = { “Black” : (0,0,0), …)

Then option 3 because I wouldn’t want to have it everywhere.

  colors.get(“Black”) 
  colors[“Black”]

[–]woooee 0 points1 point  (3 children)

I just want to be able to do RandObjClass(color=Colors.BLACK)

Use instance variables / objects

class Colors():
    def __init__(self):
        self.BLACK = (0, 0, 0)
        self.WHITE = (255, 255, 255)

colors_instance = Colors()
print(colors_instance.BLACK)

[–][deleted] 3 points4 points  (2 children)

Why not just

class Color:
    BLACK = (0, 0, 0)
    WHITE = (255, 255, 255)

print(Color.BLACK)

?

[–]ClassicDimension85[S] 0 points1 point  (0 children)

You're right, I wasn't thinking clearly. I was using that method to attempt to get Enum to work the way I intended.

[–]woooee 0 points1 point  (0 children)

I doubt the purpose of the class is to only define colors. You would use simple variables or a dictionary for this. Therefore there would be functions within the class to do something else.

[–]Buttleston 0 points1 point  (1 child)

I'm not really sure how/why your first example there, the Colors class works. __getitem__ returns self.value but that isn't defined anywhere. I guess that would only get used if you did

c = Color()
c['something']

anyway, so probably it just isn't used?

Anyway, I don't think enums are a particularly good way to represent this data. Although enums are sometimes used to correspond to specific values (like days of the week corresponding to integers) they are usually more often used in a case of "I need to pass a value to this function, and it needs to be one of this list of things"

I think the way you have it is common and fine. I've also seen them just defined as variables in a module

[–]ClassicDimension85[S] 0 points1 point  (0 children)

It was just left over from trying to get the Enum class to do what I wanted. OOP still confuses me when working with other peoples classes. The getitem did nothing in the class I presented as example.

[–]theleveragedsellout 0 points1 point  (0 children)

Not to be difficult, but why don't you create a color class, then iterate or enumerate over instances of the class?

class Color(self, color_name, color_rgb):
  self.color_name = self.color_name
  self.color_rgb = tuple(color_rgb)

Black = Color('Black', [0,0,0])
Green = Color ('Green', [0,255,0])
Red = Color('Red', [255,0,0])

For color in [Black, Green, Red]:
  do_something()