Плохая практика для запуска кода в конструкторе, который, вероятно, потерпит неудачу?
мой вопрос - скорее вопрос дизайна.
В Python, если код в вашем "конструкторе" терпит неудачу, объект не будет определен. Таким образом:
someInstance = MyClass("test123") #lets say that constructor throws an exception
someInstance.doSomething() # will fail, name someInstance not defined.
У меня действительно есть ситуация, когда будет много кодового копирования, если я удалю код, подверженный ошибкам, из моего конструктора. В основном мой конструктор заполняет несколько атрибутов (через IO, где многое может пойти не так), к которым можно получить доступ с различными геттерами. Если я удалю код из контрструктора, у меня будет 10 геттеров с кодом вставки скопировать что-то вроде:
- Атрибут действительно установлен?
- выполнить некоторые действия ввода-вывода для заполнения атрибута
- возвращает содержимое рассматриваемой переменной
Мне это не нравится, потому что все мои получатели будут содержать много кода. Вместо этого я выполняю операции ввода-вывода в центральном расположении, конструкторе и заполняю все мои атрибуты.
Каков правильный способ сделать это?
Ответы
Ответ 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, поэтому это, вероятно, не проблема для вас.
Ответ 8
Кажется, у Нила был хороший момент: мой друг просто указал мне на это:
http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization
что в основном то, что сказал Нил...