• 12

A PHP Error was encountered

Severity: Notice

Message: Undefined index: userid

Filename: views/question.php

Line Number: 191


File: /home/prodcxja/public_html/questions/application/views/question.php
Line: 191
Function: _error_handler

File: /home/prodcxja/public_html/questions/application/controllers/Questions.php
Line: 433
Function: view

File: /home/prodcxja/public_html/questions/index.php
Line: 315
Function: require_once

name Punditsdkoslkdosdkoskdo

Class-level read-only properties in Python

Is there some way to make a class-level read-only property in Python? For instance, if I have a class Foo, I want to say:


but prevent anyone from saying:


EDIT: I like the simplicity of Alex Martelli's solution, but not the syntax that it requires. Both his and ~unutbu's answers inspired the following solution, which is closer to the spirit of what I was looking for:

class const_value (object):
    def __init__(self, value):
        self.__value = value

    def make_property(self):
        return property(lambda cls: self.__value)

class ROType(type):
    def __new__(mcl,classname,bases,classdict):
        class UniqeROType (mcl):

        for attr, value in classdict.items():
            if isinstance(value, const_value):
                setattr(UniqeROType, attr, value.make_property())
                classdict[attr] = value.make_property()

        return type.__new__(UniqeROType,classname,bases,classdict)

class Foo(object):
    BAR = const_value(1)
    BAZ = 2

class Bit(object):
    BOO = const_value(3)
    BAN = 4

Now, I get:

# 1
# 2
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# AttributeError: can't set attribute

I prefer this solution because:

  • The members get declared inline instead of after the fact, as with type(X).foo = ...
  • The members' values are set in the actual class's code as opposed to in the metaclass's code.

It's still not ideal because:

  • I have to set the __metaclass__ in order for const_value objects to be interpreted correctly.
  • The const_values don't "behave" like the plain values. For example, I couldn't use it as a default value for a parameter to a method in the class.
      • 2
    • @mattb As far as I know that will only create object level readonly attributes. The question was to create class level readonly attributes.
    • Why would anyone ever try to set Foo.CLASS_PROPERTY? It's typical style in Python to choose simplicity over planning for preventing a theoretical instance where someone does something stupid. There isn't anything you can do to keep people from doing stupid stuff with your code.
      • 1
    • @Mike: You're right; common sense would tell people to not modify these properties. And yet, occasionally, they get modified. That's why I like for it to be spelled out (more explicitly than with all-caps). Same rationale as using private members. Explicit interfaces are good. Don't get me wrong: I like simplicity too. I'd much prefer if there were some built-in facility for this (like a @classproperty decorator or something). As it is, I'm not even sure I would use the above solution if it requires setting the __metaclass__ each time. All-caps may win out, for now.
      • 1
    • I think it's counter to the intent of stack-exchange to edit your question and insert an answer. Instead I'd suggest that if you have a better answer to just write an answer among all other answers. There's nothing preventing you from answering your own question, you can even accept that answer after a while.

The existing solutions are a bit complex -- what about just ensuring that each class in a certain group has a unique metaclass, then setting a normal read-only property on the custom metaclass. Namely:

>>> class Meta(type):
...   def __new__(mcl, *a, **k):
...     uniquemcl = type('Uniq', (mcl,), {})
...     return type.__new__(uniquemcl, *a, **k)
>>> class X: __metaclass__ = Meta
>>> class Y: __metaclass__ = Meta
>>> type(X).foo = property(lambda *_: 23)
>>> type(Y).foo = property(lambda *_: 45)
>>> X.foo
>>> Y.foo

this is really much simpler, because it's based on nothing more than the fact that when you get an instance's attribute descriptors are looked up on the class (so of course when you get a class's attribute descriptors are looked on the metaclass), and making class/metaclass unique isn't terribly hard.

Oh, and of course:

>>> X.foo = 67
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute

just to confirm it IS indeed read-only!

  • 9
Reply Report

The ActiveState solution that Pynt references makes instances of ROClass have read-only attributes. Your question seems to ask if the class itself can have read-only attributes.

Here is one way, based on Raymond Hettinger's comment:

#!/usr/bin/env python
def readonly(value):
    return property(lambda self: value)

class ROType(type):
    CLASS_PROPERTY = readonly(1)

class Foo(object):

# 1

# AttributeError: can't set attribute

The idea is this: Consider first Raymond Hettinger's solution:

class Bar(object):
    CLASS_PROPERTY = property(lambda self: 1)

It shows a relatively simple way to give bar a read-only property.

Notice that you have to add the CLASS_PROPERTY = property(lambda self: 1) line to the definition of the class of bar, not to bar itself.

So, if you want the class Foo to have a read-only property, then the parent class of Foo has to have CLASS_PROPERTY = property(lambda self: 1) defined.

The parent class of a class is a metaclass. Hence we define ROType as the metaclass:

class ROType(type):
    CLASS_PROPERTY = readonly(1)

Then we make Foo's parent class be ROType:

class Foo(object):
  • 4
Reply Report

Found this on ActiveState:

# simple read only attributes with meta-class programming

# method factory for an attribute get method
def getmethod(attrname):
    def _getmethod(self):
        return self.__readonly__[attrname]

    return _getmethod

class metaClass(type):
    def __new__(cls,classname,bases,classdict):
        readonly = classdict.get('__readonly__',{})
        for name,default in readonly.items():
            classdict[name] = property(getmethod(name))

        return type.__new__(cls,classname,bases,classdict)

class ROClass(object):
    __metaclass__ = metaClass
    __readonly__ = {'a':1,'b':'text'}

if __name__ == '__main__':
    def test1():
        t = ROClass()
        print t.a
        print t.b

    def test2():
        t = ROClass()
        t.a = 2


Note that if you try to set a read-only attribute (t.a = 2) python will raise an AttributeError.

  • 0
Reply Report
      • 2
    • This works for instance level properties, but not class-level (i.e. I still couldn't say ROClass.a and expect the value to be 1).

Trending Tags