""" This is one of my favourite packages - collecting meta implementations.
Most useful is probably ``proxify``, ok, it is impressively useful here and there. But also take a look at the ``lift``
utilities, however, they may be validly replaced with ``myfunctools.convert``.
Enjoy.
"""
import abc
from contextlib import contextmanager
from .myfunctools import use_as_needed
import wrapt
import inspect
from functools import partial
import inspect
from collections import defaultdict
"""
make one object like another
============================
different versions of inplace-copy
"""
[docs]def morph(instance1, instance2):
""" makes instance1 to be instance2, however works inplace, i.e. preserves all references to instance1 """
instance1.__class__ = instance2.__class__
instance1.__dict__ = instance2.__dict__
return instance1 # just for convenience, if needed
# Proxy:
"""
The following proxy is an alternative to pyjack.replace_all_refs. It looks almost identical in use.
Difference? the proxy could be adapted to have extra functionalities (see https://pypi.python.org/pypi/ProxyTypes/0.9)
the pyjack.replace_all_refs might fail with weakrefs or other fancy stuff... don't know.
Indeed a first test showed that pyjack.replace_all_refs does not works perfect...
"""
[docs]def proxify(a, b):
""" makes a a proxy for b, in place """
if isinstance(a, Proxifier):
a = get_subject(a)
if isinstance(b, Proxifier):
b = get_subject(b)
if a == b: # otherwise infinte loops occur
return a
# else
old_class, old_dict = a.__class__, a.__dict__
a.__class__ = Proxifier
Proxifier.__init__(a, b, old_class, old_dict)
return a
[docs]def get_subject(a):
""" returns the final subject of possible several proxy layers """
if hasattr(a, "__subject__"):
return get_subject(a.__subject__)
else:
return a
[docs]class Proxifier(object):
# taken from https://pypi.python.org/pypi/ProxyTypes/0.9
# modifed such that it does not use __slot__ and that it has __original__
""" Delegates all operations (except ``.__subject__, .__original__``) to another object
``__original__`` returns a new instance of the originally proxified object
"""
def __init__(self, subject, old_class, old_dict):
self.__old_class__ = old_class
self.__old_dict__ = old_dict
self.__subject__ = subject
def __call__(self,*args,**kw):
return self.__subject__(*args,**kw)
def __getattribute__(self, attr, oga=object.__getattribute__):
if attr in ('__subject__', '__old_class__', '__old_dict__'):
return oga(self, attr)
if attr == '__original__':
old_class = oga(self, '__old_class__')
old_dict = oga(self, '__old_dict__')
proxified = old_class.__new__(old_class)
proxified.__dict__ = old_dict
return proxified
subject = oga(self, '__subject__')
return getattr(subject, attr)
def __setattr__(self,attr,val, osa=object.__setattr__):
if attr in ('__subject__', '__old_class__', '__old_dict__'):
osa(self,attr,val)
else:
setattr(self.__subject__,attr,val)
def __delattr__(self,attr, oda=object.__delattr__):
if attr in ('__subject__', '__old_class__', '__old_dict__'):
oda(self, attr)
else:
delattr(self.__subject__,attr)
def __nonzero__(self):
return bool(self.__subject__)
def __getitem__(self,arg):
return self.__subject__[arg]
def __setitem__(self,arg,val):
self.__subject__[arg] = val
def __delitem__(self,arg):
del self.__subject__[arg]
def __getslice__(self,i,j):
return self.__subject__[i:j]
def __setslice__(self,i,j,val):
self.__subject__[i:j] = val
def __delslice__(self,i,j):
del self.__subject__[i:j]
def __contains__(self,ob):
return ob in self.__subject__
for name in 'repr str hash len abs complex int long float iter oct hex'.split():
exec "def __%s__(self): return %s(self.__subject__)" % (name,name)
for name in 'cmp', 'coerce', 'divmod':
exec "def __%s__(self,ob): return %s(self.__subject__,ob)" % (name,name)
for name,op in [
('lt','<'), ('gt','>'), ('le','<='), ('ge','>='),
('eq','=='), ('ne','!=')
]:
exec "def __%s__(self,ob): return self.__subject__ %s ob" % (name,op)
for name,op in [('neg','-'), ('pos','+'), ('invert','~')]:
exec "def __%s__(self): return %s self.__subject__" % (name,op)
for name, op in [
('or','|'), ('and','&'), ('xor','^'), ('lshift','<<'), ('rshift','>>'),
('add','+'), ('sub','-'), ('mul','*'), ('div','/'), ('mod','%'),
('truediv','/'), ('floordiv','//')
]:
exec (
"def __%(name)s__(self,ob):\n"
" return self.__subject__ %(op)s ob\n"
"\n"
"def __r%(name)s__(self,ob):\n"
" return ob %(op)s self.__subject__\n"
"\n"
"def __i%(name)s__(self,ob):\n"
" self.__subject__ %(op)s=ob\n"
" return self\n"
) % locals()
del name, op
# Oddball signatures
def __rdivmod__(self,ob):
return divmod(ob, self.__subject__)
def __pow__(self,*args):
return pow(self.__subject__,*args)
def __ipow__(self,ob):
self.__subject__ **= ob
return self
def __rpow__(self,ob):
return pow(ob, self.__subject__)
"""
class tree helpers
==================
"""
[docs]def clcoancl(*cls_list):
""" read as closest common ancestor class
taken from: http://stackoverflow.com/questions/15788725/how-to-determine-the-closest-common-ancestor-class"""
mros = [list(inspect.getmro(cls)) for cls in cls_list]
track = defaultdict(int)
while mros:
for mro in mros:
cur = mro.pop(0)
track[cur] += 1
if track[cur] == len(cls_list):
return cur
if len(mro) == 0:
mros.remove(mro)
return None # or raise, if that's more appropriate
"""
LIFT functionality
==================
"""
[docs]class NotLiftable(TypeError):
pass
"""
variant 0 as static function decorator
--------------------------------------
"""
# TODO include into generic lift function?
[docs]def lift_from(*classes):
""" shall decorate class method ideally
>>> class A(object):
... pass
...
>>> class B(A):
... @lift_from(A)
... @staticmethod
... def lift(a,_b):
... a.__class__ = B
... a._b = _b
...
>>> class C(B):
... def __init__(self, _c):
... self._c = _c
...
... @lift_from(B)
... @staticmethod
... def lift(b, _c):
... b.__class__ = C
... b._c = _c
...
... def __str__(self):
... return "%s(_b=%s,_c=%s)" % (self.__class__.__name__, self._b, self._c)
...
>>> a = A()
>>> C.lift(a, _b="b", _c="c")
>>> print a
C(_b=b,_c=c)
"""
@wrapt.decorator
def wrapper(lift, instance, args, kwargs):
to_lift = args[0]
if not isinstance(to_lift, classes):
for c in classes:
if hasattr(c, "lift"):
try:
use_as_needed(c.lift, kwargs, args=[to_lift]) # preprocess lift
break
except NotLiftable:
pass
else:
raise NotLiftable("Not liftable from class %s" % to_lift.__class__)
# final lift:
return use_as_needed(lift, kwargs, args=args) #args are only applied to direct lift
return wrapper
"""
variante 1 as Metaclass
-----------------------
"""
@contextmanager
[docs]def super_liftable(cls, self):
""" this is kind of a hack to replace super.super, however I haven't found any other nice way to do it """
if cls is object:
raise NotLiftable()
liftables = [l for l in cls.__bases__ if type(l).__name__ == "Liftable"]
if not liftables:
raise NotLiftable()
orig_class = self.__class__
self.__class__ = liftables[0]
yield self
self.__class__ = orig_class
[docs]def LiftableFrom(base_cls_name):
class Liftable(abc.ABCMeta):
def __init__(cls, name, bases, dct):
# for base_cls nothing should be done, as this is the one to refer to by Lifting
if not cls.__name__ == base_cls_name:
if "__init__" in dct:
raise TypeError("Descendents of Liftable are not allowed to have own __init__ method. Instead overwrite __initialize__")
def lifted__init__(self, **kwargs):
with super_liftable(cls, self) as s:
use_as_needed(s.__init__, kwargs)
if hasattr(self, "__initialize__"):
use_as_needed(self.__initialize__, kwargs)
cls.__init__ = lifted__init__
#setattr(cls, "__init__", lifted__init__)
super(Liftable, cls).__init__(name, bases, dct)
Liftable.base_cls_name = base_cls_name
#Liftable.__name__ = "LiftableFrom" + base_cls_name # to show that this is possible
return Liftable
"""
variant 2 as class factory
--------------------------
"""
@contextmanager
[docs]def mysuper(cls, self):
orig_class = self.__class__
self.__class__ = cls
yield self
self.__class__ = orig_class
[docs]def Lift(cls):
""" class decorator """
class _Lift(cls):
__metaclass__ = abc.ABCMeta
def __init__(self, **kwargs):
with mysuper(cls, self) as s:
use_as_needed(s.__init__, kwargs)
# #TODO the following does not work, but would be the first thing to try
# #gives TypeError: <method-wrapper '__init__' of C object at 0x7f0ee504a610> is not a Python function
# #i.e. super(cls, self).__init__ is not an inspectable function as one would expect
# use_as_needed(super(cls, self).__init__, kwargs)
use_as_needed(self.__initialize__, kwargs)
@abc.abstractmethod
def __initialize__(self, **kwargs):
return NotImplemented()
_Lift.__name__ = "_Lift_" + cls.__name__
return _Lift
"""
common lift functionality
-------------------------
"""
[docs]def lift(self, new_class, **kwargs):
# Stop Conditions:
if self.__class__ is new_class:
return # nothing to do
elif new_class is object: # Base Case
# break recursion at once:
raise NotLiftable()
liftables = [l for l in new_class.__bases__ if type(l).__name__ == "Liftable"]
lifts = [l.__base__ for l in new_class.__bases__ if l.__name__.startswith("_Lift_")]
ls = liftables + lifts
if not ls:
raise NotLiftable()
# recursive case:
if not self.__class__ is ls[0]: # it would also be possible to use tree like left-first-search here
lift(self, ls[0], **kwargs)
# own case:
self.__class__ = new_class
if hasattr(self, '__initialize__'):
use_as_needed(self.__initialize__, kwargs)
[docs]def delift(self, base_class):
self.__class__ = base_class
[docs]def next_common_liftable_ancestor(classA, classB):
a = [c for c in classA.__mro__ if type(c).__name__=='Liftable']
b = [c for c in classB.__mro__ if type(c).__name__=='Liftable']
"""uses a for order"""
for i in a:
if i in b:
return i
[docs]def relift(self, new_class, base_class=None, **kwargs):
if base_class is None:
base_class = next_common_liftable_ancestor(self.__class__, new_class)
delift(self, base_class)
lift(self, new_class, **kwargs)