@staticmethod with @property

I want

Stats.singleton.twitter_count += 1

and I thought I could do

class Stats:
singleton_object = None


@property
@staticmethod
def singleton():
if Stats.singleton_object:
return Stats.singleton_object
Stats.singleton_object = Stats()
return Stats.singleton()

But it throws an exception:

>>> Stats.singleton.a = "b"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'property' object has only read-only attributes (assign to .a)
51668 次浏览

Static methods don't make sense in Python. That's because they do nothing that class methods can't, and are class methods much easier to extend in the future (when multiple class methods use each other etc).

What you need is simply a class method property.

I have a class method property from my code here. It is only read-only, that was all I needed (so the rest is an exercise to the reader):

class ClassProperty (property):
"""Subclass property to make classmethod properties possible"""
def __get__(self, cls, owner):
return self.fget.__get__(None, owner)()


# how I would use it
class Stats:
singleton_object = None
@ClassProperty
@classmethod
def singleton(cls):
if cls.singleton_object is None:
cls.singleton_object = cls()
return cls.singleton_object

Singletons are pointless in python.

class A:
class_var = object()


# two objects
a, b = A(), A()


# same var everywhere
assert a.class_var is b.class_var is A.class_var

Python's ints are differnt from simple objects, so it's not always that simple . But for your purposes, this seems to be enough:

class Stats:
twitter_count = 0


Stats.twitter_count +=1
Stats.twitter_count +=1
assert Stats.twitter_count == 2

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.

Following up with what KyleAlanHale wrote:

His example works great, until you try and do:

Stats.singleton = 5

This won't give you an error, it will overwrite the function, so that when you type next

single = Stats.singleton
print single

You'll get

5

I think you're best off using Kyle's answer without the @classproperties decoration.

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

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

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__()
class StaticProperty(object):
def __init__(self, function):
self.function = function
def __get__(self, *args, **kwargs):
print("static property")
return self.function()




class test():
def __init__(self):
        

@StaticProperty
def greetings():
return ("Hello Stack overflow")


print(test.greetings)