0

I have a Python class that takes an arguments to set the state of an object. This argument must be one option in a list of available options; for example, a traffic light can either be red, green or orange.

I have two approaches to handle this problem. I can use a string to set the state, but then I'll have to do additional error checking and it is unclear without looking at the code what states are available. Alternatively I can create a class that acts as an enum.

Which of these methods (or what other method) should I use?

Example with strings:

class Light(object): 
    def __init__(self, color):
        self.color = color

    def turn_on(self):
        if self.color == 'red':
            self.turn_on_red_light()
        if self.color == 'green':
            self.turn_on_green_light()
        if self.color == 'orange':
            self.turn_on_orange_light()

    def turn_on_red_light():
        # do stuff
        return

    def turn_on_green_light():
        # do stuff
        return

    def turn_on_orange_light():
        # do stuff
        return

light = Light('red')
light.turn_on()

Alternative using simple "enum":

class Colors(object):
    red, green, orange = range(3)

class Light(object):
    def __init__(self, color):
        self.color = color

    def turn_on(self):
        if self.color == Colors.red:
            self.turn_on_red_light()  # as above
        if self.color == Colors.green:
            self.turn_on_green_light()  # as above
        if self.color == Colors.orange:
            self.turn_on_orange_light()  # as above

light = Light(Colors.red)
light.turn_on()

Maybe this is not the best example as you can just call light.turn_on_red_light(), but then that logic to decide what function to call must sit somewhere else. Let's pretend that is not an option.

I come across this situation often. Frequently I use it to define 'settings' within the class. I'd be interested to know what is the preferred method in Python or any other ways it can be improved?

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
Kritz
  • 7,099
  • 12
  • 43
  • 73
  • As an example: Tkinter has some constants, like `tkinter.END`, `tkinter.N`, and so on, but they simply point to strings (`'end'` and `'n'` for those cases). Just set up your class with a string parameter, with the expectation that it'll break if someone uses it wrong. After all, they could still use it wrong with an enum (e.g. `Colors.blue`). – TigerhawkT3 Oct 30 '15 at 12:10
  • Please note, my question is not how to use an enum but whether I should us one – Kritz Oct 30 '15 at 12:12
  • Note that whichever route you take to those constants you can simplify with e.g. a dictionary class attribute `COLOR_FUNCS = {Colors.red: turn_on_red_light, ...}` then `self.COLOR_FUNCS[self.color]()`. – jonrsharpe Oct 30 '15 at 12:14

1 Answers1

2

It is very popular to simply accept strings, and then break if the wrong one is passed. Often, "settings" as you describe are provided as constants, but, as in your example, they simply point to things you could send in anyway, like 0 for red.

As an example, Tkinter has several constants that refer to strings, like tkinter.END for 'end', tkinter.S for 's', and so on. Programmers working with Tkinter can then do things like text.get('1.0', tkinter.END) or text.get('1.0', 'end') interchangeably.

Provide such constants if you like, but keep in mind that they won't necessarily be used - people may prefer using the more-concise 0 over Colors.red - or, for a Tkinter example, 'nsew' over tkinter.N + tkinter.S + tkinter.E + tkinter.W. As such, I'd recommend having your constants point to an easy-to-understand string, like 'red' for red, if you do choose to make constants available. If you find it easier and more maintainable to omit such constants, there's nothing wrong with that.

TigerhawkT3
  • 48,464
  • 6
  • 60
  • 97
  • And if you use the dictionary approach I suggest above, handling non-options is as simple as `if input_ not in options: raise ValueError('invalid option: {!r}'.format(input_))`. – jonrsharpe Oct 30 '15 at 12:23
  • Perfect, thanks for the input, exactly what I was looking for – Kritz Oct 30 '15 at 12:25