O que é uma alternativa ao execfile no Python 3?

352

Parece que eles cancelaram no Python 3 toda a maneira fácil de carregar rapidamente um script removendo execfile()

Existe uma alternativa óbvia que estou perdendo?

RS
fonte
11
reloadestá de volta, como imp.reloaddesde 3.2.
Dougal 16/05
18
Se você estiver usando o Python, interativamente, considere usar o IPython: %run script_namefunciona com todas as versões do Python.
Michael
11
Como o 3.4 impé importlib (que deve ser importado): importlib.reload(mod_name)importa e executa mod_name.
P. Wormer
3
o que há de errado com o runfile ("filename.py")?
Mousomer 27/11
11
Obrigado @mousomer !! Eu estava precisamente procurando a funcionalidade, runfile()pois precisava executar um script Python que fosse executado em seu próprio espaço para nome (em vez de executar no espaço para nome que chamava ). Minha aplicação: adicione o diretório do script chamado ao caminho do sistema ( sys.path) usando o __file__atributo: se usarmos execfile()ou equivalente no Python 3 ( exec(open('file.py').read())), o script incluído será executado no espaço de nomes de chamada e, portanto, será __file__resolvido com o nome do arquivo de chamada .
mastropi 29/02

Respostas:

389

De acordo com a documentação , em vez de

execfile("./filename") 

Usar

exec(open("./filename").read())

Vejo:

Pedro Vagner
fonte
54
Alguma idéia de por que eles fariam isso? Isso é muito mais detalhado do que antes. Além disso, ele não funciona para mim no Python3.3. Eu recebo "Não existe esse arquivo ou diretório" quando exec (open ('./ some_file'). Read ()). Eu tentei incluir a extensão '.py' e também excluir a './' também
JoeyC
25
Menos trivialmente, isso não fornece números de linha quando exceções são geradas, assim como execfile ().
KDN 27/07/2015
35
Você precisará closelidar com esse arquivo também. Outra razão para não gostar da mudança de python 2.
Rebs
3
@Rebs que você não precisa para fechar o identificador de arquivo nesse exemplo, isso será feito automaticamente (pelo menos em CPython regular)
tiho
4
Os @Rebs nos objetos CPython são coletados como lixo assim que sua contagem de referências chega a 0, apenas as referências circulares podem atrasar isso ( stackoverflow.com/questions/9449489/… ). Nesse caso, isso deve acontecer logo após o retorno de read (). E objetos de arquivo estão fechadas a eliminação (NB: Eu percebo este link diz explicitamente "arquivos sempre fechar", que é de fato boa prática a seguir em geral)
tiho
219

Você deveria ler o arquivo e executar o código sozinho. A corrente 2to3 substitui

execfile("somefile.py", global_vars, local_vars)

Como

with open("somefile.py") as f:
    code = compile(f.read(), "somefile.py", 'exec')
    exec(code, global_vars, local_vars)

(A chamada de compilação não é estritamente necessária, mas associa o nome do arquivo ao objeto de código, facilitando um pouco a depuração.)

Vejo:

Benjamin Peterson
fonte
3
Isso funciona para mim. No entanto, notei que você escreveu os argumentos locais e globais na ordem errada. Na verdade, é: exec (objeto [, globais [, locais]]). Obviamente, se você teve os argumentos invertidos no original, o 2to3 produzirá exatamente o que você disse. :)
Nathan Shively-Sanders
3
Foi com satisfação que descobri que, se você pode omitir global_vars e local_vars, a substituição do python3 aqui também funciona no python2. Mesmo que execseja uma declaração em python2, exec(code)funciona porque os parâmetros são ignorados.
medmunds 5/03/2013
2
+1 para usar a compilação. Meu "somefile.py"continha o inspect.getsourcefile(lambda _: None)que estava falhando sem a compilação, porque o inspectmódulo não conseguia determinar de onde vinha o código.
ArtOfWarfare
16
Isso é ... realmente feio. Alguma idéia de por que eles se livraram do execfile () na 3.x? execfile também facilitou a transmissão de argumentos da linha de comando.
aneccodeal
3
open("somefile.py")pode estar incorreto se somefile.pyusar uma codificação de caracteres diferente de locale.getpreferredencoding(). tokenize.open()poderia ser usado em seu lugar.
JFS
73

Embora exec(open("filename").read())muitas vezes seja fornecido como uma alternativa execfile("filename"), ele perde detalhes importantes execfilesuportados.

A função a seguir para Python3.x é a mais próxima possível de ter o mesmo comportamento de executar um arquivo diretamente. Isso corresponde a correr python /path/to/somefile.py.

