python中 ab c 2结果是_ABSP第9章:使用python整理文件

这个文已经躺在草稿里面差不多两个月了,今天看不下去赶紧写完算了

整理文件

在第8章,你已经学习了如何建立文件并写入内容。除了这些,python还可以对硬盘上的现有文档做整理。我们都有这样的经历,面对一个文件夹里成堆的文件,复制粘贴移动压缩……。类似的工作还包括:对当前文件夹下的所有子文件夹中的pdf文件作一个备份

将当前文件夹下所有文件的起首零位删去,比如spam001.txt, spam002.txt, spam003.txt,等等。或者把现有的文件名加上起首零位。

将若干文件夹内容进行ZIP压缩(相当于一个简单的备份系统了)

每次做类似上面这些单调工作的时候我都会希望能有一个自动化工具。用Python你可以让电脑自动化,无差错的迅速处理。

考虑到我们接下来要处理文件,最好你先保证自己可以看到文件的扩展名(.txt, .pdf, .jpg等等)。在mac osx或者linux下面文件名是默认显示的,不过在windows里面,已知文件的扩展名往往会被省略。要保证文件扩展名正确显示,进入开始>控制面板>外观和个性化>文件夹选项,在查看页,把隐藏已知文件类型的扩展名前面的对号去掉。

SHUTIL模块

shutil(全称是shell utilities)模块具有复制、移动、重命名和删除文件的功能。使用shutil的方法,先要import shutil

复制文件、文件夹

用shutil模块可以复制文件和文件夹。shutil.copy(source, destination)会把source路径下的文件复制到destination中。如果destination路径中指定了文件名,拷贝过去的文件会采用新的文件名,如果没有制定文件名就沿用原来的。source和destination两个都是字符串类型的参数。shutil.copy()返回的是文件拷贝以后的新路径。

在互动变成环境里输入下面代码了解shutil.copy()如何工作:

>>> import shutil, os

>>> os.chdir('C:\\')

❶ >>> shutil.copy('C:\\spam.txt', 'C:\\delicious')

'C:\\delicious\\spam.txt'

❷ >>> shutil.copy('eggs.txt', 'C:\\delicious\\eggs2.txt')

'C:\\delicious\\eggs2.txt'

os.chdir()是更换当前工作目录,下面的第一个shutil把C:\spam.txt复制到C:\delicious文件夹中,从返回值可以看到因为没有指定新的文件名[1],拷贝后的文件名字也是spam.txt。第二个shutil.copy()也作了文件的拷贝,但是拷贝目的除了制定目录,还指定了新的文件名[2]。所以拷贝后的文件名字是eggs2.txt。

shutil.copy()可以拷贝单一文件,而shutil.copytree()则会把一个文件夹,以及该文件夹下面的子文件夹都一同拷贝。使用shutil.copytree(source, destination)可以把source所指向的文件夹整个复制到destiniation所指向的位置。source和destination两个都是字符串类型的参数。shutil.copytree()返回的是拷贝目标的路径。

试一下下面的代码:

>>> import shutil, os

>>> os.chdir('C:\\')

>>> shutil.copytree('C:\\bacon', 'C:\\bacon_backup')

'C:\\bacon_backup'

上面的代码里,shutil.copytree()会新建一个名为bacon_backup的文件夹,其内容与原bacon文件夹相同。你现在可以放心的用这个函数备份原来的bacon文件夹了。

将文件,文件夹移动和重命名

使用shutil.move(source,destination)可以将文件从一个位置(source)移动到另外一个位置(destination),该函数会把新位置的路径返回。

如果destination指向的是一个文件夹,那么源文件会被移动到destination的路径,保持原先的名字。比如下面的代码所做的:

>>> import shutil

>>> shutil.move('C:\\bacon.txt', 'C:\\eggs')

'C:\\eggs\\bacon.txt'

加入C:\下面已经有了eggs这个文件夹,上面的代码的意思是把C:\bacon.txt移动到C:\eggs这个文件夹里面。为什么c:\eggs这个文件夹在python里面要写成c:\\eggs? 参见ABSP第8章 文件读写

如果c:\eggs这个文件夹里面已经有了bacon.txt的话,c:\eggs\bacon.txt会被覆盖。注意,文件的覆盖是很容易发生的,所以在使用move()的时候要自己小心。

destination除了指派目标文件夹还可以指派文件名,在下面的例子里面,源文件被移动,并重命名了

