Zanurkuj w Pythonie/Znajdowanie ścieżki

Czasami, kiedy uruchomimy skrypt języka Python z linii poleceń, chcielibyśmy wiedzieć, w jakim miejscu na dysku ten skrypt się znajduje.

To jeden z tych brzydkich, małych trików, które ciężko wymyślić samemu (o ile to w ogóle możliwe), ale za to łatwo zapamiętać, jeśli już się to zobaczy. Kluczem do tego problemu jest sys.argv. Jak widzieliście w rozdziale 9 ("Przetwarzanie XML"), jest to lista przechowująca argumenty linii poleceń. Dodatkowo, lista ta przechowuje również nazwę uruchamianego programu, dokładnie taką, jaka została przekazana w linii poleceń, a na jej podstawie można już ustalić położenie programu na dysku.

Przykład 16.3. fullpath.py

Jeśli jeszcze tego nie zrobiliście, możecie pobrać ten oraz inne przykłady używane w tej książce.

import sys, os

print 'sys.argv[0] =', sys.argv[0]             #(1)
pathname = os.path.dirname(sys.argv[0])        #(2)
print 'path =', pathname
print 'full path =', os.path.abspath(pathname) #(3)
  1. Niezależnie od tego, w jaki sposób uruchomicie skrypt, sys.argv[0] będzie zawsze zawierać nazwę skryptu, w dokładnie takiej postaci, w jakiej pojawiła się ona w linii poleceń. Jak wkrótce zobaczymy, nazwa może, choć nie musi, zawierać informację o pełnej ścieżce.
  2. os.path.dirname pobiera napis zawierający nazwę pliku i zwraca fragment tego napisu zawierający ścieżkę do katalogu, w którym plik się znajduje. Jeśli podana nazwa pliku nie zawiera informacji o ścieżce, wywołanie os.path.dirname zwróci napis pusty.
  3. Kluczową funkcją jest os.path.abspath. Pobiera ona nazwę ścieżkową, która może być częściowa (względna) lub pusta, i zwraca pełną kwalifikowaną nazwę ścieżkową.

Funkcja os.path.abspath wymaga pewnych wyjaśnień. Jest ona bardzo elastyczna i może przyjmować nazwy ścieżkowe w dowolnej postaci.

Przykład 16.4. Dalsze wyjaśnienia dotyczące os.path.abspath

>>> import os
>>> os.getcwd()                        #(1)
/home/you
>>> os.path.abspath( )                #(2)
/home/you
>>> os.path.abspath('.ssh')            #(3)
/home/you/.ssh
>>> os.path.abspath('/home/you/.ssh')  #(4)
/home/you/.ssh
>>> os.path.abspath('.ssh/../foo/')    #(5)
/home/you/foo

  1. os.getcwd() zwraca bieżący katalog roboczy.
  2. Wywołanie os.path.abspath z napisem pustym zwraca bieżący katalog roboczy, tak samo jak os.getcwd().
  3. Wywołanie os.path.abspath z częściową nazwą ścieżkową powoduje skonstruowanie pełnej kwalifikowanej nazwy ścieżkowej w oparciu o bieżący katalog roboczy.
  4. Wywołanie os.path.abspath z pełną nazwą ścieżkową zwraca tę nazwę.
  5. os.path.abspath normalizuje nazwę ścieżkową, którą zwraca. Zwróćcie uwagę, że powyższy przykład będzie działał nawet wówczas, jeśli katalog "foo" nie istnieje. Funkcja os.path.abspath nigdy nie sprawdza istnienia elementów składowych ścieżki na dysku; dokonuje ona jedynie manipulacji na napisach.

Przykład 16.5. Przykładowe wyjście z programu fullpath.py

[you@localhost py]$ python /home/you/diveintopython/common/py/fullpath.py #(1)
sys.argv[0] = /home/you/diveintopython/common/py/fullpath.py
path = /home/you/diveintopython/common/py
full path = /home/you/diveintopython/common/py
[you@localhost diveintopython]$ python common/py/fullpath.py               #(2)
sys.argv[0] = common/py/fullpath.py
path = common/py
full path = /home/you/diveintopython/common/py
[you@localhost diveintopython]$ cd common/py
[you@localhost py]$ python fullpath.py                                     #(3)
sys.argv[0] = fullpath.py
path = 
full path = /home/you/diveintopython/common/py
  1. W pierwszym przypadku sys.argv[0] zawiera pełną ścieżkę do skryptu. Można użyć funkcji os.path.dirname w celu usunięcia nazwy skryptu, otrzymując pełną ścieżkę do katalogu, w którym znajduje się skrypt. Funkcja os.path.abspath zwraca dokładnie to samo, co otrzymała na wejściu.
  2. Jeśli skrypt jest uruchomiony przy użyciu ścieżki względnej, sys.argv[0] w dalszym ciągu zwraca dokładnie to, co pojawiło się w linii poleceń. Wywołanie os.path.dirname zwróci częściową nazwę ścieżkową (ścieżkę względną względem bieżącego katalogu), natomiast os.path.abspath z częściowej nazwy ścieżkowej skonstruuje pełną ścieżkę (ścieżkę bezwzględną).
  3. Jeśli skrypt jest uruchomiony z bieżącego katalogu bez podawania jakiejkolwiek ścieżki, os.dir.pathname zwróci po prostu pusty napis. Podając pusty napis do os.path.abspath otrzymamy ścieżkę do bieżącego katalogu, a tego dokładnie oczekujemy, ponieważ z tego właśnie katalogu uruchamialiśmy skrypt.

Dodatek. Jeden z czytelników był rozczarowany zaprezentowanym wyżej rozwiązaniem, ponieważ chciał uruchomić wszystkie testy jednostkowe znajdujące się w bieżącym katalogu, niekoniecznie zaś w katalogu, w którym umieszczony jest program regression.py. Zasugerował on następujące podejście:

Przykład 16.6. Uruchomienie skryptu z bieżącego katalogu

import sys, os, re, unittest

def regressionTest():
    path = os.getcwd()       #(1)
    sys.path.append(path)    #(2)
    files = os.listdir(path) #(3)
  1. Zamiast ustalania ścieżki z testami na katalog, w którym znajduje się obecnie wykonywany skrypt, ustalamy ją na bieżący katalog roboczy. Będzie to ten katalog, w którym byliśmy w momencie uruchomienia skryptu, a więc niekoniecznie oznacza katalog, w którym znajduje się skrypt. (Jeśli nie chwytasz tego od razu, przeczytaj to zdanie powoli kilka razy).
  2. Dodajemy tę ścieżkę do ścieżki wyszukiwania bibliotek języka Python, dzięki czemu w momencie dynamicznego importowania modułów z testami jednostkowymi Python będzie mógł je odnaleźć. Nie trzeba było tego robić w sytuacji, w której ścieżką z testami była ścieżka do uruchomionego skryptu, ponieważ Python zawsze przeszukuje katalog, w którym znajduje się uruchomiony skrypt.
  3. Pozostała część funkcji pozostaje bez zmian.

Dzięki tej technice możliwe jest powtórne użycie skryptu regression.py w wielu projektach. Wystarczy umieścić skrypt w pewnym katalogu wspólnym dla wielu projektów, a następnie, przed jego uruchomieniem, zmienić katalog na katalog projektu, którego testy chcemy uruchomić. Po uruchomieniu skryptu zostaną odnalezione i uruchomione wszystkie testy projektu, znajdujące się w katalogu projektu, nie zaś testy znajdujące się w katalogu wspólnym dla projektów, w którym umieszczony został skrypt.