Reputation: 1496
I am working within a larger python module where "projects" containing assemblies of dict-like class instances can be saved, but in order to save a class's attributes, they must be stored in a specific dictionary in the class. I can have my attributes accessible as simple attributes (self.x
instead of self.properties['x']
) by writing them as properties. But I'm up to 9 of these things, and giving each of them a getter, setter, and deleter seems like such a waste of space, especially since they're all so trivial. Is there a better way?
Too long:
class MyClass(dict):
@property
def variable1(self):
return self.properties.get('variable1', None)
@variable1.setter
def variable1(self, value):
self.properties['variable1'] = value
@variable1.deleter
def variable1(self):
self.properties['variable1'] = None
# ... same for variables 2 - 8, so boring
@property
def variable9(self):
return self.properties.get('variable9', None)
@variable9.setter
def variable9(self, value):
self.properties['variable9'] = value
@variable9.deleter
def variable9(self):
self.properties['variable9'] = None
def __init__(self, variable1='default1', variable9='default9'):
self.properties = dict(variable1=variable1, variable9=variable9)
dict.__init__(self)
How can I loop over property declarations so this will be shorter?
BONUS: If I do a loop, is there a way to include minor customizations to some of the variables, should the need arise (maybe declare the things to list as keys in a dictionary, where values are instructions for minor customizations)?
Testing / usage of above example:
from var_test import MyClass # If you saved it in var_test.py
a = MyClass(variable1=1234)
b = MyClass(variable1='wheee')
assert a.variable1 is not b.variable1
assert a.properties['variable1'] == a.variable1
assert a.variable1 == 1234
Upvotes: 2
Views: 502
Reputation: 1496
The best I could do was to loop over an exec
call to assign the results of a property-generating function. It's shorter than the thing in the question statement, but I feel like there must be a better way:
import copy
class MyClass2(dict):
properties = {} # Temporary declaration of properties during startup
save_attrs = ['variable{}'.format(i) for i in range(1, 10)]
@staticmethod
def _make_property(props, name, init_value=None, doc=None):
"""Creates a property which can be assigned to a variable, with getter, setter, and deleter methods"""
props[name] = init_value
def getter1(self):
return self.properties.get(name, None)
def setter1(self, value):
self.properties[name] = value
def deleter1(self):
self.properties[name] = None
getter1.__name__ = name
setter1.__name__ = name
deleter1.__name__ = name
return property(getter1, setter1, deleter1, doc)
for attr in save_attrs:
exec("{attr:} = _make_property.__func__(properties, '{attr:}')".format(attr=attr))
def __init__(self, **kw):
# Instance-specific reassignment of properties, so instances won't share values
self.properties = copy.deepcopy(self.properties)
for attr in self.save_attrs:
kw_attr = kw.pop(attr, None)
if kw_attr is not None:
self.properties[attr] = kw_attr
dict.__init__(self)
This passes the test:
from var_test import MyClass2 as mc # If you saved it in var_test.py
a = mc(variable1=1234)
b = mc(variable1='wheee')
assert a.variable1 is not b.variable1
assert a.properties['variable1'] == a.variable1
assert a.variable1 == 1234
Upvotes: 0
Reputation: 1121484
Yes, you can create property objects dynamically, and add them to a class:
def gen_property(name):
def getter(self):
return self.properties.get(name, None)
def setter(self, value):
self.properties[name] = value
def deleter(self):
self.properties[name] = None
return property(getter, setter, deleter)
class MyClass(dict):
def __init__(self, variable1='default1', variable9='default9'):
self.properties = dict(variable1=variable1, variable9=variable9)
dict.__init__(self)
for number in range(1, 10):
name = 'variable{}'.format(number)
setattr(MyClass, name, gen_property(name))
However, it is probably much cleaner to use the attribute access customisation hooks and proxy the attribute names to the self.properties
dictionary there:
class MyClass(dict):
def __init__(self, variable1='default1', variable9='default9'):
self.properties = dict(variable1=variable1, variable9=variable9)
dict.__init__(self)
def __getattr__(self, name):
if name.startswith('variable') and name[8:].isdigit():
return self.properties.get(name, None)
raise AttributeError(name)
def __setattr__(self, name, value):
if name.startswith('variable') and name[8:].isdigit():
self.properties[name] = value
return
super().__setattr__(name, value)
def __delattr__(self, name):
if name.startswith('variable') and name[8:].isdigit():
self.properties[name] = None
return
super().__delattr__(name)
Upvotes: 2