>>> shutil.move('C:\\bacon.txt', 'C:\\eggs\\new_bacon.txt')

'C:\\eggs\\new_bacon.txt'

上面的代码意思是,把C:\bacon.txt移动到C:\eggs里面,并将C:\eggs\下的bacon.txt重命名为new_bacon.txt。

上面的代码都假定c盘下面是有一个文件夹名叫eggs的,但是如果c盘下面没有这个文件夹的话,move()不会帮你新建这个文件夹,而是把bacon.txt重命名为eggs,没有扩展名。

>>> shutil.move('C:\\bacon.txt', 'C:\\eggs')

'C:\\eggs'

如果C盘下面没有eggs这个目录的话,destination参数会被认为是这样的:C:\是目录,eggs是文件名,所以bacon.txt就被重命名为eggs了。作为一个txt文件却没有了txt这个扩展名也是很糟糕的事情呢。在你的程序里面像这样的bug是没那么容易发现的,因为python不会认为这是有什么问题的,只是实际的执行效果和你想的完全不是那么一回事。所以用move()的时候要小心,再次提醒。

再次提醒,move()不会帮你建文件夹,所以destination路径里面的文件夹都是需要存在的。具体我们看一下下面的代码:

>>> shutil.move('spam.txt', 'c:\\does_not_exist\\eggs\\ham')

Traceback (most recent call last):

File "C:\Python34\lib\shutil.py", line 521, in move

os.rename(src, real_dst)

FileNotFoundError: [WinError 3] The system cannot find the path specified:

'spam.txt' -> 'c:\\does_not_exist\\eggs\\ham'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):

File "", line 1, in

shutil.move('spam.txt', 'c:\\does_not_exist\\eggs\\ham')

File "C:\Python34\lib\shutil.py", line 533, in move

copy2(src, real_dst)

File "C:\Python34\lib\shutil.py", line 244, in copy2

copyfile(src, dst, follow_symlinks=follow_symlinks)

File "C:\Python34\lib\shutil.py", line 108, in copyfile

with open(dst, 'wb') as fdst:

FileNotFoundError: [Errno 2] No such file or directory: 'c:\\does_not_exist\\

eggs\\ham'

上面的代码返回错误有一大长串文字,大概是这么个意思,c:\does_not_exist\eggs\ham这个目录不存在。python在does_not_exist里面找不到eggs,更找不到ham,毕竟does_not_exist文件夹本来就没有。由于目标目录不存在,所以移动操作无法执行。move()要是真能帮你新建文件夹,万一出了bug也是很头疼的。

永久性删除文件与文件夹

使用os模块下的函数也可以删除单个文件或单个空文件夹,但是到了需要删除一个文件以及其中的所有子目录的时候,shutil可以派上用场。os.unlink(path)可以删除path指向的单个文件

os.rmdir(path)可以删除path指向的目录,但该目录必须为空

shutil.rmtree(path)删除path指向的文件夹及其子文件夹

在程序里面使用上面代码的时候务必小心!在真正执行删除操作以前,先把上面提到的几个函数comment掉,用print把各个path输出一下,看看哪些文件将要被删除,有没有搞错的地方。下面的代码有点小问题,本来是想删除扩展名为txt的文件,但是打错了,结果变成了删除所有rxt文件。

import os

for filename in os.listdir():

if filename.endswith('.rxt'):

os.unlink(filename)

如果你刚好当前工作目录下面有rxt扩展名的重要文件那这一下造成的损失是灾难性的。python不管你什么回收站,它是直接永久删除的。所以在运行上面的程序以前,你应该这么操作一下:

import os

for filename in os.listdir():

if filename.endswith('.rxt'):

#os.unlink(filename)

print(filename)

刚才的os.unlink(filename)被comment掉了,不会运行。而通过输出filename,我们可以看到程序出了问题。啊,扩展名应该是txt不是rxt。现在程序就几行,一眼就能看出来不对头,但是如果程序复杂起来以后你就没法保证自己不出错了。

当你确认自己的程序没有问题,看输出也没什么不对的,现在就可以删掉print(filename),然后把刚才comment掉的东西恢复成正常代码,运行一下。

用send2trash模块安全的删除文件

因为python自带的shutil.rmtree()是直接删除,无法恢复,实际使用过程中还是挺吓人的。更好的办法还是弄一个回收站,万一不小心删错了还能补救。python有个第三方的包send2trash可以实现这个功能。要安装这个包,在terminal(windows下面可以用cmd或者powershell)使用pip install send2trash就可以了。附件A对第三方包的安装作了更详尽的解释。

