Como determinar corretamente o diretório de script atual?

260

Gostaria de ver qual é a melhor maneira de determinar o diretório de script atual em python?

Descobri que, devido às muitas maneiras de chamar código python, é difícil encontrar uma boa solução.

Aqui estão alguns problemas:

  • __file__não está definido se o script for executado com exec,execfile
  • __module__ é definido apenas em módulos

Casos de uso:

  • ./myfile.py
  • python myfile.py
  • ./somedir/myfile.py
  • python somedir/myfile.py
  • execfile('myfile.py') (de outro script, que pode estar localizado em outro diretório e que pode ter outro diretório atual.

Sei que não existe uma solução perfeita, mas estou procurando a melhor abordagem que resolva a maioria dos casos.

A abordagem mais usada é, os.path.dirname(os.path.abspath(__file__))mas isso realmente não funciona se você executar o script de outra com exec().

Aviso

Qualquer solução que use o diretório atual falhará, isso pode ser diferente com base na maneira como o script é chamado ou pode ser alterado dentro do script em execução.

Bogdan
fonte
1
Você pode ser mais específico de onde precisa saber de onde vem o arquivo? - no código que está importando o arquivo (host com reconhecimento de inclusão) ou no arquivo importado? (escravo autoconsciente)
synthesizerpatel
3
Veja a pathlibsolução de Ron Kalian se você estiver usando o python 3.4 ou superior: stackoverflow.com/a/48931294/1011724
Dan
Portanto, a solução NÃO é usar nenhum diretório atual no código, mas usar algum arquivo de configuração?
precisa saber é o seguinte
Descoberta interessante, que acabei de fazer: Ao fazer a python myfile.pypartir de um shell, ele funciona, mas ambos :!python %e :!python myfile.pyde dentro do vim falham com O sistema não consegue encontrar o caminho especificado. Isso é muito chato. Alguém pode comentar sobre o motivo por trás disso e possíveis soluções alternativas?
inVader 27/11

Respostas:

231
os.path.dirname(os.path.abspath(__file__))

é realmente o melhor que você vai conseguir.

É incomum executar um script com exec/ execfile; normalmente você deve usar a infraestrutura do módulo para carregar scripts. Se você deve usar esses métodos, sugiro definir __file__a opção de globalspassar para o script para que ele possa ler esse nome de arquivo.

Não há outra maneira de obter o nome do arquivo no código executado: como você observa, o CWD pode estar em um local completamente diferente.

bobince
fonte
2
Nunca diga nunca? De acordo com isso: stackoverflow.com/a/18489147 responda que uma solução de plataforma cruzada é abspath (getsourcefile (lambda: 0))? Ou há algo mais que estou perdendo?
11118 Jeff Ellen
131

Se você realmente deseja cobrir o caso pelo qual um script é chamado execfile(...), você pode usar o inspectmódulo para deduzir o nome do arquivo (incluindo o caminho). Tanto quanto sei, isso funcionará para todos os casos que você listou:

filename = inspect.getframeinfo(inspect.currentframe()).filename
path = os.path.dirname(os.path.abspath(filename))
Sven Marnach
fonte
4
Acho que esse é realmente o método mais robusto, mas questiono a necessidade declarada do OP. Muitas vezes vejo desenvolvedores fazer isso quando estão usando arquivos de dados em locais relativos ao módulo em execução, mas os arquivos de dados IMO devem ser colocados em um local conhecido.
Ryan Ginstrom
14
@Ryan LOL, se seria ótimo se você pudesse definir um "local conhecido" que seja multiplataforma e que também venha com o módulo. Estou pronto para apostar que o único local seguro é o local do script. Observe que isso não exige que o script grave nesse local, mas para a leitura de dados é seguro.
sorin
1
Ainda assim, a solução não é boa, basta tentar ligar chdir()antes da função, isso mudará o resultado. Também chamar o script python de outro diretório alterará o resultado, portanto não é uma boa solução.
Sorin
2
os.path.expanduser("~")é uma maneira multiplataforma de obter o diretório do usuário. Infelizmente, essa não é a melhor prática do Windows para onde colar dados de aplicativos.
Ryan Ginstrom
6
@sorin: Eu tentei chdir()antes de executar o script; produz resultado correto. Eu tentei chamar o script de outro diretório e também funciona. Os resultados são os mesmos inspect.getabsfile()da solução baseada em .
JFS
43
#!/usr/bin/env python
import inspect
import os
import sys

def get_script_dir(follow_symlinks=True):
    if getattr(sys, 'frozen', False): # py2exe, PyInstaller, cx_Freeze
        path = os.path.abspath(sys.executable)
    else:
        path = inspect.getabsfile(get_script_dir)
    if follow_symlinks:
        path = os.path.realpath(path)
    return os.path.dirname(path)

print(get_script_dir())

Funciona em CPython, Jython, Pypy. Funciona se o script for executado usando execfile()(as soluções baseadas em sys.argv[0]e __file__falhariam aqui). Funciona se o script estiver dentro de um arquivo zip executável (/ um ovo) . Funciona se o script for "importado" ( PYTHONPATH=/path/to/library.zip python -mscript_to_run) de um arquivo zip; retorna o caminho do arquivo morto neste caso. Funciona se o script for compilado em um executável independente ( sys.frozen). Funciona para links simbólicos ( realpathelimina links simbólicos). Funciona em um intérprete interativo; retorna o diretório de trabalho atual neste caso.

jfs
fonte
Funciona perfeitamente bem com o PyInstaller.
gaborous
1
Existe algum motivo para getabsfile(..)não ser mencionado na documentaçãoinspect ? Ele aparece na fonte vinculada a essa página.
precisa
@EvgeniSergeev pode ser um bug. É um invólucro simples getsourcefile(), getfile()documentado.
JFS
24

No Python 3.4+, você pode usar o pathlibmódulo mais simples :

from inspect import currentframe, getframeinfo
from pathlib import Path

filename = getframeinfo(currentframe()).filename
parent = Path(filename).resolve().parent
Eugene Yarmash
fonte
2
Excelente simplicidade!
Cometsong 22/02
3
Você provavelmente pode usar Path(__file__)(não há necessidade do inspectmódulo).
Peque 21/03
A @Peque fazendo isso produz um caminho que inclui o nome do arquivo atual, não o diretório pai. Se eu estou tentando obter o diretório script atual, a fim de apontar para um arquivo no mesmo diretório, por exemplo, esperando para carregar um arquivo de configuração no mesmo diretório como o roteiro, Path(__file__)/path/to/script/currentscript.pyquando o OP queria chegar/path/to/script/
Davos
8
Ah, eu entendi errado, você quer evitar o módulo de inspeção e usar algo como parent = Path(__file__).resolve().parent Isso é muito melhor.
Davos
3
@Dut A. Você deve usar .joinpath()(ou o /operador) para isso, não +.
Eugene Yarmash
13

A os.path...abordagem foi a 'coisa feita' no Python 2.

No Python 3, você pode encontrar o diretório do script da seguinte maneira:

from pathlib import Path
cwd = Path(__file__).parents[0]
Ron Kalian
fonte
11
Ou apenas Path(__file__).parent. Mas cwdé um nome impróprio, esse não é o diretório de trabalho atual , mas o diretório do arquivo . Eles podem ser os mesmos, mas geralmente não é o caso.
Nuno André
5

Basta usar os.path.dirname(os.path.abspath(__file__))e examinar com muito cuidado se existe uma necessidade real do caso em que execé usado. Pode ser um sinal de design problemático se você não conseguir usar o script como um módulo.

Lembre-se do Zen do Python # 8 , e se você acredita que há um bom argumento para um caso de uso em que ele deve funcionar exec, informe-nos mais alguns detalhes sobre o plano de fundo do problema.

wim
fonte
2
Se você não estiver executando com exec (), perderá o contexto do depurador. Também exec () deve ser consideravelmente mais rápido do que iniciar um novo processo.
Sorin
@sorin Não é uma questão de exec vs iniciar um novo processo, então esse é um argumento simples. É uma questão de exec vs usando uma chamada de importação ou função.
Wim
4

Seria

import os
cwd = os.getcwd()

faça o que você quiser? Não sei o que exatamente você quer dizer com o "diretório de scripts atual". Qual seria a saída esperada para os casos de uso que você forneceu?

Will McCutchen
fonte
3
Isso não ajudaria. Acredito que @bogdan está procurando o diretório do script que está no topo da pilha de chamadas. ou seja, em todos os casos, ele deve imprimir o diretório em que 'myfile.py' fica. No entanto, seu método imprimiria apenas o diretório do arquivo que chama exec('myfile.py'), o mesmo que __file__e sys.argv[0].
precisa saber é o seguinte
Sim, isso faz sentido. Eu só queria ter certeza de que o @bogdan não estava ignorando algo simples, e não sabia exatamente o que eles queriam.
Will McCutchen
3

Primeiro .. alguns casos de uso ausentes aqui, se estamos falando de maneiras de injetar código anônimo ..

code.compile_command()
code.interact()
imp.load_compiled()
imp.load_dynamic()
imp.load_module()
__builtin__.compile()
loading C compiled shared objects? example: _socket?)

