Eu tenho um servidor Python de longa duração e gostaria de poder atualizar um serviço sem reiniciar o servidor. Qual'é a melhor maneira de fazer isso?
if foo.py has changed:
unimport foo <-- How do I do this?
import foo
myfoo = foo.Foo()
Você pode recarregar um módulo quando ele já tiver sido importado, utilizando a função reload
builtin:
from importlib import reload # Python 3.4+ only.
import foo
while True:
# Do some things.
if is_changed(foo):
foo = reload(foo)
Em Python 3, a reload
foi movida para o módulo imp`. Em 3.4, imp
foi depreciado em favor de importlib
, e reload
foi adicionado a este último. Ao apontar para 3 ou mais tarde, ou referencie o módulo apropriado ao chamar reload
ou importe-o.
Acho que é isto que tu queres. Servidores Web como o servidor de desenvolvimento do Django usam isto para que você possa ver os efeitos das mudanças de código sem reiniciar o processo do servidor em si.
Para citar os documentos:
O código dos módulos Python é recompilado e o código em nível de módulo reexecutado, definindo um novo conjunto de objetos que estão vinculados a nomes no módulo dicionário. A função init de módulos de extensão não é chamado de segunda vez. Como com todos os outros objetos em Python, os objetos antigos são apenas recuperados após a sua contagem de referência cair para zero. Os nomes no módulo namespace são atualizados para apontar para qualquer objetos novos ou modificados. Outros referências aos objetos antigos (tais como nomes externos ao módulo) não são ressalto para se referir aos novos objetos e deve ser atualizado em cada namespace onde elas ocorrem se isso for desejado.
Como você observou na sua pergunta, você terá que reconstruir os objetos Foo
se a classe Foo
residir no módulo foo
.
Pode ser especialmente difícil apagar um módulo se ele não for puro Python.
Aqui estão algumas informações de: Como posso realmente apagar um módulo importado?
Você pode usar sys.getrefcount() para descobrir o número real de referências.
>>> import sys, empty, os
>>> sys.getrefcount(sys)
9
>>> sys.getrefcount(os)
6
>>> sys.getrefcount(empty)
3
Números superiores a 3 indicam que vai ser difícil livrar-se do módulo. O "vazio" caseiro (não contendo nada) módulo deve ser lixo coletado após
>>> del sys.modules["empty"]
>>> del empty
uma vez que a terceira referência é um artefacto da função getrefcount().
reload(module)
, mas somente se for completamente autônomo. Se qualquer outra coisa tiver uma referência ao módulo (ou qualquer objeto pertencente ao módulo), então você receberá erros sutis e curiosos causados pelo antigo código pendurado por mais tempo do que você esperava, e coisas como isinstance
não funcionam em versões diferentes do mesmo código.
Se você tem dependências unidirecionais, você também deve recarregar todos os módulos que dependem do módulo recarregado para se livrar de todas as referências ao código antigo. E depois recarregar os módulos que dependem dos módulos recarregados, recursivamente.
Se você tiver dependências circulares, o que é muito comum, por exemplo, quando você está lidando com a recarga de um pacote, você deve descarregar todos os módulos do grupo de uma só vez. Você não pode fazer isso com reload()
porque ele irá reimportar cada módulo antes que suas dependências tenham sido atualizadas, permitindo que as referências antigas se insiram em novos módulos.
A única maneira de fazer isso neste caso é hackear sys.modules
, o que é meio que sem suporte. Você teria que passar e excluir cada entrada sys.modules' que você queria recarregar na próxima importação, e também excluir entradas cujos valores são
Nenhum' para lidar com um problema de implementação relacionado com o cache de importações relativas falhadas. Não é muito legal, mas desde que você tenha um conjunto de dependências totalmente independente que não deixe referências fora de sua base de código, é viável.
Provavelmente é melhor reiniciar o servidor. :-)