Created
March 13, 2012 14:12
-
-
Save 1st1/2029032 to your computer and use it in GitHub Desktop.
Alternate PEP 362 Implementation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## | |
# Copyright (c) 2011 Sprymix Inc. | |
# All rights reserved. | |
# | |
# See LICENSE for details. | |
## | |
import inspect | |
import collections | |
import itertools | |
_void = object() | |
class BindError(TypeError): | |
pass | |
class Parameter: | |
__slots__ = ('name', 'position', 'default', 'keyword_only', 'annotation') | |
def __init__(self, name, position, *, | |
default=_void, annotation=_void, keyword_only=False): | |
self.name = name | |
self.position = position | |
if default is not _void: | |
self.default = default | |
self.keyword_only = keyword_only | |
if annotation is not _void: | |
self.annotation = annotation | |
def __repr__(self): | |
return '<{} at 0x{:x} {!r} pos:{}>'.format(self.__class__.__name__, id(self), | |
self.name, self.position) | |
class BoundArguments: | |
__slots__ = ('_args', '_kwargs', '_varargs', '_varkwargs') | |
def __init__(self, args, kwargs, varargs, varkwargs): | |
self._args = args | |
self._kwargs = kwargs | |
self._varargs = varargs | |
self._varkwargs = varkwargs | |
@property | |
def args(self): | |
if self._varargs: | |
return tuple(self._args.values()) + self._varargs | |
else: | |
return tuple(self._args.values()) | |
@property | |
def kwargs(self): | |
if self._varkwargs: | |
return dict(itertools.chain(self._kwargs.items(), self._varkwargs.items())) | |
else: | |
return dict(self._kwargs) | |
class Signature: | |
__slots__ = ('name', 'args', 'kwonlyargs', 'vararg', 'varkwarg', '_map', 'return_annotation') | |
def __init__(self, func): | |
self.name = func.__name__ | |
self.args = [] | |
self.kwonlyargs = [] | |
self.vararg = None | |
self.varkwarg = None | |
argspec = inspect.getfullargspec(func)[:4] | |
func_code = func.__code__ | |
# Parameter information. | |
pos_count = func_code.co_argcount | |
keyword_only_count = func_code.co_kwonlyargcount | |
positional = argspec[0] | |
keyword_only = func_code.co_varnames[pos_count:(pos_count+keyword_only_count)] | |
fxn_defaults = func.__defaults__ | |
if fxn_defaults: | |
pos_default_count = len(fxn_defaults) | |
else: | |
pos_default_count = 0 | |
idx = 0 | |
# Non-keyword-only parameters w/o defaults. | |
non_default_count = pos_count - pos_default_count | |
for name in positional[:non_default_count]: | |
self.args.append(Parameter(name, idx, annotation=self._find_annotation(func, name))) | |
idx += 1 | |
# ... w/ defaults. | |
for offset, name in enumerate(positional[non_default_count:]): | |
self.args.append(Parameter(name, idx, | |
default=fxn_defaults[offset], | |
annotation=self._find_annotation(func, name))) | |
idx += 1 | |
# *args | |
if func_code.co_flags & 0x04: | |
name = func_code.co_varnames[pos_count + keyword_only_count] | |
self.vararg = Parameter(name, idx, | |
annotation=self._find_annotation(func, name)) | |
idx += 1 | |
# Keyword-only parameters. | |
for name in keyword_only: | |
default_value = _void | |
if func.__kwdefaults__ and name in func.__kwdefaults__: | |
default_value = func.__kwdefaults__[name] | |
self.kwonlyargs.append(Parameter(name, idx, keyword_only=True, | |
default=default_value, | |
annotation=self._find_annotation(func, name))) | |
idx += 1 | |
# **kwargs | |
if func_code.co_flags & 0x08: | |
index = pos_count + keyword_only_count | |
if func_code.co_flags & 0x04: | |
index += 1 | |
name = func_code.co_varnames[index] | |
self.varkwarg = Parameter(name, idx, | |
annotation=self._find_annotation(func, name)) | |
self.args = tuple(self.args) | |
self.kwonlyargs = tuple(self.kwonlyargs) | |
self._map = {arg.name: arg for arg in self} | |
# Return annotation. | |
if 'return' in func.__annotations__: | |
self.return_annotation = func.__annotations__['return'] | |
def __getitem__(self, arg_name): | |
return self._map[arg_name] | |
def __iter__(self): | |
for arg in self.args: | |
yield arg | |
if self.vararg: | |
yield self.vararg | |
for arg in self.kwonlyargs: | |
yield arg | |
if self.varkwarg: | |
yield self.varkwarg | |
def _find_annotation(self, func, name): | |
try: | |
return func.__annotations__[name] | |
except KeyError: | |
return _void | |
def render_args(self, for_apply=False): | |
result = [] | |
def render_arg(arg): | |
if for_apply: | |
return '{}={}'.format(arg.name, arg.name) | |
else: | |
try: | |
default = arg.default | |
except AttributeError: | |
return arg.name | |
else: | |
return '{}={!r}'.format(arg.name, default) | |
for arg in self.args: | |
result.append(render_arg(arg)) | |
if self.vararg: | |
result.append('*{}'.format(self.vararg.name)) | |
if self.kwonlyargs: | |
if not self.vararg and not for_apply: | |
result.append('*') | |
for arg in self.kwonlyargs: | |
result.append(render_arg(arg)) | |
if self.varkwarg: | |
result.append('**{}'.format(self.varkwarg.name)) | |
return ', '.join(result) | |
def bind(self, *args, **kwargs): | |
_args = collections.OrderedDict() | |
_kwargs = {} | |
_varargs = None | |
_varkwargs = None | |
arg_specs = iter(self.args) | |
arg_specs_ex = () | |
arg_vals = iter(args) | |
while True: | |
try: | |
arg_val = next(arg_vals) | |
except StopIteration: | |
try: | |
arg_spec = next(arg_specs) | |
except StopIteration: | |
break | |
else: | |
if hasattr(arg_spec, 'default') or arg_spec.name in kwargs: | |
arg_specs_ex = (arg_spec,) | |
break | |
else: | |
raise BindError('bind: {func}: {arg!r} parameter lacking default value'. \ | |
format(func=self.name, arg=arg_spec.name)) | |
else: | |
try: | |
arg_spec = next(arg_specs) | |
except StopIteration: | |
if self.vararg: | |
_varargs = (arg_val,) + tuple(arg_vals) | |
break | |
else: | |
raise BindError('bind: {func}: too many positional arguments'. \ | |
format(func=self.name)) | |
else: | |
if arg_spec.name in kwargs: | |
raise BindError('bind: {func}: multiple values for keyword argument {arg!r}'. \ | |
format(arg=arg_spec.name, func=self.name)) | |
_args[arg_spec.name] = arg_val | |
for arg_spec in itertools.chain(arg_specs_ex, arg_specs, self.kwonlyargs): | |
arg_name = arg_spec.name | |
try: | |
arg_val = kwargs[arg_name] | |
except KeyError: | |
if not hasattr(arg_spec, 'default'): | |
raise BindError('bind: {func}: {arg!r} parameter lacking default value'. \ | |
format(arg=arg_name, func=self.name)) | |
else: | |
_kwargs[arg_name] = arg_val | |
kwargs.pop(arg_name) | |
if kwargs: | |
if self.varkwarg: | |
_varkwargs = kwargs | |
else: | |
raise BindError('bind: {func}: too many keyword arguments'. \ | |
format(func=self.name)) | |
return BoundArguments(_args, _kwargs, _varargs, _varkwargs) | |
def signature(func): | |
try: | |
return func.__signature__ | |
except AttributeError: | |
sig = Signature(func) | |
func.__signature__ = sig | |
return sig |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment