16.2. 查找路径

从命令行运行 Python 脚本时,了解当前运行的脚本在磁盘上的位置有时很有用。

这是一个非常隐蔽的小技巧,几乎不可能自己想出来,但是一旦你看到了就很容易记住。关键在于 sys.argv。正如你在第 9 章,*XML 处理*中看到的,这是一个包含命令行参数列表的列表。但是,它也包含运行脚本的名称,与从命令行调用时完全相同,这些信息足以确定其位置。

示例 16.3. fullpath.py

如果您还没有这样做,您可以下载本书中使用的这个示例和其他示例


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 无论您如何运行脚本,sys.argv[0] 始终包含脚本的名称,与其在命令行上显示的名称完全相同。这可能包含也可能不包含任何路径信息,您很快就会看到。
2 os.path.dirname 将文件名作为字符串,并返回目录路径部分。如果给定的文件名不包含任何路径信息,则 os.path.dirname 返回一个空字符串。
3 os.path.abspath 是这里的关键。它接受一个路径名,该路径名可以是部分路径名,甚至可以是空白的,并返回一个完全限定的路径名。

os.path.abspath 需要进一步解释。它非常灵活;它可以接受任何类型的路径名。

示例 16.4. 对 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() 返回当前工作目录。
2 使用空字符串调用 os.path.abspath 将返回当前工作目录,与 os.getcwd() 相同。
3 使用部分路径名调用 os.path.abspath 将根据当前工作目录从中构造一个完全限定的路径名。
4 使用完整路径名调用 os.path.abspath 只会返回它。
5 os.path.abspath 还会对其返回的路径名进行规范化。请注意,即使我实际上没有“foo”目录,此示例也能正常工作。os.path.abspath 从不检查您的实际磁盘;这只是一些字符串操作。
Note
您传递给 os.path.abspath 的路径名和文件名不需要存在。
Note
os.path.abspath 不仅构造完整的路径名,还会对其进行规范化。这意味着,如果您位于 /usr/ 目录中,则 os.path.abspath('bin/../local/bin') 将返回 /usr/local/bin。它通过使路径尽可能简单来规范化路径。如果您只想规范化这样的路径名而不将其转换为完整路径名,请改用 os.path.normpath

示例 16.5. 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 在第一种情况下,sys.argv[0] 包含脚本的完整路径。然后,您可以使用 os.path.dirname 函数去除脚本名称并返回完整的目录名,而 os.path.abspath 只会返回您提供的内容。
2 如果使用部分路径名运行脚本,则 sys.argv[0] 仍将包含命令行上显示的内容。os.path.dirname 然后会为您提供一个部分路径名(相对于当前目录),而 os.path.abspath 将根据该部分路径名构造一个完整的路径名。
3 如果从当前目录运行脚本而不提供任何路径,则 os.path.dirname 将只返回一个空字符串。如果给定一个空字符串,os.path.abspath 将返回当前目录,这就是您想要的,因为脚本是从当前目录运行的。
Note
osos.path 模块中的其他函数一样,os.path.abspath 是跨平台的。如果您在 Windows(使用反斜杠作为路径分隔符)或 Mac OS(使用冒号)上运行,则结果看起来会与我的示例略有不同,但它们仍然可以工作。这就是 os 模块的全部意义所在。

**附录。**一位读者对这个解决方案不满意,他希望能够运行当前目录中的所有单元测试,而不是 regression.py 所在的目录。他建议改用这种方法

示例 16.6. 在当前目录中运行脚本

import sys, os, re, unittest

def regressionTest():
    path = os.getcwd()       1
    sys.path.append(path)    2
    files = os.listdir(path) 3
1 您没有将 path 设置为当前运行脚本所在的目录,而是将其设置为当前工作目录。这将是您在运行脚本之前所在的目录,它不一定与脚本所在的目录相同。(多读几遍这句话,直到您理解为止。)
2 将此目录追加到 Python 库搜索路径中,以便在您稍后动态导入单元测试模块时,Python 可以找到它们。当 path 是当前运行脚本的目录时,您不需要这样做,因为 Python 始终会在该目录中查找。
3 函数的其余部分相同。

这种技术将允许您在多个项目上重复使用此 regression.py 脚本。只需将脚本放在一个公共目录中,然后在运行它之前切换到项目的目录。将找到并测试该项目的所有单元测试,而不是 regression.py 所在的公共目录中的单元测试。