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.
-
1\$\begingroup\$ So, to be clear, you want class properties? Properties that are bound to the class not the instance. \$\endgroup\$Peilonrayz– Peilonrayz ♦2019年06月05日 20:38:25 +00:00Commented 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\$Noctis Skytower– Noctis Skytower2019年06月05日 20:42:23 +00:00Commented Jun 5, 2019 at 20:42
1 Answer 1
- You've masked a bug, in
__setattr__
a property raises anAttributeError
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
Explore related questions
See similar questions with these tags.