You can define actual Python properties that reflect the state of fields
:
class BulkData:
def __init__(self, fields, floats=None):
if floats is None:
floats = []
self.fields = fields
self.floats = floats
def __str__(self):
return ''.join([str(x) for x in self.fields])
class Node(BulkData):
def __init__(self, x, y, z):
super().__init__([x, y, z])
@property
def x(self):
return self.fields[0]
@x.setter(self, v):
self.fields[0] = v
@property
def y(self):
return self.fields[1]
@y.setter(self, v):
self.fields[1] = v
@property
def z(self):
return self.fields[2]
@z.setter(self, v):
self.fields[2] = v
Instead of instance attributes, you define property
instances that "wrap" a particular slot of the underlying fields
attribute.
If you are thinking, that's a lot of code... you're right. Let's fix that.
class FieldReference(property):
def __init__(self, pos):
def getter(self):
return self.fields[pos]
def setter(self, v):
self.fields[pos] = v
super().__init__(getter, setter)
class Node(BulkData):
def __init__(self, x, y, z):
super().__init__([x, y, z])
x = FieldReference(0)
y = FieldReference(1)
z = FieldReference(2)
Here, we subclass property
and hard-code the getter and setter functions to operate on the position specified by the argument to the property. Note that self
in getter
and setter
are distinct from self
in __init__
, but getter
and setter
are both closures with access to __init__
's argument pos
.
You might also want to read the Descriptor how-to, specifically the section on properties to understand why FieldReference
is defined the way it is.