Search code examples
pythonfile-renamepathlib

Is Path.replace equivalent to os.replace or shutil.move?


The documentation of the pathlib.Path.replace method states:

Rename this file or directory to the given target. If target points to an existing file or directory, it will be unconditionally replaced.

This lacks a bit of detail. For comparison, here's the documentation of os.replace:

Rename the file or directory src to dst. If dst is a directory, OSError will be raised. If dst exists and is a file, it will be replaced silently if the user has permission. The operation may fail if src and dst are on different filesystems. If successful, the renaming will be an atomic operation (this is a POSIX requirement).

The important part being "The operation may fail if src and dst are on different filesystems". Unlike os.replace, shutil.move does not have this problem:

If the destination is on the current filesystem, then os.rename() is used. Otherwise, src is copied to dst using copy_function and then removed.

So, which of these functions is Path.replace using? Is there any risk of Path.replace failing because the destination is on a different file system?


Solution

  • Path(x).replace(y) just calls os.replace(x, y). You can see this in the source code:

    class _NormalAccessor(_Accessor):
        # [...]
        replace = os.replace
        # [...]
    
    _normal_accessor = _NormalAccessor()
    
    # [...]
    
    class Path(PurePath):
        # [...]
        def _init(self,
                  # Private non-constructor arguments
                  template=None,
                  ):
            self._closed = False
            if template is not None:
                self._accessor = template._accessor
            else:
                self._accessor = _normal_accessor
    
        # [...]
    
        def replace(self, target):
            """
            Rename this path to the given path, clobbering the existing
            destination if it exists.
            """
            if self._closed:
                self._raise_closed()
            self._accessor.replace(self, target)