Mas, a verdadeira questão é: qual é o seu objetivo - você está tentando impor algum tipo de segurança? Ou você está apenas interessado no que está sendo carregado.

Se você estiver interessado em segurança , o nome do arquivo que está sendo importado via exec / execfile é irrelevante - você deve usar o rexec , que oferece o seguinte:

Este módulo contém a classe RExec, que suporta os métodos r_eval (), r_execfile (), r_exec () e r_import (), que são versões restritas das funções padrão do Python eval (), execfile () e as instruções exec e import. O código executado neste ambiente restrito somente terá acesso aos módulos e funções que são considerados seguros; você pode subclassar o RExec para adicionar ou remover recursos, conforme desejado.

No entanto, se isso é mais uma busca acadêmica ... aqui estão algumas abordagens patetas que você poderá aprofundar um pouco ..

Scripts de exemplo:

./deep.py

print ' >> level 1'
execfile('deeper.py')
print ' << level 1'

./deeper.py

print '\t >> level 2'
exec("import sys; sys.path.append('/tmp'); import deepest")
print '\t << level 2'

/tmp/deepest.py

print '\t\t >> level 3'
print '\t\t\t I can see the earths core.'
print '\t\t << level 3'

./codespy.py

import sys, os

def overseer(frame, event, arg):
    print "loaded(%s)" % os.path.abspath(frame.f_code.co_filename)

