Read-only class properties

Discussion in 'Python' started by George Sakkis, Jul 10, 2005.

  1. 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
     
    George Sakkis, Jul 10, 2005
    #1
    1. Advertising

  2. On 10 Jul 2005 13:38:22 -0700, "George Sakkis" <> 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
     
    Bengt Richter, Jul 11, 2005
    #2
    1. Advertising

  3. 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
     
    Michael Spencer, Jul 11, 2005
    #3
  4. On Sun, 10 Jul 2005 21:10:36 -0700, Michael Spencer <> 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
     
    Bengt Richter, Jul 11, 2005
    #4
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. K B
    Replies:
    0
    Views:
    546
  2. keithb
    Replies:
    2
    Views:
    8,021
    keithb
    Jun 7, 2006
  3. Greg Brunet

    Static class properties (read-only)

    Greg Brunet, Aug 7, 2003, in forum: Python
    Replies:
    3
    Views:
    501
    Alex Martelli
    Aug 7, 2003
  4. Paul Moore
    Replies:
    9
    Views:
    605
    Roy Smith
    Feb 21, 2005
  5. A
    Replies:
    1
    Views:
    326
    Ian Collins
    Nov 3, 2010
Loading...

Share This Page