def execfile(filepath, globals=None, locals=None):
    if globals is None:
        globals = {}
    globals.update({
        "__file__": filepath,
        "__name__": "__main__",
    })
    with open(filepath, 'rb') as file:
        exec(compile(file.read(), filepath, 'exec'), globals, locals)

# execute the file
execfile("/path/to/somefile.py")

Notas:

  • Usa leitura binária para evitar problemas de codificação
  • Garantido para fechar o arquivo (Python3.x alerta sobre isso)
  • Define __main__, alguns scripts dependem disso para verificar se estão carregando como um módulo ou não, por exemplo.if __name__ == "__main__"
  • A configuração __file__é melhor para mensagens de exceção e alguns scripts são usados __file__para obter os caminhos de outros arquivos em relação a eles.
  • Aceita argumentos globais e locais opcionais, modificando-os no local da mesma execfileforma - para que você possa acessar quaisquer variáveis ​​definidas lendo novamente as variáveis ​​após a execução.

  • Ao contrário do Python2, execfileisso não modifica o namespace atual por padrão. Para isso, você deve passar explicitamente em globals()& locals().

ideasman42
fonte
68

Como sugerido recentemente na lista de discussão python-dev , o módulo runpy pode ser uma alternativa viável. Citando essa mensagem:

https://docs.python.org/3/library/runpy.html#runpy.run_path

import runpy
file_globals = runpy.run_path("file.py")

Existem diferenças sutis para execfile:

  • run_pathsempre cria um novo espaço para nome. Ele executa o código como um módulo, portanto, não há diferença entre globais e locais (é por isso que existe apenas um init_globalsargumento). Os globais são retornados.

    execfileexecutado no espaço para nome atual ou no espaço para nome fornecido. A semântica de localse globals, se fornecida, era semelhante aos locais e globais dentro de uma definição de classe.

  • run_path pode não apenas executar arquivos, mas também ovos e diretórios (consulte a documentação para obter detalhes).

Jonas Schäfer
fonte
11
Por alguma razão, ele exibe na tela muitas informações que não foram solicitadas a serem impressas (' builtins ' etc no Anaconda Python 3). Existe alguma maneira de desativar isso para que apenas as informações que eu imprimo com print () sejam visualizadas?
John Donn
Também é possível obter todas as variáveis ​​no espaço de trabalho atual, em vez de todas elas serem armazenadas file_globals? Isso economizaria a necessidade de digitar o valor file_globals['...']para cada variável.
Adriaan
11
"Além disso, não é garantido que todas as funções e classes definidas pelo código executado funcionem corretamente após o retorno de uma função runpy." Digno de nota, dependendo do seu caso de uso
nodakai
@Adriaan Execute "globals (). Update (file_globals)". Pessoalmente, eu gosto mais dessa solução, porque posso detectar erros antes de decidir atualizar o espaço de trabalho atual.
Ron Kaminsky
@nodakai Obrigado pela informação, eu perdi isso. Nunca tive nenhum problema como esse ainda, imagino o que provavelmente desencadeará isso.
Ron Kaminsky
21

Este é melhor, pois leva os globais e locais do chamador:

import sys
def execfile(filename, globals=None, locals=None):
    if globals is None:
        globals = sys._getframe(1).f_globals
    if locals is None:
        locals = sys._getframe(1).f_locals
    with open(filename, "r") as fh:
        exec(fh.read()+"\n", globals, locals)
Noam
fonte
Na verdade, este é o mais próximo do py2 execfile. Até funcionou para mim ao usar pytests onde outras soluções postadas acima falharam. Valeu! :)
Boriel
17

Você pode escrever sua própria função:

def xfile(afile, globalz=None, localz=None):
    with open(afile, "r") as fh:
        exec(fh.read(), globalz, localz)

Se você realmente precisava ...

Evan Fosmark
fonte
11
-1: a declaração exec não funciona dessa maneira. O código não é executado em nenhuma versão do python.
Nosklo 22/08/09
6
-1: os valores padrão dos parâmetros são avaliados no momento da definição da função, direcionando ambos globalse localsapontam para o espaço para nome global do módulo que contém a definição, execfile()e não para o espaço para nome global e local do chamador. A abordagem correta é usar Nonecomo valor padrão e determinar os globais e locais do chamador por meio dos recursos de introspecção do inspectmódulo.
precisa saber é o seguinte
12

Se o script que você deseja carregar estiver no mesmo diretório que o que você executa, talvez "importar" faça o trabalho?

Se você precisar importar código dinamicamente, vale a pena examinar a função interna __ import__ e o módulo imp .

>>> import sys
>>> sys.path = ['/path/to/script'] + sys.path
>>> __import__('test')
<module 'test' from '/path/to/script/test.pyc'>
>>> __import__('test').run()
'Hello world!'

