File: pairtype.py

package info (click to toggle)
pypy 5.6.0%2Bdfsg-4
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 97,040 kB
  • ctags: 185,069
  • sloc: python: 1,147,862; ansic: 49,642; cpp: 5,245; asm: 5,169; makefile: 529; sh: 481; xml: 232; lisp: 45
file content (133 lines) | stat: -rw-r--r-- 4,033 bytes parent folder | download | duplicates (8)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
"""
Two magic tricks for classes:

    class X:
        __metaclass__ = extendabletype
        ...

    # in some other file...
    class __extend__(X):
        ...      # and here you can add new methods and class attributes to X

Mostly useful together with the second trick, which lets you build
methods whose 'self' is a pair of objects instead of just one:

    class __extend__(pairtype(X, Y)):
        attribute = 42
        def method((x, y), other, arguments):
            ...

    pair(x, y).attribute
    pair(x, y).method(other, arguments)

This finds methods and class attributes based on the actual
class of both objects that go into the pair(), with the usual
rules of method/attribute overriding in (pairs of) subclasses.

For more information, see test_pairtype.
"""

class extendabletype(type):
    """A type with a syntax trick: 'class __extend__(t)' actually extends
    the definition of 't' instead of creating a new subclass."""
    def __new__(cls, name, bases, dict):
        if name == '__extend__':
            for cls in bases:
                for key, value in dict.items():
                    if key == '__module__':
                        continue
                    # XXX do we need to provide something more for pickling?
                    setattr(cls, key, value)
            return None
        else:
            return super(extendabletype, cls).__new__(cls, name, bases, dict)


def pair(a, b):
    """Return a pair object."""
    tp = pairtype(a.__class__, b.__class__)
    return tp((a, b))   # tp is a subclass of tuple

pairtypecache = {}

def pairtype(cls1, cls2):
    """type(pair(a,b)) is pairtype(a.__class__, b.__class__)."""
    try:
        pair = pairtypecache[cls1, cls2]
    except KeyError:
        name = 'pairtype(%s, %s)' % (cls1.__name__, cls2.__name__)
        bases1 = [pairtype(base1, cls2) for base1 in cls1.__bases__]
        bases2 = [pairtype(cls1, base2) for base2 in cls2.__bases__]
        bases = tuple(bases1 + bases2) or (tuple,)  # 'tuple': ultimate base
        pair = pairtypecache[cls1, cls2] = extendabletype(name, bases, {})
    return pair

def pairmro(cls1, cls2):
    """
    Return the resolution order on pairs of types for double dispatch.

    This order is compatible with the mro of pairtype(cls1, cls2).
    """
    for base2 in cls2.__mro__:
        for base1 in cls1.__mro__:
            yield (base1, base2)

class DoubleDispatchRegistry(object):
    """
    A mapping of pairs of types to arbitrary objects respecting inheritance
    """
    def __init__(self):
        self._registry = {}
        self._cache = {}

    def __getitem__(self, clspair):
        try:
            return self._cache[clspair]
        except KeyError:
            cls1, cls2 = clspair
            for c1, c2 in pairmro(cls1, cls2):
                if (c1, c2) in self._cache:
                    return self._cache[(c1, c2)]
            else:
                raise

    def __setitem__(self, clspair, value):
        self._registry[clspair] = value
        self._cache = self._registry.copy()

def doubledispatch(func):
    """
    Decorator returning a double-dispatch function

    Usage
    -----
        >>> @doubledispatch
        ... def func(x, y):
        ...     return 0
        >>> @func.register(basestring, basestring)
        ... def func_string_string(x, y):
        ...     return 42
        >>> func(1, 2)
        0
        >>> func('x', u'y')
        42
    """
    return DoubleDispatchFunction(func)

class DoubleDispatchFunction(object):
    def __init__(self, func):
        self._registry = DoubleDispatchRegistry()
        self._default = func

    def __call__(self, arg1, arg2, *args, **kwargs):
        try:
            func = self._registry[type(arg1), type(arg2)]
        except KeyError:
            func = self._default
        return func(arg1, arg2, *args, **kwargs)

    def register(self, cls1, cls2):
        def decorator(func):
            self._registry[cls1, cls2] = func
            return func
        return decorator