I have a question about Python style...
Let's suppose I want a class with a class attribute that is quite big (a lut table, for example). Or that can't be "pre-computed" in source code.
I want the class attribute to be set either at the time of the class declaration, or at the time of the creation of the first instance, or at the first access to the lut, I don't care.
I see plently of ways to do it, but I really like none of them.
Edit: just to be clear, I use [ 1, 2, 3 ] in the following example to avoid making the examples complex, consider it the result of a (classmethod or staticmethod) function call... Or course, if it was really [ 1, 2, 3 ], I could define it directly...
Solution 1: do it at the creation of the first instance
Code:
class cfoo :
lut = None
@classmethod
def InitLut(cls) :
cls.lut = [ 1, 2, 3 ]
def __init__(self) :
if self.lut is None :
type(self).InitLut()
I'm not that fond of putting an initializer of a class attribute in the __init__ function, and I don't find type(self) to call a classmethod really nice.
Solution 2: use a function called at class definition
Code:
def InitLut() :
return [ 1, 2, 3 ]
class cfoo :
lut = InitLut()
def __init__(self) :
pass
I kinda like this method, but I not fond of InitLut being declared *outside* of the class. I would prefer a @staticmethod inside cfoo, but I don't see how to call it?
Solution 2b: use a class method
Code:
class cfoo :
@classmethod
def InitLut(cls) :
cls.lut = [ 1, 2, 3 ]
lut = None
def __init__(self) :
pass
cfoo.InitLut()
The initialization function is inside, but that require a call from outside.
Solution 2c: well, why not using a class decorator for this...
Code:
def InitLut(cls) :
setattr(cls, "lut", [ 1, 2, 3 ])
return cls
@InitLut
class cfoo() :
def __init__(self) :
pass
I doubt it's better, it even hides the class attribute (you could keep it in the definition, but well...)
Solution 2d: well, let's use a decorator that force the initialization
Code:
def CallInit(cls) :
cls.Init()
return cls
@CallInit
class cfoo() :
@classmethod
def Init(cls) :
cls.lut = [ 1, 2, 3 ]
def __init__(self) :
pass
Not convincing either, I'd say.
Solution 3: use a property for the class attribute, so you can create a getter that initialize the lut at first use
Code:
class cfoo :
def get_lut(self) :
if type(self)._lut is None :
type(self)._lut = [ 1, 2, 3 ]
return type(self)._lut
lut = property(get_lut)
_lut = None
def __init__(self) :
pass
I don't like it either, you need a _lut attribute (or do really dirty tricks so that you don't fall in an infinite loop with the getter), and there's some overhead because you'll check initialization each time you try to access the attribute lut.
Solution 3bis: alternative using decorator:
Code:
class cfoo :
@property
def lut(self) :
if type(self)._lut is None :
type(self)._lut = [ 1, 2, 3 ]
return type(self)._lut
_lut = None
def __init__(self) :
pass
Same issues, and doesn't seem really readable to me...
Solution 4: be nasty, use @property so that you can call a function the first time you try to access to the class attribute, and use this function to redefine the class attribute to a normal attribute.
Code:
class cfoo :
@property
def lut(self) :
setattr(type(self), "lut", [1, 2, 3 ])
return type(self).lut
def __init__(self) :
pass
I somehow like it's self-contained, doesn't have overhead, but it's still a trick where the class kinda "evolve" at first access to lut, and it may not be the most obvious thing to understand.
I'll stop there, I have plently of other suggestions (using metaclasses, using inheritance, using __init__ as a class initializer and redefining it before exiting to initialize instances for next calls...) but there's not a single one that I find 100% appealing. Bundling the class in a module may be the most interesting solution, but I think it would be a waste to create (and import) a module just because you want to initialize a class attribute...
Any suggestion? I feel like I missed something obvious...
Googling it is difficult, especially when a majority of people doesn't even understand the different between a class attribute and the attribute of an instance :/