[Python-Dev] pep 362 - 5th edition
Nick Coghlan
ncoghlan at gmail.com
Wed Jun 20 02:39:34 CEST 2012
On Wed, Jun 20, 2012 at 3:53 AM, Yury Selivanov <yselivanov.ml at gmail.com> wrote:
> On 2012-06-19, at 1:03 PM, Ethan Furman wrote:
>> At some point it was suggested that Signature be put in provisionally so we could modify the API if needed -- are we doing that?
>
> Well, it doesn't have much of an API right now (just few methods)
Right, provisional API status is a fairly blunt instrument that we
should only use if we can't find any other way to allow the standard
library to progress.
In this particular case, we have a superior alternative: distill the
API down to the bare minimum that is needed to provide a more robust,
cross-implementation format for describing callable signatures. We can
then implement that bare minimum API for 3.3 as the foundation for
higher level layered APIs that offer more flexibility, and/or capture
more information about the callables.
Further comments on the PEP and implementation:
1. The PEP should specify the constructor signatures for Signature and
Parameter objects (I'd also prefer it if "kind" was permitted as a
positional argument)
2. The constructor for Parameter objects should require that names for
positional-only parameters start with "<" and end with ">" to ensure
they can always be distinguished from standard parameters in signature
string representations and in BoundArguments.parameters
3. The standard Signature constructor should accept an iterable of
Parameter objects directly (with the return annotation as an optional
keyword-only "annotation" argument) and enforce the following
constraints:
- permitted parameter binding order is strictly (POSITIONAL_ONLY,
POSITIONAL_OR_KEYWORD, VAR_POSITIONAL, KEYWORD_ONLY, VAR_KEYWORD)
- all parameter names must be distinct (otherwise bind() won't work properly)
- if a positional only parameter is not named (param.name is None), it
is given a name based on its position in the parameter list ("<0>",
"<1>", etc)
4. The current Signature constructor should become a "from_function"
class method
With these changes, the following would become straightforward:
>>> def f1(a): pass
>>> str(signature(f1))
(a)
>>> def f2(*args): a, = args
>>> f.__signature__ = Signature([Parameter("<a>",
Parameter.POSITIONAL_ONLY])
>>> str(signature(f2))
(<a>)
>>> def f3(*args): a, = args
>>> f.__signature__ = Signature([Parameter(None, Parameter.POSITIONAL_ONLY])
>>> str(signature(f3))
(<0>)
5. Given the updated constructor signature, we can revisit the
question of immutable signature objects (even just using read-only
properties for public attributes and exposing a dict proxy for the
parameter list). Instead of mutating the parameter list, you would
instead write code like:
new_sig = Signature(old_sig.parameters[1:])
In my opinion, that's a *much* nicer approach than copying an existing
signature object and mutating it.
6. I think "return_annotation" can safely be abbreviated to just
"annotation". The fact it is on the Signature object rather than an
individual parameter is enough to identify it as the return
annotation.
7. The idea of immutable Signature objects does highlight an annoyance
with the "attribute may be missing" style APIs. To actually duplicate
a signature correctly, including its return annotation (and assuming
the attribute is renamed), you would have to do something like:
try:
note = {"annotation": old_sig.annotation}
except AttributeError:
note = {}
new_sig = Signature(old_sig.parameters[1:], **note)
There's an alternative approach to optional attributes that's often
easier to work with: expose them as containers. In this case, since we
want to make them easy to pass as keyword-only arguments, one way to
achieve that would be expose an "optional" attribute on both Signature
and Parameter objects. Then the above would be written as:
new_sig = Signature(old_sig.parameters[1:], **old_sig.optional)
And copying a Parameter would be:
new_param = Parameter("new name", old_param.kind, **old_param.optional)
If we even keep that at all for the initial version of the API, the
direct "default" and "annotation" attributes would just be read-only
properties that accessed the "optional" container (reporting
AttributeError if the corresponding attribute was missing)
8. Not essential, but I suggest moving most of the parameter
formatting details to a Parameter.__str__ method
9. The PEP should explicitly note that we're taking a deliberately
strict approach to the notion of signature and parameter equivalence
by assuming that all parameter names have semantic significance.
Looser checks that ignore the names of positional and variable keyword
parameters can be handled with Signature.bind() or by implementing
custom key or comparison functions.
10. Similar to the discussion of convenience properties on Signature
objects themselves, I now think we should drop the "args" and "kwargs"
properties from the initial version of BoundArguments. Instead, I
propose the following attributes:
- arguments (mapping of parameter names to values supplied as arguments)
- defaults (mapping of unbound parameter names with defaults to
their default values)
- unbound (set of unbound names, always empty for bind(), may have
entries for bind_partial())
Cheers,
Nick.
--
Nick Coghlan | ncoghlan at gmail.com | Brisbane, Australia
More information about the Python-Dev
mailing list