This recipe is a rewrite of a portion of http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/157768 . It shows an easy way to write read-only attributes with the help of meta classes.
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 | # simple read only attributes with meta-class programming
# method factory for an attribute get method
def getmethod(attrname):
def _getmethod(self):
return self.__readonly__[attrname]
return _getmethod
class metaClass(type):
def __new__(cls,classname,bases,classdict):
readonly = classdict.get('__readonly__',{})
for name,default in readonly.items():
classdict[name] = property(getmethod(name))
return type.__new__(cls,classname,bases,classdict)
class ROClass(object):
__metaclass__ = metaClass
__readonly__ = {'a':1,'b':'text'}
if __name__ == '__main__':
def test1():
t = ROClass()
print t.a
print t.b
def test2():
t = ROClass()
t.a = 2
test1()
|
This example shows that even a few lines of code (in this case the definition of the meta class) can save quite a lot of "classic", handwritten code.
The main idea behind this example is the automatic creation of property methods and the binding of an attribute to them.
Further reading: Python in a Nutshell by Alex Martelli The sources of SQLObject (http://sqlobject.org/) by Ian Bicking
This a very good solution. I like it at least as much as my own.
Sean Ross
Simplified a bit and contrasted with non-metaclass programming. Nice example of how metaclasses work.
It can be simplified a bit by eliminating the nested getmethod() helper function and replacing for-loop with:
Though it is an instructive metaclass example, it isn't actually an improvement over non-metaclass code. The following is shorter, clearer, simpler, and faster.
One shortcoming of this solution is that the read-only properties that are created are not associated with any instance attributes. Generally, the reason you would use a property is so that you can control how the values of your instance attributes are exposed to users of your classes. Since your read-only properties have no instance attribute association, what's being created here are, essentially, class constants. This is fine, if this is all you're looking for. However, you are losing part of the benefit of having a read-only property.
By creating your read-only properties with an association to an instance attribute you remain able to change the value that property will return. For example, if you have a read-only property foo and it is associated with a private instance variable __foo, i.e. instance.foo returns the value of instance._classname__foo, then if the value of __foo is changed by some internal operation, say in some other method call, then the value returned by subsequent calls to instance.foo will also change. In other words, instance.foo is no longer just a constant, it's read-only, and these two things are not the same.
Of course, you could change the value of instance.__readonly__['foo'], internally, to accomplish the above behaviour. Still, why not just have instance.__foo available, if you want/need it?
Make __new__ cooperative.
Excelent point. That's actually quite a nice point. ALL examples I've ever encountered ware doing the "magic" stuff within the __new__ method of the metaclass. Since a class is more or less an instance of its metaclass, it might be better to use __init__ instead of __new__ as __init__ is much more natural for most python programmers.
__new__ vs __init__ in metaclasses. In this example __new__ and __init__ would be interchangeable, but in general overriding
__init__
has severe limitations with respect to overriding__new__
, since the 'name', 'bases' and 'dic' arguments cannot be directly changed. Let me show an example:The output of this script is
False
: the dictionary cannot be changed in the__init__
method. However, replacingdic['changed']=True
withcls.changed=True
would work. Analougously, changingcls.__name__
would work. On the other hand,__bases__
is a read-only attribute and cannot be changed once the class has been created, therefore there is no way it can be touched in__init__
. Nevertheless,__bases__
could be changed in__new__
before the class creation.These are the reasons why often people prefer __new__ over __init__.
Alternative descriptor implementation. I have an implementation that does something similar; it's actually a write-once implementation, since you have to set the value at least once. It doesn't use metaclasses, but instead uses a descriptor (similar to property):
http://blog.colorstudy.com/ianb/weblog/2004/07/15.html#P131