Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Instantiate only unique objects of a class

I'm trying to create a class that only creates an instance if the arguments passed in during instantiation are a unique combination. If the combination of arguments have previously been passed in, then return the instance that has already been previously created. I'd like for this class to be inherited by other classes so they inherit the same behavior. This is my first attempt at a solution,

The base/parent class to be inherited:

class RegistryType(type):
    def __init__(cls, name, bases, namespace, *args):
        cls.instantiated_objects = {}


class AdwordsObject(object, metaclass=RegistryType):
    api = AdWordsAPI()

    def __new__(cls, *args):
        object_name = '-'.join(args)
        if object_name in cls.instantiated_objects:
            return cls.instantiated_objects[object_name]
        else:
            obj = super(AdwordsObject, cls).__new__(cls)
            cls.instantiated_objects[object_name] = obj
            # cls.newt_connection.commit()
            return obj

And this is how it's being used in the child class:

class ProductAdGroup(AdwordsObject):
    # init method only called if object being instantiated hasn't already been instantiated
    def __init__(self, product_name, keyword_group):
        self.name = '-'.join([product_name, keyword_group])

    @classmethod
    def from_string(cls, name: str):
        arguments = name.split('-')
        assert len(arguments) == 2, 'Incorrect ad group name convention. ' \
                                    'Use: Product-KeywordGroup'
        ad_group = cls(*arguments)
        return ad_group

I've ran the program with this setup but it seems like a new dict is being created every time ProductAdGroup() is being created so the memory is exploding... even though the program returns the instance that had already been previously instantiated.

Is there anyway to fix this? Thanks!!!

like image 521
Alex Luis Arias Avatar asked Aug 04 '17 16:08

Alex Luis Arias


1 Answers

Your code seems to be right - the only thing incorrect above is that your __init__ method will always be called when instantiating a new class, regardless of a previous instance being returned by __new__ or not.

So, if you create extra objects in your __init__ method, that may be the cause of your memory leak - however, if you bind these new objects to the instane (self), they shuld just override a previously created object in the same place - which would them be freed. . In the code posted here, that happens with self.name- it may be that your real __init__ does more things, and associate new objects to other places than the instance (like, apending them to a list). If your __init__ methods are just as shown the cause for your memory growing is not evident in the code you supply.

As an extra advice, but not related to the problem you relate, I add that you don't need a metaclass for this at all.

Just check for the existence of an cls.instantiated_objects dict in the __new__ method itself. Not writting an unneeded metaclass will simplify your codebase, avoid metaclass conflicts if your class hierarchy evolves, and may even do away with your problem if there is more code on your metaclass than you are showing here.

The base class __new__ method can be rewritten something like this:

class AdwordsObject(object):
    def __new__(cls, *args):
        if not cls.__dict__.get("instantiated_objects"):
            cls.instantiated_objects = {}
        name = '-'.join(args)
        if name in cls.instantiated_objects:
            return cls.instantiated_objects[name]
        instance = super().__new__(cls)
        cls.instantiated_objects[name] = instance
        return instance

And there is no more need for a custom metaclass.

like image 117
jsbueno Avatar answered Oct 19 '22 18:10

jsbueno