Revision: 46
http://pymultimethods.svn.sourceforge.net/pymultimethods/?rev=46&view=rev
Author: davisn
Date: 2009-11-28 19:27:24 +0000 (Sat, 28 Nov 2009)
Log Message:
-----------
Initial support for variable arguments
Modified Paths:
--------------
branches/varargs/multidispatch.py
branches/varargs/test/manual1.py
Modified: branches/varargs/multidispatch.py
===================================================================
--- branches/varargs/multidispatch.py 2009-08-19 01:16:02 UTC (rev 45)
+++ branches/varargs/multidispatch.py 2009-11-28 19:27:24 UTC (rev 46)
@@ -111,6 +111,8 @@
self.named_by_value = dict()
self.named_rules = dict() #(param_name, type) -> Rule
self.implementation = None
+ self.varargs = None
+ self.kwargs = None
# We implement the __get__ attribute descriptor protocol in order to make
# MultiMethods appear as methods on new-style class instances.
@@ -120,7 +122,7 @@
else:
return self
- def extend(self, *types):
+ def extend(self, *types, **kwargs):
def register(func):
if isinstance(func, MultiMethod):
assert func is self #Multimethods shouldn't be "mixed" like this, should they?
@@ -128,12 +130,13 @@
defaults = []
if getFuncDefaults(func):
defaults = getFuncDefaults(func)
- self.addRule(types, getFuncCode(func).co_varnames, defaults, func)
+ code = getFuncCode(func)
+ self.addRule(types, code.co_varnames[:code.co_argcount], defaults, func, **kwargs)
self.last_registered_function = func
return self
return register
- def addRule(self, types, names, defaults, method):
+ def addRule(self, types, names, defaults, method, varargs=False, kwargs=False):
"""
Adds a new rule to the MultiMethod.
@@ -144,11 +147,21 @@
defaults: Default values for the last len(defaults) parameters.
This list is used to provide default parameter matching.
method: The function that gets called on a match.
+ varargs: Whether the rule should match left-over positional arguments
+ kwargs: Whether the rule should match left-over keyword arguments
If a matching rule already exists, it is overwritten.
"""
if not types:
self.implementation = method
+ if varargs:
+ #print("Setting self.varargs to", method)
+ self.varargs = MultiMethod()
+ self.varargs.implementation = method
+ self.varargs.addRule([], [], [], method, kwargs=kwargs)
+ if kwargs:
+ self.kwargs = MultiMethod()
+ self.kwargs.implementation = method
else:
if len(names) == len(defaults):
new_defaults = defaults[1:]
@@ -157,24 +170,24 @@
if isinstance(types[0], ClassType) or isinstance(types[0], type) or isinstance(types[0], ClassID):
if types[0] not in self.rules:
self.rules[types[0]] = Rule()
- self.rules[types[0]].next.addRule(types[1:], names[1:], new_defaults, method)
+ self.rules[types[0]].next.addRule(types[1:], names[1:], new_defaults, method, varargs, kwargs)
rule = Rule()
if (names[0], types[0]) in self.named_rules:
rule = self.named_rules[(names[0], types[0])]
- rule.next.addRule(types[1:], names[1:], new_defaults, method)
+ rule.next.addRule(types[1:], names[1:], new_defaults, method, varargs, kwargs)
if len(defaults) == len(names):
rule.defaultable = True
self.named_rules[(names[0], types[0])] = rule
else:
if types[0] not in self.by_value:
self.by_value[types[0]] = Rule()
- self.by_value[types[0]].next.addRule(types[1:], names[1:], new_defaults, method)
+ self.by_value[types[0]].next.addRule(types[1:], names[1:], new_defaults, method, varargs, kwargs)
rule = Rule()
if (names[0], types[0]) in self.named_by_value:
rule = self.named_by_value[(names[0], types[0])]
- rule.next.addRule(types[1:], names[1:], new_defaults, method)
+ rule.next.addRule(types[1:], names[1:], new_defaults, method, varargs, kwargs)
self.named_by_value[(names[0], types[0])] = rule
def __call__(self, *args, **named):
@@ -197,7 +210,7 @@
return method(*args, **named)
def resolve(self, args, kwargs):
- #print "resolve(%s, %s)" % (str(args), str(kwargs))
+ #print("resolve(%s, %s)" % (str(args), str(kwargs)))
if not args:
if not kwargs:
if self.implementation:
@@ -209,7 +222,13 @@
function = self.resolveByValue(args[0], args[1:], kwargs)
if function:
return function
- return self.resolveByType(args[0].__class__, args[1:], kwargs)
+ function = self.resolveByType(args[0].__class__, args[1:], kwargs)
+ if function:
+ return function
+ #print("Varargs is", self.varargs)
+ #print("KWArgs is", self.kwargs)
+ if self.varargs:
+ return self.varargs.resolve([], kwargs)
def resolveByValue(self, value, args, kwargs):
try: #Necessary, for now at least, in case value is not hashable
@@ -253,13 +272,14 @@
return None
def resolveKeywords(self, named):
+ #print("resolveKeywords(%s)" % named)
defaultable = []
for name in named:
value = named[name]
if not isinstance(value, list):
#print "Trying (%s, %s)" % (str(name), str(value))
if (name, value) in self.named_by_value:
- #print "Potential match found!"
+ #print("Potential match found!")
names = named.copy()
del names[name]
function = self.named_by_value[(name, value)].next.resolve([], names)
@@ -287,32 +307,34 @@
method = rule.next.resolve([], named)
if method:
return method
+ if self.kwargs:
+ return self.kwargs.implementation
__registry__ = dict()
__last_registered__ = dict()
registerMultiMethod = MultiMethod()
-def __registerFunction__(name, func, types):
+def __registerFunction__(name, func, types, **kwargs):
#print "%s -> %s" % (str(types), str(func))
if name in __registry__:
mm = __registry__[name]
else:
mm = MultiMethod()
__registry__[name] = mm
- return mm.extend(*types)(func)
+ return mm.extend(*types, **kwargs)(func)
registerMultiMethod.addRule((str, FunctionType, list), ["name", "func", "types"], [], __registerFunction__)
-def __registerMultiMethod__(name, func, types):
+def __registerMultiMethod__(name, func, types, **kwargs):
func = __last_registered__[(name)]
- return registerMultiMethod(name, func, types)
+ return registerMultiMethod(name, func, types, **kwargs)
registerMultiMethod.addRule((str, MultiMethod, list), ["name", "func", "types"], [], __registerMultiMethod__)
-def multimethod(*types):
+def multimethod(*types, **kwargs):
def register(func):
if isinstance(func, MultiMethod):
func = func.last_registered_function
- return __registerFunction__(func.__name__, func, types)
+ return __registerFunction__(func.__name__, func, types, **kwargs)
return register
Modified: branches/varargs/test/manual1.py
===================================================================
--- branches/varargs/test/manual1.py 2009-08-19 01:16:02 UTC (rev 45)
+++ branches/varargs/test/manual1.py 2009-11-28 19:27:24 UTC (rev 46)
@@ -1,4 +1,4 @@
-# Copyright 2007 Nathan Davis
+# Copyright 2007-2009 Nathan Davis
# This file is part of PyMultimethods
#
@@ -192,6 +192,37 @@
def anything_test(obj):
return obj.__class__
+@multimethod(varargs=True)
+def test_varargs(*args):
+ return ('VarArgs',) + args
+
+@multimethod(kwargs=True)
+def test_varargs(**kwargs):
+ return kwargs
+
+@multimethod(varargs=True, kwargs=True)
+def test_varargs2(*args, **kwargs):
+ return [args, kwargs]
+
+@multimethod(int, varargs=True)
+def test_varargs3(a, *args):
+ return args
+
+@multimethod(int, int, varargs=True, kwargs=True)
+def test_varargs4(a, b=4, *args, **kwargs):
+ c = a + b
+ for arg in args:
+ c += arg
+ return [c, kwargs]
+
+@multimethod(int, int, varargs=True)
+def test_varargs5(a, b, *args):
+ return [1, args]
+
+@multimethod(int, int, kwargs=True)
+def test_vargs5(a, c, **kwargs):
+ return [2, kwargs]
+
class TestOldNewMix(unittest.TestCase):
def testOldStyle(self):
self.assertEquals(oldnew_mix(old_foo, old_bar), ("OldFoo", "OldBar"))
@@ -284,6 +315,48 @@
#actual.append(sum(1,1,1,1,1))
#actual.append(sum(1,1,2,1,.5))
+class TestVarArgs(unittest.TestCase):
+ def test1(self):
+ self.assertEquals(test_varargs(1,2,3), ('VarArgs', 1,2,3))
+ self.assertEquals(test_varargs(a=1, b=2), {'a': 1, 'b': 2})
+ self.assertRaises(NotImplementedError, test_varargs, 1, b=2)
+
+ def test2(self):
+ self.assertEquals(test_varargs2(1,2,a=1,b=2), [(1,2),{'a':1, 'b':2}])
+
+ def test3(self):
+ self.assertEquals(test_varargs3(1,2), (2,))
+ self.assertEquals(test_varargs3(a=3), tuple())
+
+ self.assertRaises(NotImplementedError, test_varargs3, a=3, b=4)
+
+ def test4(self):
+ self.assertEquals(test_varargs4(1,2), [3, {}])
+ self.assertEquals(test_varargs4(1), [5, {}])
+ self.assertEquals(test_varargs4(1, d=6), [5, {'d': 6}])
+ self.assertEquals(test_varargs4(1, d=2, b=8, c=3), [9, {'d': 2, 'c': 3}])
+ self.assertEquals(test_varargs4(1,2,3,4), [10, {}])
+ self.assertEquals(test_varargs4(1,2,3,4,d='sdf'), [10, {'d': 'sdf'}])
+
+ self.assertRaises(NotImplementedError, test_varargs4)
+ self.assertRaises(NotImplementedError, test_varargs4, b=1)
+
+ #These should be handled more gracefully
+ self.assertRaises(TypeError, test_varargs4, 1, d=2, b=8, a=3)
+ self.assertRaises(TypeError, test_varargs4, 1,2,3,4, b='abc')
+
+ def test5(self):
+ #self.assertEquals(test_varargs5(1,2), [2, {}]) #Expected result?
+ self.assertEquals(test_varargs5(1, b=2), [1, ()])
+ #self.assertEquals(test_varargs5(1, c=2), [2, {}]) #NotImplementedError
+ self.assertEquals(test_varargs5(a=1, b=2), [1, ()])
+ #self.assertEquals(test_varargs5(a=1, c=2), [2, {}]) #NotImplementedError
+ #self.assertEquals(test_varargs5(1, 2, d=123), [2, {'d': 123}]) #NotImplementedError
+ self.assertEquals(test_varargs5(1, 2, 3, 4), [1, (3, 4)])
+
+ self.assertRaises(NotImplementedError, test_varargs5, 1, 2, 3, d=5)
+ self.assertRaises(TypeError, test_varargs5, 1, 2, c=5) #NotImplementedError
+
class FutureTests(unittest.TestCase):
"""
Tests that "should" work, but don't
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|