2017-08-04 99 views
5

Próbuję utworzyć klasę, która tworzy tylko instancję, jeśli argumenty przekazane podczas tworzenia wystąpień są unikalną kombinacją. Jeśli kombinacja argumentów została wcześniej przekazana, zwróć instancję, która została wcześniej utworzona. Chciałbym, aby ta klasa była dziedziczona przez inne klasy, więc dziedziczą to samo zachowanie. Jest to moja pierwsza próba rozwiązania,Wywoływanie tylko unikatowych obiektów klasy

Klasa bazowa/rodzic być dziedziczone:

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 

I tak to jest używane w klasie dziecko:

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 uruchomił program z tą konfiguracją, ale wydaje się, że za każdym razem, gdy tworzona jest grupa ProductAdGroup(), tworzony jest nowy dict, aby pamięć eksplodowała ... nawet jeśli program zwraca instancję, która została wcześniej utworzona.

Czy mimo to można to naprawić? Dzięki!

+0

> Nowy dict jest tworzony za każdym razem, gdy tworzona jest grupa ProductAdGroup(). Skąd wiesz ? – aristotll

+0

@aristotll Nie jestem pewien, czy tak jest, ale może. Wiem, że coś dzieje się z dyktami, ponieważ sprawdziłem, ile obiektów jest tworzonych dla wszystkich typów w różnych punktach programu, a dykcje stale rosną –

Odpowiedz

1

Twój kod wydaje się mieć rację - jedyną wadą powyższą jest, że twoja metoda __init__ będzie zawsze wywoływana podczas tworzenia instancji nowej klasy, niezależnie od tego, czy poprzednie wystąpienie zostało zwrócone przez __new__, czy też nie.

Tak więc, jeśli utworzysz dodatkowe obiekty w metodzie __init__, może to być przyczyną wycieku pamięci - jednak, jeśli zwiążesz te nowe obiekty z instanem (self), to one tylko zastąpią wcześniej utworzony obiekt w w tym samym miejscu - które zostaną uwolnione. . W zamieszczonym tutaj kodzie dotyczy to self.name - może się zdarzyć, że Twój prawdziwy __init__ zrobi więcej rzeczy i powiąże nowe obiekty z innymi miejscami niż instancja (np. Z listą do listy). Jeśli twoje metody __init__ są po prostu pokazane, przyczyna wzrostu pamięci nie jest oczywista w dostarczanym przez ciebie kodzie.

Jako dodatkową poradę, ale niezwiązaną z problemem, który się z Tobą wiąże, dodam, że nie potrzebujesz do tego celu metaclass.

Wystarczy sprawdzić, czy istnieje sam dyktafon cls.instantiated_objects w metodzie . Niepowiadanie niepotrzebnej metaclass uprości twój kod, unikniesz konfliktu metaclass, jeśli rozwinie się hierarchia klasowa, a może nawet zlikwidujesz twój problem, jeśli jest więcej kodu na metaclassie niż tutaj.

Podstawa klasa __new__ metoda może być zapisane tak:

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 

I nie ma już potrzeby niestandardowych metaklasą.