so_confused
so_confused

Reputation: 95

Static class method properties in Python

As the title says, I'm trying to give set/get functionality to my static classes in python. Basically, for a given static class with static attribute _x, I want to define a static property x that gives custom get and set function wrappers for _x.

Strangely it seems like the official Python docs say this functionality should exist. The docs say that as of version 3.9, you can use the @classmethod decorator to wrap property methods. However, it seems that this only works for getters, and not setters. For example, when I try to define a static property using this method, I get the following error:

class Foo:
  _x = 0

  @classmethod
  @property
  def x(self):
    return self._x

  @classmethod
  @x.setter
  def x(self, n):
    self._x = n

AttributeError: 'classmethod' object has no attribute 'setter'

If I flip the order, like:

class Foo:
  _x = 0

  @property
  @classmethod
  def x(self):
    return self._x

  @x.setter
  @classmethod
  def x(self, n):
    self._x = n

Then Foo.x returns a property object instead of calling the property's fget function.

So it seems this approach is not going to work. I then tried to exploit the way that function decorators work, by redefining Foo:

class Foo:
  _x = 0

  def __get_x(self):
    return self._x

  def __set_x(self, n):
    self._x = n

  x = classmethod(property(fget=__get_x, fset=__set_x))

But no luck. Whenever I try to set Foo.x, i.e. with Foo.x = 2, it does not call the property setter but instead directly overwrites x entirely.

One solution I've seen bounced around is using python metaclasses to implement this functionality. While that could fix the issue, it isn't ideal because A. I have many different static classes in my project and B. I use Sphinx to generate documentation for my code and it doesn't always play nice with metaclasses.

I've been looking for a solution to this issue for a while and I'm not sure what the best "pythonic" fix for it is. If metaclasses are the "cleanest" solution, how can I implement them in such a way that I don't have to write a lot of redundant code? Maybe I could write a custom function decorator that enables class property-esque functionality? Any help would be appreciated.

Upvotes: 3

Views: 3441

Answers (1)

kindall
kindall

Reputation: 184191

An instance's properties must be defined on its class. To define properties on a class, the rule still holds: when considering the class as an instance, its properties must be defined on the class's class (its metaclass).

class Meta(type):

    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, n):
        self._x = n

class Foo(metaclass=Meta):
    _x = 0

Despite the drawbacks of using metaclasses, this is the way that works.

Upvotes: 5

Related Questions