使用send2trash比python常规的删除工具要安全很多,它会把删除的文件先发送到你电脑的回收站里面。如果你的代码真的不小心出了问题误删了文件的话,你可以稍后从回收站里面找到这些文件并恢复。

在装好了send2trash以后,试试下面的代码:

>>> import send2trash

>>> baconFile = open('bacon.txt', 'a') # creates the file

>>> baconFile.write('Bacon is not a vegetable.')

25

>>> baconFile.close()

>>> send2trash.send2trash('bacon.txt')

总体而言,使用send2trash.send2trasn()是更为安全的手段。当然如果你是想释放磁盘空间,那还是要使用os和shutil的相关函数,毕竟send2trash是发到回收站,实际上是没有删除的。

遍历一个目录

比如你现在想把某个文件夹下的所有文件重命名,不只是文件夹里的,子文件夹里面的也要。也就是说你想把整个目录树撸一遍,沿途所有的文件都弄到。涉及遍历的东西总是多少有些不好写,好在python已经提供了一个函数可以处理这个需求。

比如现在有这么个C:\delicious文件夹,里面的结构是这样的:一个示例文件夹,内含3个子文件夹和4个文件

下面的程序使用了os.walk()对上图的目录树进行遍历:

import os

for folderName, subfolders, filenames in os.walk('C:\\delicious'):

print('The current folder is ' + folderName)

for subfolder in subfolders:

print('SUBFOLDER OF ' + folderName + ': ' + subfolder)

for filename in filenames:

print('FILE INSIDE ' + folderName + ': '+ filename)

print('')

os.walk()函数只拿到了一个参数:目标文件夹的路径。把os.walk()放在for循环里面就可以对目录树作遍历了,这基本上就和你把range()放进for循环来遍历一个范围内的数字一样。和range()不同的是,os.walk()在每次遍历都会返回由3个值组成的tuple:当前文件夹的名称

当前文件夹的子文件夹的名称list

当前文件夹下所有文件的名称list

(当前文件夹值的是在当前遍历中,程序走到的文件夹。而current working directory这个当前工作目录是不会被改变的)

就像你在for i in range(10):里面可以用i也可以用j一样,循环里的三个变量也是可以自己起名字的,为了方便辨认,我们起名为folderName, subfolders和filenames。

当你运行程序的时候,可以看到下面这样的输出结果:

The current folder is C:\delicious

SUBFOLDER OF C:\delicious: cats

SUBFOLDER OF C:\delicious: walnut

FILE INSIDE C:\delicious: spam.txt

The current folder is C:\delicious\cats

FILE INSIDE C:\delicious\cats: catnames.txt

FILE INSIDE C:\delicious\cats: zophie.jpg

The current folder is C:\delicious\walnut

SUBFOLDER OF C:\delicious\walnut: waffles

The current folder is C:\delicious\walnut\waffles

FILE INSIDE C:\delicious\walnut\waffles: butter.txt.根据系统的不同,是输出C:\delicious还是其他的形式(比如我在jupyter上面输出的是相对路径)可能各有不同。

在os.walk()返回的tuple里面除了当前文件夹以外还有文件夹下目录和文件各自的list,对这些列表你可以做进一步的循环逐个输出内容。根据你的需要可以把上面程序里面的print()换成其他的函数,实现特定功能。当然如果你不需要子目录或者当前目录下文件,把对应的for整个去掉就是了。

使用zipfile模块压缩文件

你可能已经很熟悉zip文档了(也就是那些扩展名是.zip的文件),一个zip压缩包里面可以包含很多文件。将文件压缩可以减少存储空间,这对于在线传输是很有用的。而且由于zip文件可以保留文件的关系,又可以存储各种文档,作为文件的打包也是很有用的工具。这样把多个文档打包以后的压缩文件也可以叫做归档文件。一个通知加附件1234567打个包要比一群附件扔进邮件来的好很多。(当然这也见仁见智就是了)

不扯太远了,实际上python已经自带了可以压缩,解压缩zip文档的模块: zipfile。假如现在你手上有个叫做example.zip的文件,里面包含的文件如下图所示:

你可以从随书附件里面找到这个zip文件,当然也可以自己弄一个类似的zip文档(我个人使用7zip软件管理zip文件)

读取ZIP文件

