Read-only class properties

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • George Sakkis

    Read-only class properties

    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__
    [color=blue][color=green][color=darkred]
    >>> Foo.TheAnswer[/color][/color][/color]
    The Answer according to Foo is 42[color=blue][color=green]
    >> Foo.TheAnswer = 0[/color][/color]
    exceptions.Attr ibuteError
    ....
    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(f unction):
    class Descriptor(obje ct):
    def __get__(self, obj, objtype):
    return function(objtyp e)
    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

  • Bengt Richter

    #2
    Re: Read-only class properties

    On 10 Jul 2005 13:38:22 -0700, "George Sakkis" <gsakkis@rutger s.edu> wrote:
    [color=blue]
    >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__
    >[color=green][color=darkred]
    >>>> Foo.TheAnswer[/color][/color]
    >The Answer according to Foo is 42[color=green][color=darkred]
    >>> Foo.TheAnswer = 0[/color][/color]
    >exceptions.Att ributeError
    >...
    >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(f unction):
    > class Descriptor(obje ct):
    > def __get__(self, obj, objtype):
    > return function(objtyp e)
    > 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.TheAnswe r. What am I missing here ?
    >[/color]
    I suspect type(Foo).TheAn swer 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.__setatt r__ 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(f unction):
    class Descriptor(obje ct):
    def __get__(self, obj, objtype):
    return function(objtyp e)
    def __set__(self, obj, value):
    raise AttributeError, "can't set class attribute"
    return Descriptor()

    class Foo(object):
    class __metaclass__(t ype):
    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(c ls):
    return "Another Answer according to %s is 43" % cls.__name__

    class Bar(object):
    class __metaclass__(t ype):
    def __setattr__(cls , name, value):
    attr = cls.__dict__.ge t(name)
    if hasattr(attr, '__set__'):
    attr.__set__(cl s, 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(c ls):
    return "Another Answer according to %s is 43" % cls.__name__


    if __name__ == '__main__':
    print Foo.TheAnswer
    Foo.notTheAnswe r = 'ok'
    print 'Foo.notTheAnsw er is %r' % Foo.notTheAnswe r
    print Foo.AnotherAnsw er
    try: Foo.TheAnswer = 123
    except AttributeError, e: print '%s: %s' %(e.__class__._ _name__, e)
    try: Foo.AnotherAnsw er = 456
    except AttributeError, e: print '%s: %s' %(e.__class__._ _name__, e)
    print Bar.TheAnswer
    Bar.notTheAnswe r = 'ok'
    print 'Bar.notTheAnsw er is %r' % Bar.notTheAnswe r
    print Bar.AnotherAnsw er
    try: Bar.TheAnswer = 123
    except AttributeError, e: print '%s: %s' %(e.__class__._ _name__, e)
    try: Bar.AnotherAnsw er = 456
    except AttributeError, e: print '%s: %s' %(e.__class__._ _name__, e)
    ------------------------------------------------------------------------
    Result:

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

    Regards,
    Bengt Richter

    Comment

    • Michael Spencer

      #3
      Re: Read-only class properties

      Bengt Richter wrote:
      ....[color=blue]
      >
      > class Foo(object):
      > class __metaclass__(t ype):
      > 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(c ls):
      > return "Another Answer according to %s is 43" % cls.__name__
      >[/color]

      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.[color=blue][color=green][color=darkred]
      >>> def classproperty(f unction):[/color][/color][/color]
      ... class Descriptor(obje ct):
      ... def __get__(self, obj, objtype):
      ... return function(objtyp e)
      ... def __set__(self, obj, value):
      ... raise AttributeError, "can't set class attribute"
      ... return Descriptor()
      ...[color=blue][color=green][color=darkred]
      >>> class A(object):[/color][/color][/color]
      ... class __metaclass__(t ype):
      ... @classproperty
      ... def TheAnswer(cls):
      ... return "The Answer according to %s is 42" % cls.__name__
      ...[color=blue][color=green][color=darkred]
      >>> A.TheAnswer[/color][/color][/color]
      'The Answer according to __metaclass__ is 42'[color=blue][color=green][color=darkred]
      >>> A.TheAnswer = 3[/color][/color][/color]
      Traceback (most recent call last):
      File "<input>", line 1, in ?
      File "<input>", line 6, in __set__
      AttributeError: can't set class attribute[color=blue][color=green][color=darkred]
      >>> class B(A): pass[/color][/color][/color]
      ...[color=blue][color=green][color=darkred]
      >>> B.TheAnswer[/color][/color][/color]
      'The Answer according to __metaclass__ is 42'[color=blue][color=green][color=darkred]
      >>>[/color][/color][/color]


      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

      Comment

      • Bengt Richter

        #4
        Re: Read-only class properties

        On Sun, 10 Jul 2005 21:10:36 -0700, Michael Spencer <mahs@telcopart ners.com> wrote:
        [color=blue]
        >Bengt Richter wrote:
        >...[color=green]
        >>
        >> class Foo(object):
        >> class __metaclass__(t ype):
        >> 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(c ls):
        >> return "Another Answer according to %s is 43" % cls.__name__
        >>[/color]
        >
        >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.[color=green][color=darkred]
        > >>> def classproperty(f unction):[/color][/color]
        > ... class Descriptor(obje ct):
        > ... def __get__(self, obj, objtype):
        > ... return function(objtyp e)
        > ... def __set__(self, obj, value):
        > ... raise AttributeError, "can't set class attribute"
        > ... return Descriptor()
        > ...[color=green][color=darkred]
        > >>> class A(object):[/color][/color]
        > ... class __metaclass__(t ype):
        > ... @classproperty
        > ... def TheAnswer(cls):
        > ... return "The Answer according to %s is 42" % cls.__name__
        > ...[color=green][color=darkred]
        > >>> A.TheAnswer[/color][/color]
        > 'The Answer according to __metaclass__ is 42'[color=green][color=darkred]
        > >>> A.TheAnswer = 3[/color][/color]
        > Traceback (most recent call last):
        > File "<input>", line 1, in ?
        > File "<input>", line 6, in __set__
        > AttributeError: can't set class attribute[color=green][color=darkred]
        > >>> class B(A): pass[/color][/color]
        > ...[color=green][color=darkred]
        > >>> B.TheAnswer[/color][/color]
        > 'The Answer according to __metaclass__ is 42'[color=green][color=darkred]
        > >>>[/color][/color]
        >
        >
        >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
        >[/color]
        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,
        [color=blue][color=green][color=darkred]
        >>> class A(object):[/color][/color][/color]
        ... class __metaclass__(t ype):
        ... 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(TheAns wer, __refuse)
        ...
        ...[color=blue][color=green][color=darkred]
        >>> A.TheAnswer[/color][/color][/color]
        'The Answer according to A is 42'[color=blue][color=green][color=darkred]
        >>> A.TheAnswer = 123[/color][/color][/color]
        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:
        [color=blue][color=green][color=darkred]
        >>> a=A()
        >>> a.TheAnswer[/color][/color][/color]
        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:
        [color=blue][color=green][color=darkred]
        >>> type(a).mro()[/color][/color][/color]
        [<class '__main__.A'>, <type 'object'>]
        [color=blue][color=green][color=darkred]
        >>> type(A).mro()[/color][/color][/color]
        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...
        [color=blue][color=green][color=darkred]
        >>> type(A).mro(typ e(A))[/color][/color][/color]
        [<class '__main__.__met aclass__'>, <type 'type'>, <type 'object'>]

        or
        [color=blue][color=green][color=darkred]
        >>> type.mro(A)[/color][/color][/color]
        [<class '__main__.A'>, <type 'object'>][color=blue][color=green][color=darkred]
        >>> type.mro(type(A ))[/color][/color][/color]
        [<class '__main__.__met aclass__'>, <type 'type'>, <type 'object'>]

        or even
        [color=blue][color=green][color=darkred]
        >>> type.__dict__['mro'][/color][/color][/color]
        <method 'mro' of 'type' objects>[color=blue][color=green][color=darkred]
        >>> type.__dict__['mro'](A)[/color][/color][/color]
        [<class '__main__.A'>, <type 'object'>][color=blue][color=green][color=darkred]
        >>> type.__dict__['mro'](type(A))[/color][/color][/color]
        [<class '__main__.__met aclass__'>, <type 'type'>, <type 'object'>][color=blue][color=green][color=darkred]
        >>> type(A)[/color][/color][/color]
        <class '__main__.__met aclass__'>

        Regards,
        Bengt Richter

        Comment

        Working...