Eric D.
Eric D.

Reputation: 315

Using Enums to inspect attributes of a class/instance in python

I'm working through a mechanical systems (for example, a simple helical gear) and want to prevent users from assigning values not found in an associated enum. I am new to enums and 'pythonic' approaches to answering this question...so my request is two-fold:

  1. Is it more appropriate in python to let input errors happen and use subsequent code to manage the fallout?
  2. Should the enum be defined within the class that uses it? I don't intend for this enum to be available elsewhere...I feel weird just letting it hang out at the beginning of my file.

Here's the code:

HAND = Enum('HAND', ['LH', 'RH'])


class HelicalGear(Gear):
    def __init__(self, hand):
        self.type_ = 'Helical'
        self.hand = hand

    @property
    def hand(self):
        return self._hand

    @hand.setter
    def hand(self, hand):
        if not hand:
            raise ValueError("Gear hand definition required")
        elif hand not in [e.name for e in HAND]:
            raise ValueError("Gear hand must be LH or RH")

        self._hand = hand

Upvotes: 2

Views: 295

Answers (2)

Ethan Furman
Ethan Furman

Reputation: 69248

  1. Error handling should happen as close to the origin of the error as possible. So whether users are entering data in or functions/classes are accepting data, that data should be checked and dealt with immediately.

  2. Where you define the Enum is entirely up to you, but I tend to leave them at the top level for several reasons:

    • Other code that needs to use them:
      somemod.HAND.LH, or
      somemod.HelicalGear.HAND.LH?
    • What if you later create another class that has right- and left- handed items?

Minor correction: your __init__ should be setting self._hand. As MisterMiyagi commented: using hand instead of _hand has the advantage of the error detecting code in hand.setter.

In your setter code I would do something like:

@hand.setter
def hand(self, hand):
    if hand in HAND:
        # already an Enum, pass
        pass
    elif hand in HAND.__members__:
        # it's the name of a hand
        hand = HAND[hand]
    else:
        # numeric?
        hand = HAND(hand)
        # if not, a ValueError will be raised:
        # `ValueError: 0 is not a valid Hand`
    # save the Enum value
    self._hand = hand  # or hand.value or hand.name depending on what you want
                       # returned when HelicalGear.hand is accessed

Upvotes: 3

MisterMiyagi
MisterMiyagi

Reputation: 52139

There is no answer appropriate in general - it really depends on how you use your objects.

An important concept of python is duck typing - that is, whether an object is appropriate is defined by its features, not its type. In your example, think about what an enum is: a mapping of a name to an integer.

So, should one enforce that users provide the enum entry? Or accept the name as well, as in your code? If following code just operates on the enum value, simply providing that value (e.g. 1 for LH) would work as well.

Personally, I prefer to do implicit type checking in client code that actually uses data[1], not strict type checking by interface code that only stores values. The later requires to explicitly write something that is implicitly performed later on anyways. This means you have to keep your type checking up to date with any client code.

So the question shouldn't be "do I raise an error on wrong input?" but "can I identify wrong input without using it?". If you answer the later with "yes" then it's simply more practical to raise an error right away. If you answer it with "dunno, the enum may be applicable outside my class" then being too quick on raising errors will cost you lots of flexibility.


[1] The exception is input sanitization. For example, if you feed input to an eval, better be safe than sorry.

Upvotes: 1

Related Questions