要读取一个ZIP文档的内容,你首先需要新建一个ZipFile对象(注意Z和F是大写)。ZipFile对象和File对象在概念上是相似的,后者由open()返回。File对象也好,ZipFile对象也好,都是一种抓手,程序可以通过这些抓手和实际的文件进行互动。要生成一个ZipFile文件的话,使用zipfile模块的ZipFile()方法。这个方法接受目标zip文档的路径作为参数(路径是字符串类型)。重申一下,在上面的描述里面zipfile是模块名称,ZipFile()是方法名称。

看一下下面的程序示例:

>>> import zipfile, os

>>> os.chdir('C:\\') # move to the folder with example.zip

>>> exampleZip = zipfile.ZipFile('example.zip')

>>> exampleZip.namelist()

['spam.txt', 'cats/', 'cats/catnames.txt', 'cats/zophie.jpg']

>>> spamInfo = exampleZip.getinfo('spam.txt')

>>> spamInfo.file_size

13908

>>> spamInfo.compress_size

3828

❶ >>> 'Compressed file is%sx smaller!' % (round(spamInfo.file_size / spamInfo

.compress_size, 2))

'Compressed file is 3.63x smaller!'

>>> exampleZip.close()

一行一行的讲一下。首先第一行是导入对应的模块,第二行是把当前工作目录设置成C:\盘,第三行新建一个ZipFile对象,起名为exampleZip。

ZipFile对象具有namelist()这个方法,可以把压缩包里面所有文件或者文件夹的名称列出来。从名称列表里可以获得各个文件的名称,把这些名称交给getinfo()方法。getinfo()方法会返回特定文件的ZipInfo对象。ZipInfo具有很多属性,比如file_size和compress_size。这两个属性的单位都是字节,分别代表压缩文件原始大小和压缩后大小。如果说ZipFile保存着整个压缩包的信息,ZipInfo包含的就是包内特定文件的信息。

代码[1]计算的是压缩包的压缩效率,将特定文件的原始大小除以压缩后大小,round四舍五入到小数点以后第2位。

和open()一样,ZipFile对象在完成操作以后也要记得close()。

解压缩ZIP文件

extractal()方法可以把ZipFile对象的内容全部解压缩到当前工作目录。参见以下示例:

>>> import zipfile, os

>>> os.chdir('C:\\') # move to the folder with example.zip

>>> exampleZip = zipfile.ZipFile('example.zip')

❶ >>> exampleZip.extractall()

>>> exampleZip.close()

运行完上述代码以后,example.zip的内容会被解压缩到C:\里面。extractall()方法可以接受一个可选参数:你可以传入一个文件夹的名字,让压缩包的内容解压到制定的文件夹。如果传入参数指向的文件夹不存在,extractall()会帮你新建一个。举个例子,如果[1]这里的代码替换成:exampleZip.extractall("C:\\delicious"),如果C:\delicious不存在,程序会先新建这个文件夹,然后再把压缩包内容放进去。这和平时我们使用解压缩软件的行为是很相似的。

除了extractall(),还可以使用extract()方法吧ZipFile里面的单个文件提取出来。试试下面的代码:

>>> exampleZip.extract('spam.txt')

'C:\\spam.txt'

>>> exampleZip.extract('spam.txt', 'C:\\some\\new\\folders')

'C:\\some\\new\\folders\\spam.txt'

>>> exampleZip.close()

extract()至少要有一个参数,指向被解压缩的文件,而且参数要和namelist()返回的文件名列表对的上。和extractall()一样,你还可以额外传入一个目标文件夹参数,这样python就不会把文件默认提取到当前工作文件夹了。同样和extractall()一样,如果目标文件夹不存在,python会帮你新建一个。extract()会返回解压文件的绝对路径。

新建一个ZIP文件

如果要用python压缩文档,建立ZIP文件,你必须用write模式新建一个ZipFile对象,在使用ZipFile()方法的时候,加入第二个参数"w"(这和open()很像)。

要把一个文件加入压缩包,将指向这个文件的路径作为参数交给ZipFile对象的write()方法即可。write()方法的第一个参数是要加入文件的路径。第二个参数是压缩模式的名称,这个参数告诉电脑使用什么样的压缩算法进行压缩。一般来说只要用zipfile.ZIP_DEFLATED这个值就ok了。(这个值使用deflate压缩算法,对于所有数据类型的压缩都不错)现在试试下面的代码:

>>> import zipfile

>>> newZip = zipfile.ZipFile('new.zip', 'w')

>>> newZip.write('spam.txt', compress_type=zipfile.ZIP_DEFLATED)

