Skip to content

Instantly share code, notes, and snippets.

@kyouko-taiga
Last active August 9, 2023 06:05
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kyouko-taiga/de5ece0792d2f5fe8fb3 to your computer and use it in GitHub Desktop.
Save kyouko-taiga/de5ece0792d2f5fe8fb3 to your computer and use it in GitHub Desktop.
A python class wrapper to instrument/override properties
# This snippet was written by Dimitri Racordon (kyouko.taiga@gmail.com)
#
# Copyright (c) 2015 Dimitri Racordon
# Licensed under the The MIT License (MIT).
class lazy(object):
# This class is heavily inspired by the werkzeug.utils.cached_property
# decorator. It transforms a class method to a lazy property, evaluated
# the first time the property is accessed.
_missing = object
def __init__(self, func, name=None):
self.__name__ = name or func.__name__
self.__module__ = func.__module__
self.__doc__ = func.__doc__
self.func = func
def __get__(self, instance, type=None):
if instance is None:
return self
value = instance.__dict__.get(self.__name__, self._missing)
if value is self._missing:
value = self.func(instance)
instance.__dict__[self.__name__] = value
return value
class WrapperBase(type):
# This metaclass is heavily inspired by the Object Proxying python recipe
# (http://code.activestate.com/recipes/496741/). It adds special methods
# to the wrapper class so it can proxy the wrapped class. In addition, it
# adds a field __overrides__ in the wrapper class dictionary, containing
# all attributes decorated to be overriden.
_special_names = [
'__abs__', '__add__', '__and__', '__call__', '__cmp__', '__coerce__',
'__contains__', '__delitem__', '__delslice__', '__div__', '__divmod__',
'__eq__', '__float__', '__floordiv__', '__ge__', '__getitem__',
'__getslice__', '__gt__', '__hash__', '__hex__', '__iadd__', '__iand__',
'__idiv__', '__idivmod__', '__ifloordiv__', '__ilshift__', '__imod__',
'__imul__', '__int__', '__invert__', '__ior__', '__ipow__', '__irshift__',
'__isub__', '__iter__', '__itruediv__', '__ixor__', '__le__', '__len__',
'__long__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__',
'__neg__', '__oct__', '__or__', '__pos__', '__pow__', '__radd__',
'__rand__', '__rdiv__', '__rdivmod__', '__reduce__', '__reduce_ex__',
'__repr__', '__reversed__', '__rfloorfiv__', '__rlshift__', '__rmod__',
'__rmul__', '__ror__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__',
'__rtruediv__', '__rxor__', '__setitem__', '__setslice__', '__sub__',
'__truediv__', '__xor__', 'next',
]
def __new__(cls, classname, bases, attrs):
def make_method(name):
def method(self, *args, **kwargs):
mtd = getattr(object.__getattribute__(self, "_wrapped"), name)
return mtd(*args, **kwargs)
return method
for name in cls._special_names:
attrs[name] = make_method(name)
overrides = attrs.get('__overrides__', [])
overrides.extend(k for k,v in attrs.items() if isinstance(v, lazy))
attrs['__overrides__'] = overrides
return type.__new__(cls, classname, bases, attrs)
class Wrapper(metaclass=WrapperBase):
# This class acts as a proxy for the wrapped instance it is passed. All
# access to its attributes are delegated to the wrapped class, except
# those contained in __overrides__.
__slots__ = ['_wrapped', '__weakref__']
def __init__(self, wrapped):
object.__setattr__(self, '_wrapped', wrapped)
def __getattribute__(self, attr):
if attr in object.__getattribute__(self, '__overrides__'):
return object.__getattribute__(self, attr)
# If the requested attribute wasn't overriden, then we delegate to
# the wrapped class.
return getattr(object.__getattribute__(self, '_wrapped'), attr)
def __setattr__(self, attr, value):
setattr(object.__getattribute__(self, '_wrapped'), attr, value)
def __nonzero__(self):
return bool(object.__getattribute__(self, '_wrapped'))
def __str__(self):
return str(object.__getattribute__(self, '_wrapped'))
def __repr__(self):
return repr(object.__getattribute__(self, '_wrapped'))
# =============================================================================
class MyClass(object):
def __init__(self, foo=None, bar=None):
self.setattr_unless_none('foo', foo)
self.setattr_unless_none('bar', bar)
def setattr_unless_none(self, name, value):
if value is not None:
setattr(self, name, value)
def __lt__(self, rhs):
return id(self) < id(rhs)
class MyWrapper(Wrapper):
def __init__(self, *args, **kwargs):
super().__init__(MyClass(*args, **kwargs))
@lazy
def foo(self):
return 'lazy evaluated'
a = MyWrapper()
b = MyWrapper(foo='defined in kwargs')
print(a.foo) # prints "lazy evaluated"
print(b.foo) # prints "defined in kwargs"
print(a < b) # prints id(a) < id(b)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment