A simple method to implement the type checker in Python 3, python type

Source: Internet
Author: User

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)

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.