Search code examples
pythontype-hintingtypingduck-typing

python : using type hints to dynamically check types


python supports type hints:

https://docs.python.org/3/library/typing.html

I was wondering if these hints can also be used to dynamically enforce types during runtime.

For example:

class C:

    def __init__(self):
        self.a : int = 0

    def __str__(self):
        return str(self.a)

    @classmethod
    def get(cls,**kwargs):
        c = cls()
        for k,v  in kwargs.items():
            setattr(c,k,v) 
            # ValueError exception thrown here ?
        return c

attrs = {"a":"a"} # developer wanted an int !
c = C.get(**attrs)
print(c)

In short, I'd like to avoid to re-enter the type of the attribute "a" in the get function:

    @classmethod
    def get(cls,**kwargs):
        c = cls()
        for k,v  in kwargs.items():
            if k=="a" and not isinstance(v,int):
                raise ValueError()
            setattr(c,k,v) 
        return c

Would there be a way to "reuse" the information given in the constructor that "a" is expected to be an int ?

Note: answer to this question shows that at least for functions introspection on the arguments type hints can be accessed:

How to introspect on PEP 484 type hints?


Solution

  • I was wondering if these hints can also be used to dynamically enforce types during runtime.

    In some cases and with external lib - the answer is yes. Read below.

    If the actual use-case you have is simple like your C class I would go and use dataclass and a library like dacite. You will not be able to create c2 since you are not passing an int.
    So dacite managed to "see" that a should be int and raised an exception

    from dataclasses import dataclass
    from dacite import from_dict
    @dataclass
    class C:
      a:int = 0
    
    d1 = {'a':3}
    
    c1: C = from_dict(C,d1)
    print(c1)
    print(C.__annotations__)
    
    d2 = {'a':'3'}
    
    c2: C = from_dict(C,d2)
    

    output

    C(a=3)
    {'a': <class 'int'>}
    
    Traceback (most recent call last):
      File "main.py", line 14, in <module>
        c2: C = from_dict(C,d2)
      File "/opt/virtualenvs/python3/lib/python3.8/site-packages/dacite/core.py", line 68, in from_dict
        raise WrongTypeError(field_path=field.name, field_type=field.type, value=value)
    dacite.exceptions.WrongTypeError: wrong value type for field "a" - should be "int" instead of value "3" of type "str"