Answer two open issues and add a good-sized example.
This commit is contained in:
parent
32a07e3faa
commit
f819101ea2
160
pep-0362.txt
160
pep-0362.txt
|
@ -79,10 +79,7 @@ A Signature object has the following structure attributes:
|
|||
The keys are of the variable parameter with values of the
|
||||
annotation. If an annotation does not exist for a variable
|
||||
parameter then the key does not exist in the dict.
|
||||
* has_annotation : bool
|
||||
Signifies whether the function has an annotation for the return
|
||||
type.
|
||||
* annotation : object
|
||||
* return_annotation : object
|
||||
If present, the attribute is set to the annotation for the return
|
||||
type of the function.
|
||||
* 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
|
||||
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
|
||||
a function. When it is to be created is discussed in
|
||||
`Open Issues`_.
|
||||
|
@ -129,17 +134,11 @@ The structure of the Parameter object is:
|
|||
to represent keyword-only parameters was rejected to prevent
|
||||
variable type usage and as a possible point of errors,
|
||||
respectively.
|
||||
* has_default : bool
|
||||
True if the parameter has a default value, else False.
|
||||
* default_value : object
|
||||
The default value for the parameter, if present, else the
|
||||
attribute does not exist. This is done so that the attribute is
|
||||
not accidentally used if no default value is set as any default
|
||||
value could be a legitimate default value itself.
|
||||
attribute does not exist.
|
||||
* keyword_only : bool
|
||||
True if the parameter is keyword-only, else False.
|
||||
* has_annotation : bool
|
||||
True if the parameter has an annotation, else False.
|
||||
* annotation
|
||||
Set to the annotation for the parameter. If ``has_annotation`` is
|
||||
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.
|
||||
|
||||
|
||||
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
|
||||
===========
|
||||
|
||||
|
@ -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).
|
||||
|
||||
|
||||
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``?
|
||||
------------------------------------------------------------
|
||||
|
||||
|
|
Loading…
Reference in New Issue