Skip to content

Instantly share code, notes, and snippets.

@flisboac
Last active May 30, 2016 09:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save flisboac/bfd81543f553c154ff90bb0b9aab4c1e to your computer and use it in GitHub Desktop.
Save flisboac/bfd81543f553c154ff90bb0b9aab4c1e to your computer and use it in GitHub Desktop.
#/usr/bin/env python
default_impl = object()
no_impl = object()
def _empty_impl():
pass
def _is_empty_impl(fn, pfn = None):
return _empty_impl.__code__.co_code == fn.__code__.co_code
class AttrValue(object):
def __init__(self, value = None, assigned = None):
if isinstance(value, type(self)):
other = value
value = other.value
if assigned is None: assigned = other.assigned
self.value = value
self.assigned = assigned if assigned is not None else True
AttrValue.UNASSIGNED = AttrValue(assigned = False)
AttrValue.NONE = AttrValue()
class Attr(object):
default_impl = default_impl
no_impl = no_impl
def __init__(self,
_cls = None,
class_field_name = None,
doc = None):
self._class_field_name = class_field_name or 'attr'
self.__doc__ = doc
self._cls = None
self._properties = []
if _cls:
self(_cls)
def __call__(self, _cls = None):
if _cls is None:
return lambda fn: self(fn)
pending_attr = getattr(_cls, self._class_field_name, None)
if pending_attr:
if not pending_attr.__doc__ and self.__doc__:
pending_attr.__doc__ = self.__doc__
else:
pending_attr = self
pending_attr._apply(_cls, self._class_field_name)
return _cls
def _apply(self, cls, class_field_name):
self._cls = cls
self._class_field_name = class_field_name
if not self.__doc__ and self._cls.__doc__:
self.__doc__ = self._cls.__doc__
initializer = self._prepare_initializer(cls = self._cls)
setattr(self._cls, self._class_field_name, self)
setattr(self._cls, '__init__', initializer)
def _prepare_initializer(self,
method = None,
cls = None,
doc = None,
initialize_properties = True):
initializer = self._prepare_method(method, cls, '__init__', doc,
initialize_properties)
return initializer
def _prepare_method(self,
method = None,
cls = None,
name = None,
doc = None,
initialize_properties = False):
if not method:
method = getattr(cls, name, None)
def modified_method(this, *args, **kwargs):
if initialize_properties:
self.initialize_properties(this)
if method:
method(this, *args, **kwargs)
modified_method._attr = self
if not doc and method:
doc = method.__doc__
modified_method.__doc__ = doc # Could do better
if method:
modified_method.__name__ = method.__name__
return modified_method
def _add_property(self, property):
self._properties.append(property)
def property(self,
_fget=None,
fset=None,
fdel=None,
doc=None,
name = None,
internal_field_name = None,
value = AttrValue.UNASSIGNED):
if _fget is None:
return lambda fn: self.property(fn, fset, fdel, doc, name,
internal_field_name, value)
property = _Property(_fget, fset, fdel, self, doc,
name, internal_field_name, value)
return property
def getter(self,
_fget=None,
fdel=None,
doc=None,
writable = None,
deletable = None,
name = None,
internal_field_name = None,
value = AttrValue.UNASSIGNED):
if _fget is None:
return lambda fn: self.getter(fn, fdel, doc, writable, deletable,
name, internal_field_name, value)
fset = no_impl if not writable else default_impl
if not fdel:
fdel = no_impl if not deletable else default_impl
return self.property(_fget, fset, fdel, doc,
name, internal_field_name, value)
def setter(self,
_fset=None,
fdel=None,
doc=None,
readable = None,
deletable = None,
name = None,
internal_field_name = None,
value = AttrValue.UNASSIGNED):
if _fset is None:
return lambda fn: self.setter(fn, fdel, doc, readable, deletable,
name, internal_field_name, value)
fget = no_impl if not readable else default_impl
if not fdel:
fdel = no_impl if not deletable else default_impl
return self.property(fget, _fset, fdel, doc,
name, internal_field_name, value)
def accessor(self,
_faccessor=None,
fdel=None,
doc=None,
deletable = None,
name = None,
internal_field_name = None,
value = AttrValue.UNASSIGNED):
if _faccessor is None:
return lambda fn: self.accessor(fn, fdel, doc, deletable,
name, internal_field_name, value)
if not fdel:
fdel = no_impl if not deletable else default_impl
return self.property(_faccessor, _faccessor, fdel, doc,
name, internal_field_name, value)
def initialize_properties(self, obj):
for property in self._properties:
property.initialize_field(obj)
class _Property(object):
def __init__(self,
_fget=None,
fset=None,
fdel=None,
attr = None,
doc=None,
name = None,
internal_field_name = None,
initial_value = AttrValue.UNASSIGNED):
self._parent = attr
self._internal_field_name = internal_field_name
self._initial_value = AttrValue(initial_value)
self.__doc__ = doc
self._property_name = name
self.getter(_fget).setter(fset).deleter(fdel)
if self._parent:
self._parent._add_property(self)
def __get__(self, obj, obj_type=None):
if obj is None:
return self
if self._get is None:
raise AttributeError("Property '"
+ self.get_property_name() + "' from class '"
+ _full_class_name(obj) + "' is not readable.")
return self._get(obj)
def __set__(self, obj, value):
if self._set is None:
raise AttributeError("Property '"
+ self.get_property_name() + "' from class '"
+ _full_class_name(obj) + "' is not writable.")
self._set(obj, value)
def __delete__(self, obj):
if self._del is None:
raise AttributeError("Property '"
+ self.get_property_name() + "' from class '"
+ _full_class_name(obj) + "' is not deletable.")
self._del(obj)
def is_getter(self):
return self._get is not None
def is_setter(self):
return self._set is not None
def is_accessor(self):
return self.is_getter() and self.is_setter()
def is_single_accessor(self):
return self.is_accessor() and self._get == self._set
def initialize_field(self, obj):
if self._initial_value.assigned:
self.raw_set(obj, self._initial_value)
def raw_get(self, obj, default = AttrValue.UNASSIGNED, fail_if_not_assigned = False):
value = default
internal_field_name = self.get_internal_field_name()
if internal_field_name:
value = AttrValue(getattr(obj, internal_field_name))
if not value.assigned and fail_if_not_assigned:
raise AttributeError("Value not assigned for property '"
+ self.get_property_name() + "' (internal field name: '"
+ internal_field_name + "').")
return value
def raw_set(self, obj, value, fail_if_not_assigned = False):
success = False
internal_field_name = self.get_internal_field_name()
if internal_field_name:
raw_value = value
assigned = True
if isinstance(value, AttrValue):
raw_value = value.value
assigned = value.assigned
if assigned:
setattr(obj, internal_field_name, value)
success = True
elif hasattr(obj, internal_field_name):
delattr(obj, internal_field_name)
success = True
if not success and fail_if_not_assigned:
raise AttributeError("Could not assign value to property '"
+ self.get_property_name()
+ "' (internal field name: '"
+ internal_field_name + "').")
return success
def raw_del(self, obj, fail_if_not_assigned = False):
return self.raw_set(obj, AttrValue.UNASSIGNED, fail_if_not_assigned)
def any_get(self, obj, objType = None, default = AttrValue.UNASSIGNED):
value = None
if self.is_getter():
value = AttrValue(self.__get__(obj, objType))
if not value:
value = self.raw_get(obj, default)
return value
def any_set(self, obj, value):
success = False
if self.is_setter():
success = True
self.__set__(obj, value)
else:
value = self.raw_set(obj, value)
return value
def get_property_name(self):
return (self._property_name
or (self.is_getter() and self._get.__name__)
or (self.is_setter() and self._set.__name__)
or None
)
def get_internal_field_name(self):
internal_field_name = self._internal_field_name
if not internal_field_name:
property_name = self.get_property_name()
if property_name:
internal_field_name = "_%s" % property_name
return internal_field_name
def _set_fn(self, fname, fn, impl):
implement_method = False
empty_impl = None
impl.__name__ = ''
if fn is default_impl:
implement_method = True
elif fn is no_impl or fn is None:
setattr(self, fname, None)
else:
empty_impl = _is_empty_impl(fn)
if not empty_impl:
setattr(self, fname, fn)
else:
implement_method = True
if implement_method:
setattr(self, fname, impl)
if empty_impl:
getattr(self, fname).__name__ = fn.__name__
if not self.__doc__ and getattr(self, fname) and getattr(self, fname).__doc__:
self.__doc__ = getattr(self, fname).__doc__
return self
def getter(self, _fget):
return self._set_fn('_get', _fget, lambda this: self.raw_get(this, fail_if_not_assigned = True).value)
def setter(self, _fset):
return self._set_fn('_set', _fset, lambda this, value: self.raw_set(this, value, fail_if_not_assigned = True))
def deleter(self, _fdel):
return self._set_fn('_del', _fdel, lambda this: self.raw_del(this, fail_if_not_assigned = True))
def initialize_field(self, obj):
if self._initial_value.assigned:
self.raw_set(obj, self._initial_value.value)
def attr(_cls = None, *args, **kargs):
if _cls is None:
return Attr(None, *args, **kargs)
Attr(_cls, *args, **kargs)
return _cls
if __name__ == '__main__':
# Examples below
@attr
class A(object):
# This is just a helper object
attr = attr()
# A single method implementing both get and set
@attr.accessor(value = 0)
def x(self, v = None):
if v is not None:
self._x = v
else:
return self._x
# Separate methods declared together, where the setter is not trivial
@attr.setter(value = 0, readable = True)
def y(self, v = None):
if v is not None:
self._y = v
else:
return self._y
# Separate methods declared together, where the getter is not trivial
@attr.getter(value = None, writable = True)
def z(self):
if self._z is None:
return "oops"
return self._z
@attr.getter(value = 0, writable = True)
def w(self): pass
a = A()
print(a.x)
print(a.y)
print(a.z)
print(a.w)
a.x = 10
print(a.x)
a.x = None
print(a.x)
a.y = a.x * 65
print(a.y)
a.z = 101
print(a.z)
a.z = 2**10 + 0.5
print(a.z)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment