How to create a class property?

In python, I can add a method to a class with the @classmethod decorator. Is there a similar decorator for adding 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 @property def i( self ): return self.an_i def inc_i( self ): self.an_i += 1 # is this even possible? @classproperty def I( cls ): return cls.the_I @classmethod def inc_I( cls ): cls.the_I += 1 e = Example() assert ei == 20 e.inc_i() assert ei == 21 assert Example.I == 10 Example.inc_I() assert Example.I == 11 

Is the syntax I used above or will it need something more?

I want the class properties to be such that I can lazy load class attributes that seem reasonable enough.

+101
python properties class-method
Mar 04 2018-11-11T00:
source share
9 answers

Here's how I do it:

 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 @classproperty def bar(cls): return cls._bar @bar.setter def bar(cls, value): cls._bar = value # test instance instantiation foo = Bar() assert foo.bar == 1 baz = Bar() assert baz.bar == 1 # test static variable baz.bar = 5 assert foo.bar == 5 # test setting variable on the class Bar.bar = 50 assert baz.bar == 50 assert foo.bar == 50 

The setter did not work when we call Bar.bar because we call TypeOfBar.bar.__set__ , which is not Bar.bar.__set__ .

Adding a metaclass definition solves the following:

 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) 

Everything will be fine now.

+73
Mar 04 '11 at 8:12
source share

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 cannot use this for writable properties. As long as eI = 20 raises an AttributeError , Example.I = 20 overwrite the property object itself.

+33
Mar 04 '11 at 10:13
source share

I think you can do this with a metaclass. Because a metaclass can be like a class for a class (if that makes sense). I know that you can assign the __call__() method to the metaclass to override the class call, MyClass() . I wonder if a similar property decor works on a metaclass. (I have not tried this before, but now I'm curious ...)

[Update:]

Wow, it works:

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

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.

+24
Mar 04 2018-11-11T00:
source share

[answer written based on python 3.4; the metaclass syntax is different in 2, but I think the technique will work anyway]

You can do this with a metaclass ... basically. Dappavit almost works, but I think it has a flaw:

 class MetaFoo(type): @property def thingy(cls): return cls._thingy class Foo(object, metaclass=MetaFoo): _thingy = 23 

This gives you a cool property in Foo, but there is 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)) else: 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 get the class property from the instance?

I banged my head about this for quite some time before finding what I thought was the answer. Python @properties is a subset of descriptors, and from the descriptive documentation (my selection):

The default behavior for accessing attributes is to get, set, or remove an attribute from an object dictionary. For example, ax has a search chain starting with a.__dict__['x'] , then type(a).__dict__['x'] and continuing through the base classes of type(a) excluding metaclasses .

Thus, the method resolution order does not include our class properties (or anything else defined in the metaclass). You can subclass the built-in property decorator, which behaves differently, but (quote). I have the impression that the developers have a good reason (which I don’t understand) for this.

This does not mean that we are out of luck; we can access the properties of the class itself simply ... and we can get the class from type(self) inside the instance, which we can use to create @property dispatchers:

 class Foo(object, metaclass=MetaFoo): _thingy = 23 @property def thingy(self): return type(self).thingy 

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

This is not 100% satisfactory to me - to make adjustments both in the metaclass and in the object class, it seems that this violates the DRY principle. But the latter is just a single line dispatcher; I basically agree with this, and you could probably squeeze it to lambda or something else if you really wanted to.

+17
Aug 7 '16 at 3:36 on
source share

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

I found that the following method works. Define a metaclass with all the properties and class settings you want. IE, I need a class with a title property with an installer. Here is what I wrote:

 class TitleMeta(type): @property def title(self): return getattr(self, '_title', 'Default Title') @title.setter 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 usual, except that it uses 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 little strange to define this metaclass, as we did above, if we use it in only one class. In this case, if you use the Python 2 style, you can define a metaclass inside the class body. Thus, it is not defined in the module area.

+4
Feb 28 '16 at 20:08
source share

If you only need lazy loading, then you can only have a class initialization method.

 EXAMPLE_SET = False class Example(object): @classmethod def initclass(cls): global EXAMPLE_SET if EXAMPLE_SET: return cls.the_I = 'ok' EXAMPLE_SET = True def __init__( self ): Example.initclass() self.an_i = 20 try: 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 more predictable.

You may be looking for a Singleton design template. There's a good SO Q about general state implementation in Python.

+1
Mar 04 '11 at 5:25
source share

I accidentally found 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) @property def thingy(cls): if not inspect.isclass(cls): cls = type(cls) return cls._thingy @thingy.setter 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 is the best of all answers:

The "meta property" is added to the class so that it will still be an instance property.

  1. No need to redefine a thing in any of the classes
  2. The property works as a "class property" for both the instance and the class.
  3. You have the option to set up _thingy inheritance

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

  def __new__(mc1, name, bases, nmspc): nmspc.update({'thingy': MetaFoo.services, '_thingy': None}) return super(MetaFoo, mc1).__new__(mc1, name, bases, nmspc) 
0
Jan 04 '19 at 19:21
source share
 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 @classproperty def bar(cls): return cls.__bar @bar.setter def bar(cls, value): cls.__bar = value 
0
Jul 05 '19 at 6:31
source share

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

0
Jul 16 '19 at 10:30
source share



All Articles