如何在不使用 try 语句的情况下使用 Python 检查文件是否存在?
现在从 Python 3.4 开始可用,使用文件名导入并实例化 Path 对象,并检查 is_file 方法(注意,对于指向常规文件的符号链接,这也会返回 True):
>>> from pathlib import Path
>>> Path('/').is_file()
False
>>> Path('/initrd.img').is_file()
True
>>> Path('/doesnotexist').is_file()
False
如果您使用的是 Python 2,则可以从 pypi pathlib2 反向移植 pathlib 模块,或者从 os.path 模块检查 isfile:
>>> import os
>>> os.path.isfile('/')
False
>>> os.path.isfile('/initrd.img')
True
>>> os.path.isfile('/doesnotexist')
False
现在上面可能是这里最好的实用直接答案,但是可能存在竞争条件(取决于您要完成的工作),并且底层实现使用try,但 Python在其实现中到处使用try。
因为 Python 在任何地方都使用try,所以确实没有理由避免使用它的实现。
但是这个答案的其余部分试图考虑这些警告。
更长,更迂腐的答案
自 Python 3.4 起可用,使用 pathlib 中的新 Path 对象。请注意,.exists 不太正确,因为目录不是文件(除了在 unix 意义上 everything 是文件)。
>>> from pathlib import Path
>>> root = Path('/')
>>> root.exists()
True
所以我们需要使用is_file:
>>> root.is_file()
False
这是is_file的帮助:
is_file(self)
Whether this path is a regular file (also True for symlinks pointing
to regular files).
所以让我们得到一个我们知道是文件的文件:
>>> import tempfile
>>> file = tempfile.NamedTemporaryFile()
>>> filepathobj = Path(file.name)
>>> filepathobj.is_file()
True
>>> filepathobj.exists()
True
默认情况下,NamedTemporaryFile 在关闭时删除文件(并在不再存在引用时自动关闭)。
>>> del file
>>> filepathobj.exists()
False
>>> filepathobj.is_file()
False
如果您深入研究the implementation,您会发现is_file 使用try:
def is_file(self):
"""
Whether this path is a regular file (also True for symlinks pointing
to regular files).
"""
try:
return S_ISREG(self.stat().st_mode)
except OSError as e:
if e.errno not in (ENOENT, ENOTDIR):
raise
# Path doesn't exist or is a broken symlink
# (see https://bitbucket.org/pitrou/pathlib/issue/12/)
return False
比赛条件:为什么我们喜欢尝试
我们喜欢try,因为它避免了竞争条件。使用try,您只需尝试读取您的文件,期待它存在,如果没有,您捕获异常并执行任何有意义的回退行为。
如果您想在尝试读取文件之前检查它是否存在,并且您可能正在删除它,然后您可能正在使用多个线程或进程,或者另一个程序知道该文件并可以将其删除 - 您可能会冒险竞态条件如果你检查它存在的机会,因为你然后竞速在它的条件(它的存在)改变之前打开它。
竞态条件很难调试,因为有一个非常小的窗口可能会导致您的程序失败。
但如果这是您的动机,您可以使用suppress 上下文管理器获取try 语句的值。
在没有 try 语句的情况下避免竞争条件:suppress
Python 3.4 为我们提供了 suppress 上下文管理器(以前的 ignore 上下文管理器),它在语义上以更少的行数完成完全相同的事情,同时(至少在表面上)满足了避免@987654359 的原始要求@声明:
from contextlib import suppress
from pathlib import Path
用法:
>>> with suppress(OSError), Path('doesnotexist').open() as f:
... for line in f:
... print(line)
...
>>>
>>> with suppress(OSError):
... Path('doesnotexist').unlink()
...
>>>
对于早期的 Python,您可以使用自己的 suppress,但没有 try 会比使用更冗长。我确实相信这实际上是在 Python 3.4 之前的任何级别都没有使用 try 的唯一答案,因为它使用上下文管理器代替:
class suppress(object):
def __init__(self, *exceptions):
self.exceptions = exceptions
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
if exc_type is not None:
return issubclass(exc_type, self.exceptions)
尝试一下可能会更容易:
from contextlib import contextmanager
@contextmanager
def suppress(*exceptions):
try:
yield
except exceptions:
pass
其他不符合“无需尝试”要求的选项:
isfile
import os
os.path.isfile(path)
来自docs:
os.path.isfile(path)
如果 path 是现有的常规文件,则返回 True。这遵循象征性的
链接,因此islink() 和isfile() 对于同一路径都可以为真。
但是如果你检查这个函数的source,你会发现它确实使用了一个try语句:
# This follows symbolic links, so both islink() and isdir() can be true
# for the same path on systems that support symlinks
def isfile(path):
"""Test whether a path is a regular file"""
try:
st = os.stat(path)
except os.error:
return False
return stat.S_ISREG(st.st_mode)
>>> OSError is os.error
True
它所做的只是使用给定的路径来查看它是否可以获得关于它的统计信息,捕获OSError,然后检查它是否是一个文件,如果它没有引发异常。
如果您打算对该文件执行某些操作,我建议您直接尝试使用 try-except 来避免竞争条件:
try:
with open(path) as f:
f.read()
except OSError:
pass
os.access
适用于 Unix 和 Windows 的是os.access,但要使用你必须传递标志,它不区分文件和目录。这更多用于测试真正的调用用户是否可以在提升的权限环境中访问:
import os
os.access(path, os.F_OK)
它也遇到与isfile 相同的竞争条件问题。来自docs:
注意:
使用 access() 检查用户是否被授权,例如打开一个文件
在实际这样做之前使用 open() 创建一个安全漏洞,因为
用户可能会利用检查和检查之间的短时间间隔
打开文件来操作它。最好使用 EAFP
技巧。例如:
if os.access("myfile", os.R_OK):
with open("myfile") as fp:
return fp.read()
return "some default data"
最好写成:
try:
fp = open("myfile")
except IOError as e:
if e.errno == errno.EACCES:
return "some default data"
# Not a permission error.
raise
else:
with fp:
return fp.read()
避免使用os.access。它是一个低级函数,与上面讨论的高级对象和函数相比,用户出错的机会更多。
对另一个答案的批评:
另一个答案是关于os.access:
就个人而言,我更喜欢这个,因为在底层,它调用原生 API(通过“${PYTHON_SRC_DIR}/Modules/posixmodule.c”),但它也为可能的用户错误打开了一扇门,而且它不像 Pythonic作为其他变体:
这个答案说它更喜欢一种非 Pythonic、容易出错的方法,没有任何理由。它似乎鼓励用户在不了解它们的情况下使用低级 API。
它还创建了一个上下文管理器,通过无条件返回True,允许所有异常(包括KeyboardInterrupt 和SystemExit!)静默通过,这是隐藏错误的好方法。
这似乎鼓励用户采用不良做法。