Есть ли способ создать экземпляр класса без вызова __init__? - PullRequest
56 голосов
/ 17 июня 2011

Есть ли способ обойти конструктор __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, мне как-то нужно вызвать конструктор.

СТАРЫЙ РЕДАКТИРОВАТЬ:

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

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__. Конечно, я мог бы просто сохранить их, но что, если данные - это огромный объект, который я не хочу постоянно носить в памяти или даже сохранять на диск?

Ответы [ 6 ]

52 голосов
/ 17 июня 2011

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

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

Когда объекты создаются, более или менее это происходит:

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

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

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

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

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

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

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

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

19 голосов
/ 17 июня 2011

Используйте 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>
9 голосов
/ 06 сентября 2013

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

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()

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

8 голосов
/ 17 июня 2011

Не совсем.Цель __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__,который я на самом деле не знал (хотя это имеет смысл).Спасибо Рош!

6 голосов
/ 27 апреля 2014

Я читал кулинарную книгу по 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__'
>>> 
2 голосов
/ 17 июня 2011

Как я уже сказал в своем комментарии, вы можете изменить свой метод __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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...