Python: É uma má forma gerar exceções dentro de __init__?

128

É considerado péssimo criar exceções __init__? Em caso afirmativo, qual é o método aceito de gerar um erro quando determinadas variáveis ​​de classe são inicializadas como Noneou de um tipo incorreto?

krizajb
fonte
Em C, é uma má forma gerar uma exceção no destruidor, mas o construtor deve estar bem.
Calyth 02/10/2009
6
@Calyth, você quer dizer C ++ - não existem construtores ou destruidores em C.
Alex Martelli
4
... ou exceções, para esse assunto.
Matt Wilding
@Calyth: lol, remova essa afirmação absurda feita por um erro de digitação inocente ;-)
lpapp
4
@lpapp yes. C ++. FML. E não pude editar esse comentário. Assim, a internet deve documentar minha idiotice;)
Calyth

Respostas:

159

Criar exceções dentro __init__()é absolutamente bom. Não há outra boa maneira de indicar uma condição de erro em um construtor, e há muitas centenas de exemplos na biblioteca padrão em que a construção de um objeto pode gerar uma exceção.

A classe de erro a ser levantada, é claro, depende de você. ValueErroré melhor se o construtor recebeu um parâmetro inválido.

John Millikin
fonte
14
As exceções mais usadas no construtor são ValueErrore TypeError.
Denis Otkidach 02/10/2009
9
Apenas apontando que __init__não é o construtor, é o inicializador. Você pode querer editar a linha Não há outra boa maneira de indicar uma condição de erro dentro de um construtor, ..
Haris
26

É verdade que a única maneira adequada de indicar um erro em um construtor é gerar uma exceção. É por isso que em C ++ e em outras linguagens orientadas a objetos que foram projetadas com a segurança de exceções em mente, o destruidor não é chamado se uma exceção for lançada no construtor de um objeto (o que significa que a inicialização do objeto está incompleta). Geralmente, esse não é o caso de linguagens de script, como Python. Por exemplo, o código a seguir lança um AttributeError se socket.connect () falhar:

class NetworkInterface:
    def __init__(self, address)
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.connect(address)
        self.stream = self.socket.makefile()

    def __del__(self)
        self.stream.close()
        self.socket.close()

O motivo é que o destruidor do objeto incompleto é chamado após a tentativa de conexão falhar, antes que o atributo do fluxo tenha sido inicializado. Você não deve evitar lançar exceções de construtores, apenas estou dizendo que é difícil escrever um código totalmente seguro para exceção em Python. Alguns desenvolvedores de Python evitam o uso de destruidores por completo, mas isso é assunto de outro debate.

Seppo Enarvi
fonte
Esta resposta é realmente útil. Não é apenas falar sobre a "luz verde", mas menciona um exemplo com o "destruidor".
Lpapp # 8/14
Seu código python falha __del__porque é mal escrito. Você teria exatamente o mesmo problema se tivesse this->p_socket->close()um destruidor em C ++. No C ++, você não faria isso - deixaria o objeto membro se destruir. Faça o mesmo em python.
Eric
1
@ Eric Eu não teria o problema em C ++ porque C ++ não destrói objetos que não foram inicializados corretamente . Na verdade, é um idioma muito comum no C ++ alocar recursos no construtor e desalocá-los no destruidor . Você sugere que o objeto membro se destrua (desalocando os recursos em seu destruidor) - então o mesmo problema surge ao escrever a classe do membro.
Seppo Enarvi 15/11
11

Não vejo nenhuma razão para que esteja em má forma.

Pelo contrário, uma das coisas pelas quais as exceções são conhecidas por se sair bem, em vez de retornar códigos de erro, é que os códigos de erro geralmente não podem ser retornados pelos construtores. Portanto, pelo menos em linguagens como C ++, gerar exceções é a única maneira de sinalizar erros.

Edan Maor
fonte
6

A biblioteca padrão diz:

>>> f = file("notexisting.txt")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 2] No such file or directory: 'notexisting.txt'

Também não vejo realmente nenhuma razão para que isso seja considerado uma forma ruim.

sth
fonte
3

Eu acho que é o caso perfeito para a ValueErrorexceção embutida .

Urso de pelúcia
fonte
2

Eu concordo com todas as opções acima.

Realmente, não há outra maneira de sinalizar que algo deu errado na inicialização de um objeto além de gerar uma exceção.

Na maioria das classes de programas em que o estado de uma classe depende totalmente das entradas para essa classe, podemos esperar que algum tipo de ValueError ou TypeError seja gerado.

Classes com efeitos colaterais (por exemplo, um que cria redes ou gráficos) podem gerar um erro no init se (por exemplo) o dispositivo de rede não estiver disponível ou o objeto de tela não puder ser gravado. Isso me parece sensato, porque muitas vezes você quer saber sobre as condições de falha o mais rápido possível.

Salim Fadhley
fonte
2

Gerar erros do init é inevitável em alguns casos, mas fazer muito trabalho no init é um estilo ruim. Você deve fazer uma fábrica ou uma pseudo-fábrica - um método de classe simples que retorna um objeto configurado.

Ctrl-C
fonte