A Python property is accessed like a normal attribute, but is implemented using method calls. This is a recipe for dictproperty: an attribute that is accessed like an indexed (dictionary) attribute, but is implemented using method calls.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | class dictproperty(object):
class proxy(object):
def __init__(self, fget, fset, fdel):
self._fget = fget
self._fset = fset
self._fdel = fdel
self._obj = None
def setObj(self, obj):
self._obj = obj
def __getitem__(self, key):
if self._fget is None:
raise TypeError, "can't read item"
return self._fget(self._obj, key)
def __setitem__(self, key, value):
if self._fset is None:
raise TypeError, "can't set item"
self._fset(self._obj, key, value)
def __delitem__(self, key):
if self._fdel is None:
raise TypeError, "can't delete item"
self._fdel(self._obj, key)
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self._proxy = dictproperty.proxy(fget, fset, fdel)
self.__doc__ = doc
def __get__(self, obj, objtype=None):
if obj is None: return self
self._proxy.setObj(obj)
return self._proxy
|
Properties offer a way to access data like a normal attribute, but implemented using method calls. For example, rather than accessing an attribute of MyClass called foo with function calls like obj.getFoo and obj.setFoo, a property lets you simply write obj.foo. Reading from, writing to, or deleting obj.foo causes one of the corresponding functions to get invoked.
This recipe implements dictproperty, an analog to normal properties that can be used in cases where the attribute is a collection rather than a singleton. A dictproperty attribute foo can be accessed with dictionary-like syntax, i.e. obj.foo[key], but is actually implemented with calls to functions like obj.getFoo(key) and obj.setFoo(key).
In this example, MyClass contains a dictproperty attribute foo:
class MyClass(object):
def __init__(self):
self._foo = {}
def getFoo(self, key):
print 'Getting _foo[', key, ']'
return self._foo[key]
def setFoo(self, key, value):
print 'Setting _foo[', key, '] to', value
self._foo[key] = value
def delFoo(self, key):
print 'Deleting _foo[', key, ']'
del self._foo[key]
foo = dictproperty(getFoo, setFoo, delFoo, "something or other")
# A simple test
obj = MyClass()
obj.foo[3] = 5
assert obj._foo[3] == 5
assert obj.foo[3] == 5
del obj.foo[3]
assert not obj._foo.has_key(3)
Of course, in this example, the getFoo and setFoo do nothing more than access a real dictionary _foo, but in a real application these functions could be arbitrarily complex.
dictproperty is a minimal descriptor class that creates a proxy object, which implements __getitem__, __setitem__ and __delitem__, passing requests through to the functions that the user provided to the dictproperty constructor.
Note that since dictproperty reuses a single proxy object for each attribute access, the attribute is not thread-safe unless accesses are protected with some sort of lock. Thread safety could also be achieved by modifying dictproperty to create a new proxy instance on each attribute access.
See Recipe 205183 for a tidy property idiom.
New proxy instance on each attribute access. I think you should definitely go with the new proxy instance each time. No reason to lose thread safety when you don't have to.