Question What is the proper way to wrap data in Python? Is the __init__
and self.
boilerplate needed?
As a class Sometimes I wish to create classes that represent Data, but don't have complex computational mechanics inside. For example:
class DataClass:
def __init__(self, a, b, c):
self.a = b
self.b = b
self.c = c
def sum(self):
return self.a + self.b + self.c
dc = DataClass(1,2,3)
print(dc.sum())
I dislike this code, the mathematics of a + b + c
is hard to read due to the self.
and the constructor is boilerplate. Moreover, I would create this class whenever I noticed that a tuple or dict around a, b and c is getting messy. In particular, tuples are ugly when the returned values change in amount or order, as happend with cv2.findContours
between opencv versions 3 and 4 (returning 3, respectively 2 values). Lastly, I often copy-paste boilerplate stuff, easily allowing for painful mistakes, as is the case here with self.a = b
.
As a function Therefore I often do something like this:
def DataFunc(a, b, c):
class Result:
def sum(self):
return a + b + c
return Result()
df = DataFunc(1,2,3)
print(df.sum())
It works and it is significantly more transparent (imho), but also a bit weird w.r.t. types, i.e.
rf2 = ResultFunc(1,2,3)
print(type(rf) == type(rf2)) # -> False
Moreover, the data members are not accessible (rf.a
, rf.b
, etc).
As a class with an annotator Finally, one can create an annotator to add the constructor:
def add_constructor(*vars):
def f(class_):
def init(self, *args, **kwargs):
unasigned = set(vars[len(args):]) - set(kwargs.keys())
if len(unasigned) > 0:
raise ValueError(f"Missing argument(s): {' '.join(unasigned)}")
for name, value in zip(vars, args):
setattr(self, name, value)
for name, value in kwargs.items():
setattr(self, name, value)
setattr(class_, '__init__', init)
return class_
return f
@add_constructor('a', 'b', 'c')
class DataAnnot:
def sum(self):
return self.a + self.b + self.c
da = DataAnnot(1,2,c=3)
print(da.sum())