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.

200_success
145k22 gold badges190 silver badges478 bronze badges
asked Jun 5, 2019 at 19:56
\$\endgroup\$
2
  • 1
    \$\begingroup\$ So, to be clear, you want class properties? Properties that are bound to the class not the instance. \$\endgroup\$ Commented Jun 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\$ Commented Jun 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
AlexV
7,3532 gold badges24 silver badges47 bronze badges
answered Jun 5, 2019 at 22:58
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.