4
\$\begingroup\$

Classes in Python do not have native support for static properties. A meta-class can rather easily add this support as shown below. Are there any problems programmers might experience if they use this implementation?

#! /usr/bin/env python3 class StaticProperty(type): def __getattribute__(cls, name): attribute = super().__getattribute__(name) try: return attribute.__get__(cls, type(cls)) except AttributeError: return attribute def __setattr__(cls, name, value): try: super().__getattribute__(name).__set__(cls, value) except AttributeError: super().__setattr__(name, value) class Test(metaclass=StaticProperty): __static_variable = None @property def static_variable(cls): assert isinstance(cls, StaticProperty) return cls.__static_variable @static_variable.setter def static_variable(cls, value): assert isinstance(cls, StaticProperty) cls.__static_variable = value def __init__(self): self.__value = None @property def value(self): assert isinstance(self, Test) return self.__value @value.setter def value(self, value): assert isinstance(self, Test) self.__value = value def main(): print(repr(Test.static_variable)) Test.static_variable = '1st Hello, world!' print(repr(Test.static_variable)) instance = Test() print(repr(instance.value)) instance.value = '2nd Hello, world!' print(repr(instance.value)) assert Test._Test__static_variable == '1st Hello, world!' assert instance._Test__value == '2nd Hello, world!' if __name__ == '__main__': main() 

My first inclination is that the property class should be sub-classed as static_property and should be checked for in StaticProperty.__new__ to ensure it is being used properly.

\$\endgroup\$
2
  • 1
    \$\begingroup\$So, to be clear, you want class properties? Properties that are bound to the class not the instance.\$\endgroup\$
    – Peilonrayz
    CommentedJun 5, 2019 at 20:38
  • \$\begingroup\$@Peilonrayz Yes, but the example code seems to illustrate that both kinds of properties are possible. Some might find it confusing, though, if there is no distinction between a class and instance property.\$\endgroup\$CommentedJun 5, 2019 at 20:42

1 Answer 1

4
\$\begingroup\$
  • You've masked a bug, in __setattr__ a property raises an AttributeError if the setter hasn't been defined. This causes you to overwrite the property.
  • (As you've said) There's no distinction between a class property and an instance property. You can change it so there is, but it doesn't allow the property to only be defined on the class, not the instance.
  • You can just define the static properties on the metaclass. This removes a lot of the headaches.
  • If you really want to define everything onto the class not the metaclass then you can make the metaclass hoist the wanted functions into a new metaclass. This means everything works as if you only defined two metaclasses with the properties correctly defined.

No fancy changes:

class MyMeta(type): @property def class_(self): return self._class @class_.setter def class_(self, value): self._class = value @property def class_instance(self): return self._class_instance @class_instance.setter def class_instance(self, value): self._class_instance = value class Test(metaclass=MyMeta): class_instance = MyMeta.class_instance @property def instance(self): return self._instance @instance.setter def instance(self, value): self._instance = value 

Hoisting:

class classproperty(property): pass class classinstanceproperty(property): pass class StaticProperty(type): def __new__(self, name, bases, props): class_properties = {} to_remove = {} for key, value in props.items(): if isinstance(value, (classproperty, classinstanceproperty)): class_properties[key] = value if isinstance(value, classproperty): to_remove[key] = value for key in to_remove: props.pop(key) HoistMeta = type('HoistMeta', (type,), class_properties) return HoistMeta(name, bases, props) class Test(metaclass=StaticProperty): @classproperty def class_(self): return self._class @class_.setter def class_(self, value): self._class = value @classinstanceproperty def class_instance(self): return self._class_instance @class_instance.setter def class_instance(self, value): self._class_instance = value @property def instance(self): return self._instance @instance.setter def instance(self, value): self._instance = value 

These both pass the following tests: (I could only get your approach to work with instance and class instance)

 test = Test() test._instance = None test.instance = True assert test._instance is True assert test.instance is True test.instance = False assert test._instance is False assert test.instance is False Test._instance = None Test.instance = True Test.instance = False assert Test._instance is None test._instance = True if Test._instance is not True: print("instance can't be used after modifying class") Test._class_instance = None Test.class_instance = True assert Test._class_instance is True Test.class_instance = False assert Test._class_instance is False test = Test() test._class_instance = None test.class_instance = True assert test._class_instance is True assert Test._class_instance is False test.class_instance = False assert test._class_instance is False Test._class = None Test.class_ = True assert Test._class is True Test.class_ = False assert Test._class is False test = Test() test._class = None test.class_ = True assert test._class is None assert Test._class is False test.class_ = False assert test._class is None 
\$\endgroup\$

    Start asking to get answers

    Find the answer to your question by asking.

    Ask question

    Explore related questions

    See similar questions with these tags.