Подтвердить что ты не робот

Плохая практика для запуска кода в конструкторе, который, вероятно, потерпит неудачу?

мой вопрос - скорее вопрос дизайна. В Python, если код в вашем "конструкторе" терпит неудачу, объект не будет определен. Таким образом:

someInstance = MyClass("test123") #lets say that constructor throws an exception
someInstance.doSomething() # will fail, name someInstance not defined.

У меня действительно есть ситуация, когда будет много кодового копирования, если я удалю код, подверженный ошибкам, из моего конструктора. В основном мой конструктор заполняет несколько атрибутов (через IO, где многое может пойти не так), к которым можно получить доступ с различными геттерами. Если я удалю код из контрструктора, у меня будет 10 геттеров с кодом вставки скопировать что-то вроде:

  • Атрибут действительно установлен?
  • выполнить некоторые действия ввода-вывода для заполнения атрибута
  • возвращает содержимое рассматриваемой переменной

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

Каков правильный способ сделать это?

4b9b3361

Ответ 1

Я не разработчик Python, но в целом лучше избегать сложных/подверженных ошибкам операций в вашем конструкторе. Одним из способов этого было бы поместить метод "LoadFromFile" или "Init" в ваш класс, чтобы заполнить объект из внешнего источника. Затем этот метод load/init следует вызывать отдельно после построения объекта.

Ответ 2

Существует разница между конструктором в С++ и методе __init__ в Python. В С++ задачей конструктора является создание объекта. Если он терпит неудачу, деструктор не называется. Поэтому, если какие-либо ресурсы были приобретены до исключение было сделано, очистка должна быть выполнена до выхода из конструктора. Таким образом, некоторые предпочитают двухфазную конструкцию с большей частью выполненной конструкции вне конструктора (ть).

Python имеет более чистую двухфазную конструкцию (конструкция, затем инициализация). Однако многие люди путают метод __init__ (инициализатор) с конструктором. Фактический конструктор в Python называется __new__. В отличие от С++, он не принимает экземпляр, но возвращает один. Задачей __init__ является инициализация созданного экземпляра. Если в __init__ возникает исключение, деструктор __del__ (если он есть) будет вызван как ожидалось, потому что объект уже был создан (хотя он и не был правильно инициализирован) к моменту появления __init__.

Отвечая на ваш вопрос:

В Python, если код в вашем "конструктор" терпит неудачу, объект заканчивается не определено.

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

Каков правильный способ сделать это?

В Python инициализируйте объекты в __init__ и не волнуйтесь об исключениях. В С++ используйте RAII.


Обновить [об управлении ресурсами]:

В сборках с мусором, если вы имеете дело с ресурсами, особенно ограниченными, такими как соединения с базой данных, лучше не выпускать их в деструкторе. Это потому, что объекты уничтожаются недетерминированным способом, и если вы иметь цикл ссылок (что не всегда легко сказать), и хотя бы один из объектов в цикле имеет деструктор, он никогда не будет уничтожен. У собранных мусором языков есть другие средства для работы с ресурсами. В Python это с инструкцией.

Ответ 3

В С++, по крайней мере, нет ничего плохого в том, чтобы помещать в конструктор непристойный код - вы просто бросаете исключение, если возникает ошибка. Если код необходим для правильной сборки объекта, на самом деле нет альтернативы (хотя вы можете абстрагировать код на подфункции или лучше в конструкторах подобъектов). Худшая практика состоит в том, чтобы частично построить объект, а затем ожидать, что пользователь вызовет другие функции, чтобы каким-то образом завершить строительство.

Ответ 4

Это не плохая практика сама по себе.

Но я думаю, что вы можете быть здесь чем-то другим. В вашем примере метод doSomething() не вызывается, когда конструктор MyClass терпит неудачу. Попробуйте следующий код:

class MyClass:
def __init__(self, s):
    print s
    raise Exception("Exception")

def doSomething(self):
    print "doSomething"

try:
    someInstance = MyClass("test123")
    someInstance.doSomething()
except:
    print "except"

Он должен печатать:

test123
except

Для вашего программного обеспечения вы можете задать следующие вопросы:

  • Какова должна быть область действия переменной someInstance? Кто его обслуживает? Каковы их требования?

  • Где и как следует обрабатывать ошибку для случая, когда одно из ваших 10 значений недоступно?

  • Должны ли все 10 значений кэшироваться во время построения или кэшироваться один за другим, когда они нужны в первый раз?

  • Может ли код ввода/вывода реорганизоваться в вспомогательный метод, так что выполнение чего-то аналогичного 10 раз не приводит к повторению кода?

  • ...

Ответ 5

Один общий шаблон - двухфазная конструкция, также предложенная Энди Уайтом.

Первая фаза: Обычный конструктор.

Вторая фаза: операции, которые могут выйти из строя.

Интеграция двух: добавьте метод factory для выполнения обеих фаз и сделайте конструктор protected/private, чтобы предотвратить создание вне метода factory.

О, и я не разработчик Python.

Ответ 6

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

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

Ответ 7

Опять же, у меня мало опыта работы с Python, однако на С# лучше попытаться избежать конструктора, который генерирует исключение. Примером тому, почему это приходит на ум, является то, что вы хотите разместить свой конструктор в точке, где его невозможно окружить блоком try {} catch {}, например инициализацией поля в классе:

class MyClass
{
    MySecondClass = new MySecondClass();
    // Rest of class
}

Если конструктор MySecondClass выдает исключение, которое вы хотите обработать внутри MyClass, тогда вам нужно реорганизовать вышеупомянутое - его, конечно, не конец света, а приятный.

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

В качестве оптимизации вам нужно, чтобы getter (или метод инициализации) установил для некоторого типа IsInitialised значение boolean в true, чтобы указать, что (потенциально дорогостоящая) инициализация не требуется делать снова.

В псевдокоде (С#, потому что я просто испортил синтаксис Python):

class MyClass
{
    private bool IsInitialised = false;

    private string myString;

    public void Init()
    {
        // Put initialisation code here
        this.IsInitialised = true;
    }

    public string MyString
    {
        get
        {
            if (!this.IsInitialised)
            {
                this.Init();
            }

            return myString;
        }
    }
}

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