Read-only class properties

G

George Sakkis

I'm trying to write a decorator similar to property, with the
difference that it applies to the defining class (and its subclasses)
instead of its instances. This would provide, among others, a way to
define the equivalent of class-level constants:

class Foo(object):
@classproperty
def TheAnswer(cls):
return "The Answer according to %s is 42" % cls.__name__
exceptions.AttributeError
....
AttributeError: can't set class attribute

I read the 'How-To Guide for Descriptors'
(http://users.rcn.com/python/download/Descriptor.htm) that describes
the equivalent python implementation of property() and classmethod()
and I came up with this:

def classproperty(function):
class Descriptor(object):
def __get__(self, obj, objtype):
return function(objtype)
def __set__(self, obj, value):
raise AttributeError, "can't set class attribute"
return Descriptor()

Accessing Foo.TheAnswer works as expected, however __set__ is
apparently not called because no exception is thrown when setting
Foo.TheAnswer. What am I missing here ?

George
 
B

Bengt Richter

I'm trying to write a decorator similar to property, with the
difference that it applies to the defining class (and its subclasses)
instead of its instances. This would provide, among others, a way to
define the equivalent of class-level constants:

class Foo(object):
@classproperty
def TheAnswer(cls):
return "The Answer according to %s is 42" % cls.__name__

exceptions.AttributeError
...
AttributeError: can't set class attribute

I read the 'How-To Guide for Descriptors'
(http://users.rcn.com/python/download/Descriptor.htm) that describes
the equivalent python implementation of property() and classmethod()
and I came up with this:

def classproperty(function):
class Descriptor(object):
def __get__(self, obj, objtype):
return function(objtype)
def __set__(self, obj, value):
raise AttributeError, "can't set class attribute"
return Descriptor()

Accessing Foo.TheAnswer works as expected, however __set__ is
apparently not called because no exception is thrown when setting
Foo.TheAnswer. What am I missing here ?
I suspect type(Foo).TheAnswer is not found and therefore TheAnswer.__set__ is not
looked for, and therefore it becomes an ordinary attribute setting. I suspect this
is implemented in object.__setattr__ or type.__setattr__ as the case may be, when
they are inherited. So I introduced a __setattr__ in type(Foo) by giving Foo
a metaclass as its type(Foo). First I checked whether the attribute type name was
'Descriptor' (not very general ;-) and raised an attribute error if so.
Then I made a class Bar version of Foo and checked for __set__ and called that
as if a property of type(Bar) instances. See below.


----< classprop.py >----------------------------------------------------
def classproperty(function):
class Descriptor(object):
def __get__(self, obj, objtype):
return function(objtype)
def __set__(self, obj, value):
raise AttributeError, "can't set class attribute"
return Descriptor()

class Foo(object):
class __metaclass__(type):
def __setattr__(cls, name, value):
if type(cls.__dict__.get(name)).__name__ == 'Descriptor':
raise AttributeError, 'setting Foo.%s to %r is not allowed' %(name, value)
type.__setattr__(cls, name, value)
@classproperty
def TheAnswer(cls):
return "The Answer according to %s is 42" % cls.__name__
@classproperty
def AnotherAnswer(cls):
return "Another Answer according to %s is 43" % cls.__name__

class Bar(object):
class __metaclass__(type):
def __setattr__(cls, name, value):
attr = cls.__dict__.get(name)
if hasattr(attr, '__set__'):
attr.__set__(cls, value) # like an instance attr setting
else:
type.__setattr__(cls, name, value)
@classproperty
def TheAnswer(cls):
return "The Answer according to %s is 42" % cls.__name__
@classproperty
def AnotherAnswer(cls):
return "Another Answer according to %s is 43" % cls.__name__


if __name__ == '__main__':
print Foo.TheAnswer
Foo.notTheAnswer = 'ok'
print 'Foo.notTheAnswer is %r' % Foo.notTheAnswer
print Foo.AnotherAnswer
try: Foo.TheAnswer = 123
except AttributeError, e: print '%s: %s' %(e.__class__.__name__, e)
try: Foo.AnotherAnswer = 456
except AttributeError, e: print '%s: %s' %(e.__class__.__name__, e)
print Bar.TheAnswer
Bar.notTheAnswer = 'ok'
print 'Bar.notTheAnswer is %r' % Bar.notTheAnswer
print Bar.AnotherAnswer
try: Bar.TheAnswer = 123
except AttributeError, e: print '%s: %s' %(e.__class__.__name__, e)
try: Bar.AnotherAnswer = 456
except AttributeError, e: print '%s: %s' %(e.__class__.__name__, e)
------------------------------------------------------------------------
Result:

[18:17] C:\pywk\clp>py24 classprop.py
The Answer according to Foo is 42
Foo.notTheAnswer is 'ok'
Another Answer according to Foo is 43
AttributeError: setting Foo.TheAnswer to 123 is not allowed
AttributeError: setting Foo.AnotherAnswer to 456 is not allowed
The Answer according to Bar is 42
Bar.notTheAnswer is 'ok'
Another Answer according to Bar is 43
AttributeError: can't set class attribute
AttributeError: can't set class attribute

Regards,
Bengt Richter
 
M

Michael Spencer

Bengt Richter wrote:
....
class Foo(object):
class __metaclass__(type):
def __setattr__(cls, name, value):
if type(cls.__dict__.get(name)).__name__ == 'Descriptor':
raise AttributeError, 'setting Foo.%s to %r is not allowed' %(name, value)
type.__setattr__(cls, name, value)
@classproperty
def TheAnswer(cls):
return "The Answer according to %s is 42" % cls.__name__
@classproperty
def AnotherAnswer(cls):
return "Another Answer according to %s is 43" % cls.__name__

or, simply put the read-only descriptor in the metaclass:

Python 2.4 (#60, Nov 30 2004, 11:49:19) [MSC v.1310 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information. ... class Descriptor(object):
... def __get__(self, obj, objtype):
... return function(objtype)
... def __set__(self, obj, value):
... raise AttributeError, "can't set class attribute"
... return Descriptor()
... ... class __metaclass__(type):
... @classproperty
... def TheAnswer(cls):
... return "The Answer according to %s is 42" % cls.__name__
... Traceback (most recent call last):
File "<input>", line 1, in ?


this means that the getter doesn't automatically get a reference to the class
(since it is a method of metaclass), which may or may not matter, depending on
the application

Michael
 
B

Bengt Richter

Bengt Richter wrote:
...
class Foo(object):
class __metaclass__(type):
def __setattr__(cls, name, value):
if type(cls.__dict__.get(name)).__name__ == 'Descriptor':
raise AttributeError, 'setting Foo.%s to %r is not allowed' %(name, value)
type.__setattr__(cls, name, value)
@classproperty
def TheAnswer(cls):
return "The Answer according to %s is 42" % cls.__name__
@classproperty
def AnotherAnswer(cls):
return "Another Answer according to %s is 43" % cls.__name__

or, simply put the read-only descriptor in the metaclass:

Python 2.4 (#60, Nov 30 2004, 11:49:19) [MSC v.1310 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.... class Descriptor(object):
... def __get__(self, obj, objtype):
... return function(objtype)
... def __set__(self, obj, value):
... raise AttributeError, "can't set class attribute"
... return Descriptor()
...... class __metaclass__(type):
... @classproperty
... def TheAnswer(cls):
... return "The Answer according to %s is 42" % cls.__name__
...Traceback (most recent call last):
File "<input>", line 1, in ?


this means that the getter doesn't automatically get a reference to the class
(since it is a method of metaclass), which may or may not matter, depending on
the application
It appears that you can use an ordinary property in the metaclass, and get the reference:
(I tried doing this but I still had the classproperty decorator and somehow inside a metaclass
it bombed or I typoed, and I forgot to try the plain property, so I hacked onwards to the
more involved __setattr__ override). Anyway,
... class __metaclass__(type):
... def TheAnswer(cls):
... return "The Answer according to %s is 42" % cls.__name__
... def __refuse(cls, v):
... raise AttributeError, "Refusing to set %s.TheAnswer to %r"%(cls.__name__, v)
... TheAnswer = property(TheAnswer, __refuse)
...
... Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 6, in __refuse
AttributeError: Refusing to set A.TheAnswer to 123

Of course, access through an instance won't see this:
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AttributeError: 'A' object has no attribute 'TheAnswer'

since TheAnswer is found in type(a)'s mro, but not type(A)'s:
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: descriptor 'mro' of 'type' object needs an argument

looks like you get type.mro as an unbound method that way...
[QUOTE= said:
>>> type.mro(A)
>>> type.mro(type(A))
[QUOTE= said:
>>> type.__dict__['mro']
>>> type.__dict__['mro'](A)
>>> type.__dict__['mro'](type(A))
>>> type(A)
[/QUOTE][/QUOTE]
<class '__main__.__metaclass__'>

Regards,
Bengt Richter
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,734
Messages
2,569,441
Members
44,832
Latest member
GlennSmall

Latest Threads

Top