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
|
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``?
|
||||||
------------------------------------------------------------
|
------------------------------------------------------------
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue