[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