2012-05-28 3 views
7

Предположим, у нас есть класс «Родитель», который по какой-то причине имеет __new__ и класс «Ребенок», который наследует его. (В моем случае я пытаюсь наследовать от 3-го класса партии, что я не могу изменить)Как правильно наследовать от суперкласса, который имеет метод __new__?

class Parent: 
    def __new__(cls, arg): 
     # ... something important is done here with arg 

Моя попытка была:

class Child(Parent): 
    def __init__(self, myArg, argForSuperclass): 
     Parent.__new__(argForSuperclass) 
     self.field = myArg 

Но пока

p = Parent("argForSuperclass") 

работает как ожидается

c = Child("myArg", "argForSuperclass") 

терпит неудачу, потому что «Ребенок» пытается вызвать метод __new__, который он наследует от «Родитель» вместо своего собственного метода __init__.

Что мне нужно изменить в «Ребенке», чтобы получить ожидаемое поведение?

+1

Посмотрите на связанные: http://stackoverflow.com/questions/674304/pythons-use-of-new-and-init – charleyc

ответ

6

Во-первых, это не считается лучшей практикой, чтобы переопределить __new__, чтобы избежать этих проблем ... Но это не ваша ошибка, я знаю. Для таких случаев, лучшая практика на наиважнейшей __new__, чтобы сделать его принять дополнительные параметры ...

class Parent(object): 
    def __new__(cls, value, *args, **kwargs): 
     print 'my value is', value 
     return object.__new__(cls, *args, **kwargs) 

...поэтому дети могут получить свои собственные:

class Child(Parent): 
    def __init__(self, for_parent, my_stuff): 
     self.my_stuff = my_stuff 

Тогда это будет работать:

>>> c = Child(2, "Child name is Juju") 
my value is 2 
>>> c.my_stuff 
'Child name is Juju' 

Однако автор вашего родительского класса не было, что разумный и дал вам эту проблему:

class Parent(object): 
    def __new__(cls, value): 
     print 'my value is', value 
     return object.__new__(cls) 

В этом случае просто переопределите __new__ в дочернем элементе, заставляя его принимать дополнительные параметры и вызывать __new__ родителя:

class Child(Parent): 
    def __new__(cls, value, *args, **kwargs): 
     return Parent.__new__(cls, value) 
    def __init__(self, for_parent, my_stuff): 
     self.my_stuff = my_stuff 
1

Как я понимаю, когда вы звоните Child("myarg", "otherarg"), что на самом деле означает что-то вроде этого:

c = Child.__new__(Child, "myarg", "otherarg") 
if isinstance(c, Child): 
    c.__init__("myarg", "otherarg") 

Вы могли:

  1. Написать альтернативный конструктор, как Child.create_with_extra_arg("myarg", "otherarg"), который инстанцирует Child("otherarg") перед тем делая все, что ему нужно.

  2. Override Child.__new__, что-то вроде этого:

.

def __new__(cls, myarg, argforsuperclass): 
    c = Parent.__new__(cls, argforsuperclass) 
    c.field = myarg 
    return c 

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

2

Ребенок не называет Родителя __new__ вместо своих собственных __init__, он называет свою собственную __new__ (наследуется от родителя) до того__init__.

Вам нужно понять, для чего предназначены эти методы и когда их назовут Python. __new__ вызывается, чтобы создать экземпляр объекта, а затем для инициализации атрибутов этого экземпляра вызывается __init__. Python передаст те же аргументы обоим методам: аргументы, переданные классу, начнут процесс.

Так что вы делаете, когда наследуете от класса с __new__, это именно то, что вы делаете, наследуя любой другой метод, который имеет другую подпись в родительском, чем вы хотите. Вам необходимо переопределить __new__, чтобы получить аргументы Child и вызвать Parent.__new__ с аргументами, которые он ожидает. Затем вы переопределяете __init__ полностью отдельно (хотя и с тем же списком аргументов, и с тем же требованием нужно вызвать Parent's __init__ со своим собственным списком ожидаемых аргументов).

Существуют две потенциальные трудности, которые могут возникнуть на вашем пути, поскольку __new__ предназначен для настройки процесса получения нового экземпляра. Нет причин, по которым он действительно должен создать новый экземпляр (он может найти тот, который уже существует), и даже ему не нужно возвращать экземпляр класса. Это может привести к следующим проблемам:

  1. If __new__ возвращает существующий объект (например, из-за Родитель ожидает, чтобы иметь возможность кэшировать экземпляры), то вы можете найти ваш __init__ вызывается на уже существующие объекты, которые может быть очень плохо, если ваши объекты должны иметь переменное состояние. Но тогда, если Родитель ожидает, что сможет это сделать, он, вероятно, ожидает множество ограничений на то, как он используется, и подклассифицирует его так, чтобы произвольно нарушить эти ограничения (например, добавление изменчивого состояния в класс, который рассчитывает быть неизменным, чтобы он мог кэшировать и перерабатывать свои экземпляры) почти наверняка не сработает, даже если вы можете правильно инициализировать свои объекты. Если такого рода вещи происходят, и нет никакой документации, рассказывающей вам, что вы должны делать с подклассом Parent, тогда сторонняя сторона, вероятно, не предполагает, что вы будете подклассифицировать родителя, и все будет сложно для тебя. Ваш лучший выбор, хотя, вероятно, было бы попытаться перенести всю вашу инициализацию на __new__, а не на __init__, уродливый, как есть.

  2. Python только вызывает метод __init__ если класс __new__ метод фактически возвращает экземпляр класса.Если Parent использует свой метод __new__ как своего рода «заводскую функцию» для возврата объектов других классов, то подклассы в простой форме, скорее всего, потерпят неудачу, если только вам не нужно делать изменения, как работает «фабрика».