Come determinare correttamente la directory di script corrente?


261

Vorrei vedere qual è il modo migliore per determinare l'attuale directory di script in Python?

Ho scoperto che, a causa dei molti modi di chiamare il codice Python, è difficile trovare una buona soluzione.

Ecco alcuni problemi:

  • __file__non è definito se lo script viene eseguito con exec,execfile
  • __module__ è definito solo in moduli

Casi d'uso:

  • ./myfile.py
  • python myfile.py
  • ./somedir/myfile.py
  • python somedir/myfile.py
  • execfile('myfile.py') (da un altro script, che può trovarsi in un'altra directory e che può avere un'altra directory corrente.

So che non esiste una soluzione perfetta, ma sto cercando l'approccio migliore che risolva la maggior parte dei casi.

L'approccio più usato è os.path.dirname(os.path.abspath(__file__))ma questo in realtà non funziona se si esegue lo script da un altro con exec().

avvertimento

Qualsiasi soluzione che utilizza la directory corrente non funzionerà, ciò può variare in base al modo in cui viene chiamato lo script o può essere modificato all'interno dello script in esecuzione.


1
Puoi essere più specifico su dove devi sapere da dove proviene il file? - nel codice che importa il file (include l'host che riconosce) o nel file che viene importato? (schiavo autocosciente)
synthesizerpatel

3
Vedi la pathlibsoluzione di Ron Kalian se stai usando Python 3.4 o versioni successive: stackoverflow.com/a/48931294/1011724
Dan

Quindi la soluzione NON è usare alcuna directory corrente nel codice, ma usare un file di configurazione?
ZhaoGang,

Interessante scoperta, ho appena fatto: quando si esegue python myfile.pyda una shell, funziona, ma entrambi :!python %e :!python myfile.pyall'interno di VIM falliscono con Il sistema non riesce a trovare il percorso specificato. Questo è abbastanza fastidioso. Qualcuno può commentare il motivo dietro questo e potenziali soluzioni alternative?
inVader,

Risposte:


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

è davvero il migliore che otterrai.

È insolito eseguire uno script con exec/ execfile; normalmente dovresti usare l'infrastruttura del modulo per caricare gli script. Se devi usare questi metodi, ti suggerisco di impostare __file__il globalspassaggio allo script in modo che possa leggere quel nome file.

Non c'è altro modo per ottenere il nome file nel codice eseguito: come noti, il CWD potrebbe trovarsi in una posizione completamente diversa.


2
Mai dire mai? Secondo questo: stackoverflow.com/a/18489147 rispondere a una soluzione multipiattaforma è abspath (getsourcefile (lambda: 0))? O c'è qualcos'altro che mi manca?
Jeff Ellen,

131

Se vuoi davvero coprire il caso in cui viene chiamato uno script execfile(...), puoi usare il inspectmodulo per dedurre il nome del file (incluso il percorso). Per quanto ne so, questo funzionerà per tutti i casi che hai elencato:

filename = inspect.getframeinfo(inspect.currentframe()).filename
path = os.path.dirname(os.path.abspath(filename))

4
Penso che questo sia davvero il metodo più efficace, ma metto in dubbio la necessità dichiarata del PO di farlo. Vedo spesso che gli sviluppatori lo fanno quando usano file di dati in posizioni relative al modulo in esecuzione, ma i file di dati IMO dovrebbero essere collocati in una posizione nota.
Ryan Ginstrom,

14
@Ryan LOL, se sarebbe fantastico se si fosse in grado di definire una "posizione nota" che è multipiattaforma e che viene fornita con il modulo. Sono pronto a scommettere che l'unica posizione sicura è la posizione dello script. Nota, questo non rispetta il fatto che lo script dovrebbe scrivere in questa posizione, ma per leggere i dati è sicuro.
sorin,

1
Tuttavia, la soluzione non è buona, prova a chiamare chdir()prima della funzione, cambierà il risultato. Anche la chiamata dello script Python da un'altra directory modificherà il risultato, quindi non è una buona soluzione.
sorin,

2
os.path.expanduser("~")è un modo multipiattaforma per ottenere la directory dell'utente. Sfortunatamente, questa non è la migliore procedura di Windows per dove incollare i dati delle applicazioni.
Ryan Ginstrom,

6
@sorin: ho provato chdir()prima di eseguire lo script; produce un risultato corretto. Ho provato a chiamare lo script da un'altra directory e funziona anche. I risultati sono gli stessi della inspect.getabsfile()soluzione basata su .
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())

Funziona su CPython, Jython, Pypy. Funziona se lo script viene eseguito utilizzando execfile()( sys.argv[0]e le __file__soluzioni basate su base fallirebbero qui). Funziona se lo script si trova all'interno di un file zip eseguibile (/ un uovo) . Funziona se lo script è "importato" ( PYTHONPATH=/path/to/library.zip python -mscript_to_run) da un file zip; in questo caso restituisce il percorso dell'archivio. Funziona se lo script è compilato in un eseguibile standalone ( sys.frozen). Funziona con realpathcollegamenti simbolici ( elimina i collegamenti simbolici). Funziona in un interprete interattivo; in questo caso restituisce la directory di lavoro corrente.


Funziona perfettamente con PyInstaller.
gaborous

1
C'è qualche motivo per cui getabsfile(..)non è menzionato nella documentazione perinspect ? Appare nella fonte collegata a quella pagina.
Evgeni Sergeev,

@EvgeniSergeev potrebbe essere un bug. È un semplice wrapper in giro getsourcefile(), getfile()che sono documentati.
jfs,

24

In Python 3.4+ puoi usare il pathlibmodulo più semplice :

from inspect import currentframe, getframeinfo
from pathlib import Path

filename = getframeinfo(currentframe()).filename
parent = Path(filename).resolve().parent

2
Eccellente semplicità!
Cometsong,

3
Probabilmente è possibile utilizzare Path(__file__)(non è necessario per il inspectmodulo).
Peque,

@Peque farlo produce un percorso che include il nome del file corrente, non la directory principale. Se sto cercando di ottenere la directory di script corrente per puntare a un file nella stessa directory, ad esempio aspettandomi di caricare un file di configurazione nella stessa directory dello script, Path(__file__)/path/to/script/currentscript.pyquando l'OP voleva ottenere/path/to/script/
Davos,

8
Oh, ho frainteso, intendi evitare il modulo Inspect e usare semplicemente qualcosa parent = Path(__file__).resolve().parent di molto più bello.
Davos,

3
@Dut A. Dovresti usare .joinpath()(o l' /operatore) per questo, no +.
Eugene Yarmash,

13

L' os.path...approccio era la "cosa fatta" in Python 2.

In Python 3, puoi trovare la directory di script come segue:

from pathlib import Path
cwd = Path(__file__).parents[0]

11
O semplicemente Path(__file__).parent. Ma cwdè un termine improprio, non è la directory di lavoro corrente , ma la directory del file . Potrebbero essere uguali, ma di solito non è così.
Nuno André,

5

Basta usare os.path.dirname(os.path.abspath(__file__))ed esaminare con molta attenzione se esiste una reale necessità per il caso in cui execviene utilizzato. Potrebbe essere un segno di design problematico se non sei in grado di utilizzare lo script come modulo.

Tieni a mente Zen of Python # 8 e, se ritieni che ci sia un buon argomento per un caso d'uso in cui deve funzionare exec, ti preghiamo di farci sapere qualche dettaglio in più sullo sfondo del problema.


2
Se non si esegue con exec () si perderà il contesto del debugger. Inoltre, exec () dovrebbe essere considerevolmente più veloce dell'avvio di un nuovo processo.
sorin,

@sorin Non è una questione di exec vs avvio di un nuovo processo, quindi questo è un argomento di paglia. È una questione di exec vs utilizzo di una chiamata di importazione o di funzione.
mercoledì

4

Voluto

import os
cwd = os.getcwd()

Fai quello che vuoi? Non sono sicuro di cosa significhi esattamente la "directory di script corrente". Quale sarebbe l'output previsto per i casi d'uso forniti?


3
Non sarebbe d'aiuto. Credo che @bogdan stia cercando la directory per lo script che si trova in cima allo stack delle chiamate. vale a dire in tutti i suoi casi, dovrebbe stampare la directory in cui si trova "myfile.py". Tuttavia, il tuo metodo stampa solo la directory del file che chiama exec('myfile.py'), lo stesso di __file__e sys.argv[0].
Zhang18,

Sì, ha senso. Volevo solo assicurarmi che @bogdan non trascurasse qualcosa di semplice e non sapevo dire esattamente cosa volessero.
Will McCutchen,

3

Innanzitutto ... un paio di casi d'uso mancanti qui se stiamo parlando di modi per iniettare codice anonimo ..

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

Ma la vera domanda è: qual è il tuo obiettivo: stai cercando di applicare una sorta di sicurezza? Oppure sei solo interessato a cosa viene caricato.

Se sei interessato alla sicurezza , il nome del file che viene importato tramite exec / execfile non ha importanza: dovresti usare rexec , che offre quanto segue:

Questo modulo contiene la classe RExec, che supporta i metodi r_eval (), r_execfile (), r_exec () e r_import (), che sono versioni limitate delle funzioni standard di Python eval (), execfile () e le istruzioni exec e import. Il codice eseguito in questo ambiente limitato avrà accesso solo a moduli e funzioni ritenute sicure; è possibile sottoclassare RExec aggiungere o rimuovere funzionalità come desiderato.

Tuttavia, se questo è più di una ricerca accademica ... ecco un paio di approcci sciocchi che potresti essere in grado di scavare un po 'più a fondo in ..

Script di esempio:

./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)

Produzione

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

Certo, questo è un modo ad alta intensità di risorse per farlo, tracceresti tutto il tuo codice .. Non molto efficiente. Ma penso che sia un approccio nuovo dal momento che continua a funzionare anche man mano che approfondisci il nido. Non puoi ignorare "eval". Sebbene sia possibile sovrascrivere execfile ().

Nota, questo approccio copre solo exec / execfile, non 'import'. Per l'aggancio del 'modulo' di livello superiore potresti essere in grado di usare sys.path_hooks (Scrittura per gentile concessione di PyMOTW).

Questo è tutto quello che ho in cima alla mia testa.


2

Ecco una soluzione parziale, ancora migliore di tutte quelle pubblicate finora.

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__))

Ora funzionerà tutte le chiamate ma se qualcuno usa chdir()per cambiare la directory corrente, anche questo fallirà.

Appunti:

  • sys.argv[0]non funzionerà, tornerà -cse si esegue lo script conpython -c "execfile('path-tester.py')"
  • Ho pubblicato un test completo su https://gist.github.com/1385555 e siete invitati a migliorarlo.

1

Questo dovrebbe funzionare nella maggior parte dei casi:

import os,sys
dirname=os.path.dirname(os.path.realpath(sys.argv[0]))

5
Questa soluzione utilizza la directory corrente ed è esplicitamente indicato nella domanda che tale soluzione fallirà.
stelle il

1

Speriamo che questo aiuti: - Se esegui uno script / modulo da qualsiasi luogo, sarai in grado di accedere alla __file__variabile che è una variabile del modulo che rappresenta la posizione dello script.

D'altra parte, se stai usando l'interprete non hai accesso a quella variabile, dove otterrai un nome NameErrore os.getcwd()ti darà la directory errata se stai eseguendo il file da qualche altra parte.

Questa soluzione dovrebbe darti quello che stai cercando in tutti i casi:

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

Non l'ho testato a fondo ma ha risolto il mio problema.


Questo darà file, non directory
Shital Shah
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.