2011-06-17 2 views
41

Есть ли способ обойти конструктор __init__ класса в python?Есть ли способ создать экземпляр класса без вызова __init__?

Пример:

class A(object):  
    def __init__(self): 
     print "FAILURE" 

    def Print(self): 
     print "YEHAA" 

Теперь я хотел бы создать экземпляр A. Это может выглядеть так, однако этот синтаксис неверен.

a = A 
a.Print() 

EDIT:

Еще более сложный пример:

Предположим, у меня есть объект C, цель которого он должен хранить один единственный параметр и сделать некоторые расчеты с ним. Параметр, однако, не передается как таковой, но встроен в огромный файл параметров. Это может выглядеть примерно так:

class C(object): 
    def __init__(self, ParameterFile): 
     self._Parameter = self._ExtractParamterFile(ParameterFile) 
    def _ExtractParamterFile(self, ParameterFile): 
     #does some complex magic to extract the right parameter 
     return the_extracted_parameter 

Теперь я хотел бы сбросить и загрузить экземпляр этого объекта C. Однако, когда я загружаю этот объект, у меня есть только одна переменная self._Parameter, и я не могу вызвать конструктор, потому что он ожидает файл параметров.

@staticmethod 
    def Load(file): 
     f = open(file, "rb") 
     oldObject = pickle.load(f) 
     f.close() 

     #somehow create newObject without calling __init__ 
     newObject._Parameter = oldObject._Parameter 
     return newObject 

Другими словами, невозможно создать экземпляр без передачи файла параметров. Однако в моем «реальном» случае это не файл параметров, а какой-то огромный мусор данных, которые я, конечно же, не хочу носить в памяти или даже хранить на диске.

И так как я хочу вернуть экземпляр C из метода Load Мне нужно как-то вызвать конструктор.

OLD EDIT:

Более сложный пример, который объясняет почему я задаю вопрос:

class B(object):  
    def __init__(self, name, data): 
     self._Name = name 
     #do something with data, but do NOT save data in a variable 

    @staticmethod 
    def Load(self, file, newName): 
     f = open(file, "rb") 
     s = pickle.load(f) 
     f.close() 

     newS = B(???) 
     newS._Name = newName 
     return newS 

Как вы можете видеть, поскольку data не хранится в переменной класса я не могу перейдите к __init__. Конечно, я мог бы просто сохранить его, но что, если данные - огромный объект, который я не хочу постоянно носить в памяти или даже сохранять на диске?

+5

С какой целью вы хотите сделать это? –

+2

Странный вопрос .... что на самом деле пытается? –

+0

У меня есть «незакрашенный» объект, из которого я хочу создать новый объект того же типа.Однако из этого незакрашенного объекта у меня недостаточно данных для подачи на __init__. В C++ я бы перегрузил конструктор и сделал его закрытым, в Python я потерял ^^. Однако текущие параметры в __init__ имеют смысл. Это мое затруднительное положение. – Woltan

ответ

43

Вы могут обходить __init__ по телефону __new__ напрямую. Затем вы можете создать объект данного типа и вызвать альтернативный метод для __init__. Это то, что сделает pickle.

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

При создании объектов, более или менее это происходит:

a = A.__new__(A, *args, **kwargs) 
a.__init__(*args, **kwargs) 

Вы можете пропустить второй шаг.

Вот почему вы не должны сделать это: Целью __init__ является для инициализации объекта, заполните все поля и убедитесь, что __init__ методов родительских классов также называется.С pickle это исключение, поскольку оно пытается сохранить все данные, связанные с объектом (в том числе любые поля/переменные экземпляра, которые установлены для объекта), и поэтому все, что было установлено в __init__ в предыдущее время, будет восстановлено рассол, нет необходимости называть его снова.

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

Кроме того, сделав это, вы будете более или менее переопределять создание экземпляра Python и создавать свои собственные. Python уже делает это для вас довольно хорошо, не нужно заново изобретать его, и это смутит людей, использующих ваш код.

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

  1. Поддержка различных подписей для __init__, которые обрабатывают свои дела с помощью дополнительных аргументов.
  2. Создайте несколько методов класса, которые служат альтернативными конструкторами. Убедитесь, что все они создают экземпляры класса обычным способом (т. Е. Вызывая __init__), как показал Роман Боднарчук, выполняя дополнительную работу или что-то еще. Лучше всего передать все данные классу (и __init__ справится с этим), но если это невозможно или неудобно, вы можете установить некоторые переменные экземпляра после создания экземпляра и выполнить __init__ инициализацию.

Если __init__ имеет дополнительный шаг (например, как обработка, которая data аргумент, хотя вы должны были бы быть более конкретным), вы можете сделать это необязательный аргумент, или сделать нормальный метод, который делает обработку .. . или оба.

+1

Можно даже использовать 'a = object .__ new __ (A)', если вы не хотите перезаписывать 'A .__ new__' для вызова. – kay

+3

Допустимо использовать это, если вы строите шаблон фабрики и хотите делегировать строительство другому классу, но также хотите иметь конструкцию по умолчанию в случае, если вы не используете фабрику для этого класса. 'class ThreadPoolFactory: def PersistentThreadPool(): ThreadPool .__ new__ .... build и log it' – gbtimmon

