How to make a class property?


In python I can add a method to a class with the @classmethod decorator. Is there a similar decorator to add a property to a class? I can better show what I’m talking about.

class Example(object):
   the_I = 10
   def __init__( self ):
      self.an_i = 20

   def i( self ):
      return self.an_i

   def inc_i( self ):
      self.an_i += 1

   # is this even possible?
   def I( cls ):
      return cls.the_I

   def inc_I( cls ):
      cls.the_I += 1

e = Example()
assert e.i == 20
assert e.i == 21

assert Example.I == 10
assert Example.I == 11

Is the syntax I’ve used above possible or would it require something more?

The reason I want class properties is so I can lazy load class attributes, which seems reasonable enough.

Asked By: deft_code



I think you may be able to do this with the metaclass. Since the metaclass can be like a class for the class (if that makes sense). I know you can assign a __call__() method to the metaclass to override calling the class, MyClass(). I wonder if using the property decorator on the metaclass operates similarly.

Wow, it works:

class MetaClass(type):    
    def getfoo(self):
        return self._foo
    foo = property(getfoo)
    def bar(self):
        return self._bar
class MyClass(object):
    __metaclass__ = MetaClass
    _foo = 'abc'
    _bar = 'def'

Note: This is in Python 2.7. Python 3+ uses a different technique to declare a metaclass. Use: class MyClass(metaclass=MetaClass):, remove __metaclass__, and the rest is the same.

Answered By: dappawit

If you only need lazy loading, then you could just have a class initialisation method.

class Example(object):
   def initclass(cls):
       global EXAMPLE_SET 
       if EXAMPLE_SET: return
       cls.the_I = 'ok'
       EXAMPLE_SET = True

   def __init__( self ):
      self.an_i = 20

    print Example.the_I
except AttributeError:
    print 'ok class not "loaded"'
foo = Example()
print foo.the_I
print Example.the_I

But the metaclass approach seems cleaner, and with more predictable behavior.

Perhaps what you’re looking for is the Singleton design pattern. There’s a nice SO QA about implementing shared state in Python.

Answered By: Apalala

Here’s how I would do this:

class ClassPropertyDescriptor(object):

    def __init__(self, fget, fset=None):
        self.fget = fget
        self.fset = fset

    def __get__(self, obj, klass=None):
        if klass is None:
            klass = type(obj)
        return self.fget.__get__(obj, klass)()

    def __set__(self, obj, value):
        if not self.fset:
            raise AttributeError("can't set attribute")
        type_ = type(obj)
        return self.fset.__get__(obj, type_)(value)

    def setter(self, func):
        if not isinstance(func, (classmethod, staticmethod)):
            func = classmethod(func)
        self.fset = func
        return self

def classproperty(func):
    if not isinstance(func, (classmethod, staticmethod)):
        func = classmethod(func)

    return ClassPropertyDescriptor(func)

class Bar(object):

    _bar = 1

    def bar(cls):
        return cls._bar

    def bar(cls, value):
        cls._bar = value

# test instance instantiation
foo = Bar()
assert == 1

baz = Bar()
assert == 1

# test static variable = 5
assert == 5

# test setting variable on the class = 50
assert == 50
assert == 50

The setter didn’t work at the time we call, because we are calling, which is not

Adding a metaclass definition solves this:

class ClassPropertyMetaClass(type):
    def __setattr__(self, key, value):
        if key in self.__dict__:
            obj = self.__dict__.get(key)
        if obj and type(obj) is ClassPropertyDescriptor:
            return obj.__set__(self, value)

        return super(ClassPropertyMetaClass, self).__setattr__(key, value)

# and update class define:
#     class Bar(object):
#        __metaclass__ = ClassPropertyMetaClass
#        _bar = 1

# and update ClassPropertyDescriptor.__set__
#    def __set__(self, obj, value):
#       if not self.fset:
#           raise AttributeError("can't set attribute")
#       if inspect.isclass(obj):
#           type_ = obj
#           obj = None
#       else:
#           type_ = type(obj)
#       return self.fset.__get__(obj, type_)(value)

Now all will be fine.

Answered By: Mahmoud Abdelkader

If you define classproperty as follows, then your example works exactly as you requested.

class classproperty(object):
    def __init__(self, f):
        self.f = f
    def __get__(self, obj, owner):
        return self.f(owner)

The caveat is that you can’t use this for writable properties. While e.I = 20 will raise an AttributeError, Example.I = 20 will overwrite the property object itself.

Answered By: jchl

As far as I can tell, there is no way to write a setter for a class property without creating a new metaclass.

I have found that the following method works. Define a metaclass with all of the class properties and setters you want. IE, I wanted a class with a title property with a setter. Here’s what I wrote:

class TitleMeta(type):
    def title(self):
        return getattr(self, '_title', 'Default Title')

    def title(self, title):
        self._title = title
        # Do whatever else you want when the title is set...

Now make the actual class you want as normal, except have it use the metaclass you created above.

# Python 2 style:
class ClassWithTitle(object):
    __metaclass__ = TitleMeta
    # The rest of your class definition...

# Python 3 style:
class ClassWithTitle(object, metaclass = TitleMeta):
    # Your class definition...

