Read-only class properties

Bengt Richter bokr at oz.net
Sun Jul 10 21:20:06 EDT 2005


On 10 Jul 2005 13:38:22 -0700, "George Sakkis" <gsakkis at 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.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 ?
>
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



More information about the Python-list mailing list