在平时的项目开发中,文件操作是非常频繁的一个场景,前面python文件操作中带领大家学些了基本的文件操作,但是还有一类特殊的文件就是压缩包文件,如jar包,apk包本质上都是压缩包,对这类文件处理要使用python中的zipfile模块。
首先来看一下ZipFile类的定义
class zipfile.ZipFile(file, mode='r', compression=ZIP_STORED, allowZip64=True)
可以看到ZipFile包含四个参数,其中后面的三个参数都是自带默认值的参数,各个参数的含义如下:
- file,文件的路径或者文件对象本身,如果是文件路径通常是一个字符串
- mode,打开zip包的模式,和签名讲解的打开文件的模式一样
- compression:压缩类型,因为zip包是压缩包,是采用某种压缩算法压缩后的文件格式,因此会存在不同的压缩类型,如ZIP_STORED, ZIP_DEFLATED, ZIP_BZIP2 or ZIP_LZMA等
- allowZip64,该值默认为True,当zipfile大于4 GiB时,zipfile将创建使用ZIP64扩展名的ZIP文件
注意ZipFile和open一样也支持with as语句,因此好的习惯是通过with as语句来使用ZipFile,这样zipfile对象会自动调用close()函数来关闭读写操作当with语句块结束时,即使出现异常也会close(),这样能够避免我们忘记手动调用close()函数,像下面这样:
with zipfile.ZipFile(jarPath) as jar:
jar.write('test.txt')
然后来看下ZipFile类常用的函数
ZipFile.namelist()
返回zip包所有的entry名称的list,注意返回的entry名称中包含目录路径,如X/Y/z.txt。
ZipFile.getinfo(name)
name为传入的entry的名称,注意需要包括目录路径,如某zip包解压后目录层级关系为X/Y/z.txt,则需传入X/Y/z.txt,若直接传入z.txt,因为该zip包中不存在该entry,因此会抛出KeyError错误,如果entry存在,则会返回和该entry名称对应的ZipInfo对象。ZipInfo对象中定义了很多有用的信息,如压缩文件的压缩类型,压缩后的大小,压缩前的大小。常用的属性及其含义如下:
- ZipInfo.compress_type,该entry的压缩类型,常见的压缩类型包括ZIP_STORED和ZIP_DEFLATED
- ZipInfo.compress_size,该entry压缩后的大小
- ZipInfo.file_size,该entry压缩前的大小
- ZipInfo.CRC ,该entry的CRC校验码
除了属性外,ZipInfo还包括一些很有用的函数,如ZipInfo.is_dir()函数可以判断该entry是目录还是文件,如果为目录返回True
ZipFile.extract(member, path=None, pwd=None)
解压出名称为member的entry到当前工作路径下,该函数各个参数含义如下:
- member,待解压的entry名称,同样名称包含路径,如X/Y/z.txt
- path,解压目录,默认值为None,解压到当前工作目录,如果指定了该值,则会解压到path路径下
- pwd,解压的密码,如果该zip在压缩时进行过加密操作,则需要传入该值
ZipFile.write(filename, arcname=None, compress_type=None)
将名为filename的文件添加到zip包中,该函数各个参数含义如下:
- filename,需要添加(写入)到zip包中的文件,通常是一个文件路径的字符串
- arcname,添加到zip包中的文件的名称,默认为None,如果不指定该值,则默认采用和filename一样的名称
- compress_type,压缩到zip包中时的压缩类型,如ZIP_STORED,ZIP_DEFLATED
虽然ZipFile为我们提供了很多API,但是很遗憾目前的python中ZipFile还未给我们提供一个不解压直接删除zip包中文件的函数,这里我们可以自己定义一个类来继承自ZipFile,然后自己实现直接删除zip包中某一个entry的函数,包括直接删除文件或者文件夹。代码如下:
import zipfile
from zipfile import ZipFile
class ZipManager(ZipFile):
# 删除zip包中的文件,不能是文件夹
def remove(self, member):
if "a" not in self.mode:
raise RuntimeError('remove() requires mode "a"')
if not self.fp:
raise RuntimeError(
"Attempt to modify ZIP archive that was already closed")
# Make sure we have an info object
if isinstance(member, zipfile.ZipInfo):
# 'member' is already an info object
zinfo = member
else:
# Get info object for member
zinfo = self.getinfo(member)
# To remove the member we need its size and location in the archive
fname = zinfo.filename
zlen = len(zinfo.FileHeader()) + zinfo.compress_size
fileofs = zinfo.header_offset
# Modify all the relevant file pointers
for info in self.infolist():
if info.header_offset > fileofs:
info.header_offset = info.header_offset - zlen
# Remove the zipped data
self.fp.seek(fileofs + zlen)
after = self.fp.read()
self.fp.seek(fileofs)
self.fp.write(after)
self.fp.truncate()
# Fix class members with state
self._didModify = True
self.filelist.remove(zinfo)
del self.NameToInfo[fname]
#删除zip包中的文件夹
def delete_dir(self,dir_name):
entrys = self.namelist()
is_dir_existed=False
for delete_entry in entrys:
if delete_entry.startswith(dir_name):
is_dir_existed=True
self.remove(delete_entry)
if not is_dir_existed:
print 'entry dir which you input is not existed'
在这里我们定义了一个ZipManager类继承自ZipFile类,然后自定义了remove函数用来直接删除zip包中的某一个文件,delete_dir函数来删除文件夹。这样对zip包的操作就比较完整了。
综合实例
这里实现一个操作就是替换zip包中某个文件,给一个apk文件X.apk,替换文件Y,将apk包中的Z替换为Y,要求替换后,Y的压缩类型要与原apk包中的文件保持一致。之所以选该实例,是因为很显然该实例包括了删除原zip包中的Z,添加Y到zip包中,也就是说该实例可以把ZipFile中的重要函数和属性都用上。当然要用到前面我们扩展的ZipManager类来实现直接删除某一项文件的操作。代码如下:
def replace_file_from_zip(zip_file_path, file_to_replace, entry_name):
if not os.path.exists(zip_file_path) or not zip_file_path.endswith('.apk'):
print "zipfile is not existed or it's not a apk file"
return;
if not os.path.exists(file_to_replace):
print "file which you want to replace is not existed"
return;
apk = ZipManager(zip_file_path, 'a')
if entry_name not in apk.namelist():
print "can not find entryName file which you input in zip file or this it's a dir not a file"
return;
else:
compress_type=apk.getinfo(entry_name).compress_type
apk.remove(entry_name)
apk.write(file_to_replace,entry_name,compress_type)
apk.close()
在这里我们用到了ZipFile的getrinfo函数来获取ZipInfo对象,然后通过ZipInfo.compress_type属性来得到该entry的压缩类型,然后调用我们扩展的ZipManager的remove函数直接删除该entry,最后调用write函数向zip包中添加文件,同时指定添加的文件的名称和压缩类型,这个实例可以说是zip包操作中综合性最高的一个操作。
注:本文首次发表于huqi.tech,请勿转载,如需转载,请注明出处:huqi.tech
扫描下方二维码实时接收最新技术干货推送
扫描二维码实时接收最新技术干货推送,而且会不定期的发布互联网名企内推机会哦!

文章评论