[Python-checkins] r53848 - sandbox/trunk/pep362/pep362.py sandbox/trunk/pep362/test_pep362.py

brett.cannon python-checkins at python.org
Thu Feb 22 00:54:21 CET 2007


Author: brett.cannon
Date: Thu Feb 22 00:54:17 2007
New Revision: 53848

Added:
   sandbox/trunk/pep362/pep362.py   (contents, props changed)
   sandbox/trunk/pep362/test_pep362.py   (contents, props changed)
Log:
Initial commit of PEP 362 implementation.  Torn directly out of local branch of
trunk where code was integrated into the 'inspect' module (and test_inspect),
so will need to fix imports and tests before any of this is usable.


Added: sandbox/trunk/pep362/pep362.py
==============================================================================
--- (empty file)
+++ sandbox/trunk/pep362/pep362.py	Thu Feb 22 00:54:17 2007
@@ -0,0 +1,174 @@
+class BindError(TypeError):
+    """Represent a failure of inspect.Signature.bind() being able to to
+    determine if a binding of arguments to parameters is possible."""
+    pass
+
+class Signature(object):
+
+    """Object to represent the signature of a function/method."""
+
+    def __init__(self, func):
+        """Initialize from a function or method object."""
+        if hasattr(func, 'im_func'):
+            func = func.im_func
+        self.name = func.__name__
+
+        argspec = getargspec(func)
+
+        self.var_args = argspec[1] if (argspec[1] is not None)  else ''
+        self.var_kw_args = argspec[2] if (argspec[2] is not None) else ''
+
+        arg_count = len(argspec[0])
+        defaults_start = (arg_count - len(argspec[3])
+                            if argspec[3] else arg_count)
+        parameters = []
+        for index, arg_name in enumerate(argspec[0]):
+            if isinstance(arg_name, list):
+                arg_name = self.__list2tuple(arg_name)
+
+            if index >= defaults_start:
+                parameters.append(Parameter(arg_name, index, True,
+                                            argspec[3][index - defaults_start]))
+            else:
+                parameters.append(Parameter(arg_name, index, False))
+
+        self.parameters = tuple(parameters)
+
+    @classmethod
+    def __list2tuple(cls, list_):
+        if not isinstance(list_, list):
+            return list_
+        else:
+            return tuple(cls.__list2tuple(x) for x in list_)
+
+    def __str__(self):
+        """String representation of a signature as one might write it in source
+        code."""
+        result = "%s(" % self.name
+        result += ", ".join(str(param) for param in self.parameters)
+        if self.var_args:
+            if self.parameters:
+                result +=", "
+            result += "*%s" % self.var_args
+        if self.var_kw_args:
+            if self.parameters or self.var_args:
+                result += ", "
+            result += "**%s" % self.var_kw_args
+        result += ")"
+        return result
+
+    @classmethod
+    def __tuple_bind_ok(cls, tuple_, arg):
+        """Verify that 'arg' will unpack properly to be used with 'tuple_'."""
+        try:
+            if len(tuple_) != len(arg):
+                return False
+        except TypeError:
+            raise BindError("cannot determine the length of the argument")
+        if (hasattr(arg, '__iter__') and hasattr(arg, 'next') and
+                callable(arg.__iter__) and callable(arg.next)):
+            raise IndexError("do not want to mutate an iterator")
+        for tuple_item, arg_item in zip(tuple_, arg):
+            if isinstance(tuple_item, tuple):
+                if not cls.__tuple_bind_ok(tuple_item, arg_item):
+                    return False
+        return True
+
+    @classmethod
+    def __positional_bind(cls, parameter, arg, bindings):
+        """Bind 'argument' to 'parameter' in 'bindings' if it is a legitimate
+        binding.
+
+        A binding can be illegitimate if the parameter is a tuple and the
+        argument will either unpack improperly or it cannot be determined if it
+        will without possibly mutating the object (e.g., if it is an
+        iterator).
+        
+        """
+        if isinstance(parameter, tuple):
+            if not cls.__tuple_bind_ok(parameter, arg):
+                raise TypeError("cannot unpack argument for %s" % parameter)
+        bindings[parameter] = arg
+
+    def bind(self, *args, **kwargs):
+        """Return a dictionary mapping function arguments to their parameter
+        variables, if possible.
+        
+        If the only way to determine the proper binding when tuple parameters
+        are present is to posssibly mutate an iterator then the method gives up
+        and raises a BindError.  This is to prevent something like a generator
+        which is about to be used for an actual function call from being
+        exhausted by this method."""
+        bindings = {}
+        arg_names_seq = [param.name for param in self.parameters]
+        arg_names_set = set(arg_names_seq)
+        arg_names_cnt = len(arg_names_set)
+        required_args = set(param.name for param in self.parameters
+                                if not param.has_default)
+        required_args_cnt = len(required_args)
+        # *args.
+        if self.var_args:
+            bindings[self.var_args] = args[arg_names_cnt:]
+            args = args[:arg_names_cnt]
+        if len(args) > arg_names_cnt:
+            raise TypeError("too many positional arguments provided")
+        for arg_name, value in zip(arg_names_seq, args):
+            self.__positional_bind(arg_name, value, bindings)
+        # Keyword arguments.
+        var_kw_args = {}
+        for key, value in kwargs.items():
+            if key not in arg_names_set:
+                if not self.var_kw_args:
+                    raise TypeError("unexpected keyword argument: %r" % key)
+                else:
+                    var_kw_args[key] = value
+            else:
+                if key in bindings:
+                    raise TypeError("got multiple values for argument %r" %
+                            key)
+                else:
+                    bindings[key] = value
+            del kwargs[key]
+        if kwargs:
+            raise TypeError("too many keyword arguments provided")
+        # **kwargs.
+        if self.var_kw_args:
+            bindings[self.var_kw_args] = var_kw_args
+        # Default values.
+        for param in self.parameters:
+            if param.has_default:
+                if param.name not in bindings:
+                    bindings[param.name] = param.default_value
+
+        # Make sure all required arguments are bound to.
+        for bound in bindings.iterkeys():
+            try:
+                required_args.remove(bound)
+            except KeyError:
+                pass
+        else:
+            if required_args:
+                raise TypeError("too few arguments provided")
+        return bindings
+
+
+def getsignature(func):
+    """Return a Signature object for the function or method.
+
+    If possible, return the existing value stored in __signature__.  If that
+    attribute does not exist, then try to store the Signature object at that
+    attribute if possible (but is not required).
+
+    """
+    if hasattr(func, 'im_func'):
+        func = func.im_func
+    sig = Signature(func)
+    if not hasattr(func, '__signature__'):
+        try:
+            func.__signature__ = sig
+        except AttributeError:
+            pass
+    else:
+        sig = func.__signature__
+
+    return sig

