[Python-Dev] PEP 362: 4th edition (original) (raw)

Nick Coghlan ncoghlan at gmail.com
Tue Jun 19 03:22:37 CEST 2012


On Mon, Jun 18, 2012 at 5:08 PM, Jim Jewett <jimjjewett at gmail.com> wrote:

On Sat, Jun 16, 2012 at 11:27 AM, Nick Coghlan <ncoghlan at gmail.com> wrote:

No. This is the full set of binding behaviours. "self" is just an ordinary "POSITIONALORKEYWORD" argument (or POSITIONALONLY, in some builtin cases). Or no longer a "parameter" at all, once the method is bound.  Except it sort of still is.  Same for the space parameter in PyPy.  I don't expect the stdlib implementation to support them initially, but I don't want it to get in the way, either.  A supposedly closed set gets in the way.

It's not supposedly closed, it is closed: Python doesn't support any other ways of binding arguments to parameters. Now, you can have additional state on a callable that gets used by that callable (such as closure and globals on a function, or self on a method, or arbitrary state on an object that implements call) but that extra state is not part of the call signature, and thus should not be exposed on the result of inspect.getsignature(). Remember, this object is not meant to be a representation of the full state of a callable, it's solely about providing a consistent introspection mechanism that allows arbitrary callables to define how they will bind arguments to parameters.

That's why I keep pointing out that there will always need to be a higher level object that brings in other related information, such as the docstring, the name of the callable, etc. This is not an API that describes everything that is worth knowing about an arbitrary callable, nor is it intended to be.

I believe you have raised a legitimate question regarding whether or not there is sufficient variation in behaviour amongst the parameter kinds for it to be worth using a subclassing approach to override str and eq, compared to just implementing a few "kind" checks in the base implementation.

However, given that the possible binding behaviours (positional only, keyword-or-positional, excess positional, keyword-only, excess keywords) is a closed set, I think a subclass based solution is overkill and adds excessive complexity to the public API.

Cheers, Nick.

P.S. A more complete response to the question of what constitutes "suitable complexity" for this API.

Consider the following implementation sketch for a kind based implementation that pushes as much behaviour as is reasonable into the Parameter object (including the definition of equality and decisions on how the parameter should be displayed):

_sentinel = object()
class Parameter:
     def __init__(self, name, kind, default=_sentinel,

annotation=_sentinel): if not name: raise ValueError("All parameters must be named for introspection purposes (even positional-only parameters)") self.name = name if kind not in Parameter.KINDS: raise ValueError("Unrecognised parameter binding type {}".format(kind)) self.kind = kind if default is not _sentinel: if kind.startswith("VAR"): raise ValueError("Cannot specify default value for {} parameter".format(kind)) self.default = default if annotation is not _sentinel: self.annotation = annotation

    def _get_key(self):
        default = getattr(self, "default", _sentinel)
        annotation = getattr(self, "annotation", _sentinel)
        if self.kind in (Parameter.KEYWORD_OR_POSITIONAL,

Parameter.KEYWORD_ONLY): # The name is considered significant for parameters that can be specified # as keyword arguments return (self.name, self.kind, default, annotation) # Otherwise, we don't really care about the name return (self.kind, default, annotation)

    def __eq__(self, other):
        if not isinstance(other, Parameter):
            return NotImplemented
        return self._get_key() == other._get_key()

    def __str__(self):
        kind = self.kind
        components = []
        if kind == Parameter.VAR_POSITIONAL:
            components += ["*"]
        elif kind == Parameter.VAR_KEYWORD:
            components += ["**"]
        if kind == Parameter.POSITIONAL_ONLY:
            components += ["<", self.name, ">"]
        else:
            components += [self.name]
        try:
            default = self.default
        except AttributeError:
            pass
        else:
            components += ["=", repr(default)]
        try:
            annotation = self.annotation
        except AttributeError:
            pass
        else:
            components += [":", repr(annotation)]
        return "".join(components)

The points of variation:

Now, suppose we dispense with the kind attribute and use subclassing instead:

_sentinel = object()
class _ParameterBase:
    """Common behaviour for all parameter types"""
     def __init__(self, name, default=_sentinel, annotation=_sentinel):
         if not name:
             raise ValueError("All parameters must be named for

introspection purposes") self.name = name self.kind = kind if default is not _sentinel: self.default = default if annotation is not _sentinel: self.annotation = annotation

    def _get_key(self):
        default = getattr(self, "default", _sentinel)
        annotation = getattr(self, "annotation", _sentinel)
        return (self.name, default, annotation)

    def __eq__(self, other):
        if not isinstance(other, type(self)):
            return NotImplemented
        return self._get_key() == other._get_key()

    def __str__(self):
        components = [self.name]
        try:
            default = self.default
        except AttributeError:
            pass
        else:
            components += ["=", repr(default)]
        try:
            annotation = self.annotation
        except AttributeError:
            pass
        else:
            components += [":", repr(annotation)]
        return "".join(components)

class Parameter(_ParameterBase):
    """Representation of a normal Python function parameter"""

class KeywordOnlyParameter(_ParameterBase):
    """Representation of a keyword-only Python function parameter"""

class PositionalOnlyParameter(_ParameterBase):
    """Representation of a positional-only parameter"""
    def __init__(self, index, name=None,
                 default=_sentinel, annotation=_sentinel):
        if name is not None:
            display_name = "<{}:{}>".format(index, name)
        else:
            display_name = "<{}>".format(index)
        super().__init__(display_name, default, annotation)

    def _get_key(self):
        default = getattr(self, "default", _sentinel)
        annotation = getattr(self, "annotation", _sentinel)
        return (default, annotation)

class _VarParameterBase(_ParameterBase):
    """Common behaviour for variable argument parameters"""

class VarPositionalParameter(_VarParameterBase):
    """Representation of a parameter for excess positional arguments"""
    def __str__(self):
        return "*" + super().__str__()

class VarKeywordParameter(_VarParameterBase):
    """Representation of a parameter for excess keyword arguments"""
    def __str__(self):
        return "**" + super().__str__()

Cheers, Nick.

-- Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia



More information about the Python-Dev mailing list