-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpairtype.py
More file actions
133 lines (111 loc) · 3.94 KB
/
Copy pathpairtype.py
File metadata and controls
133 lines (111 loc) · 3.94 KB
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