Python – How to force/ensure class attributes are a specific type

classpythonvariables

How do I restrict a class member variable to be a specific type in Python?


Longer version:

I have a class that has several member variables which are set externally to the class. Due to the way they're used, they must be of specific types, either int or list.

If this was C++, I would simply make them private and do type-checking in the 'set' function. Given that that isn't possible, is there any way to restrict the type of the variables so that an error/exception occurs at runtime if they're assigned a value of incorrect type? Or do I need to check their type within every function that uses them?

Best Answer

You can use a property like the other answers put it - so, if you want to constrain a single attribute, say "bar", and constrain it to an integer, you could write code like this:

class Foo(object):
    def _get_bar(self):
        return self.__bar
    def _set_bar(self, value):
        if not isinstance(value, int):
            raise TypeError("bar must be set to an integer")
        self.__bar = value
    bar = property(_get_bar, _set_bar)

And this works:

>>> f = Foo()
>>> f.bar = 3
>>> f.bar
3
>>> f.bar = "three"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in _set_bar
TypeError: bar must be set to an integer
>>> 

(There is also a new way of writing properties, using the "property" built-in as a decorator to the getter method - but I prefer the old way, like I put it above).

Of course, if you have lots of attributes on your classes, and want to protect all of them in this way, it starts to get verbose. Nothing to worry about - Python's introspection abilities allow one to create a class decorator that could automate this with a minimum of lines.

def getter_setter_gen(name, type_):
    def getter(self):
        return getattr(self, "__" + name)
    def setter(self, value):
        if not isinstance(value, type_):
            raise TypeError(f"{name} attribute must be set to an instance of {type_}")
        setattr(self, "__" + name, value)
    return property(getter, setter)

def auto_attr_check(cls):
    new_dct = {}
    for key, value in cls.__dict__.items():
        if isinstance(value, type):
            value = getter_setter_gen(key, value)
        new_dct[key] = value
    # Creates a new class, using the modified dictionary as the class dict:
    return type(cls)(cls.__name__, cls.__bases__, new_dct)

And you just use auto_attr_checkas a class decorator, and declar the attributes you want in the class body to be equal to the types the attributes need to constrain too:

...     
... @auto_attr_check
... class Foo(object):
...     bar = int
...     baz = str
...     bam = float
... 
>>> f = Foo()
>>> f.bar = 5; f.baz = "hello"; f.bam = 5.0
>>> f.bar = "hello"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in setter
TypeError: bar attribute must be set to an instance of <type 'int'>
>>> f.baz = 5
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in setter
TypeError: baz attribute must be set to an instance of <type 'str'>
>>> f.bam = 3 + 2j
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in setter
TypeError: bam attribute must be set to an instance of <type 'float'>
>>>