@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
@property name
is equivalent toname = property(name)
, the fget parameter is the name() I method@name.setter
is equivalent toname.setter(name)
, the firstname
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