Package: dm.reuse
Utilities to reuse (slightly modified) objects in new contexts.Currently, there is are two utilities:rebindFunctionandOverridingProxy.rebindFunctionrebindFunctionallows to reuse the code of a function while changing
name, globals, default arguments, properties and/or names used.Lets look at a trivial example. Functionfaccesses global variablesiandj.Examples>>> i = 1; j = 2
>>> def f(): return i, j
...
>>> f()
(1, 2)We want to derive a new functiongwhich bindsito-1:>>> from dm.reuse import rebindFunction
>>> g=rebindFunction(f, i=-1)
>>> g()
(-1, 2)We can specify the rebinds not only via keyword arguments but via
a dictionary as well:>>> g=rebindFunction(f, dict(i=-1, j=-2))
>>> g()
(-1, -2)Usually, the function name is taken over from the original function,
but it can be changed:>>> f.__name__
'f'
>>> g.__name__
'f'
>>> g=rebindFunction(f, dict(i=-1, j=-2), funcName='g')
>>> g.__name__
'g'
>>> g()
(-1, -2)The originals function docstring is taken over, too – unless
overridden:>>> f.func_doc = 'some documentation'
>>> g=rebindFunction(f, dict(i=-1, j=-2))
>>> f.__doc__ is g.__doc__
True
>>> g=rebindFunction(f, dict(i=-1, j=-2), funcDoc='some new documentation')
>>> g.__doc__
'some new documentation'Default values for arguments can be added, removed or changed.
Unknown arguments are recognized:>>> def f(a1, a2=2): return a1, a2
...
>>> g=rebindFunction(f, argRebindDir=dict(a1=1))
>>> g()
(1, 2)>>> from dm.reuse import REQUIRED
>>> g=rebindFunction(f, argRebindDir=dict(a2=REQUIRED))
>>> g(1) #doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
  ...
TypeError: f() takes exactly 2 arguments (1 given)>>> g=rebindFunction(f, argRebindDir=dict(a2=10))
>>> g(1)
(1, 10)>>> g=rebindFunction(f, argRebindDir=dict(a3=10))
Traceback (most recent call last):
  ...
ValueError: unknown arguments in `argRebindDir`: a3Finally, function properties can be rebound withpropRebindDir.
We are careful, to give the new function a separate new property dict.>>> f.prop='p'
>>> g=rebindFunction(f)
>>> g.prop
'p'
>>> g=rebindFunction(f, propRebindDir=dict(prop='P', prop2='p2'))
>>> g.prop, g.prop2
('P', 'p2')
>>> f.__dict__
{'prop': 'p'}Occationally, functions use local imports which are not adequate
in the new context. In order to provide control over them, names
used inside the function code can be changed.>>> def f(a): import codecs; return codecs, a
...
>>> g=rebindFunction(f, nameRebindDir=dict(codecs='urllib'))
>>> r = g(1)
>>> r[0].__name__, r[1]
('urllib', 1)This way, references to global variables can be changed as well.>>> i1, i2 = 1, 2
>>> def f(): return i1
...
>>> g=rebindFunction(f, nameRebindDir=dict(i1='i2'))
>>> g()
2OverridingProxyOccasionally, you must work with an object, whose overall functionality
is mostly adequate but small aspects need to be changed.
In those cases,OverridingProxymight be of help. It allows
to proxy an object. The proxy mostly behaves like the proxied
object but some attributes/methods are overridden.ExamplesWe set up a play objectcand pretend that we must use it
with modified attributeattr1. We achieve this with a proxy
which behaves almost likecbut has the attribute changed.>>> from dm.reuse.proxy import OverridingProxy, make_proxy
>>>
>>> class C(object):
...   attr1 = 1
...   attr2 = 2
...   def f(self): return self.attr1, self.attr2
...   def g(self): return self.f()
...
>>> c = C()
>>> p = make_proxy(c, attr1='overridden attr1')
>>> p.__class__ is C
True
>>> isinstance(p, C)
True
>>> p.attr1
'overridden attr1'The change is also effective when we call methods which use
the overrriden attribute.>>> p.f()
('overridden attr1', 2)If we assign a new value to a proxy attribute, then the
corresponding attribute on the proxied object is changed.
As a consequence, changing overridden attributes have
apparently no effect – this is unintuitive and might be changed
in the future.>>> p.attr2 = "new attr2"
>>> p.f()
('overridden attr1', 'new attr2')
>>> p.attr1 = "new attr1"
>>> c.attr1
'new attr1'
>>> p.f()
('overridden attr1', 'new attr2')You can use the methodset_proxy_attributeto change
the proxies attributes rather than those of the proxied method.>>> p.set_proxy_attribute("attr1", "new overridden attr1")
>>> p.f()
('new overridden attr1', 'new attr2')A proxy compares usually equal to its proxied object. Sometimes,
this works also for the inverse. But, two
proxies for the same proxied object compare usually equal.>>> p == c
True
>>> c == p
True
>>> make_proxy(c) == p
TrueUsually, you would not override simple attributes but methods.
This is easier to achieve with a custom proxy class.
In many cases, an overriding method will want to call the
overridden method; this is possible viacall_proxied_method.>>> class MyProxy(OverridingProxy):
...   def g(self):
...     print ("g called")
...     return self.call_proxied_method("g")
...
>>> p = make_proxy(c, MyProxy, attr1="overridden attr1")
>>> p.g()
g called
('overridden attr1', 'new attr2')The proxies have limited support for special methods and
thereby support e.g. subscription. Note that this support
is incomplete and surprises are possible.>>> class MyDict(dict, C): pass
...
>>> md = MyDict(dict(a=1, b=2))
>>> p = make_proxy(md, attr1="overridden attr1")
>>> p.f()
('overridden attr1', 2)
>>> p["a"]
1
>>> class MySequence(C):
...   seq = 0, 1, 2
...   def __getitem__(self, i): return self.seq[i]
...
>>> c = MySequence()
>>> p = make_proxy(c, seq=(10, 11, 12))
>>> p[0]
10
>>> p = make_proxy(c)
>>> p[0]
0Usually, descriptors, too, see the modified state.>>> class WithProperty(C):
...   @property
...   def attr1_prop(self): return self.attr1
...
>>> c = WithProperty()
>>> p = make_proxy(c, attr1="overridden attr1")
>>> p.attr1_prop
'overridden attr1'History2.2rebindFunctionnow supports keywordonly arguments (Python 3)2.1addedOverridingProxy, allowing to reuse objects with
small modifications2.0added partial support for Python 3 (keyword only arguments and
annotations are not yet supported)dropped support for Python before 2.71.1nameRebindDirsupport added