Answer two open issues and add a good-sized example.

This commit is contained in:
Brett Cannon 2007-11-17 04:20:22 +00:00
parent 32a07e3faa
commit f819101ea2
1 changed files with 122 additions and 38 deletions

View File

@ -79,10 +79,7 @@ A Signature object has the following structure attributes:
The keys are of the variable parameter with values of the The keys are of the variable parameter with values of the
annotation. If an annotation does not exist for a variable annotation. If an annotation does not exist for a variable
parameter then the key does not exist in the dict. parameter then the key does not exist in the dict.
* has_annotation : bool * return_annotation : object
Signifies whether the function has an annotation for the return
type.
* annotation : object
If present, the attribute is set to the annotation for the return If present, the attribute is set to the annotation for the return
type of the function. type of the function.
* parameters : list(Parameter) * parameters : list(Parameter)
@ -95,6 +92,14 @@ A Signature object has the following structure attributes:
being the value the parameter would have if this function was being the value the parameter would have if this function was
called with the given arguments. called with the given arguments.
Signature objects also have the following methods:
* __getitem__(self, key : str) -> Parameter
Returns the Parameter object for the named parameter.
* __iter__(self)
Returns an iterator that returns Parameter objects in their
sequential order based on their 'position' attribute.
The Signature object is stored in the ``__signature__`` attribute of The Signature object is stored in the ``__signature__`` attribute of
a function. When it is to be created is discussed in a function. When it is to be created is discussed in
`Open Issues`_. `Open Issues`_.
@ -129,17 +134,11 @@ The structure of the Parameter object is:
to represent keyword-only parameters was rejected to prevent to represent keyword-only parameters was rejected to prevent
variable type usage and as a possible point of errors, variable type usage and as a possible point of errors,
respectively. respectively.
* has_default : bool
True if the parameter has a default value, else False.
* default_value : object * default_value : object
The default value for the parameter, if present, else the The default value for the parameter, if present, else the
attribute does not exist. This is done so that the attribute is attribute does not exist.
not accidentally used if no default value is set as any default
value could be a legitimate default value itself.
* keyword_only : bool * keyword_only : bool
True if the parameter is keyword-only, else False. True if the parameter is keyword-only, else False.
* has_annotation : bool
True if the parameter has an annotation, else False.
* annotation * annotation
Set to the annotation for the parameter. If ``has_annotation`` is Set to the annotation for the parameter. If ``has_annotation`` is
False then the attribute does not exist to prevent accidental use. False then the attribute does not exist to prevent accidental use.
@ -157,6 +156,118 @@ directly on the im_func function object since that is what decorators
work with. work with.
Examples
========
Annotation Checker
------------------
::
def quack_check(fxn):
"""Decorator to verify arguments and return value quack as they should.
Positional arguments.
>>> @quack_check
... def one_arg(x:int): pass
...
>>> one_arg(42)
>>> one_arg('a')
Traceback (most recent call last):
...
TypeError: 'a' does not quack like a <type 'int'>
*args
>>> @quack_check
... def var_args(*args:int): pass
...
>>> var_args(*[1,2,3])
>>> var_args(*[1,'b',3])
Traceback (most recent call last):
...
TypeError: *args contains a a value that does not quack like a <type 'int'>
**kwargs
>>> @quack_check
... def var_kw_args(**kwargs:int): pass
...
>>> var_kw_args(**{'a': 1})
>>> var_kw_args(**{'a': 'A'})
Traceback (most recent call last):
...
TypeError: **kwargs contains a value that does not quack like a <type 'int'>
Return annotations.
>>> @quack_check
... def returned(x) -> int: return x
...
>>> returned(42)
42
>>> returned('a')
Traceback (most recent call last):
...
TypeError: the return value 'a' does not quack like a <type 'int'>
"""
# Get the signature; only needs to be calculated once.
sig = Signature(fxn)
def check(*args, **kwargs):
# Find out the variable -> value bindings.
bindings = sig.bind(*args, **kwargs)
# Check *args for the proper quack.
try:
duck = sig.var_annotations[sig.var_args]
except KeyError:
pass
else:
# Check every value in *args.
for value in bindings[sig.var_args]:
if not isinstance(value, duck):
raise TypeError("*%s contains a a value that does not "
"quack like a %r" %
(sig.var_args, duck))
# Remove it from the bindings so as to not check it again.
del bindings[sig.var_args]
# **kwargs.
try:
duck = sig.var_annotations[sig.var_kw_args]
except (KeyError, AttributeError):
pass
else:
# Check every value in **kwargs.
for value in bindings[sig.var_kw_args].values():
if not isinstance(value, duck):
raise TypeError("**%s contains a value that does not "
"quack like a %r" %
(sig.var_kw_args, duck))
# Remove from bindings so as to not check again.
del bindings[sig.var_kw_args]
# For each remaining variable ...
for var, value in bindings.items():
# See if an annotation was set.
try:
duck = sig[var].annotation
except AttributeError:
continue
# Check that the value quacks like it should.
if not isinstance(value, duck):
raise TypeError('%r does not quack like a %s' % (value, duck))
else:
# All the ducks quack fine; let the call proceed.
returned = fxn(*args, **kwargs)
# Check the return value.
try:
if not isinstance(returned, sig.return_annotation):
raise TypeError('the return value %r does not quack like '
'a %r' % (returned,
sig.return_annotation))
except AttributeError:
pass
return returned
# Full-featured version would set function metadata.
return check
Open Issues Open Issues
=========== ===========
@ -180,33 +291,6 @@ objects? The name of the argument can easily be retrieved from the
key (and the name would be used as the hash for a Parameter object). key (and the name would be used as the hash for a Parameter object).
Provide a mapping of parameter name to Parameter object?
--------------------------------------------------------
While providing access to the parameters in order is handy, it might
also be beneficial to provide a way to retrieve Parameter objects from
a Signature object based on the parameter's name. Which style of
access (sequential/iteration or mapping) will influence how the
parameters are stored internally and whether __getitem__ accepts
strings or integers.
One possible compromise is to have ``__getitem__`` provide mapping
support and have ``__iter__`` return Parameter objects based on their
``position`` attribute. This allows for getting the sequence of
Parameter objects easily by using the ``__iter__`` method on Signature
object along with the sequence constructor (e.g., ``list`` or
``tuple``).
Remove ``has_*`` attributes?
----------------------------
If an EAFP approach to the API is taken, both ``has_annotation`` and
``has_default`` are unneeded as the respective ``annotation`` and
``default_value`` attributes are simply not set. It's simply a
question of whether to have a EAFP or LBYL interface.
Have ``var_args`` and ``_var_kw_args`` default to ``None``? Have ``var_args`` and ``_var_kw_args`` default to ``None``?
------------------------------------------------------------ ------------------------------------------------------------