一、不太完美的开端
最近在审核一个实习生写的Python代码,其中有一个文件复制的功能,代码如下:
with open('source.txt', 'rb') as f, open('target.txt', 'wb') as g:
while True:
block = f.read(8*1024) # 每次复制8KB字节
if not block: # end of file
break
g.write(block)其实这段代码从实现上看没有任何问题,也非常完美,不过这里有一个问题。项目使用了Python语言,而Python语言拥有强大的API后援团。对于复制文件这样的基础操作,不可能没有现成的API,难道非要写n行代码才能搞定吗?尽管代码实现没有任何问题,但有现成的API(这里指的是官方原生的API)为何不用呢?而且Python的API都是经过成千上万人验证的,出错几率很低。如果自己写代码,很可能会引入未知的bug。所以,这里对广大初学者的建议是:如果有现成API,应该尽量使用现成的API,没有必要什么都自己实现(想炫耀自己牛叉的除外)。

据我所知,在Python中,可以实现复杂文件功能的函数至少有10个,也许更多。所以在开发Python项目时,尤其是复杂的项目,了解Python中的API非常必要,不一定所有的API都会使用,但至少要知道Python到底可以实现什么功能,如果用到,再研究具体的用法不迟。这里插播一天广告:我即将推出Python API深度详解系列文章,包含了Python中几乎所有的核心API的用法,这些API是Python中的瑰宝,请大家持续关注哦!
于是我告诉这位同学,编写Python代码应尽可能使用原生API实现,要求他重新编写这段代码,于是这位同学又编写出了下面的代码:
from pathlib import Path
source = Path('source.txt')
destination = Path('target.txt')
destination.write_bytes(source.read_bytes())
open(destination, 'wb').write(open(source, 'rb').read())没错,你是用了新的API,但和复制文件没有直接关系啊!还有,这不还是使用了很多行代码吗!区别只是一次性读取了文件中的所有内容,换汤不换药啊!于是,我又启发这位同学,我希望用一行代码搞定,are you ok?

二、 复制文件的Python API真是太丰富了
那位同学做得怎么样,我还没有看,因为已经到周末了,趁着休息写了这篇文章,顺道总结下关于复制文件的各种Python API,希望那位同学可以在网上搜索到这篇文件,如果没搜索到,就只能自己想了!
Python中至少有3个模块提供了复制文件的函数,这3个模块是shutil、os和subprocess。其中shutil模块提供的是纯的复制文件的函数,而os和subprocess并未直接提供复制文件的函数,而是提供了执行系统命令的函数,通过系统命令可以间接复制文件,例如macOS和Linux的cp命令用于复制文件,而Windows的copy命令用于复制文件。所以如果使用os和subprocess模块中的API复制文件,就要考虑到跨平台特性了。
shutil模块中用于复制文件的主要函数如下:
os和subprocess函数主要是一些用于执行命令的函数,如system、call等,这些在本文后面的内容中会详细介绍。
三、shutil模块,复制文件函数的集中营
shutil模块中有大量的函数可以用来复制文件,这一节将详细介绍这些函数的用法和差异。
1. copyfile函数
该函数的原型如下:
copyfile(src, dst)copyfile函数用于复制文件内容(不包含元数据,如文件的权限)。src参数表示源文件,dst表示目标文件。dst必须是完整的目标文件名。如果src和dst是同一文件,会抛出shutil.Error异常。dst必须是可写的,否则会抛出IOError。如果dst已经存在,该文件会被替换。对于特殊文件,例如字符或块设备和管道不能使用此功能,因为copyfile会打开并阅读文件。
例子:
from shutil import copyfile
# 相对路径
copyfile("test.txt", "xxxx.txt")
# 绝对路径
copyfile("/file/test.txt", "/product/product.txt")2. copy函数
该函数的原型如下:
copy(src, dst)copy函数与copyfile函数类似,都是用于复制文件的,但与copyfile函数有如下两点区别:
(1)dst可以是文件,也可以是目录,如果是目录,则目标文件名与原文件名相同;
(2)copy函数不仅会复制文件内容,也会复制文件的权限。但并不会复制其他的状态信息,如最后访问时间,最后修改时间等;
例子:
from shutil import copy
# dst是目录,会生成/product/test.txt文件
copy("test.txt", "/product")
# det是文件
copy("test.txt", "/product/abcd.txt")3. copy2函数
与copy函数的功能类似,但除了copy函数的功能外,还会复制文件的状态信息,如最后访问时间,最后修改时间等。类似Linux的cp -p命令。
例子:
from shutil import copy2
# dst是目录,会生成/product/test.txt文件
copy2("test.txt", "/product")
# det是文件
copy2("test.txt", "/product/abcd.txt")4. copymode函数
该函数并不复制文件本身,而是复制文件的访问权限,所以dst必须存在。
例子:
from shutil import copymode
# /product/xyz.txt必须存在,复制后,会发现test.txt与xyz.txt文件的访问权限相同了
copy2("test.txt", "/product/xyz.txt")5. copyfileobj函数
copyfileobj函数的两个参数src和dst并不是字符串形式的文件或目录的路径,而是打开文件的句柄,需要先使用open函数打开文件。
例子:
file_src = 'test.txt'
# 打开源文件
f_src = open(file_src, 'rb')
file_dest = 'okokok.txt'
# 打开目标文件
f_dest = open(file_dest, 'wb')
from shutil import copyfileobj
# 开始复制文件
copyfileobj(f_src, f_dest)我一口气讲了5个函数,又增加了很多奇怪的知识,不过还有更奇怪的呢!继续往下看!

四、利用系统的命令复制文件
不管是Windows、macOS,还是Linux,或是其他任何操作系统,都会提供用于复制文件的命令,例如,macOS和Linux用于复制文件的命令是cp;Windows用于复制文件的命令是copy。
如果读者不熟悉这两个命令,可以看下帮助:
执行cp --help,会得到如图1所示的帮助信息:

图1
执行copy /?命令,会得到如图2所示的帮助信息:

图2
os模块和subprocess模块都提供了丰富的函数用于执行系统命令。
例子:
# 使用os模块
import os
# 在Linux、Unix或macOS下
os.system('cp src.txt dst.txt')
# 在Windows下
os.system('copy src.txt dst.txt')
# 使用subprocess模块
import subprocess
# 在Linux、Unix或macOS下
status = subprocess.call('cp src.txt dst.txt', shell=True)
# 在Windows下
status = subprocess.call('copy src.txt dst.txt', shell=True)好了,现在我们已经介绍了Python中用于复制文件的主要函数(还有其他函数,但功能类似),感觉自己就像个文件侠,复制文件终于可以不用写这么多代码了,欧耶!

- EOF -