@staticmethod with @property

In this non-data descriptor solution linters doesn't complaint, because it is a staticmethod. In singleton definition just change last line to return Stats.singleton (without a call).

class staticproperty(staticmethod):
    def __get__(self, *_):         
        return self.__func__()

I guess giving a Python code snippet to show how do property and staticmethod work would be helpful.

Both of them are descriptors which implements get or set

property is a data descriptor (Refers to Descriptor HowTo Guide)

class Property(object):
    "Emulate PyProperty_Type() in Objects/descrobject.c"

    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

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

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)

    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.__doc__)

Take a simple example

class Foo:
    def __init__(self):
        self._name = None

    @property  # I
    def name(self):
        return self._name

    @name.setter  # II
    def name(self, value):
        self._name = value
  1. @property name is equivalent to name = property(name), the fget parameter is the name() I method
  2. @name.setter is equivalent to name.setter(name), the first name is the property created in step 1, the second one is name II, the fset. Look like a trick!

staticmethod is a non-data descriptor (Refers to Static Methods and Class Methods)

class StaticMethod(object):
    "Emulate PyStaticMethod_Type() in Objects/funcobject.c"

    def __init__(self, f):
        self.f = f

    def __get__(self, obj, objtype=None):
        return self.f

User kaizer.se was onto something as far as the original question goes. I took it a step further in terms of simplicity, so that it now requires only a single decorator:

class classproperty(property):
    def __get__(self, cls, owner):
        return classmethod(self.fget).__get__(None, owner)()

Usage:

class Stats:
    _current_instance = None

    @classproperty
    def singleton(cls):
        if cls._current_instance is None:
            cls._current_instance = Stats()
        return cls._current_instance

As noted, this way of creating a singleton is not a good design pattern; if that must be done, a metaclass factory is a much better way to do it. I was just excited about the prospect of a class property though, so, there it is.


The easiest method I've found is using an instance property to wrap a class member:

class MyClass:
  _configured = False

  @property
  def configured(self) -> bool:
    print("configured.getter")
    return self.__class__._configured
  
  @configured.setter
  def configured(self, value: bool) -> None:
    print("configured.setter")
    self.__class__._configured = value

  @classmethod
  def is_class_configured(cls) -> bool:
    print("is_class_configured")
    return cls._configured

m1 = MyClass()
print(f"m1.configured: {m1.configured}\n")
print(f"MyClass._configured: {MyClass._configured}\n")
print(f"m1.is_class_configured(): {m1.is_class_configured()}\n")
m1.configured = True
print(f"setting m1.configured = True")
print(f"------------------------------")
print(f"m1.configured: {m1.configured}\n")
print(f"MyClass._configured: {MyClass._configured}\n")
print(f"m1.is_class_configured(): {m1.is_class_configured()}\n")

configured.getter
m1.configured: False

MyClass._configured: False

is_class_configured
m1.is_class_configured(): False

configured.setter
setting m1.configured = True
------------------------------
configured.getter
m1.configured: True

MyClass._configured: True

is_class_configured
m1.is_class_configured(): True