You could write a general purpose decorator for this. Assuming you add a function called __lazyinit__ to your class, this decorator will ensure it is called before each of the decorated method that require the full initialization of the object.
def lazyinit(f):
def doInit(self,*args,**kwargs):
self.__lazyinit__()
return f(self,*args,**kwargs)
return doInit
class A:
def __init__(self,p1,p2):
self._p1 = p1
self._p2 = p2
self._initComplete = False
def __lazyinit__(self):
if self._initComplete: return
self._p3 = self._p1 + self._p2
self._initComplete = True
@lazyinit # works for functions
def funcX(self):
print(self._p3)
@property
@lazyinit # also works for properties
def p3(self):
return self._p3
Examples:
a = A(2,3)
a._p3 # Attribute Error
a.funcX() # prints 5
a2 = A(4,5)
a2._p3 # Attribute Error
print(a2.p3) # prints 9
Note that this isn't much different from adding a call to self.__lazyinit__() at the beginning of every function but it does place that concern/aspect outside of the function's code
If you're only using this for properties you could place the @property logic inside of your decorator to form a @lazyproperty decorator that combines the two
Alternatively ...
If you don't want to make any changes to the original class, you could write a function that dynamically creates a wrapper class for your object instance. The function would return the un-initialized object and let the __getattr__ initialize the object when a reference is made to an attribute that is not already present (i.e. because the instance is not initialized).
def lazy(objClass, *args, **kwargs):
class LazyClass(objClass):
def __getattr__(self,name):
self.__init__(*args,**kwargs)
return getattr(self,name)
return objClass.__new__(LazyClass)
class A:
def __init__(self,p1,p2):
print('initing A')
self._p1 = p1
self._p2 = p2
self._p3 = p1 + p2
a = lazy(A,2,3)
print("instantiated A") # 'initing A' not yet printed
print(a._p3)
# initing A
# 5
print(a._p3)
# 5
functools.partial(for any initial arguments) plus__getattr__(to intercept attribute access). See e.g. stackoverflow.com/q/26091833/3001761 for most of it.__getattr__seems like a strong option, I just prefer to avoid__getattr__for as long as possible since it brakes things. Additional source of complexity here is gettingisinstance(wrapper(A), A)to work.