Есть ли способ обойти конструктор __init__
класса в python?
Пример:
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__
. Конечно, я мог бы просто сохранить его, но что, если данные являются огромным объектом, который я не хочу постоянно носить в памяти или даже сохранять на диске?
Ответ 1
Вы можете обойти __init__
, позвонив __new__
напрямую. Затем вы можете создать объект данного типа и вызвать альтернативный метод для __init__
. Это то, что pickle
будет делать.
Однако, во-первых, я бы очень хотел подчеркнуть, что это то, что вы не должны делать, и что бы вы ни пытались достичь, есть лучшие способы сделать это, некоторые из которых были упомянуты в других ответах. В частности, не рекомендуется пропускать вызов __init__
.
Когда объекты создаются, более или менее это происходит:
a = A.__new__(A, *args, **kwargs)
a.__init__(*args, **kwargs)
Вы можете пропустить второй шаг.
Вот почему вы не должны этого делать: Цель __init__
- инициализировать объект, заполнить все поля и убедиться, что методы __init__
родительских классов также вызываются. С pickle
это исключение, потому что он пытается сохранить все данные, связанные с объектом (включая любые поля/переменные экземпляра, которые установлены для объекта), и поэтому все, что было установлено __init__
в предыдущий раз, будет восстановлено pickle, нет необходимости звонить снова.
Если вы пропустите __init__
и воспользуетесь альтернативным инициализатором, у вас будет своего рода дублирование кода - в двух местах будут заполнены переменные экземпляра, и будет легко пропустить одно из них в одном из инициализаторов или случайно сделать два поля заполняют, действуют по-разному. Это дает возможность скрытых ошибок, которые не так просто отследить (вам нужно знать, какой инициализатор был вызван), и код будет сложнее поддерживать. Не говоря уже о том, что вы будете в еще большем беспорядке, если будете использовать наследование - проблемы будут идти вверх по цепочке наследования, потому что вам придется использовать этот альтернативный инициализатор везде по цепочке.
Кроме того, вы будете более или менее переопределять создание экземпляров Python и создавать свои собственные. Python уже делает это для вас довольно хорошо, не нужно изобретать его заново, и это может смутить людей, использующих ваш код.
Вот что лучше всего сделать вместо этого: Используйте один метод __init__
который должен вызываться для всех возможных экземпляров класса, который правильно инициализирует все переменные экземпляра. Для разных режимов инициализации используйте любой из двух подходов:
- Поддерживайте различные подписи для
__init__
которые обрабатывают ваши случаи, используя необязательные аргументы. - Создайте несколько методов класса, которые служат альтернативными конструкторами. Убедитесь, что все они создают экземпляры класса обычным способом (то есть, вызывая
__init__
), как показано Романом Боднарчуком, при выполнении дополнительной работы или чего-то еще. Лучше всего, если они передадут все данные классу (и __init__
обрабатывает их), но если это невозможно или неудобно, вы можете установить некоторые переменные экземпляра после того, как экземпляр был создан, и __init__
инициализацию.
Если __init__
имеет необязательный шаг (например, обработку этого аргумента data
, хотя вам нужно быть более конкретным), вы можете либо сделать его необязательным аргументом, либо создать обычный метод, который выполняет обработку... или оба.
Ответ 2
Используйте classmethod
decorator для вашего метода 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>
Ответ 3
Взяв ваш вопрос в буквальном смысле, я бы использовал мета-классы:
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()
Это может быть полезно, например, для копирования конструкторов без загрязнения списка параметров.
Но для этого было бы лучше работать и заботиться, чем предлагаемый вами взлом.
Ответ 4
Не совсем. Цель __init__
- создать экземпляр объекта, и по умолчанию он действительно ничего не делает. Если метод __init__
не делает то, что вы хотите, и это не значит, что ваш собственный код изменится, вы можете его отключить. Например, взяв ваш класс A, мы могли бы сделать следующее, чтобы избежать вызова этого метода __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)
ОБНОВЛЕНИЕ: Рош Отличный ответ указал, что вы не можете позвонить __init__
, внедрив __new__
, о котором я действительно не знал (хотя это имеет смысл). Спасибо Рош!
Ответ 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__'
>>>
Ответ 6
Как я уже сказал в своем комментарии, вы можете изменить свой метод __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