test.py:

def run():
        return "Hello world!"

Se você estiver usando o Python 3.1 ou posterior, verifique também o importlib .

ascobol
fonte
Esta foi a resposta correta para mim. Este blog faz um bom trabalho explicando importlib dev.to/0xcrypto/dynamic-importing-stuff-in-python--1805
Nick Brady
9

Aqui está o que eu tinha ( filejá está atribuído ao caminho do arquivo com o código fonte nos dois exemplos):

execfile(file)

Aqui está o que eu substituí por:

exec(compile(open(file).read(), file, 'exec'))

Minha parte favorita: a segunda versão funciona muito bem no Python 2 e 3, o que significa que não é necessário adicionar uma lógica dependente da versão.

ArtOfWarfare
fonte
5

Observe que o padrão acima falhará se você estiver usando declarações de codificação PEP-263 que não são ascii ou utf-8. Você precisa encontrar a codificação dos dados e codificá-los corretamente antes de entregá-los a exec ().

class python3Execfile(object):
    def _get_file_encoding(self, filename):
        with open(filename, 'rb') as fp:
            try:
                return tokenize.detect_encoding(fp.readline)[0]
            except SyntaxError:
                return "utf-8"

    def my_execfile(filename):
        globals['__file__'] = filename
        with open(filename, 'r', encoding=self._get_file_encoding(filename)) as fp:
            contents = fp.read()
        if not contents.endswith("\n"):
            # http://bugs.python.org/issue10204
            contents += "\n"
        exec(contents, globals, globals)
Eric
fonte
3
O que é "o padrão acima"? Use os links ao se referir a outras postagens no StackOverflow. Termos de posicionamento relativo como "o acima" não funcionam, pois existem três maneiras diferentes de classificar as respostas (por votos, data ou atividade) e a mais comum (por votos) é volátil. Com o tempo, sua postagem e as publicações em torno da sua terminarão com pontuações diferentes, o que significa que elas serão reorganizadas e essas comparações serão menos úteis.
ArtOfWarfare
Muito bom ponto. E como eu escrevi esta resposta há quase seis meses, presumo que por "padrão acima" eu quis dizer stackoverflow.com/a/2849077/165082 (que infelizmente você precisa clicar para resolver) ou, melhor ainda, a resposta de Noam:
Eric
2
Geralmente, quando quero consultar outras respostas para a mesma pergunta da minha resposta, digito "Resposta de Noam" (por exemplo) e vinculo o texto à resposta a que estou me referindo, apenas para o caso de a resposta ser desassociada do usuário no futuro, IE, porque o usuário altera o nome da conta ou a postagem se torna um wiki comum, porque muitas edições foram feitas nela.
ArtOfWarfare
Como você obtém o URL para uma "resposta" específica em uma postagem, excluindo o nome do autor da resposta?
DevPlayer
Veja a fonte e obtenha o ID. Por exemplo, sua pergunta seria stackoverflow.com/questions/436198/… . Eu sou tudo para um método melhor, mas não vejo nada quando eu pairar perto do comentário
Eric
4

Além disso, embora não seja uma solução Python pura, se você estiver usando IPython (como provavelmente deveria), você pode:

%run /path/to/filename.py

O que é igualmente fácil.

RS
fonte
1

Eu sou apenas um novato aqui, então talvez seja pura sorte se eu encontrar isso:

Depois de tentar executar um script no prompt do intérprete >>> com o comando

    execfile('filename.py')

para o qual eu recebi um "NameError: name 'execfile' não está definido", tentei um método muito básico

    import filename

funcionou bem :-)

Espero que isso possa ser útil e obrigado a todos pelas ótimas dicas, exemplos e todos os trechos de código comentados por especialistas que são uma ótima inspiração para os novatos!

Eu uso o Ubuntu 16.014 LTS x64. Python 3.5.2 (padrão, 17 de novembro de 2016, 17:05:23) [GCC 5.4.0 20160609] no linux

Claude
fonte
0

Para mim, a abordagem mais limpa é usar importlibe importar o arquivo como módulo por caminho, assim:

from importlib import util

def load_file(name, path):
    spec = util.spec_from_file_location(name, path)
    module = util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module

Exemplo de uso

Vamos ter um arquivo foo.py:

print('i got imported')
def hello():
    print('hello from foo')

Agora basta importar e usá-lo como um módulo normal:

>>> foo = load_file('foo', './foo.py')
i got imported
>>> foo.hello()
hello from foo

Eu prefiro essa técnica a abordagens diretas, como exec(open(...))porque ela não bagunça seus espaços de nome ou mexe desnecessariamente $PATH.

Arminius
fonte