Reputation: 519
I am trying to track the external modification of entries of a mutable python object (e.g., a list tor dictionary). This ability is particularly helpful in the following two situations:
1) When one would like to avoid the assignment of unwanted values to the mutable python object. Here's a simple example where x must be a list of integers only:
class foo(object):
def __init__(self,x):
self.x = x
def __setattr__(self,attr_name,attr_value):
# x must be a list of integers only
if attr_name == 'x' and not isinstance(attr_value,list):
raise TypeError('x must be a list!')
elif attr_name == 'x' and len([a for a in attr_value if not isinstance(a,int)]) > 0:
raise TypeError('x must be a list of integers only')
self.__dict__[attr_name] = attr_value
# The following works fine and it throws an error because x has a non-integer entry
f = foo(x = ['1',2,3])
# The following assigns an authorized list to x
f = foo(x = [1,2,3])
# However, the following does not throw any error.
#** I'd like my code to throw an error whenever a non-integer value is assigned to an element of x
f.x[0] = '1'
print 'f.x = ',f.x
2) When one needs to update a number of other variables after modifying the mutable Python object. Here's an example, where x is a dictionary and x_vals
needs to get updated whenever any changes (such as deleting an entry or assigning a new value for a particular key) are made to x
:
class foo(object):
def __init__(self,x,y = None):
self.set_x(x)
self.y = y
def set_x(self,x):
"""
x has to be a dictionary
"""
if not isinstance(x,dict):
raise TypeError('x must be a dicitonary')
self.__dict__['x'] = x
self.find_x_vals()
def find_x_vals(self):
"""
NOTE: self.x_vals needs to get updated each time one modifies x
"""
self.x_vals = self.x.values()
def __setattr__(self,name,value):
# Any Changes made to x --> NOT SURE HOW TO CODE THIS PART! #
if name == 'x' or ...:
raise AttributeError('Use set_x to make changes to x!')
else:
self.__dict__[name] = value
if __name__ == '__main__':
f = foo(x={'a':1, 'b':2, 'c':3}, y = True)
print f.x_vals
# I'd like this to throw an error asking to use set_x so self.x_vals
# gets updated too
f.x['a'] = 5
# checks if x_vals was updated
print f.x_vals
# I'd like this to throw an error asking to use set_x so self.x_vals gets updated too
del f.x['a']
print f.x_vals
Upvotes: 4
Views: 117
Reputation: 69100
You cannot use property
because the thing you are trying to protect is mutable, and property
only helps with the get
ing, set
ing, and delete
ing of the object itself, not that objects internal state.
What you could do is create a dict
subclass (or just a look-a-like if you only need a couple of the dict
abilities) to manage access. Then your custom class could manage the __getitem__
, __setitem__
, and __delitem__
methods.
Update for question revision
My original answer is still valid -- whether you use property
or __getattribute__
1 you still have the basic problem: once you hand over the retrieved attribute you have no control over what happens to it nor what it does.
You have two options to work around this:
create subclasses of the classes you want to protect, and put the restrictions in them (from my original answer), or
create a generic wrapper to act as a gateway.
A very rough example of the gateway wrapper:
class Gateway():
"use this to wrap an object and provide restrictions to it's data"
def __init__(self, obj, valid_key=None, valid_value=None):
self.obj = obj
self.valid_key = valid_key
self.valid_value = valid_value
def __setitem__(self, name, value):
"""
a dictionary can have any value for name, any value for value
a list will have an integer for name, any value for value
"""
valid_key = self.valid_key
valid_value = self.valid_value
if valid_key is not None:
if not valid_key(name):
raise Exception('%r not allowed as key/index' % type(name))
if valid_value is not None:
if not valid_value(value):
raise Exception('%r not allowed as value' % value)
self.obj[name] = value
and a simple example:
huh = Gateway([1, 2, 3], valid_value=lambda x: isinstance(x, int))
huh[0] = '1'
Traceback (most recent call last):
...
Exception: '1' not allowed as value
To use Gateway
you will need to override more methods, such as append
(for list
).
1 Using __getattribute__
is not advised as it is the piece that controls all the aspects of attribute lookup. It is easy to get wrong.
Upvotes: 1
Reputation: 2065
You could make x_vals
a property like that:
@property
def x_vals(self):
return self.x.values()
And it would keep x_vals
up to date each time you access it. It would event be faster because you wouldn't have to update it each time you change x
.
If your only problem is keeping x_vals
up to date, it's going to solve it, and save you the hassle of subclassing stuff.
Upvotes: 2