使用Python中的pathlib复制文件

159

我尝试使用 pathlib 复制一个文件。

import pathlib
import shutil

my_file=pathlib.Path('/etc/hosts')
to_file=pathlib.Path('/tmp/foo')
shutil.copy(my_file, to_file)

我收到了这个异常:

/home/foo_egs_d/bin/python /home/foo_egs_d/src/test-pathlib-copy.py
Traceback (most recent call last):
  File "/home/foo_egs_d/src/test-pathlib-copy.py", line 6, in <module>
    shutil.copy(my_file, to_file)
  File "/usr/lib/python2.7/shutil.py", line 117, in copy
    if os.path.isdir(dst):
  File "/home/foo_egs_d/lib/python2.7/genericpath.py", line 41, in isdir
    st = os.stat(s)
TypeError: coercing to Unicode: need string or buffer, PosixPath found

Process finished with exit code

如何在Python 2.7中使用pathlib复制文件?


11
这在 Python 3.6 上可以正常运行,不会出现错误。 - Anthon
@Anthon 我们使用 Python 2.7。 - guettli
6个回答

184

使用shutil.copy方法:

import pathlib
import shutil

my_file = pathlib.Path('/etc/hosts')
to_file = pathlib.Path('/tmp/foo')

shutil.copy(str(my_file), str(to_file))  # For Python <= 3.7.
shutil.copy(my_file, to_file)  # For Python 3.8+.

问题是,如果你使用Unix/Linux,则pathlib.Path创建一个PosixPath对象,如果你使用Microsoft Windows,则创建WindowsPath对象。

在旧版本的Python中,shutil.copy需要字符串作为其参数。对于它们,请在此处使用str函数。


9
别介意:我原以为pathlib是用来让事情变得更容易的。我想我还是会继续使用像以前那样的普通字符串。 - guettli
69
考虑到Pathlib可以移动/重命名和删除文件,我本来也希望它能够复制文件。 - Andrew Wagner
9
如果我猜的话,@AndrewWagner 的意思是,移动和删除文件仅涉及文件系统操作,只需更新文件系统元数据即可。而复制则涉及实际编写新数据,因此 pathlib 的开发人员可能认为这超出了其范围。 - Tim M.
27
如果有人想知道为什么在Python < 3.9中shutil.move的工作方式不同,这个问题已经在Python 3.9中得到修复,我做出了贡献 :D。 - SwimBikeRun
3
请注意:“旧版Python”指的是Python 3.7,“新版Python”是Python 3.8及以上版本。 - acl
显示剩余7条评论

57
shutil.copy()不能工作的原因是你没有使用最新版本的Python,Python 3.6 shutil.copy()可以处理Path对象(或其子类)。在旧版本的Python中,这会引发一个错误,因为那些实现shutil的函数期望传入字符串类型的参数,而不是pathlib.Path类型的参数。
你实际想要编写的内容应该是:
my_file.copy(to_file)

您可以通过创建 Path 的子类,并适应 my_file 的创建来包含此方法。但我发现将其直接嫁接 / 猴子补丁 / 鸭子打补丁到现有的 pathlib.Path 更容易。

from pathlib import Path


def _copy(self, target):
    import shutil
    assert self.is_file()
    shutil.copy(str(self), str(target))  # str() only there for Python < (3, 6)

Path.copy = _copy

只要在任何位置执行这段代码,就可以了,但必须在调用任何 Path 实例的 .copy 方法之前执行。传递给.copy()的参数可以是文件或目录。


17
据我所知,这被称为Monkey-Patching。为什么pathlib不提供它? - guettli
@guettli 可能是因为有人认为它不必要,就像你必须从shutil导入copy一样。在我的ruamel.std.pathlib库中,我有一系列这样的猴子补丁,以及一些支持过渡到pathlib的例程(随着3.6中的更改,部分例程已经变得多余)。 - Anthon
5
我会说pathlib不提供这个功能,因为这不是它的目的 - 就像os.path并不是为了文件处理本身而存在。据我所知,该模块的功能是关于文件处理和文件元数据,而不是文件处理本身。请注意,此翻译尽可能保持原意,以通俗易懂的方式表达。 - Bram Vanroy
6
@Bram Vanroy “该模块的功能与文件处理和文件元数据有关,但并非关于文件处理” 有些矛盾。你想表达什么意思? - mr.zog
1
@mr.zog,那个评论确实有错误。我的意思是pathlib不适用于执行文件操作,例如复制或移动。 - Bram Vanroy
4
虽然Path主要设计用于表示文件路径,但它也提供了mkdir()和exists()等功能,因此自然会期望它能够做许多shutil所做的事情。一个适当的关注点分离可能会将Path、File和Folder分开处理,其中File/Folder是从Path创建的,并且File具有getPath()等方法。一旦您有了File或Folder,显然在这些对象上使用copy()、copytree()等方法比在Path上更合理。顺便说一下,我尽可能地在无处不用Path。 - Oliver

