I am trying to be all fancy with sub element attribute access in a custom class hierarchy.
My fanciness works in that I can successfully use descriptors to do this.
I want to be even more fancy and make the class RefHolder (shown below in the testcase) use slots to save space.
When I try to use slots though, I get RuntimeError: maximum recursion depth exceeded
Note that I have already tried looking at existing solutions for this, the most closely matching I could find being this one:
https://stackoverflow.com/a/19566973/1671693
I have tried this in the testcase below but I am still get the runtimeerror.
Note that in the testcase, if the commented lines are used instead of the ones directly beneath them and __slots__ is removed from RefHolder,
The testcase passes.
Any suggestions?
Additionally, I am creating an object for every attribute access which seems expensive, are there any suggestions on a more efficient way of achieving the same behavior? Thanks!
import unittest
class RefHolder():
__slots__ = ['__obj', 'get_value']
def __init__(self, obj, get_value=False):
self.__dict__['__obj'] = obj
self.__dict__['get_value']=get_value
def get_sub(self, name):
#attr = self.__dict__['__obj'].find_by_name(name)
attr = self.__dict__['__obj'].__get__(self, RefHolder).find_by_name(name)
if attr is None:
raise AttributeError("Can't find field {}".format(name))
return attr
def __getattr__(self, name):
attr = self.get_sub(name)
#if self.__dict__['get_value']:
if self.__dict__['get_value'].__get__(self, RefHolder):
return attr.Value
else:
return attr
def __setattr__(self, name, value):
attr = self.get_sub(name)
#if self.__dict__['get_value']:
if self.__dict__['get_value'].__get__(self, RefHolder):
attr.Value = value
else:
raise AttributeError("{} is read only in this context".format(name))
class ContainerAccess():
__slots__ = ['get_value']
def __init__(self, get_value=False):
self.get_value = get_value
def __get__(self, obj, objtype=None):
if obj is None:
return self
return RefHolder(obj, self.get_value)
def __set__(self, obj, value):
raise AttributeError("Read Only attribute".format(value))
class PropVal():
def __init__(self, val):
self.Value = val
#property
def Value(self):
return self._value
#Value.setter
def Value(self, value):
self._value = value
class T():
get = ContainerAccess()
getv = ContainerAccess(get_value=True)
def __init__(self):
self.store = {}
self._value = 0
def find_by_name(self, name):
return self.store.get(name)
class T2(T):
pass
class TestDesc(unittest.TestCase):
def test_it(self):
t = T()
t2 = T2()
t.store['my_val'] = PropVal(5)
t.store['my_val2'] = PropVal(6)
t2.store['my_val'] = PropVal(1)
self.assertEqual(t.get.my_val.Value, 5)
self.assertEqual(t.get.my_val2.Value, 6)
self.assertEqual(t2.get.my_val.Value, 1)
t.get.my_val.Value = 6
self.assertEqual(t.get.my_val.Value, 6)
with self.assertRaises(AttributeError):
t.get.blah.Value = 6
#self.assertEqual(t.get.my_other_val.Value, None)
self.assertEqual(t.getv.my_val, 6)
t.getv.my_val = 7
self.assertEqual(t.getv.my_val, 7)
with self.assertRaises(AttributeError):
t.get.my_val = 7
Related
In Python world, IIUC, you generally have two kinds of options, every attribute is constant and frozen, like namedtuple, vs every attribute is modifiable.
You can write lots of getter/setter with #property. However, it's lots of boilerplates.
I like approaches like class Child(namedtuple(...)), however, it has the following downsides:
If in the inheritance you set __slots__ = (), then you can't add additional attribute into the child class.
If you don't set __slots__, then you can add additional attribute, however, there's no protection on assigning wrong attribute child.some_non_exist = some_value
Moreover, there's no way to optionally allow some attribute to be accessible and some attribute not.
I can have the following proposal, however, I don't know whether there's existing library for this.
Thanks!!
(I know attrs but I think it's focusing another set of problems.)
### The following is a proposal
#attr_acl
class A(object):
x = attr_acl.frozen() # x must be specified in __init__
y = attr_acl.frozen(default = 5)
z = attr_acl.mutable(default = 5)
zz = attr_acl.mutable_freezable()
g = attr_acl.mutable(is_final=True)
#attr_acl
class AA(X):
o = attr_acl.mutable()
aa = AA(x=1, zz=2, o=3)
attr_acl.freeze(obj.zz)
### The followings must raise error
aa.x = 5
aa.zz = 6
aa.some_non_exist = 7
You should look harder at attrs and dataclasses. They provide what you want:
#attr.s(slots=True)
class Coordinates(object):
x = attr.ib()
y = attr.ib()
But also you should consider whether you need to protect your attributes. It's easy to slip into a habit of overly defensive coding, where you work hard to prevent things that are not going to actually happen.
Here's a rudimentary implementation of the proposal without any dependencies, in case we want to proceed with not using attrs or dataclasses. It's only tested with the sample code in the question.
class UnsetDefaultAttributeValue(object):
pass
class AclAttribute(object):
def __init__(self, value):
self._value = value
def __str__(self):
return self._value.__str__()
class FrozenAttribute(AclAttribute):
def __init__(self, *args, **kwargs):
self._init_value_set = False
self._default_value = kwargs.get('default',
UnsetDefaultAttributeValue())
self._value = self._default_value
def __get__(self, instance, owner):
if self._value is UnsetDefaultAttributeValue and not self._init_value_set:
raise AttributeError('Value must be specified')
return self
def __set__(self, instance, value):
if (self._init_value_set):
raise AttributeError('Attribute is frozen')
self._value = value
self._init_value_set = True
class MutableAttribute(AclAttribute):
def __init__(self, *args, **kwargs):
self._init_value_set = False
self._default_value = kwargs.get('default', None)
self._is_final = kwargs.get('is_final', False)
self._value = self._default_value
def __get__(self, instance, owner):
return self
def __set__(self, instance, value):
if (self._is_final):
raise AttributeError('Attribute is finalized')
self._value = value
self._init_value_set = True
class MutableFreezableAttribute(AclAttribute):
def __init__(self, *args, **kwargs):
self._init_value_set = False
self._is_frozen = False
self._default_value = kwargs.get('default',
UnsetDefaultAttributeValue())
self._value = self._default_value
def __get__(self, instance, owner):
return self
def __set__(self, instance, value):
if (self._is_frozen):
raise AttributeError('Attribute is frozen')
self._value = value
self._init_value_set = True
def freeze(self):
self._is_frozen = True
class attr_acl(type):
def __new__(self, class_definition, *args, **kwargs):
def _init_class(self, *args, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
def _set_attr(self, key, value):
if not hasattr(self, key):
raise AttributeError('Attribute does not exist')
attr = getattr(self, key)
attr.__set__(self, value)
class_definition.__init__ = _init_class
class_definition.__setattr__ = _set_attr
return class_definition
#classmethod
def frozen(*args, **kwargs):
default_value = kwargs.get('default', None)
return FrozenAttribute(*args, **kwargs)
#classmethod
def mutable(*args, **kwargs):
default_value = kwargs.get('default', None)
return MutableAttribute(*args, **kwargs)
#classmethod
def mutable_freezable(*args, **kwargs):
default_value = kwargs.get('default', None)
return MutableFreezableAttribute(*args, **kwargs)
#classmethod
def freeze(self, attribute):
attribute.freeze()
### The following is a proposal
#attr_acl
class A(object):
x = attr_acl.frozen() # x must be specified in __init__
y = attr_acl.frozen(default = 5)
z = attr_acl.mutable(default = 5)
zz = attr_acl.mutable_freezable()
g = attr_acl.mutable(is_final=True)
#attr_acl
class AA(A):
o = attr_acl.mutable()
aa = AA(x=1, zz=2, o=3)
attr_acl.freeze(aa.zz)
### The followings must raise error
aa.x = 5
aa.zz = 6
aa.some_non_exist = 7
I'm trying to use the name in my init for my class attribute, attr but it seems that's impossible.
here's the code:
class B:
def __init__(self, val):
self.val = val
def __get__(self, instance, owner):
return owner.valEditNew(self.val)
def __set__(self, instance, value):
return
class A:
def __init__(self, name = 'def_name'):
self.name = name
attr = B('G120')
def valEditNew(val):
val += ' #edited'
return val
a = A('JJ')
print(a.attr)
that's it if i use self.name or name or ... in place of G120>>>
builtins.NameError: name 'self' is not defined
if that's not possible, can you show me the way?
If you want to access attribute of the instance that contains the descriptor object, use instance parameter of __get__ / __set__:
class B:
def __get__(self, instance, owner):
return instance.valEditNew(instance.name) # <---
def __set__(self, instance, value):
return
class A:
attr = B()
def __init__(self, name='def_name'):
self.name = name
def valEditNew(self, val):
val += ' #edited'
return val
a = A('JJ')
print(a.attr)
# prints => JJ #edited
I have created a descriptor for lists.
After testing it seems that every time I append a value to a list of one instance, it is being added to another instance as well.
Even weirder, in the unittests it keeps appending to the list, and not resetting on every test.
My descriptor main class:
class Field(object):
def __init__(self, type_, name, value=None, required=False):
self.type = type_
self.name = "_" + name
self.required = required
self._value = value
def __get__(self, instance, owner):
return getattr(instance, self.name, self.value)
def __set__(self, instance, value):
raise NotImplementedError
def __delete__(self, instance):
raise AttributeError("Can't delete attribute")
#property
def value(self):
return self._value
#value.setter
def value(self, value):
self._value = value if value else self.type()
Descriptor list class:
class ListField(Field):
def __init__(self, name, value_type):
super(ListField, self).__init__(list, name, value=[])
self.value_type = value_type
def __set__(self, instance, value):
if not isinstance(value, list):
raise TypeError("{} must be a list".format(self.name))
setattr(instance, self.name, value)
def __iter__(self):
for item in self.value:
yield item
def __len__(self):
return len(self.value)
def __getitem__(self, item):
return self.value[item]
def append(self, value):
if not isinstance(value, self.value_type):
raise TypeError("Value is list {} must be of type {}".format(self.name, self.value_type))
self.value.append(value)
Unittests:
# Class I created solely for testing purposes
class ListTestClass(object):
l = ListField("l", int)
class TestListFieldClass(unittest.TestCase):
def setUp(self):
self.listobject = ListTestClass()
def test_add(self):
# The first number is added to the list
self.listobject.l.append(2)
def test_multiple_instances(self):
# This test works just fine
l1 = ListField("l1", int)
l2 = ListField("l2", int)
l1.append(1)
l2.append(2)
self.assertEqual(l1[0], 1)
self.assertEqual(l2[0], 2)
def test_add_multiple(self):
# This test works just fine
l1 = ListField("l1", int)
l1.append(1)
l1.append(2)
self.assertEqual(l1[0], 1)
self.assertEqual(l1[1], 2)
def test_add_error(self):
# This test works just fine
with self.assertRaises(TypeError):
l1 = ListField("l1", int)
l1.append("1")
def test_overwrite_list(self):
# This test works just fine
l1 = ListField("l1", int)
l1 = []
l1.append(1)
def test_overwrite_error(self):
# This test works just fine
l1 = ListTestClass()
l1.l.append(1)
with self.assertRaises(TypeError):
l1.l = "foo"
def test_multiple_model_instances(self):
# I create 2 more instances of ListTestClass
l1 = ListTestClass()
l2 = ListTestClass()
l1.l.append(1)
l2.l.append(2)
self.assertEqual(l1.l[0], 1)
self.assertEqual(l2.l[0], 2)
The last test fails
Failure
Traceback (most recent call last):
File "/home/user/project/tests/test_fields.py", line 211, in test_multiple_model_instances
self.assertEqual(l1.l[0], 1)
AssertionError: 2 != 1
When I look at the values for l1.1 and l2.l, they both have a list containing [2, 1, 2]
What am I missing here?
I looked to the memory addresses and it seems that the lists all point to the same object.
class ListFieldTest(object):
lf1 = ListField("lf1", int)
class TestClass(object):
def __init__(self):
l1 = ListFieldTest()
l2 = ListFieldTest()
l1.lf1.append(1)
l2.lf1.append(2)
print(l1.lf1)
print(l2.lf1)
print(hex(id(l1)))
print(hex(id(l2)))
print(hex(id(l1.lf1)))
print(hex(id(l2.lf1)))
This prints
[1, 2]
[1, 2]
0x7f987da018d0 --> Address for l1
0x7f987da01910 --> Address for l2
0x7f987d9c4bd8 --> Address for l1.lf1
0x7f987d9c4bd8 --> Address for l2.lf1
ListTestClass.l is a class attribute, so it is shared by all instances of the class. Instead, you should create an instance attribute, eg in the __init__ method:
class ListTestClass(object):
def __init__(self):
self.l = ListField("l", int)
Similar remarks apply to ListFieldTest. There may be other similar problems elsewhere in your code, I haven't examined it closely.
According to this source, the proper form is
class ListTestClass(object):
l_attrib = ListField("l", int)
def __init__(self)
self.l = l_attrib
Thanks to both #PM 2Ring and volcano I found the answer.
In the end this works great for value types:
class IntTestClass(object):
i = IntegerField("i")
However for a reference type (like a list) that won't work and you have to add a new list
class ListTestClass(object):
l = ListField("l", int)
def __init__(self):
self.l = []
I have a class in which a method first needs to verify that an attribute is present and otherwise call a function to compute it. Then, ensuring that the attribute is not None, it performs some operations with it. I can see two slightly different design choices:
class myclass():
def __init__(self):
self.attr = None
def compute_attribute(self):
self.attr = 1
def print_attribute(self):
if self.attr is None:
self.compute_attribute()
print self.attr
And
class myclass2():
def __init__(self):
pass
def compute_attribute(self):
self.attr = 1
return self.attr
def print_attribute(self):
try:
attr = self.attr
except AttributeError:
attr = self.compute_attribute()
if attr is not None:
print attr
In the first design, I need to make sure that all the class attributes are set to None in advance, which can become verbose but also clarify the structure of the object.
The second choice seems to be the more widely used one. However, for my purposes (scientific computing related to information theory) using try except blocks everywhere can be a bit of an overkill given that this class doesn't really interact with other classes, it just takes data and computes a bunch of things.
Firstly, you can use hasattr to check if an object has an attribute, it returns True if the attribute exists.
hasattr(object, attribute) # will return True if the object has the attribute
Secondly, You can customise attribute access in Python, you can read more about it here: https://docs.python.org/2/reference/datamodel.html#customizing-attribute-access
Basically, you override the __getattr__ method to achieve this, so something like:
class myclass2():
def init(self):
pass
def compute_attr(self):
self.attr = 1
return self.attr
def print_attribute(self):
print self.attr
def __getattr__(self, name):
if hasattr(self, name) and getattr(self, name)!=None:
return getattr(self, name):
else:
compute_method="compute_"+name;
if hasattr(self, compute_method):
return getattr(self, compute_method)()
Make sure you only use getattr to access the attribute within __getattr__ or you'll end up with infinite recursion
Based on the answer jonrsharpe linked, I offer a third design choice. The idea here is that no special conditional logic is required at all either by the clients of MyClass or by code within MyClass itself. Instead, a decorator is applied to a function that does the (hypothetically expensive) computation of the property, and then that result is stored.
This means that the expensive computation is done lazily (only if a client tries to access the property) and only performed once.
def lazyprop(fn):
attr_name = '_lazy_' + fn.__name__
#property
def _lazyprop(self):
if not hasattr(self, attr_name):
setattr(self, attr_name, fn(self))
return getattr(self, attr_name)
return _lazyprop
class MyClass(object):
#lazyprop
def attr(self):
print('Generating attr')
return 1
def __repr__(self):
return str(self.attr)
if __name__ == '__main__':
o = MyClass()
print(o.__dict__, end='\n\n')
print(o, end='\n\n')
print(o.__dict__, end='\n\n')
print(o)
Output
{}
Generating attr
1
{'_lazy_attr': 1}
1
Edit
Application of Cyclone's answer to OP's context:
class lazy_property(object):
'''
meant to be used for lazy evaluation of an object attribute.
property should represent non-mutable data, as it replaces itself.
'''
def __init__(self, fget):
self.fget = fget
self.func_name = fget.__name__
def __get__(self, obj, cls):
if obj is None:
return None
value = self.fget(obj)
setattr(obj, self.func_name, value)
return value
class MyClass(object):
#lazy_property
def attr(self):
print('Generating attr')
return 1
def __repr__(self):
return str(self.attr)
if __name__ == '__main__':
o = MyClass()
print(o.__dict__, end='\n\n')
print(o, end='\n\n')
print(o.__dict__, end='\n\n')
print(o)
The output is identical to above.
Taking this question as a pointer, let's say there exists a class like the following:
class Container(object):
def __init__(self, **kwargs):
self._meta = defaultdict(lambda: None)
for attr, value in kwargs.iteritems():
self._meta[attr] = value
def __getattr__(self, key):
try:
return self._meta[key]
except KeyError:
raise AttributeError(key)
def __setattr__(self, key, value):
if key in ('_meta', '_hasattr'):
super(Container, self).__setattr__(key, value)
else:
self._meta[key] = value
This allows the following behavior:
c = Container()
c.a = 1
print(c.a) # 1
print(c.b) # None
Question: What is the best way to implement an operator such that the following works:
# Should delete the value of a from Container._meta
del c.a
Of course, one could obviously implement a method like,
def _delete(self, key):
...
But is there way to re-use a python operator to do this?
Just define the __delattr__ method:
def __delattr__(self, key):
del self._meta[key]