Skip to content

Instantly share code, notes, and snippets.

@1st1
Created March 13, 2012 14:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 1st1/2029032 to your computer and use it in GitHub Desktop.
Save 1st1/2029032 to your computer and use it in GitHub Desktop.
Alternate PEP 362 Implementation
##
# 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