sys.settrace(overseer)
execfile("deep.py")
sys.exit(0)

Resultado

loaded(/Users/synthesizerpatel/deep.py)
>> level 1
loaded(/Users/synthesizerpatel/deeper.py)
    >> level 2
loaded(/Users/synthesizerpatel/<string>)
loaded(/tmp/deepest.py)
        >> level 3
            I can see the earths core.
        << level 3
    << level 2
<< level 1

Obviamente, essa é uma maneira que exige muitos recursos, pois você rastreará todo o seu código. Não é muito eficiente. Mas acho que é uma abordagem inovadora, pois continua a funcionar mesmo quando você se aprofunda no ninho. Você não pode substituir 'eval'. Embora você possa substituir execfile ().

Observe que essa abordagem abrange apenas exec / execfile, não 'importação'. Para um gancho de carga de 'módulo' de nível superior, você pode usar o uso sys.path_hooks (gravação cortesia de PyMOTW).

É tudo o que tenho em cima da minha cabeça.

synthesizerpatel
fonte
2

Aqui está uma solução parcial, ainda melhor do que todas as publicadas até agora.

import sys, os, os.path, inspect

#os.chdir("..")

if '__file__' not in locals():
    __file__ = inspect.getframeinfo(inspect.currentframe())[0]

print os.path.dirname(os.path.abspath(__file__))

Agora isso funciona todas as chamadas, mas se alguém usar chdir()para alterar o diretório atual, isso também falhará.

Notas:

  • sys.argv[0]não funcionar, retornará -cse você executar o script compython -c "execfile('path-tester.py')"
  • Publiquei um teste completo em https://gist.github.com/1385555 e você pode aprimorá-lo.
sorin
fonte
1

Isso deve funcionar na maioria dos casos:

import os,sys
dirname=os.path.dirname(os.path.realpath(sys.argv[0]))
Jahid
fonte
5
Esta solução usa o diretório atual e é explicitamente declarado na pergunta que tal solução falhará.
skyking 6/12/16
1

Espero que isso ajude: - Se você executar um script / módulo de qualquer lugar, poderá acessar a __file__variável, que é uma variável de módulo que representa a localização do script.

Por outro lado, se você estiver usando o intérprete, não terá acesso a essa variável, onde receberá um nome NameErrore os.getcwd()fornecerá o diretório incorreto se estiver executando o arquivo em outro local.

Esta solução deve fornecer o que você procura em todos os casos:

from inspect import getsourcefile
from os.path import abspath
abspath(getsourcefile(lambda:0))

Ainda não o testei completamente, mas resolveu o meu problema.

Marca
fonte
Isso dará arquivo, não diretório
Shital Shah 17/04