Reputation: 973
I'm working on a toy Python typechecker, using the function annotation feature in Python 3 to define types for function parameters and return values, and I seem to have run into a problem in checking whether one function type is a subtype of another. Take these two functions:
def f(x: int, y: int) -> type(None):
pass
def g(a: object, b: int) -> type(None):
pass
I want to have my code determine that the type of g
is a subtype of the type of f
(since the type of each of f
's parameters is a subtype of the type of the parameter at the same index in g
's parameter list, and the return type of g
is a subtype of the return type of f
). However, the __annotations__
field is a dictionary:
f.__annotations__ == { 'x': int, 'y': int, 'return': type(None) }
which means it doesn't provide me with the information about parameter ordering that I think I need. Is there a reliable way to determine that x
is the first parameter to f
, just from inspecting runtime properties of f
?
Upvotes: 2
Views: 249
Reputation: 1122102
Yes, Python functions do carry that information.
Easiest would be to use the inspect.getfullargspec()
function to extract this information, or from Python 3.3 onwards, using the Signature
objects.
The inspect.getfullargspec()
return value has a .args
attribute listing the arguments in order:
>>> import inspect
>>> def f(x: int, y: int) -> type(None):
... pass
...
>>> def g(a: object, b: int) -> type(None):
... pass
...
>>> inspect.getfullargspec(f)
FullArgSpec(args=['x', 'y'], varargs=None, varkw=None, defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'NoneType'>})
>>> inspect.getfullargspec(f).args
['x', 'y']
>>> inspect.getfullargspec(g).args
['a', 'b']
Annotations are included too:
>>> inspect.getfullargspec(f).annotations
{'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'NoneType'>}
>>> inspect.getfullargspec(g).annotations
{'return': <class 'NoneType'>, 'a': <class 'object'>, 'b': <class 'int'>}
Signature objects are richer still:
>>> sig_f = inspect.signature(f)
>>> sig_g = inspect.signature(g)
>>> sig_f.parameters
mappingproxy(OrderedDict([('x', <Parameter at 0x1031f1ea8 'x'>), ('y', <Parameter at 0x102e00228 'y'>)]))
>>> sig_f.parameters['x'].annotation
<class 'int'>
>>> sig_g.parameters['b'].annotation
<class 'int'>
>>> sig_f.return_annotation == sig_g.return_annotation
True
where Signature.parameters
uses an ordered dictionary, letting you compare parameters in the correct order.
Upvotes: 5