>>> newZip.close()

上面的代码首先引入zipfile模块,随后建立ZipFile对象(写入模式),第三行将spam.txt加入压缩包,最后一行和open()一样,在完成文件写入工作以后关闭ZipFile对象。

记住,和文件的写入一样,当你用w模式打开一个文件时,你是在覆盖原文件!如果你想在原来的zip包基础上增加文件,使用zipfile.ZipFile()的第二个参数使用a模式。

练手项目:将文件命名中日期书写方式从美式转成欧式

假如说现在你老板给你发了几千个文件,文件名中日期部分按照美式习惯书写(月-日-年),但是现在要按照欧式重命名(日-月-年)。这显然是非常无聊却又非常耗时的工作。所以让我们写一下程序来做这个事情吧。

这个程序要能做下面的事情:搜索当前工作目录下面所有具有符合美式日期的文件

如果找到了一个文件,那就把日和月的位置互换一下。

所以代码里面需要实现下面的功能:书写一个能够识别美式日期的正则表达式

使用os.listdir()获取当前工作目录下的所有文件

遍历各个文件名,使用正则判断文件名是否具有美式日期

如果有美式日期,使用shutil.move()重命名

现在新建一个python文件renameDates.py

第一步:生成一个识别美式日期的正则

程序的第一部分负责导入必须的模块,顺便写一个识别美式日期的正则(日-月-年)。此外写一下TODO提示自己程序要包含哪些内容。

#! python3

# renameDates.py - Renames filenames with American MM-DD-YYYY date format

# to European DD-MM-YYYY.

❶ import shutil, os, re

# Create a regex that matches files with the American date format.

❷ datePattern = re.compile(r"""^(.*?) # all text before the date((0|1)?\d)- # one or two digits for the month((0|1|2|3)?\d)- # one or two digits for the day((19|20)\d\d) # four digits for the year(.*?)$ # all text after the date❸ """, re.VERBOSE)

# TODO: Loop over the files in the working directory.

# TODO: Skip files without a date.

# TODO: Get the different parts of the filename.

# TODO: Form the European-style filename.

# TODO: Get the full, absolute file paths.

# TODO: Rename the files.

这一章我们说过,shutil.move()可以用来给文件重命名:shutil的参数包括文件原来的名字以及新的名字。因为move是shutil命名空间下面的函数,所以要先导入shutil这个模块【1】。

在实际给文件重命名以前,你首先要能够鉴别那些文件需要被重命名。只有文件名中包括日期的才需要重命名,也就是像spam4-4-1984.txt和01-03-2014eggs.zip需要重命名,而不包含日期的,比如littlebrother.epub就不需要重命名了。

在程序里面我们用了一个正则表达式来实现上面的鉴别,在导入re模块以后,我们使用re.compile()建立了一个正则对象(regex object)【2】,注意【3】这里,将re.VERBOSE作为第二个参数交给re.compile可以让我们书写正则表达式的时候能够加上换行这样的空白符,方便阅读。

正则表达式开头的^(.*?)的意思是文件名里面表示日期字符以前的所有文本。比如spam4-4-1984.txt里面的spam。(*?表示的是任意数量且非贪婪匹配)((0|1)?\d)这一组正则表示的是月份(01,02,03 ... 10, 11, 12),而(0|1)?里面的?表示前面的0或1是可选的,所以04和4都可以被匹配。日期这一组正则也是出于同样的逻辑:((0|1|2|3)?\d)。

当然上面的日期正则式实际上是简化了的版本,对于像4-39-2015, 2-29-2013, 0-15-2014这样的错误日期无法甄别。毕竟日期是一个很难啃的硬骨头,对于我们当前的需求能满足的话,这一块先不作进一步深究。

至于年份一组,之所以要限制为19xx或者20xx主要由两个考虑:1:19世纪还没有电脑,2:减少非日期序列被错认的可能。

第二步:识别文件名中日期的各个部分

有了上面的正则以后,程序就会遍历os.listdir()返回的文件名列表,使用刚才写的正则去匹配。如果文件名不符合正则规则就会被跳过,如果符合的话,匹配结果会按组分别存储进不同的变量里面。接下来我们可以着手处理前面三个todo了:

#! python3

# renameDates.py - Renames filenames with American MM-DD-YYYY date format

# to European DD-MM-YYYY.

--snip--

# Loop over the files in the working directory.

for amerFilename in os.listdir('.'):

