2

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.

9
  • 1
    You could use Enthought Traits. Commented Nov 19, 2012 at 15:00
  • 3
    Don't. Python is duck typed for a reason. Commented Nov 19, 2012 at 15:03
  • @millimoose: could you post it as an answer? That's what I was looking for :) Commented Nov 21, 2012 at 9:02
  • @katrielalex The reason isn't "do not ever restrict or validate inputs to your code". Defensive programming and fail-fast behaviour is desirable in any programming language. Doing so without explicit type checks is only reasonably easy with the simple contracts of the built-in types, like "iterable", "coercible to a number", "coercible to a truth value". There's certainly a time and a place for duck typing – the internals of a module. But when doing a domain model, or at an API surface, it's mostly a bad idea. Commented Nov 21, 2012 at 12:53
  • @katrielalex Consider the situation where you create an object Foo that expects an object of type Bar but doesn't really do anything with it in the constructor. Someone calling your code makes a mistake and passes the wrong type in. Now when you actually call the method of Foo that uses a method of Bar, the caller of your code gets an AttributeError, reporting a missing method that he probably doesn't even recognize (if it's internal to your module), on a line of code that might be completely unrelated to where the error originated. This would make for "fun" debugging. Commented Nov 21, 2012 at 13:00

2 Answers 2

1

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

Sign up to request clarification or add additional context in comments.

Comments

1

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.

1 Comment

+1, I certainly agree with you on that. This came up as a solution for a very particular situation, sorry I can't be much more specific :/

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.