A simple method to implement the type checker in Python 3, python type
Example Function
To develop a type checker, we need a simple function to experiment with it. The euclidean algorithm is a perfect example:
def gcd(a, b): '''Return the greatest common divisor of a and b.''' a = abs(a) b = abs(b) if a < b: a, b = b, a while b != 0: a, b = b, a % b return a
In the preceding example, parameters a, B, and return values are of the int type. The expected types will be expressed in the form of function annotations, which is a new feature of Python 3. Next, the type check mechanism will be implemented in the form of a decorator. The first line of code of the Annotation Version is:
def gcd(a: int, b: int) -> int:
You can use "gcd. _ annotations _" to obtain a dictionary containing annotations:
>>> gcd.__annotations__{'return': <class 'int'>, 'b': <class 'int'>, 'a': <class 'int'>}>>> gcd.__annotations__['a']<class 'int'>
Note that the annotation of the returned value is stored in the key "return. This is possible because "return" is a keyword and cannot be used as a valid parameter name.
Check the return value type
The return value annotation is stored under the "return" key in the dictionary "_ annotations. We will use this value to check the returned value (assuming that the annotation exists ). We pass the parameter to the original function. If there is an annotation, We will verify its type through the value in the annotation:
def typecheck(f): def wrapper(*args, **kwargs): result = f(*args, **kwargs) return_type = f.__annotations__.get('return', None) if return_type and not isinstance(result, return_type): raise RuntimeError("{} should return {}".format(f.__name__, return_type.__name__)) return result return wrapper
We can use "a" to replace the gcd return value to test the above Code:
Traceback (most recent call last): File "typechecker.py", line 9, in <module> gcd(1, 2) File "typechecker.py", line 5, in wrapper raise RuntimeError("{} should return {}".format(f.__name__, return_type.__name__))RuntimeError: gcd should return int
The above results show that the type of the returned value is indeed checked.
Check parameter type
Function parameters exist in the "co_varnames" attribute of the associated code object. In our example, "gcd. _ code _. co_varnames" is used ". The tuples contain the names of all local variables. The tuples start with parameters. The number of parameters is stored in "co_nlocals. We need to traverse all the variables including indexes, obtain the parameter values from the parameter "args", and check the type.
The following code is obtained:
def typecheck(f): def wrapper(*args, **kwargs): for i, arg in enumerate(args[:f.__code__.co_nlocals]): name = f.__code__.co_varnames[i] expected_type = f.__annotations__.get(name, None) if expected_type and not isinstance(arg, expected_type): raise RuntimeError("{} should be of type {}; {} specified".format(name, expected_type.__name__, type(arg).__name__)) result = f(*args, **kwargs) return_type = f.__annotations__.get('return', None) if return_type and not isinstance(result, return_type): raise RuntimeError("{} should return {}".format(f.__name__, return_type.__name__)) return result return wrapper
In the above loop, I is the index starting from 0 in the array args parameter, and arg is the string containing its value. You can use "f. _ code _. co_varnames [I]" to read the parameter name. The type check code is exactly the same as the return value type check (including the error message exception ).
To check the type of the keyword parameter, we need to traverse the parameter kwargs. At this time, the type check is almost the same as that in the first loop:
for name, arg in kwargs.items(): expected_type = f.__annotations__.get(name, None) if expected_type and not isinstance(arg, expected_type): raise RuntimeError("{} should be of type {}; {} specified".format(name, expected_type.__name__, type(arg).__name__))
The obtained decorator code is as follows:
def typecheck(f): def wrapper(*args, **kwargs): for i, arg in enumerate(args[:f.__code__.co_nlocals]): name = f.__code__.co_varnames[i] expected_type = f.__annotations__.get(name, None) if expected_type and not isinstance(arg, expected_type): raise RuntimeError("{} should be of type {}; {} specified".format(name, expected_type.__name__, type(arg).__name__)) for name, arg in kwargs.items(): expected_type = f.__annotations__.get(name, None) if expected_type and not isinstance(arg, expected_type): raise RuntimeError("{} should be of type {}; {} specified".format(name, expected_type.__name__, type(arg).__name__)) result = f(*args, **kwargs) return_type = f.__annotations__.get('return', None) if return_type and not isinstance(result, return_type): raise RuntimeError("{} should return {}".format(f.__name__, return_type.__name__)) return result return wrapper
Writing the type check code into a function will make the code clearer. To simplify the code, we modify the error information, which is used when the returned value is of an invalid type. We can also use the wraps method in the functools module to copy some attributes of the packaging function to the wrapper (which makes the wrapper look more like the original function ):
def typecheck(f): def do_typecheck(name, arg): expected_type = f.__annotations__.get(name, None) if expected_type and not isinstance(arg, expected_type): raise RuntimeError("{} should be of type {} instead of {}".format(name, expected_type.__name__, type(arg).__name__)) @functools.wraps(f) def wrapper(*args, **kwargs): for i, arg in enumerate(args[:f.__code__.co_nlocals]): do_typecheck(f.__code__.co_varnames[i], arg) for name, arg in kwargs.items(): do_typecheck(name, arg) result = f(*args, **kwargs) do_typecheck('return', result) return result return wrapper
Conclusion
Annotation is a new element in Python 3. The usage in this example is very common. You can also imagine applications in many specific fields. Although the above implementation code does not meet the actual product requirements, it is originally used for conceptual verification. You can make the following improvements:
- Process additional parameters (projects that args cannot expect)
- Default Value Type check
- Supports multiple types
- Supported template types (for example, int type list)