如何快速复制文件

30

在Python程序中,最快的复制文件的方法是什么?

使用shutil.copyfile()复制文件的时间至少比使用Windows文件资源管理器或Mac Finder的标准右键点击复制>右键点击粘贴多三倍。是否有更快的替代方法来复制文件?如何加速文件复制过程?(如果文件目的地在网络驱动器上...是否会有所不同...)。

稍后编辑:

这是我最终得出的结果:

def copyWithSubprocess(cmd):        
    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

win=mac=False
if sys.platform.startswith("darwin"):mac=True
elif sys.platform.startswith("win"):win=True

cmd=None
if mac: cmd=['cp', source, dest]
elif win: cmd=['xcopy', source, dest, '/K/O/X']

if cmd: copyWithSubprocess(cmd)

您可以使用本地命令行选项,如Linux和Mac的cp以及Windows的COPY。它们应该与使用GUI时一样快。 - Ecno92
在Windows上,SHFileOperation可以让你使用本地的shell文件复制功能。 - David Heffernan
根据问题中未提及的某些因素,将文件打包成压缩存档可能有益于传输...您是否考虑使用类似rsync的工具? - moooeeeep
1
如果您关心文件所有权和ACL,请不要仅因此原因使用shutil:'在Windows上,文件所有者、ACL和备用数据流不会被复制。 ' - Michael Burns
如果我使用本地操作系统的命令(例如OSX cp),那么我是否应该使用subprocess?在Mac上是否有Python模块可以直接调用cp而无需使用子进程? - alphanumeric
4
值得注意的是,在Python 3.8中,复制文件和目录的函数已被优化,以在几个主要操作系统上更快地工作。 - Morwenn
4个回答

19

我用以下代码得到了不过度优化的最快版本:

class CTError(Exception):
    def __init__(self, errors):
        self.errors = errors

try:
    O_BINARY = os.O_BINARY
except:
    O_BINARY = 0
READ_FLAGS = os.O_RDONLY | O_BINARY
WRITE_FLAGS = os.O_WRONLY | os.O_CREAT | os.O_TRUNC | O_BINARY
BUFFER_SIZE = 128*1024

def copyfile(src, dst):
    try:
        fin = os.open(src, READ_FLAGS)
        stat = os.fstat(fin)
        fout = os.open(dst, WRITE_FLAGS, stat.st_mode)
        for x in iter(lambda: os.read(fin, BUFFER_SIZE), ""):
            os.write(fout, x)
    finally:
        try: os.close(fin)
        except: pass
        try: os.close(fout)
        except: pass

def copytree(src, dst, symlinks=False, ignore=[]):
    names = os.listdir(src)

    if not os.path.exists(dst):
        os.makedirs(dst)
    errors = []
    for name in names:
        if name in ignore:
            continue
        srcname = os.path.join(src, name)
        dstname = os.path.join(dst, name)
        try:
            if symlinks and os.path.islink(srcname):
                linkto = os.readlink(srcname)
                os.symlink(linkto, dstname)
            elif os.path.isdir(srcname):
                copytree(srcname, dstname, symlinks, ignore)
            else:
                copyfile(srcname, dstname)
            # XXX What about devices, sockets etc.?
        except (IOError, os.error), why:
            errors.append((srcname, dstname, str(why)))
        except CTError, err:
            errors.extend(err.errors)
    if errors:
        raise CTError(errors)

这段代码的运行速度比本地 Linux 的 "cp -rf" 稍慢一些。

与 shutil 相比,在本地存储到 tmfps 的增益约为 2 倍至 3 倍,而在 NFS 到本地存储的情况下,则约为 6 倍。

在分析过程中,我注意到 shutil.copy 执行了许多相当重量级的 fstat syscals。如果想进一步优化,我建议先对 src 进行单个 fstat 并重用该值。老实说,由于我的目标并不是针对几百毫秒进行优化,所以我没有继续深入研究。


2
不确定这是否只适用于Python的较新版本(3.5+),但在iter中,哨兵需要是b''才能停止。(至少在OSX上) - muppetjones
@Spencer,你在代码中做了哪些更改以使其与Python 3.5+兼容?最好的问候! - Varlor
啊,因为在 except 行中的 'why' 也会导致语法错误,应该写成 'except (IOError, os.error) as why:'。 - Varlor
为了提高性能,还可以使用 pyfastcopy 模块,它使用系统调用 sendfile()。该模块适用于 Python 2 和 3。您只需要简单地 "import pyfastcopy",然后 shutils 就会自动表现得更好。正如 @Morwenn 上面提到的,Python 3.8 将在其实现中内置 sendfile()。 - Vahid Pazirandeh
我尝试用Python 3.8.0示例中的copyfile替换shutil.copyfile,但似乎在for x in iter循环中挂起了。 - Andry
显示剩余8条评论

6

您可以直接使用正在进行复制操作的操作系统,例如 Windows:

from subprocess import call
call(["xcopy", "c:\\file.txt", "n:\\folder\\", "/K/O/X"])

/K - 复制属性。通常情况下,Xcopy会重置只读属性。
/O - 复制文件所有权和ACL信息。
/X - 复制文件审核设置(意味着/O)。


在Windows上,"xcopy"能够与"常规"子进程一起使用吗?例如:cmd = ['xcopy', source, dest, "/K/O/X"] subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - alphanumeric
那也可以。 - Michael Burns
太好了!感谢你的帮助! - alphanumeric
参数数量无效错误。 - user3600801
请注意,/O和/X标志需要提升的子进程,否则您将会得到“访问被拒绝”的结果。 - Rexovas
这是单文件复制的非常快速的选项,但对于试图线程大量文件的任何人来说,它可能运行得更慢(在最近对4000个文件的测试中慢了9倍)。我通过修改copy2缓冲区大小来获得更好的结果,就像其他答案中所述。 - Spencer

2
import sys
import subprocess

def copyWithSubprocess(cmd):        
    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

cmd=None
if sys.platform.startswith("darwin"): cmd=['cp', source, dest]
elif sys.platform.startswith("win"): cmd=['xcopy', source, dest, '/K/O/X']

if cmd: copyWithSubprocess(cmd)

1
解释经济,但这是一个很好的答案。 - Gustavo Gonçalves

0
这只是一个猜测,但是...你的时间不对...当你复制文件时,它会打开文件并将其全部读入内存,因此当你粘贴时,你只是创建一个文件并倒出你的内存内容。
在Python中。
copied_file = open("some_file").read()

ctrl + c 复制的等效操作

然后

with open("new_file","wb") as f:
     f.write(copied_file)

ctrl + v 粘贴的等效物(所以计算等价时间....)

如果你想让它对更大的数据更具有可扩展性(但速度不会像 ctrl+v/ctrl+c 那样快)

with open(infile,"rb") as fin,open(outfile,"wb") as fout:
     fout.writelines(iter(fin.readline,''))

1
我相信你会是一位优秀的教练,太好了! - Sharif Mamun
好的,我应该更具体一些。不是右键复制然后粘贴:这个模式是:1.选择文件;2.拖动文件;3.将文件放到目标文件夹中。 - alphanumeric
那么就移动它吧...这样会有很大的不同...尝试使用shutil.move代替。 - Joran Beasley
1
这个解决方案不具备可扩展性。随着文件变得越来越大,这个解决方案的可用性会降低。当文件变得很大时,您需要多次调用操作系统来将文件的部分读入内存。 - searchengine27
8
我觉得很难相信,如果你在Windows中使用CTRL + C复制了一个100千字节的文件,它会立即尝试将其加载到内存中…… - Robert Kelly
显示剩余3条评论

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