By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
428,739 Members | 1,523 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 428,739 IT Pros & Developers. It's quick & easy.

Read-only class properties

P: n/a
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__
Foo.TheAnswer
The Answer according to Foo is 42 Foo.TheAnswer = 0

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

Jul 21 '05 #1
Share this Question
Share on Google+
3 Replies


P: n/a
On 10 Jul 2005 13:38:22 -0700, "George Sakkis" <gs*****@rutgers.edu> wrote:
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__
Foo.TheAnswerThe Answer according to Foo is 42 Foo.TheAnswer = 0

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
Jul 21 '05 #2

P: n/a
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.
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 A(object): ... class __metaclass__(type):
... @classproperty
... def TheAnswer(cls):
... return "The Answer according to %s is 42" % cls.__name__
... A.TheAnswer 'The Answer according to __metaclass__ is 42' A.TheAnswer = 3 Traceback (most recent call last):
File "<input>", line 1, in ?
File "<input>", line 6, in __set__
AttributeError: can't set class attribute class B(A): pass ... B.TheAnswer 'The Answer according to __metaclass__ is 42'

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

Jul 21 '05 #3

P: n/a
On Sun, 10 Jul 2005 21:10:36 -0700, Michael Spencer <ma**@telcopartners.com> wrote:
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.
>>> 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 A(object): ... class __metaclass__(type):
... @classproperty
... def TheAnswer(cls):
... return "The Answer according to %s is 42" % cls.__name__
... >>> A.TheAnswer 'The Answer according to __metaclass__ is 42' >>> A.TheAnswer = 3 Traceback (most recent call last):
File "<input>", line 1, in ?
File "<input>", line 6, in __set__
AttributeError: can't set class attribute >>> class B(A): pass ... >>> B.TheAnswer 'The Answer according to __metaclass__ is 42' >>>

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 A(object): ... 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)
...
... A.TheAnswer 'The Answer according to A is 42' A.TheAnswer = 123 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:
a=A()
a.TheAnswer 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:
type(a).mro() [<class '__main__.A'>, <type 'object'>]
type(A).mro() 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...
type(A).mro(type(A)) [<class '__main__.__metaclass__'>, <type 'type'>, <type 'object'>]

or
type.mro(A) [<class '__main__.A'>, <type 'object'>] type.mro(type(A)) [<class '__main__.__metaclass__'>, <type 'type'>, <type 'object'>]

or even
type.__dict__['mro'] <method 'mro' of 'type' objects> type.__dict__['mro'](A) [<class '__main__.A'>, <type 'object'>] type.__dict__['mro'](type(A)) [<class '__main__.__metaclass__'>, <type 'type'>, <type 'object'>] type(A)

<class '__main__.__metaclass__'>

Regards,
Bengt Richter
Jul 21 '05 #4

This discussion thread is closed

Replies have been disabled for this discussion.