Search code examples
pythonnumpyobjectserialization

Is there a way to use classmethod to construct an instance with the instance attributes set without invoking the __init__ in python?


Here's my code, hopefully self explanatory.

import os
import numpy as np

class MyApp:
    
    def __init__(self, x, y, project_name):
        '''default constructor'''
        
        # Build Project if Non-Existent
        if not os.path.exists(project_name):
            os.makedirs(project_name)
        
        # Attribute Arrays
        self.arr1 = np.arange(x) # Dummy task based on x
        self.arr2 = np.arange(y) # Dummy task based on y
        
        # Dump Arrays
        np.save(f'{project_name}/arr1.npy', self.arr1)
        np.save(f'{project_name}/arr2.npy', self.arr2)
    
    @classmethod
    def from_project(cls, project_name):
        '''load objects from project'''
        
        # Check if project exists
        if not os.path.exists(project_name):
            raise FileNotFoundError(f'{project_name} missing')
        
        # Now load arr1 and arr2
        arr1 = np.load(f'{project_name}/arr1.npy')
        arr2 = np.load(f'{project_name}/arr2.npy')
        
        # And return an instance with self.arr1 and self.arr2 set
        pass # ????
    
    def task_A(self):
        '''do some task based on arr1'''
        return self.arr1.mean()
    
    def task_B(self):
        '''do some task based on both arr1 and arr2'''
        return np.convolve(self.arr1, self.arr2)

# Init Object
app = MyApp(x=5, y=10, project_name='testing_tmp')
print(type(app)) # <class '__main__.MyApp'>

# Initally Tasks OK
print(app.task_A()) # 2.0
print(app.task_B()) # [ 0  0  1  4 10 20 30 40 50 60 70 70 59 36]

# No Tasks after Object Deletion
del app
try:
    print(app.task_A()) # No execution
    print(app.task_B()) # No execution
except:
    pass

# Now want to instantiate object from project
app = MyApp.from_project(project_name='testing_tmp')
print(type(app)) # <class 'NoneType'>

The output is as follows:

<class '__main__.MyApp'>
2.0
[ 0  0  1  4 10 20 30 40 50 60 70 70 59 36]
<class 'NoneType'>

Is there a way to write a classmethod that would read saved object attributes from file, and return an instance with the proper attributes set, as desired?

I understand I can save the x and y values to disk as well and use something like return cls(x=disk_x, y=disk_y, project_name=project_name) but my real task would not permit that as objects created from x and y are rather expensive to compute, and I created and saved them to disk for later use for this very reason.

Editing for clarity: My goal is to use the from_project method to return an instance of the class with the self.arr1 and self.arr2 attributes correctly set, without using the __init__() method which requires setting x and y. Please see the from_project method for details as to where I got clueless. I know how to serialize and deserialize objects and class attributes, it is not my concern.

I tried using the cls attribute to the from_project classmethod to set cls.arr1 and cls.arr2 both, but it did not work.


Solution

  • Yes, I found the answer. The trick is to use the __new__ method for instantiation and bypass __init__ to then set the attributes and returning the instance. Here's the required method change to from_project.

    @classmethod
    def from_project(cls, project_name):
        '''load objects from project'''
    
        # Check if project exists
        if not os.path.exists(project_name):
            raise FileNotFoundError(f'{project_name} missing')
    
        # Now load arr1 and arr2
        arr1 = np.load(f'{project_name}/arr1.npy')
        arr2 = np.load(f'{project_name}/arr2.npy')
    
        # And return an instance with self.arr1 and self.arr2 set
    
        # First we use the __new__ method for instantiation
        instance = cls.__new__(cls)
    
        # Then we set attributes
        instance.arr1 = arr1
        instance.arr2 = arr2
    
        # Return the instance
        return instance