[Python-checkins] r45971 - in sandbox/trunk/Overload3K: Overload3K.egg-info Overload3K.egg-info/PKG-INFO Overload3K.egg-info/SOURCES.txt Overload3K.egg-info/top_level.txt overloading.py overloading.txt setup.py
phillip.eby
python-checkins at python.org
Fri May 12 05:25:16 CEST 2006
Author: phillip.eby
Date: Fri May 12 05:25:14 2006
New Revision: 45971
Added:
sandbox/trunk/Overload3K/
sandbox/trunk/Overload3K/Overload3K.egg-info/
sandbox/trunk/Overload3K/Overload3K.egg-info/PKG-INFO (contents, props changed)
sandbox/trunk/Overload3K/Overload3K.egg-info/SOURCES.txt (contents, props changed)
sandbox/trunk/Overload3K/Overload3K.egg-info/top_level.txt (contents, props changed)
sandbox/trunk/Overload3K/overloading.py (contents, props changed)
sandbox/trunk/Overload3K/overloading.txt (contents, props changed)
sandbox/trunk/Overload3K/setup.py (contents, props changed)
Log:
Proof-of-concept overloading with @defop for Py3K
Added: sandbox/trunk/Overload3K/Overload3K.egg-info/PKG-INFO
==============================================================================
--- (empty file)
+++ sandbox/trunk/Overload3K/Overload3K.egg-info/PKG-INFO Fri May 12 05:25:14 2006
@@ -0,0 +1,10 @@
+Metadata-Version: 1.0
+Name: Overload3K
+Version: 0.1
+Summary: UNKNOWN
+Home-page: UNKNOWN
+Author: UNKNOWN
+Author-email: UNKNOWN
+License: UNKNOWN
+Description: UNKNOWN
+Platform: UNKNOWN
Added: sandbox/trunk/Overload3K/Overload3K.egg-info/SOURCES.txt
==============================================================================
--- (empty file)
+++ sandbox/trunk/Overload3K/Overload3K.egg-info/SOURCES.txt Fri May 12 05:25:14 2006
@@ -0,0 +1,5 @@
+overloading.py
+setup.py
+Overload3K.egg-info/PKG-INFO
+Overload3K.egg-info/SOURCES.txt
+Overload3K.egg-info/top_level.txt
Added: sandbox/trunk/Overload3K/Overload3K.egg-info/top_level.txt
==============================================================================
--- (empty file)
+++ sandbox/trunk/Overload3K/Overload3K.egg-info/top_level.txt Fri May 12 05:25:14 2006
@@ -0,0 +1 @@
+overloading
Added: sandbox/trunk/Overload3K/overloading.py
==============================================================================
--- (empty file)
+++ sandbox/trunk/Overload3K/overloading.py Fri May 12 05:25:14 2006
@@ -0,0 +1,176 @@
+__all__ = ['signature', 'defop', 'overloaded', 'overload']
+
+def signature(*args, **kw):
+ """Decorator to set a function's __signature__
+
+ In Py3K (or sooner), this would happen automatically done from argument
+ annotations
+ """
+ def decorate(f):
+ f.__signature__ = args, kw
+ return f
+ return decorate
+
+
+def defop(gf):
+ """Decorator to register a method with a generic function
+
+ In Py3K, this would be replaced by a 'defop' statement. (Maybe in 2.x if
+ a ``__future__`` is used.)
+ """
+ def decorate(f):
+ overload(gf, f)
+ return f
+ return decorate
+
+
+class overloaded(object):
+ """A simple multi-dispatch implementation, w/caching"""
+
+ def __init__(self, default=None):
+ self.registry = {}
+ if default:
+ self.__name__ = getattr(default,'__name__',None)
+ self.__signature__ = getattr(default,'__signature__',None)
+ self[None] = default
+
+ def __call__(self, *args):
+ """Call the overloaded function."""
+ types = tuple(map(type, args))
+ func = self.cache.get(types)
+ if func is None:
+ self.cache[types] = func = self[types]
+ return func(*args)
+
+ def __setitem__(self, signature, func):
+ self.registry[signature] = func
+ self.cache = self.registry.copy()
+
+ def __getitem__(self, types):
+ registry = self.registry
+ if types in registry:
+ return registry[types]
+ best = []
+ for sig in registry:
+ if not implies(types, sig):
+ continue # skip inapplicable signatures
+ to_remove = []
+ for other in best:
+ if implies(other, sig):
+ if not implies(sig,other):
+ break # other dominates us, so don't include
+ # else we are overlapping, so add both
+ elif implies(sig,other):
+ # we dominate other, so it has to be removed
+ to_remove.append(other)
+ # else we are disjoint, so add both
+ else:
+ best.append(sig)
+ for other in to_remove:
+ best.remove(other)
+ if len(best)==1:
+ return registry[best[0]]
+
+ # Perhaps these multiple candidates all have the
+ # same implementation?
+ funcs = set(self.registry[sig] for sig in best)
+ if len(funcs) == 1:
+ return funcs.pop()
+
+ raise TypeError("ambigous methods; types=%r; candidates=%r" %
+ (types, best))
+
+
+ at overloaded
+def overload(gf, f):
+ """Add a method to an overloaded function"""
+ signature,kw = getattr(f,'__signature__',(None,None))
+ gf[signature] = f
+
+
+ at overloaded
+def implies(types, sig):
+ """Do `types` imply `sig`?"""
+ return False
+
+# Manually bootstrap tuple/None and anything/None comparisons; this
+# has to be done before the first call of any overloaded functions, including
+# overload() itself, which is called by @defop. So these bootstraps have to
+# happen before we can use @defop.
+#
+implies[object, type(None)] = lambda s,t: True
+implies[tuple, type(None)] = lambda s,t: True
+
+
+# Now that that's taken care of, we can use @defop to define everything else
+
+# defop implies(s1:tuple, s2:tuple)
+#
+ at defop(implies)
+ at signature(tuple, tuple)
+def tuple_implies(sig, types):
+ if len(sig)!=len(types):
+ return False
+ for s,t in zip(sig,types):
+ if not type_matches(s,t):
+ return False
+ return True
+
+
+ at overloaded
+def type_matches(t1,t2):
+ """Does `t1` match an argument of type `t2`?"""
+ return False
+
+type_matches[type, type] = issubclass
+
+
+
+# Now we can implement a kind of poor-man's typeclass, wherein we can treat
+# a 1-argument generic function as if it were a type. This implementation is
+# pretty crude, as a complete typeclass system should allow more sophisticated
+# ways to specify the signature(s). But that will be left as an exercise for
+# the reader at this point. ;) Note that with appropriate overloads of
+# ``overload()``, ``type_matches()``, and ``implies()``, you can totally
+# define your own framework for how overloads are chosen, so e.g. RuleDispatch
+# could just provide overloads for these operations that work with its own
+# generic function implementation and provide some extra decorators and
+# type annotation objects to plug into the base system.
+
+
+# defop type_matches(g1:type, g2:overloaded)
+#
+ at defop(type_matches)
+ at signature(type, overloaded)
+def type_implies_gf(t,gf):
+ """Here we implement the equivalent of issubclass(cls,genericfunction)"""
+ try:
+ return gf[t,] is not None
+ except TypeError:
+ return False
+
+
+# defop type_matches(g1:overloaded, g2:overloaded)
+#
+ at defop(type_matches)
+ at signature(overloaded, overloaded)
+def gf_implies_gf(g1,g2):
+ """Here we implement the equivalent of issubclass(g1,g2)"""
+ for s in g1.registry:
+ try:
+ if g2[s] is None:
+ return False
+ except TypeError:
+ return False
+ else:
+ return True # g1 implies g2 if g2 has methods for all of g1's sigs
+
+
+# Doctest suite here, picked up by "setup.py test"
+
+def additional_tests():
+ import doctest
+ return doctest.DocFileSuite(
+ 'overloading.txt', optionflags=doctest.ELLIPSIS, package=__name__,
+ )
+
Added: sandbox/trunk/Overload3K/overloading.txt
==============================================================================
--- (empty file)
+++ sandbox/trunk/Overload3K/overloading.txt Fri May 12 05:25:14 2006
@@ -0,0 +1,117 @@
+Function/Operator Overloading Demo for Py3K
+===========================================
+
+Signature Definition
+--------------------
+
+Because existing versions of Python don't support argument type annotations,
+we'll use a ``@signature`` decorator to tack a ``__signature__`` attribute on
+to functions. Because the final format for ``__signature__`` isn't decided,
+we'll use a very ad-hoc approach for now::
+
+ >>> from overloading import signature
+
+ >>> @signature(int, int)
+ ... def intadd(a, b):
+ ... return a+b
+
+ >>> intadd.__signature__
+ ((<type 'int'>, <type 'int'>), {})
+
+We'll assume that it's straightforward to change code based on this ad-hoc
+approach to use whatever is finally decided on for the ``__signature__``
+format.
+
+
+Simple Overloading
+------------------
+
+We now need to be able to create overloaded function objects and add methods
+to them. We'll use Guido's simple timing test cases::
+
+ >>> from overloading import overloaded, defop
+
+ >>> class A(object): pass
+ >>> class B(object): pass
+ >>> class C(A, B): pass
+
+ >>> def default(x, y): return "default"
+
+ >>> @overloaded
+ ... def automatic(x, y):
+ ... return default(x, y)
+
+ >>> automatic(1,2)
+ 'default'
+
+ >>> @defop(automatic)
+ ... @signature(A, B)
+ ... def methodAB(x, y): return "AB"
+
+ >>> @defop(automatic)
+ ... @signature(A, C)
+ ... def methodAC(x, y): return "AC"
+
+ >>> @defop(automatic)
+ ... @signature(B, A)
+ ... def methodBA(x, y): return "BA"
+
+ >>> @defop(automatic)
+ ... @signature(C, B)
+ ... def methodCB(x, y): return "CB"
+
+ >>> automatic(A(), B())
+ 'AB'
+
+ >>> automatic(B(), C())
+ 'BA'
+
+
+Operation-based Overloading
+---------------------------
+
+We can define a signature by reference to an existing overloaded function. For
+our example, we'll define a generic function for ``iter()``, registering the
+built-in ``iter()`` function for various types. (Since we don't have a
+built-in generic function type in Python yet.)::
+
+ >>> my_iter = overloaded()
+ >>> my_iter[list] = my_iter[str] = my_iter[tuple] = iter
+
+ >>> @defop(my_iter)
+ ... @signature(list)
+ ... def iter_list(ob):
+ ... return iter(ob)
+
+ >>> @overloaded
+ ... def flatten(ob):
+ ... yield ob
+
+ >>> @defop(flatten)
+ ... @signature(my_iter)
+ ... def flatten_iterable(ob):
+ ... for item in ob:
+ ... for item in flatten(item):
+ ... yield item
+
+ >>> @defop(flatten)
+ ... @signature(basestring)
+ ... def flatten_stringlike(ob):
+ ... yield ob
+
+ >>> list(flatten(None))
+ [None]
+
+ >>> list(flatten(["x", ["y", 1]]))
+ ['x', 'y', 1]
+
+
+
+Creating Custom Overloading
+---------------------------
+
+By overloading the ``type_matches()``, ``implies()``, and ``overloads()``
+functions, you can create your own specialized versions of @overloaded that use
+custom dispatch rules.
+
+
Added: sandbox/trunk/Overload3K/setup.py
==============================================================================
--- (empty file)
+++ sandbox/trunk/Overload3K/setup.py Fri May 12 05:25:14 2006
@@ -0,0 +1,9 @@
+from setuptools import setup
+
+setup(
+ name = "Overload3K",
+ version = "0.1",
+ py_modules = ['overloading'],
+ test_suite = 'overloading',
+)
+
More information about the Python-checkins
mailing list