How do I check whether a file exists without exceptions?

后端 未结 30 1833
北海茫月
北海茫月 2020-11-21 05:07

How do I check if a file exists or not, without using the try statement?

30条回答
  •  有刺的猬
    2020-11-21 05:36

    How do I check whether a file exists, using Python, without using a try statement?

    Now available since Python 3.4, import and instantiate a Path object with the file name, and check the is_file method (note that this returns True for symlinks pointing to regular files as well):

    >>> from pathlib import Path
    >>> Path('/').is_file()
    False
    >>> Path('/initrd.img').is_file()
    True
    >>> Path('/doesnotexist').is_file()
    False
    

    If you're on Python 2, you can backport the pathlib module from pypi, pathlib2, or otherwise check isfile from the os.path module:

    >>> import os
    >>> os.path.isfile('/')
    False
    >>> os.path.isfile('/initrd.img')
    True
    >>> os.path.isfile('/doesnotexist')
    False
    

    Now the above is probably the best pragmatic direct answer here, but there's the possibility of a race condition (depending on what you're trying to accomplish), and the fact that the underlying implementation uses a try, but Python uses try everywhere in its implementation.

    Because Python uses try everywhere, there's really no reason to avoid an implementation that uses it.

    But the rest of this answer attempts to consider these caveats.

    Longer, much more pedantic answer

    Available since Python 3.4, use the new Path object in pathlib. Note that .exists is not quite right, because directories are not files (except in the unix sense that everything is a file).

    >>> from pathlib import Path
    >>> root = Path('/')
    >>> root.exists()
    True
    

    So we need to use is_file:

    >>> root.is_file()
    False
    

    Here's the help on is_file:

    is_file(self)
        Whether this path is a regular file (also True for symlinks pointing
        to regular files).
    

    So let's get a file that we know is a file:

    >>> import tempfile
    >>> file = tempfile.NamedTemporaryFile()
    >>> filepathobj = Path(file.name)
    >>> filepathobj.is_file()
    True
    >>> filepathobj.exists()
    True
    

    By default, NamedTemporaryFile deletes the file when closed (and will automatically close when no more references exist to it).

    >>> del file
    >>> filepathobj.exists()
    False
    >>> filepathobj.is_file()
    False
    

    If you dig into the implementation, though, you'll see that is_file uses 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
    

    Race Conditions: Why we like try

    We like try because it avoids race conditions. With try, you simply attempt to read your file, expecting it to be there, and if not, you catch the exception and perform whatever fallback behavior makes sense.

    If you want to check that a file exists before you attempt to read it, and you might be deleting it and then you might be using multiple threads or processes, or another program knows about that file and could delete it - you risk the chance of a race condition if you check it exists, because you are then racing to open it before its condition (its existence) changes.

    Race conditions are very hard to debug because there's a very small window in which they can cause your program to fail.

    But if this is your motivation, you can get the value of a try statement by using the suppress context manager.

    Avoiding race conditions without a try statement: suppress

    Python 3.4 gives us the suppress context manager (previously the ignore context manager), which does semantically exactly the same thing in fewer lines, while also (at least superficially) meeting the original ask to avoid a try statement:

    from contextlib import suppress
    from pathlib import Path
    

    Usage:

    >>> with suppress(OSError), Path('doesnotexist').open() as f:
    ...     for line in f:
    ...         print(line)
    ... 
    >>>
    >>> with suppress(OSError):
    ...     Path('doesnotexist').unlink()
    ... 
    >>> 
    

    For earlier Pythons, you could roll your own suppress, but without a try will be more verbose than with. I do believe this actually is the only answer that doesn't use try at any level in the Python that can be applied to prior to Python 3.4 because it uses a context manager instead:

    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)
    

    Perhaps easier with a try:

    from contextlib import contextmanager
    
    @contextmanager
    def suppress(*exceptions):
        try:
            yield
        except exceptions:
            pass
    

    Other options that don't meet the ask for "without try":

    isfile

    import os
    os.path.isfile(path)
    

    from the docs:

    os.path.isfile(path)

    Return True if path is an existing regular file. This follows symbolic links, so both islink() and isfile() can be true for the same path.

    But if you examine the source of this function, you'll see it actually does use a try statement:

    # 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
    

    All it's doing is using the given path to see if it can get stats on it, catching OSError and then checking if it's a file if it didn't raise the exception.

    If you intend to do something with the file, I would suggest directly attempting it with a try-except to avoid a race condition:

    try:
        with open(path) as f:
            f.read()
    except OSError:
        pass
    

    os.access

    Available for Unix and Windows is os.access, but to use you must pass flags, and it does not differentiate between files and directories. This is more used to test if the real invoking user has access in an elevated privilege environment:

    import os
    os.access(path, os.F_OK)
    

    It also suffers from the same race condition problems as isfile. From the docs:

    Note: Using access() to check if a user is authorized to e.g. open a file before actually doing so using open() creates a security hole, because the user might exploit the short time interval between checking and opening the file to manipulate it. It’s preferable to use EAFP techniques. For example:

    if os.access("myfile", os.R_OK):
        with open("myfile") as fp:
            return fp.read()
    return "some default data"
    

    is better written as:

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

    Avoid using os.access. It is a low level function that has more opportunities for user error than the higher level objects and functions discussed above.

    Criticism of another answer:

    Another answer says this about os.access:

    Personally, I prefer this one because under the hood, it calls native APIs (via "${PYTHON_SRC_DIR}/Modules/posixmodule.c"), but it also opens a gate for possible user errors, and it's not as Pythonic as other variants:

    This answer says it prefers a non-Pythonic, error-prone method, with no justification. It seems to encourage users to use low-level APIs without understanding them.

    It also creates a context manager which, by unconditionally returning True, allows all Exceptions (including KeyboardInterrupt and SystemExit!) to pass silently, which is a good way to hide bugs.

    This seems to encourage users to adopt poor practices.

提交回复
热议问题