It’s a bit weird to define this metaclass as we did above if we’ll only ever use it on the single class. In that case, if you’re using the Python 2 style, you can actually define the metaclass inside the class body. That way it’s not defined in the module scope.

Answered By: ArtOfWarfare

[answer written based on python 3.4; the metaclass syntax differs in 2 but I think the technique will still work]

You can do this with a metaclass…mostly. Dappawit’s almost works, but I think it has a flaw:

class MetaFoo(type):
    def thingy(cls):
        return cls._thingy

class Foo(object, metaclass=MetaFoo):
    _thingy = 23

This gets you a classproperty on Foo, but there’s a problem…

print("Foo.thingy is {}".format(Foo.thingy))
# Foo.thingy is 23
# Yay, the classmethod-property is working as intended!
foo = Foo()
if hasattr(foo, "thingy"):
    print("Foo().thingy is {}".format(foo.thingy))
    print("Foo instance has no attribute 'thingy'")
# Foo instance has no attribute 'thingy'
# Wha....?

What the hell is going on here? Why can’t I reach the class property from an instance?

I was beating my head on this for quite a while before finding what I believe is the answer. Python @properties are a subset of descriptors, and, from the descriptor documentation (emphasis mine):

The default behavior for attribute access is to get, set, or delete the
attribute from an object’s dictionary. For instance, a.x has a lookup chain
starting with a.__dict__['x'], then type(a).__dict__['x'], and continuing
through the base classes of type(a) excluding metaclasses.

So the method resolution order doesn’t include our class properties (or anything else defined in the metaclass). It is possible to make a subclass of the built-in property decorator that behaves differently, but (citation needed) I’ve gotten the impression googling that the developers had a good reason (which I do not understand) for doing it that way.

That doesn’t mean we’re out of luck; we can access the properties on the class itself just fine…and we can get the class from type(self) within the instance, which we can use to make @property dispatchers:

class Foo(object, metaclass=MetaFoo):
    _thingy = 23

    def thingy(self):
        return type(self).thingy

Now Foo().thingy works as intended for both the class and the instances! It will also continue to do the right thing if a derived class replaces its underlying _thingy (which is the use case that got me on this hunt originally).

This isn’t 100% satisfying to me — having to do setup in both the metaclass and object class feels like it violates the DRY principle. But the latter is just a one-line dispatcher; I’m mostly okay with it existing, and you could probably compact it down to a lambda or something if you really wanted.

Answered By: Andrew

I happened to come up with a solution very similar to @Andrew, only DRY

class MetaFoo(type):

    def __new__(mc1, name, bases, nmspc):
        nmspc.update({'thingy': MetaFoo.thingy})
        return super(MetaFoo, mc1).__new__(mc1, name, bases, nmspc)

    def thingy(cls):
        if not inspect.isclass(cls):
            cls = type(cls)
        return cls._thingy

    def thingy(cls, value):
        if not inspect.isclass(cls):
            cls = type(cls)
        cls._thingy = value

class Foo(metaclass=MetaFoo):
    _thingy = 23

class Bar(Foo)
    _thingy = 12

This has the best of all answers:

The “metaproperty” is added to the class, so that it will still be a property of the instance

  1. Don’t need to redefine thingy in any of the classes
  2. The property works as a “class property” in for both instance and class
  3. You have the flexibility to customize how _thingy is inherited

In my case, I actually customized _thingy to be different for every child, without defining it in each class (and without a default value) by:

   def __new__(mc1, name, bases, nmspc):
       nmspc.update({'thingy':, '_thingy': None})
       return super(MetaFoo, mc1).__new__(mc1, name, bases, nmspc)
Answered By: Andy
def _create_type(meta, name, attrs):
    type_name = f'{name}Type'
    type_attrs = {}
    for k, v in attrs.items():
        if type(v) is _ClassPropertyDescriptor:
            type_attrs[k] = v
    return type(type_name, (meta,), type_attrs)

class ClassPropertyType(type):
    def __new__(meta, name, bases, attrs):
        Type = _create_type(meta, name, attrs)
        cls = super().__new__(meta, name, bases, attrs)
        cls.__class__ = Type
        return cls

class _ClassPropertyDescriptor(object):
    def __init__(self, fget, fset=None):
        self.fget = fget
        self.fset = fset

    def __get__(self, obj, owner):
        if self in obj.__dict__.values():
            return self.fget(obj)
        return self.fget(owner)

    def __set__(self, obj, value):
        if not self.fset:
            raise AttributeError("can't set attribute")
        return self.fset(obj, value)

    def setter(self, func):
        self.fset = func
        return self

def classproperty(func):
    return _ClassPropertyDescriptor(func)

class Bar(metaclass=ClassPropertyType):
    __bar = 1

    def bar(cls):
        return cls.__bar

    def bar(cls, value):
        cls.__bar = value

bar = Bar()
nbar = Bar()

Answered By: thinker3

If you use Django, it has a built in @classproperty decorator.

from django.utils.decorators import classproperty

For Django 4, use:

from django.utils.functional import classproperty
Answered By: Barney Szabolcs
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.