E.Z.
E.Z.

Reputation: 6661

Python: efficient way to ensure attribute types within an object?

What's the most efficient way (where "efficient" doesn't necessarily mean fast, but "elegant", or "maintainable") to do type check when setting attributes in an object?

I can use __slots__ to define the allowed attributes, but how should I constrain the types?

Surely I can write "setter" methods for each attribute, but I find it a bit cumbersome to maintain since my type checks are usually simple.

So I'm doing something like this:

import datetime

# ------------------------------------------------------------------------------
# MyCustomObject
# ------------------------------------------------------------------------------
class MyCustomObject(object):
    pass

# ------------------------------------------------------------------------------
# MyTypedObject
# ------------------------------------------------------------------------------
class MyTypedObject(object):     
    attr_types = {'id'         : int,
                  'start_time' : datetime.time,
                  'duration'   : float,
                  'reference'  : MyCustomObject,
                  'result'     : bool,
                  'details'    : str}

    __slots__ = attr_types.keys()

    # --------------------------------------------------------------------------
    # __setattr__
    # --------------------------------------------------------------------------
    def __setattr__(self, name, value):
        if name not in self.__slots__:
            raise AttributeError(
                "'%s' object has no attribute '%s'" 
                % (self.__class__.__name__, name))
        if type(value) is not self.attr_types[name]:
                raise TypeError(
                    "'%s' object attribute '%s' must be of type '%s'" 
                    % (self.__class__.__name__, name, 
                       self.attr_types[name].__name__))
        # call __setattr__ on parent class
        super(MyTypedObject, self).__setattr__(name, value)

Which works fine for my purpose:

>>> my_typed_object            = MyTypedObject()
>>> my_typed_object.id         = "XYZ"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 28, in __setattr__
TypeError: 'MyTypedObject' object attribute 'id' must be of type 'int'
>>> my_typed_object.id         = 123
>>> my_typed_object.reference  = []
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 28, in __setattr__
TypeError: 'MyTypedObject' object attribute 'reference' must be of type 'MyCustomObject'
>>> my_typed_object.reference  = MyCustomObject()
>>> my_typed_object.start_time = "13:45"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 28, in __setattr__
TypeError: 'MyTypedObject' object attribute 'start_time' must be of type 'time'
>>> my_typed_object.start_time = datetime.time(13, 45)

Is there a better way to do this? Having worked with Python for a while now, I feel like I'm reinventing the wheel.

Upvotes: 2

Views: 373

Answers (2)

millimoose
millimoose

Reputation: 39990

A library that already implements what you're looking for (and provides a bunch of other features) is Enthought Traits.

Upvotes: 1

Daniel Roseman
Daniel Roseman

Reputation: 600059

You should ask yourself why you feel the need to do this. It's certainly not very Pythonic. Normally in Python we don't demand that attributes have specific types: instead, we document the expected types, and assume that any actual parameters conform. Note that this can mean a completely unrelated type that implements the same method: for example, we might expect that a parameter is iterable, without specifically demanding that it inherits from list or tuple.

Upvotes: 1

Related Questions