Почему __init__ не вызывается после __new__ НЕКОТОРЫХ - PullRequest
10 голосов
/ 20 февраля 2012

Позвольте мне начать с этого не повторение Почему __init__ не вызывается, если __new__ вызывается без аргументов . Я попытался тщательно построить пример кода для __new__ и __init__, который не может найти объяснения.

Основные параметры:

  • Существует базовый класс, называемый NotMine, так как он взят из другой библиотеки (я опишу в конце, здесь не важно)
  • В этом классе есть метод __init__, который в свою очередь вызывает метод _parse
  • Мне нужно переопределить метод _parse в подклассах
  • какой подкласс я создаю, неизвестно до вызова
  • Я знаю, что существуют методы фабричного проектирования, но я не могу их здесь использовать (подробнее в конце)
  • Я пытался осторожно использовать super, чтобы избежать проблем в Запись в Python: почему __init__ вызывается дважды?
  • Я знаю, что это тоже "своего рода" возможность AbstractBaseMehtod, но это не помогло

В любом случае, __init__ следует вызывать после __new__, и для каждого объяснения того, почему НЕКОТОРЫЕ примеры ниже не работают, я, кажется, могу указать на другие случаи, которые работают, и исключить объяснение.

class NotMine(object):

    def __init__(self, *args, **kwargs):
        print "NotMine __init__"
        self._parse()

    def _parse(self):
        print "NotMine _parse"

class ABC(NotMine):
    def __new__(cls,name,*args, **kwargs):
        print "-"*80
        print "Entered through the front door ABC.__new__(%s,%s,*%s,**%s)"%(cls,name,args,kwargs)
        if name == 'AA':
            obj = super(NotMine,ABC).__new__(AA,*args,**kwargs)
            print "Exiting door number 1 with an instance of: %s"%type(obj)
            return obj 
        elif name == 'BB':
            obj = super(NotMine,ABC).__new__(BB,*args,**kwargs)
            print "Exiting door number 2 with an instance of: %s"%type(obj)
            return obj
        else:
            obj = super(NotMine,ABC).__new__(cls,*args,**kwargs)
            print "Exiting door number 3 with an instance of: %s"%type(obj)
            return obj

class AA(ABC):

    def _parse(self):
       print "AA _parse"

class BB(ABC):

    def __init__(self, *args, **kw):
        print "BB_init:*%s, **%s"%(args,kw)        
        super(BB,self).__init__(self,*args,**kw)

    def _parse(self):
        print "BB _parse"

class CCC(AA):

    def _parse(self):
        print "CCCC _parse"


print("########### Starting with ABC always calls __init__ ############")
ABC("AA")            # case 1
ABC("BB")            # case 2
ABC("NOT_AA_OR_BB")  # case 3

print("########### These also all call __init__ ############")
AA("AA")           # case 4
BB("BB")           # case 5
AA("NOT_AA_OR_BB") # case 6
BB("NOT_AA_OR_BB") # case 7
CCC("ANYTHING")    # case 8

print("########### WHY DO THESE NOT CALL __init__ ############")
AA("BB")  # case 9  
BB("AA")  # case 10
CCC("BB") # case 11

Если вы выполните код, вы увидите, что для каждого вызова __new__ он сообщает «через какую дверь» он выходит и с каким типом. Я могу выйти из одной и той же "двери" с тем же объектом "типа", и у меня будет вызван __init__ в одном случае, а не в другом. Я посмотрел на mro «вызывающего» класса, и он не дает понимания, так как я могу вызвать этот класс (или подкасс, как в CCC) и вызвать __init__.

Конечные ноты: Используемая мной библиотека NotMine - это Genshi MarkupTemplate, и причина неиспользования метода проектирования Factory заключается в том, что для их TemplateLoader требуется конструктор defaultClass. Я не знаю, пока не начну разбор, который я делаю в __new__. Есть много крутой магии вуду, которую делают загрузчики и шаблоны genshi, которые стоят того.

Я могу запустить неизмененный экземпляр их загрузчика, и в настоящее время все работает, пока я ТОЛЬКО передаю класс ABC (абстрактный вид фабрики) по умолчанию. Все работает хорошо, но это необъяснимое поведение - чуть ли не ошибка позже.

UPDATE: Игнасио задал вопрос в верхней строке, если возвращаемый объект не является «экземпляром» cls , то __init__ не вызывается. Я считаю, что вызов «конструктора» (например, AA(args..) неправильный, так как он снова вызовет __new__, и вы вернетесь к тому, с чего начали. Вы можете изменить аргумент arg, чтобы выбрать другой путь. Это просто означает, что вы вызываете ABC.__new__ дважды, а не бесконечно. Рабочим решением является редактирование class ABC выше как:

class ABC(NotMine):
  def __new__(cls,name,*args, **kwargs):
    print "-"*80
    print "Entered through the front door ABC.__new__(%s,%s,*%s,**%s)"%(cls,name,args,kwargs)
    if name == 'AA':
        obj = super(NotMine,ABC).__new__(AA,*args,**kwargs)
        print "Exiting door number 1 with an instance of: %s"%type(obj)
    elif name == 'BB':
        obj = super(NotMine,ABC).__new__(BB,*args,**kwargs)
        print "Exiting door number 2 with an instance of: %s"%type(obj)
    elif name == 'CCC':
        obj = super(NotMine,ABC).__new__(CCC,*args,**kwargs)
        print "Exiting door number 3 with an instance of: %s"%type(obj)
    else:
        obj = super(NotMine,ABC).__new__(cls,*args,**kwargs)
        print "Exiting door number 4 with an instance of: %s"%type(obj)
    ## Addition to decide who calls __init__  ##
    if isinstance(obj,cls):
        print "this IS an instance of %s So call your own dam __init__"%cls
        return obj
    print "this is NOT an instance of %s So __new__ will call __init__ for you"%cls
    obj.__init__(name,*args, **kwargs)
    return obj

print("########### now, these DO CALL __init__ ############")
AA("BB")  # case 9  
BB("AA")  # case 10
CCC("BB") # case 11

Обратите внимание на последние несколько строк. Не вызывать __init__, если это «другой» класс, для меня не имеет смысла, ОСОБЕННО, когда «другой» класс все еще является подклассом класса, вызывающего __init__. Мне не нравится вышеприведенное редактирование, но, по крайней мере, теперь я получаю правила немного лучше.

Ответы [ 2 ]

10 голосов
/ 20 февраля 2012

С документация :

Если __new__() не возвращает экземпляр cls , то метод __init__() нового экземпляра вызываться не будет.

Это позволяет __new__() - возвращать новый экземпляр другого класса , который вместо этого имеет свой собственный __init__() для вызова. Вам нужно будет определить, создаете ли вы новые cls, и вместо этого вызвать соответствующий конструктор.

0 голосов
/ 21 февраля 2012

Только мои два цента здесь, но почему бы вам не использовать Python duck typing, чтобы предоставить Genshi что-то, что ведет себя как класс?

Я быстро взглянул на исходный код Genshi , и единственное требование, которое я увидел в параметре 'class' для TemplateLoader, это то, что он вызывается с заданными аргументами.

Я думаю, было бы проще смоделировать класс с помощью фабричной функции, возвращающей фактический созданный экземпляр.

...