diff --git a/pep-0362.txt b/pep-0362.txt index f9c6cdccb..fab06479e 100644 --- a/pep-0362.txt +++ b/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 + + + *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 + + **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 + + 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 + + """ + # 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``? ------------------------------------------------------------