56

自Python 3.5起,无需导入shutil模块,您可以执行以下操作:

from pathlib import Path

dest = Path('dest')
src = Path('src')
dest.write_bytes(src.read_bytes()) #for binary files
dest.write_text(src.read_text()) #for text files

对于 Python 2.7,pathlib2 提供了 read_bytesread_textwrite_byteswrite_text 方法。

由于文件将被加载到内存中,因此该方法不适用于大于计算机可用内存的文件。

根据评论,可以使用 write_bytesread_bytes 来复制文本文件,但如果您需要在复制时处理编码,则 write_textread_text 具有两个额外参数的优势:

  • encoding 是用于解码或编码文件的编码名称
  • errors 是一个可选字符串,用于指定如何处理编码和解码错误

它们与open() 中的含义相同。


有没有办法用Python 2.7来实现这个? - guettli
2
有一个针对Python 2.7的东西,叫做pathlib2,但我还没有尝试过。https://pypi.python.org/pypi/pathlib2/。源代码中包含了`read_bytes`和`write_bytes`方法,所以我认为它们应该是可用的。 - Jacques Gaudin
@GeorgeSovetov 谢谢,我已经将其添加到答案中。 - Jacques Gaudin
3
使用这种方法,一个足够大的文件会导致Python出现“MemoryError”错误。 shutil.copy没有这个问题,因为它使用shutil.copyfileobj将较大的文件缓冲成更小的块。 - Steven Rumbalski
11
这将复制文件的内容,但不包括文件权限、所有权、访问策略或其他文件元数据。OP的评论实际上表明他/她想要“复制文件”,而不仅仅是其中包含的数据。 - patrick-mooney
显示剩余5条评论

27

如何在Python 3.6中将shutil转换为接受pathlib.Path对象

此答案所述,Python 3.6中的shutil可以接受pathlib.Path对象。

由于这感觉很神奇,我决定调查一下它是如何实现的,看看是否能够在自己的类上重用这个神奇的功能。

这个改进是PEP 519的结果。

这个通用的stdlib功能没有得到一致的更新,因此文档也没有得到一致的更新,包括大部分的shutil,截至3.7版本,只在一个函数中记录了支持。欢迎来到动态类型的乐趣世界。

在文档中记录的地方,stdlib链接到"path-like objects"词汇表

一个表示文件系统路径的对象。路径类对象是一个表示路径的str或bytes对象,或者实现了os.PathLike协议的对象。支持os.PathLike协议的对象可以通过调用os.fspath()函数将其转换为str或bytes文件系统路径;os.fsdecode()和os.fsencode()分别可用于保证str或bytes结果。由PEP 519引入。
然后链接到os.PathLike的文档:
表示文件系统路径的对象的抽象基类,例如pathlib.PurePath。
版本3.6中新增。
abstractmethod __fspath__()
返回对象的文件系统路径表示形式。
该方法只应返回str或bytes对象,优先选择str。
关键实现提交似乎是:

如果你想要实现自己的路径类,可以这样做:

#!/usr/bin/env python3

class MyPath:
    def __init__(self, path):
        self.path = path
    def __fspath__(self):
        return self.path

with open(MyPath('f'), 'w'):
    pass

在Python 3.6.7和Ubuntu 18.10中进行了测试。


8

你可以使用pathlib3x - 它为Python 3.6或更新版本提供了最新的(在编写此答案时为Python 3.11.a0)Python pathlib的向后兼容版本,并提供了一些额外的函数,如copycopy2等...

$> python -m pip install pathlib3x
$> python
>>> import pathlib3x as pathlib
>>> my_file = pathlib.Path('/etc/hosts')
>>> to_file = pathlib.Path('/tmp/foo')
>>> my_file.copy(to_file)

你可以在 Github 或者 PyPi 上找到它。


声明: 我是pathlib3x库的作者。


-5
你可以使用pathlib的重命名方法来代替shutil.move()
import pathlib

my_file = pathlib.Path('/etc/hosts')
to_file = pathlib.Path('/tmp/foo')
my_file.rename(to_file)

51
使用shutil.copy()命令可以复制出两份文件:原始文件和目标文件。而如果使用Path.rename命令,则原始文件将会被覆盖,这与问题的要求无关。 - Anthon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接