Added: sandbox/trunk/pep362/test_pep362.py
==============================================================================
--- (empty file)
+++ sandbox/trunk/pep362/test_pep362.py	Thu Feb 22 00:54:17 2007
@@ -0,0 +1,255 @@
+class ParameterObjectTests(unittest.TestCase):
+
+    """Test the Parameter object."""
+
+    def test_name(self):
+        # Test that 'name' attribute works.
+        # Must test both using a string and a tuple of strings.
+        name = "test"
+        param = inspect.Parameter(name, 0, False)
+        self.failUnlessEqual(param.name, name)
+        name = ('a', ('b',))
+        param = inspect.Parameter(name, 0, False)
+        self.failUnlessEqual(param.name, name)
+
+    def test_position(self):
+        # Test the 'position' attribute.
+        pos = 42
+        param = inspect.Parameter("_", pos, False)
+        self.failUnlessEqual(param.position, pos)
+
+    def test_has_default(self):
+        # Test the 'has_default' attribute.
+        # Testing that 'default_value' is not set is handled in the testing of
+        # that attribute.
+        param = inspect.Parameter('_', 0, True, None)
+        self.failUnlessEqual(param.has_default, True)
+        param = inspect.Parameter('_', 0, False)
+        self.failUnlessEqual(param.has_default, False)
+        self.failUnlessRaises(TypeError, inspect.Parameter,
+                                ('_', 0, False, 'extra arg'))
+        self.failUnlessRaises(TypeError, inspect.Parameter,
+                                ('_', 0, True))
+        self.failUnlessRaises(TypeError, inspect.Parameter,
+                                ('_', 0, True, 'default', 'extra'))
+        
+    def test_default_value(self):
+        # Test the 'default_value' attribute.
+        # Make sure that if has_default is set to False that default_value is
+        # not defined.
+        param = inspect.Parameter('_', 0, False)
+        self.failUnless(not hasattr(param, 'default_value'))
+        default = 42
+        param = inspect.Parameter('_', 0, True, default)
+        self.failUnlessEqual(param.default_value, default)
+
+    def test_str(self):
+        # Test __str__().
+        name = "X"
+        param = inspect.Parameter(name, 0, False)
+        self.failUnlessEqual(name, str(param))
+        default_value = 42
+        param = inspect.Parameter(name, 0, True, default_value)
+        self.failUnlessEqual("%s=%s" % (name, default_value), str(param))
+
+    def test_repr(self):
+        # Test __repr__().
+        name = "X"
+        pos = 0
+        param = inspect.Parameter(name, pos, False)
+        self.failUnlessEqual("Parameter(%r, %r, False)" % (name, pos),
+                                repr(param))
+        default_value = 42
+        param = inspect.Parameter(name, pos, True, default_value)
+        self.failUnlessEqual("Parameter(%r, %r, True, %r)" %
+                                (name, pos, default_value),
+                             repr(param))
+
+
+class SignatureObjectTests(unittest.TestCase):
+
+    def test_no_args(self):
+        # Test a function with no arguments.
+        sig = inspect.Signature(mod.no_args)
+        self.failUnlessEqual('no_args', sig.name)
+        self.failUnless(not sig.var_args)
+        self.failUnless(not sig.var_kw_args)
+        self.failUnlessEqual(0, len(sig.parameters))
+
+    def test_var_args(self):
+        # Test the var_args attribute.
+        sig = inspect.Signature(mod.var_args)
+        self.failUnlessEqual('args', sig.var_args)
+        self.failUnlessEqual(0, len(sig.parameters))
+        sig = inspect.Signature(mod.no_args)
+        self.failUnlessEqual('', sig.var_args)
+
+    def test_var_kw_args(self):
+        # Test the var_kw_args attribute.
+        sig = inspect.Signature(mod.var_kw_args)
+        self.failUnlessEqual('var_kw_args', sig.name)
+        self.failUnlessEqual('kwargs', sig.var_kw_args)
+        self.failUnlessEqual(0, len(sig.parameters))
+        sig = inspect.Signature(mod.no_args)
+        self.failUnlessEqual('', sig.var_kw_args)
+
+    def test_parameter_positional(self):
+        sig = inspect.Signature(mod.no_default_args)
+        self.failUnlessEqual('no_default_args', sig.name)
+        param = sig.parameters[0]
+        self.failUnlessEqual('a', param.name)
+        self.failUnlessEqual(0, param.position)
+        self.failUnless(not param.has_default)
+        self.failUnless(not hasattr(param, 'default_value'))
+
+    def test_parameter_default(self):
+        sig = inspect.Signature(mod.default_args)
+        self.failUnlessEqual('default_args', sig.name)
+        param = sig.parameters[0]
+        self.failUnlessEqual('a', param.name)
+        self.failUnlessEqual(0, param.position)
+        self.failUnless(param.has_default)
+        self.failUnlessEqual(42, param.default_value)
+
+    def test_parameter_tuple(self):
+        sig = inspect.Signature(mod.tuple_args)
+        self.failUnlessEqual('tuple_args', sig.name)
+        param = sig.parameters[0]
+        self.failUnless(isinstance(param.name, tuple))
+        self.failUnlessEqual(('a', ('b',)), param.name)
+        self.failUnlessEqual(0, param.position)
+        self.failUnless(not param.has_default)
+        self.failUnless(not hasattr(param, 'default_value'))
+
+    def test_parameter_tuple_default(self):
+        sig = inspect.Signature(mod.default_tuple_args)
+        self.failUnlessEqual('default_tuple_args', sig.name)
+        param = sig.parameters[0]
+        self.failUnlessEqual(('a', ('b',)), param.name)
+        self.failUnlessEqual(0, param.position)
+        self.failUnless(param.has_default)
+        self.failUnlessEqual((1, (2,)), param.default_value)
+
+    def test_positioning(self):
+        sig = inspect.Signature(mod.all_args)
+        param = sig.parameters[2]
+        self.failUnlessEqual('d', param.name)
+
+    def test_getsignature(self):
+        def fresh_func():
+            pass
+        self.failUnless(not hasattr(fresh_func, '__signature__'))
+        sig = inspect.getsignature(fresh_func)
+        self.failUnlessEqual(sig, fresh_func.__signature__)
+        sig2 = inspect.getsignature(fresh_func)
+        self.failUnlessEqual(sig, sig2)
+        class FreshClass(object):
+            def fresh_method(self):
+                pass
+        sig = inspect.getsignature(FreshClass.fresh_method)
+        self.failUnlessEqual(sig, FreshClass.fresh_method.im_func.__signature__)
+
+    def test_str(self):
+        # Test __str__().
+        sig = inspect.Signature(mod.no_args)
+        self.failUnlessEqual("no_args()", str(sig))
+        sig = inspect.Signature(mod.var_args)
+        self.failUnlessEqual("var_args(*args)", str(sig))
+        sig = inspect.Signature(mod.var_kw_args)
+        self.failUnlessEqual("var_kw_args(**kwargs)", str(sig))
+        sig = inspect.Signature(mod.default_args)
+        self.failUnlessEqual("default_args(a=42)", str(sig))
+        sig = inspect.Signature(mod.no_default_args)
+        self.failUnlessEqual("no_default_args(a)", str(sig))
+        sig = inspect.Signature(mod.tuple_args)
+        self.failUnlessEqual("tuple_args((a, (b,)))", str(sig))
+        sig = inspect.Signature(mod.default_tuple_args)
+        self.failUnlessEqual("default_tuple_args((a, (b,))=(1, (2,)))",
+                             str(sig))
+        sig = inspect.Signature(mod.all_args)
+        self.failUnlessEqual("all_args(a, (b, (c,)), d=0, "
+                               "(e, (f,))=(1, (2,)), *g, **h)",
+                             str(sig))
+
+class SignatureBindTests(unittest.TestCase):
+
+    """Test Signature.bind()."""
+
+    def test_no_parameters(self):
+        sig = inspect.Signature(mod.no_args)
+        binding = sig.bind()
+        self.failUnlessEqual({}, binding)
+        self.failUnlessRaises(TypeError, sig.bind, 42)
+        self.failUnlessRaises(TypeError, sig.bind, a=0)
+
+    def test_var_parameters(self):
+        sig = inspect.Signature(mod.var_args)
+        binding = sig.bind(0, 1, 2)
+        self.failUnlessEqual({'args':(0, 1, 2)}, binding)
+        binding = sig.bind()
+        self.failUnlessEqual({'args':tuple()}, binding)
+        self.failUnlessRaises(TypeError, sig.bind, a=0)
+
+    def test_var_kw_parameters(self):
+        sig = inspect.Signature(mod.var_kw_args)
+        binding = sig.bind(a=0)
+        self.failUnlessEqual({'kwargs':{'a':0}}, binding)
+        binding = sig.bind()
+        self.failUnlessEqual({'kwargs':{}}, binding)
+        self.failUnlessRaises(TypeError, sig.bind, 42)
+
+    def test_positional_parameters(self):
+        sig = inspect.Signature(mod.no_default_args)
+        binding = sig.bind(42)
+        self.failUnlessEqual({'a':42}, binding)
+        binding = sig.bind(a=42)
+        self.failUnlessEqual({'a':42}, binding)
+        self.failUnlessRaises(TypeError, sig.bind)
+        self.failUnlessRaises(TypeError, sig.bind, 0, 1)
+        self.failUnlessRaises(TypeError, sig.bind, b=0)
+
+    def test_keyword_parameters(self):
+        sig = inspect.Signature(mod.default_args)
+        binding = sig.bind(0)
+        self.failUnlessEqual({'a':0}, binding)
+        binding = sig.bind()
+        self.failUnlessEqual({'a':42}, binding)
+        binding = sig.bind(a=0)
+        self.failUnlessEqual({'a':0}, binding)
+        self.failUnlessRaises(TypeError, sig.bind, 0, 1)
+        self.failUnlessRaises(TypeError, sig.bind, a=0, b=1)
+        self.failUnlessRaises(TypeError, sig.bind, b=1)
+
+    def test_tuple_parameter(self):
+        sig = inspect.Signature(mod.tuple_args)
+        binding = sig.bind((1, (2,)))
+        self.failUnlessEqual({('a', ('b',)):(1, (2,))}, binding)
+        arg = (1, ((2,),))
+        binding = sig.bind(arg)
+        self.failUnlessEqual({('a', ('b',)):arg}, binding)
+        self.failUnlessRaises(TypeError, sig.bind, (1,2,3))
+        self.failUnlessRaises(TypeError, sig.bind, (1, 2))
+
+    def test_default_tuple_parameter(self):
+        sig = inspect.Signature(mod.default_tuple_args)
+        binding = sig.bind()
+        self.failUnlessEqual({('a', ('b',)):(1, (2,))}, binding)
+        arg = (0, (1,))
+        binding = sig.bind(arg)
+        self.failUnlessEqual({('a', ('b',)):arg}, binding)
+
+    def test_all_parameter_types(self):
+        sig = inspect.Signature(mod.all_args)
+        binding = sig.bind(0, (1, (2,)), 3, (4, (5,)), 6, i=7)
+        expected = {'a':0, ('b', ('c',)):(1, (2,)), 'd':3,
+                ('e', ('f',)):(4, (5,)), 'g':(6,), 'h':{'i':7}}
+        self.failUnlessEqual(expected, binding)
+
+    def test_BindError(self):
+        def gen():
+            yield 0
+            yield (1,)
+        sig = inspect.Signature(mod.tuple_args)
+        self.failUnlessRaises(inspect.BindError, sig.bind, gen())
+
+


More information about the Python-checkins mailing list