2

Как я уже сказал в своем комментарии вы можете изменить метод __init__ так, что она позволяет создавать без предоставления каких-либо значений его параметров:

def __init__(self, p0, p1, p2): 
    # some logic 

станет:

def __init__(self, p0=None, p1=None, p2=None): 
    if p0 and p1 and p2: 
     # some logic 

или:

def __init__(self, p0=None, p1=None, p2=None, init=True): 
    if init: 
     # some logic 
+0

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

7

Не совсем. Цель __init__ - создать экземпляр объекта, и по умолчанию он действительно ничего не делает. Если метод __init__ не делает то, что вы хотите, и это не ваш собственный код, чтобы изменить его, вы можете выбрать его.Например, принимая свой класс А, мы могли бы сделать следующее, чтобы избежать вызова этого метода __init__:

def emptyinit(self): 
    pass 
A.__init__ = emptyinit 
a = A() 
a.Print() 

Это будет динамически переключаться из которых __init__ метода из класса, заменив его на пустой вызов. Обратите внимание, что это, вероятно, НЕ ДОЛЖНО делать, поскольку он не вызывает метод суперкласса __init__.

Вы также можете подклассифицировать его, чтобы создать свой собственный класс, который делает все то же самое, кроме переопределения метода __init__, чтобы сделать то, что вы хотите (возможно, ничего).

Возможно, однако, вы просто хотите вызвать метод из класса без создания экземпляра объекта. Если это так, вы должны изучить декораторы @classmethod и @staticmethod. Они допускают именно такой тип поведения.

В вашем коде вы поместили декоратор @staticmethod, который не принимает аргумент self. Возможно, то, что может быть лучше для этой цели будет в @classmethod, который мог бы выглядеть следующим образом:

@classmethod 
def Load(cls, file, newName): 
    # Get the data 
    data = getdata() 
    # Create an instance of B with the data 
    return cls.B(newName, data) 

UPDATE: Отличный ответ рош в указал, что вы можете избежать вызова __init__ путем внедрения __new__, что я на самом деле не знают (хотя это имеет смысл). Спасибо Рош!

14

Использование classmethod декоратор для метода Load:

class B(object):  
    def __init__(self, name, data): 
     self._Name = name 
     #store data 

    @classmethod 
    def Load(cls, file, newName): 
     f = open(file, "rb") 
     s = pickle.load(f) 
     f.close() 

     return cls(newName, s) 

Так что вы можете сделать:

loaded_obj = B.Load('filename.txt', 'foo') 

Edit:

Во всяком случае, если вы все еще хотите, чтобы пропустить __init__ метод, попробуйте __new__ :

>>> class A(object): 
...  def __init__(self): 
...    print '__init__' 
... 
>>> A() 
__init__ 
<__main__.A object at 0x800f1f710> 
>>> a = A.__new__(A) 
>>> a 
<__main__.A object at 0x800f1fd50> 
+0

Это хороший материал. +1 – motoku

+0

В этом случае в строке 'return cls (newName, s)', 'data' будет' s'. Но это не так, поскольку я не знаю, что такое «данные». – Woltan

+1

@ Вольтан Я тоже не знаю. Может быть, вам стоит еще раз расширить свой вопрос? –

9

Берем вопрос буквально я хотел бы использовать мета-классы:

class MetaSkipInit(type): 
    def __call__(cls): 
     return cls.__new__(cls) 


class B(object): 
    __metaclass__ = MetaSkipInit 

    def __init__(self): 
     print "FAILURE" 

    def Print(self): 
     print "YEHAA" 


b = B() 
b.Print() 

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

+0

Это приятное решение, показывающее, как переопределить значение по умолчанию '__new__' +' __init__' путем реализации пользовательского '__call__' метод на уровне метакласса. Он делает это без каких-либо «взломов» и позволяет вам делать другие полезные вещи (например, реализовать мухи) и сохранять это полностью скрытым от пользователя. Для пользователя он по-прежнему выглядит как обычный вызов конструктора. – dieterg

5

Я читал Python поваренную книгу и есть раздел об этом говорить: пример дается с помощью __new__ обойти __init__()

 
>>> class A: 
    def __init__(self,a): 
     self.a = a 


>>> test = A('a') 
>>> test.a 
'a' 
>>> test_noinit = A.__new__(A) 
>>> test_noinit.a 
Traceback (most recent call last): 
    File "", line 1, in 
    test_noinit.a 
AttributeError: 'A' object has no attribute 'a' 
>>> 

Однако я думаю, что это работает только в Python3. Ниже приведено 2,7

 
>>> class A: 
    def __init__(self,a): 
     self.a = a 


>>> test = A.__new__(A) 

Traceback (most recent call last): 
    File "", line 1, in 
    test = A.__new__(A) 
AttributeError: class A has no attribute '__new__' 
>>> 
+1

С 2.7 вам нужно использовать «новый класс стиля», то есть объект подкласса: 'class A (object)'. –

Смежные вопросы