mo = datePattern.search(amerFilename)

# Skip files without a date.

❶ if mo == None:

❷ continue

❸ # Get the different parts of the filename.

beforePart = mo.group(1)

monthPart = mo.group(2)

dayPart = mo.group(4)

yearPart = mo.group(6)

afterPart = mo.group(8)

--snip--

当文件名不符合正则表达式的规则时,re.search()的返回值是None,Continue语句就会跳过当前循环,处理下一个文件名。

如果文件名符合正则表达式规则,那就可以用group()获取各个组的数据,然后存到beforePart, monthPart, dayPart, yearPart和 afterPart这些变量里面。至于各个组怎么定义嘛,你可以试着从头开始读正则表达式,看到一个(就算一组。比如你可以想下面这样写一段代码帮助你计数:

datePattern = re.compile(r"""^(1) # all text before the date

(2 (3) )- # one or two digits for the month

(4 (5) )- # one or two digits for the day

(6 (7) ) # four digits for the year

(8)$ # all text after the date

""", re.VERBOSE)

记住,没看到一个(就计数一次,不用管正则里面具体是什么。现在对照原来的正则,看一下为什么monthPart, dayPart和yearPart分别是2,4和6。上面只写括号的办法对于初学正则是很好用的一个办法。

第三步:组成新的文件名,并对文件重命名

最后一步,就是把上面的几个变量按照欧式整合成一个新的文件名(日期在月份以后),并且对旧的文件重命名。现在可以把后面的三个todo给填上了:

#! python3

# renameDates.py - Renames filenames with American MM-DD-YYYY date format

# to European DD-MM-YYYY.

--snip--

# Form the European-style filename.

❶ euroFilename = beforePart + dayPart + '-' + monthPart + '-' + yearPart +

afterPart

# Get the full, absolute file paths.

absWorkingDir = os.path.abspath('.')

amerFilename = os.path.join(absWorkingDir, amerFilename)

euroFilename = os.path.join(absWorkingDir, euroFilename)

# Rename the files.

❷ print('Renaming "%s" to "%s"...' % (amerFilename, euroFilename))

❸ #shutil.move(amerFilename, euroFilename) # uncomment after testing

整合好的欧式文件名存在了euroFilename变量中【1】,随后将两个文件名的相对路径换成绝对路径。【3】将旧的文件名和新的文件名分别交给shutil.move()就可以完成文件名的替换了。

但是在上面的代码里面【3】被comment掉了,取而代之的是首先用【2】输出一个文件名替换的信息。这是为了先让我们确认一下程序不会出问题,然后才把【3】的comment去掉正式运行。

只要是涉及文件,尤其是重命名,删除这样的操作,python可是没有ctrl+z给你用的。所以务必先输出一下程序将执行的操作,确认无误才可以正式执行。

可以实现的类似功能

在很多其他事情上也可能需要用到重命名的能力:给文件名前面加上一个前缀,比如给eggs.txt前面加上spam_变成spam_eggs.txt

可以把欧式日期换回来变成美式日期

把文件名里面的leading zero(比如spam0042)的0给去掉

或者可以给文件名中的数字加上leading zero方便系统对文件名作排序

练手项目:将一个文件压缩为zip文档归档

比如现在你着手在做一个项目,有关的文件都存在一个文件夹里面,比如说,C:\AlsPythonBook。你担心自己的文件会丢失,所以你要定期给文件夹做一个快照:把整个文件夹压缩成一个zip文档。同时你又想保留多个版本,所以你希望这个zip文档的文件名包含一个序号,逐渐增加。比如AlsPythonBook_1.zip,AlsPythonBook_2.zip, AlsPythonBook_3.zip,等等。你可以手动做这样的操作,当然,这样有点麻烦,也可能出错。如果能有一个程序做这件事情就好多了。

现在我们新建一个文件命名为backupToZip.py,开始编程。

第一步:确定zip文件名称

这个程序主要的程序会保留在backToZip()这个函数里面。这样可以让你的代码更容易重复使用。在代码的最后,我们手动调用一下这个函数来实现文件备份。代码大概像下面这样:

#! python3

# backupToZip.py - Copies an entire folder and its contents into

# a ZIP file whose filename increments.

❶ import zipfile, os

def backupToZip(folder):

# Backup the entire contents of "folder" into a ZIP file.

folder = os.path.abspath(folder) # make sure folder is absolute

# Figure out the filename this code should use based on

# what files already exist.

❷ number = 1

❸ while True:

zipFilename = os.path.basename(folder) + '_' + str(number) + '.zip'

if not os.path.exists(zipFilename):

break

number = number + 1

❹ # TODO: Create the ZIP file.

# TODO: Walk the entire folder tree and compress the files in each folder.

print('Done.')

backupToZip('C:\\delicious')

一点一点做,首先先写开头的程序说明(shebang行,#!),然后是关于import的内容【1】,导入zipfile和os模块

接下来是定义backupToZip()函数。函数只有一个参数:folder。这个参数是一个指向要压缩文件夹的字符串类型数据。这个函数要做下面几件事情:(1)根据参数决定生成zip文件的文件名,(2)生成zip文件,(3)遍历folder文件中的内容,将每一个子文件夹和文件都加入到zip文件中。按照上面的设计写一下TODO,给自己后面编程也提醒。

第一部分,给zip文件命名:使用folder绝对路径的base name可以获得这个路径最下层文件夹的名字。(关于basename,可参见:ABSP第8章 文件读写)加入现在需要进行备份的文件夹是c:\delicious,那么zip文件名就应该是delicious_N.zip, N是从1开始的序列。

你可以根据现存的文件决定N的值。如果delicious_1.zip存在了,那就从2开始,如果2也存在,就从3开始,以此类推。【2】使用number变量保存这个N值,程序中使用while循环不断递增,直到出现delicious_N.zip不存在的情况(not os.path.exists(zipFilename)),break跳出循环。

第2步:建立一个新的zip文件

#! python3

# backupToZip.py - Copies an entire folder and its contents into

# a ZIP file whose filename increments.

--snip--

while True:

zipFilename = os.path.basename(folder) + '_' + str(number) + '.zip'

if not os.path.exists(zipFilename):

break

number = number + 1

# Create the ZIP file.

print('Creating%s...' % (zipFilename))

❶ backupZip = zipfile.ZipFile(zipFilename, 'w')

# TODO: Walk the entire folder tree and compress the files in each folder.

print('Done.')

backupToZip('C:\\delicious')

接下来就是第二个todo的内容,新建一个zip文件。这一步比较的简单,使用zipfile.ZipFile()方法就可以生成ZIP文件了。第一个参数是zip的文件名,这在第一步的时候我们已经设置好了,第二个参数是读写模式,一定要设置成w,这样才是写入模式。

第3步:遍历文件夹内容,加入zip文件中

利用os.walk()函数来遍历文件夹下所有的子文件夹以及文件。让你的程序像下面这样:

#! python3

# backupToZip.py - Copies an entire folder and its contents into

# a ZIP file whose filename increments.

--snip--

# Walk the entire folder tree and compress the files in each folder.

❶ for foldername, subfolders, filenames in os.walk(folder):

print('Adding files in%s...' % (foldername))

# Add the current folder to the ZIP file.

❷ backupZip.write(foldername)

# Add all the files in this folder to the ZIP file.

❸ for filename in filenames:

newBase = os.path.basename(folder) + '_'

if filename.startswith(newBase) and filename.endswith('.zip'):

continue # don't backup the backup ZIP files

backupZip.write(os.path.join(foldername, filename))

backupZip.close()

print('Done.')

backupToZip('C:\\delicious')

在这个post前半段我们已经讲了os.walk。将os.walk()放在for loop的in之后,每次遍历都会获得一个tuple:(当前文件夹的名字,子文件夹列表,文件名列表)。在程序里,每次遍历都会把当前文件夹加入zip文件,并且遍历filenames这个文件名列表,把当前文件夹下的所有文件也加入zip中。另外还会判断一下我们做的文档压缩包是不是再文件列表中,如果遇到之前做的压缩包要跳过。如果在os.walk()部分没有参照图片自己建立文件机构,现在我建议你还是仿照图片自己做一套类似的文件结构,可以不用完全和图片上的一致,但是也要有3层左右的文件结构,里面再放一些空的文本文档当做文件。之后尝试多使用几次os.walk()来让自己增进对这个函数的感知。

上面的代码,运行出来的输出应该是这样的:

Creating delicious_1.zip...

Adding files in C:\delicious...

Adding files in C:\delicious\cats...

Adding files in C:\delicious\waffles...

Adding files in C:\delicious\walnut...

Adding files in C:\delicious\walnut\waffles...

Done.

当你第二次运行的时候,第一行应该会变成Creating delicious_2.zip...以此类推。

利用上面的知识可以做的扩展:

使用文件树遍历加上压缩可以实现很多功能,比如下面这些:遍历一个文件树,但并不是归档所有文件,而是至提取一部分,比如.txt和.py文件

遍历文件树,归档除了.txt和.py以后的所有文件

遍历文件树,找到其中文件数量最多,或者占用空间最大的一个文件夹。

总结

即使你是一个经验丰富的电脑用户,你可能也主要依靠鼠标键盘手动的进行文件操作。现代的文件管理工具在处理几个文件的时候是很方便,但是文件数量如果多起来就没那么好用了。

os和shutil模块提供了文件拷贝,移动,重命名和删除的能力。在删除文件的时候,最好还是使用send2trash模块将文件移动到回收站可能更好更安全,这样不会直接永久删除文件。此外,在处理和文件操作有关的事情时,最好,不要直接让代码复制、移动、重命名或者删除。在执行上面操作以前,首先输出将要执行的动作print()输出,确认无误以后再正式执行负责文件操作的代码。

除了在某一个具体的文件夹执行上面的代码,你可能需要在某个路径下的所有子文件夹执行一个操作。这个时侯os.walk()会很有用。

zipfile模块让你可以压缩、解压缩zip文件。将zipfile模块和shutil,os联用可以让你更好的给文件打包。文档在打包以后更容易上传或者作为邮件附件发送。

到现在我们已经看到了很多的代码,不过很多时候就算你把文章里的代码原样拷贝到自己的文本编辑器里面,第一次的时候也不见的就可以顺利运行。下一章我们会关注如何对自己的代码分析、差错,让你更好的找到代码中的问题。

练习题:shutil.copy()和shutil.copytree()的区别是什么?

对文件重命名的函数是什么?

send2trash和shutil中删除函数的区别是什么?

ZipFile对象和File对象一样都有一个close()方法。那么ZipFile对象里面与File对象的open()方法对应的是什么方法?

练手项目:

你可以针对下面的问题尝试做一下开发:

选择性的拷贝:

写一个程序,遍历某个目录树,在其中寻找某类文件(比如.pdf或者.jpg),将这些文件拷贝到另外一个目录里面,但是保留原先的相对路径

删除没用的文件

每个人应该都有不少不常用,但是又非常大的文件、文件夹占用了很多空间。如果你想让硬盘空间空一些出来的话,把这些又大又不常用的文件删掉是最有效的。但是首先你要找到他们。一般现在很多的系统整理软件都具有这类的功能,但是今天我们要自己写一个。

写一个程序,遍历某个目录树,找到特别大的文件,比如那些超过了100mb的文件(你可以用os.path.getsize()获取一个文件的大小)将那些很大的文件都输出显示到屏幕上。

填补空白

比如现在有一些文件IMG001.jpg,IMG002.jpg,IMG004.jpg等等。这其中可能序号不是连续的,比如IMG003.jpg就没了。现在写一个程序找到某个文件夹下面文件序列是否有缺项,如果有的话将缺项后面的文件名全部重命名,比如上面缺了3 的时候,就把3以后的所有文件名编号-1。

作为额外的挑战,写另外一个程序让我们可以在一个文件名序列中插入一个空档,让之后可以添加文件进去。

给中文文件前面添加拼音前缀

windows虽然可以给中文文件夹按照名称排序,但是这个排序其实有些奇怪。如果你希望能更快的找到文件,最好就是能让中文名字前面加上拼音的前缀,比如“工作文件”改成“gzwj_工作文件”。中文名字转成英文拼音的步骤可以使用pypinyin这个package搞好。

from pypinyin import lazy_pinyin

import os,shutil,re,pyperclip

pathofcwd = pyperclip.paste()

assert os.path.isdir(pathofcwd),'"{}" is not path'.format(pathofcwd)

os.chdir(pyperclip.paste())

reChnFirst = re.compile(r'[\u4e00-\u9fa5]')

#只处理以名字以中文字符开始的文件夹

dirList = list(filter(lambda e:os.path.isdir(e) and reChnFirst.match(e.strip()) != None,os.listdir()))

def renameDirWithPY (dirname):

prefix = ''

for e in lazy_pinyin(dirname)[:3]:

#只要前三个字的拼音首字母

prefix += e[0]

prefix += '_'

return prefix+dirname

for e in dirList:

shutil.move(e,renameDirWithPY(e))

os.listdir()

关于pypinyin这个package,可以到这里看看:mozillazg/python-pinyin

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值