RealPython中文系列教程第116课详解

原文:RealPython

协议:CC BY-NC-SA 4.0

Python YouTube 频道的终极列表

原文:https://realpython.com/python-youtube-channels/

我们在网上找不到一个好的、最新的 Python 开发者或 Python 编程 YouTube 频道列表。

如今,在 YouTube 上学习 Python 是一个可行的选择,我们对这种新媒体能为编程教育做些什么感到兴奋。

YouTube 上有一些非常好的关注 Python 开发的频道,但是我们找不到一个既全面又最新的列表。所以我们用最好最 Pythonic 化的 YouTubers 创建了我们自己的。

我们最初是根据通过阅读论坛帖子和直接在 YouTube 上搜索 Python 频道收集的信息来编写这个列表的。我们将根据您的反馈继续添加列表。我们计划不断更新这个列表,所以如果你认为有什么遗漏或者你想看到自己的 YouTube Python 教程被添加,请随时在页面末尾留下评论或者发推文给我们。

要将一个渠道纳入我们的列表,它必须:

  • 关注 Python 教程
  • 不是全新的(超过 2000 名用户)
  • 保持活跃(新视频即将推出)拥有有趣的旧内容存档,值得观看
  • 享受 Python 的好处吧!📹🐍

    阿尔斯威加特T2】

    “大量甜蜜的计算机相关教程和一些其他可怕的视频!”

  • 网址:automatetheboringstuff.com
  • 推特: @AlSweigart
  • 订阅: YouTube
  • Remove ads

    阿纳康达公司

    “Anaconda 拥有超过 450 万用户,是世界上最受欢迎的 Python 数据科学平台。Anaconda,Inc .继续领导开放源码项目,如 Anaconda、NumPy 和 SciPy,它们构成了现代数据科学的基础。Anaconda 的旗舰产品 Anaconda Enterprise 允许组织保护、管理、扩展和扩展 Anaconda,以提供可操作的见解,推动企业和行业向前发展。”

    除了他们公司开发的视频,包括有趣的乐高动画短片和 T2 短片,Anaconda 的 YouTube 频道包含了 AnacondaCon 的所有视频,这是一个充满激情的社区,聚集了所有使用 Anaconda 发行版的数据科学家、IT 专业人员、分析师、开发人员和商业领袖。

  • 网址:anaconda.com
  • Twitter: @anacondainc
  • 订阅: YouTube
  • 克里斯蒂安·汤普森

    “关于我的一点点,关于 Python 初学者编程的很多内容。我是一名使用 Python 作为教学语言的初高中教师。我坚信任何人都可以(也应该)学习计算机编程,Python 是学习编程的完美语言。”

  • 网址:christianthompson.com
  • 推特: @tokyoedtech
  • 订阅: YouTube
  • 聪明的程序员

    “你可以在这里找到很棒的编程课程!此外,期待将你的编码技能提升到下一个水平的编程技巧和诀窍。”

  • 网址:cleverprogrammer.com
  • 推特: @cleverqazi
  • 订阅: YouTube
  • CodingEntrepreneurs

    《为企业家编程》是一个面向非技术创业者的编程系列。学习 Django、Python、API、接受支付、Stripe、jQuery、Twitter Bootstrap 等等。”

  • 网址:www.codingforentrepreneurs.com
  • 推特: @joincfe
  • 订阅: YouTube
  • 科里斯查费T2】

    “这个频道专注于为软件开发人员、程序员和工程师创建教程和演练。我们涵盖了所有不同技能水平的主题,所以无论你是初学者还是有多年经验的人,这个频道都会有适合你的东西。”

  • 网址:coreyms.com
  • 推特: @CoreyMSchafer
  • 订阅: YouTube ⋅ Patreon
  • 克里斯·霍克斯

    “我们将学习编程、网页设计、响应式网页设计、Reactjs、Django、Python、网页抓取、游戏、表单应用等等!”

  • 推特: @realchrishawkes
  • 订阅: YouTube
  • CS 道场

    “大家好!我叫 YK,我在这里制作的视频大多是关于编程和计算机科学的。”

  • 网址: csdojo.io
  • 推特: @ykdojo
  • 订阅: YouTube
  • Remove ads

    数据学院(凯文·马卡姆)

    “您正试图在数据科学领域开创自己的事业,我想帮助您实现这一目标!我的深入教程将帮助您使用 Python 和 r 等开源工具掌握关键的数据科学主题。”

  • 网址: www.dataschool.io
  • 推特: @justmarkham
  • 订阅: YouTube ⋅ Patreon
  • 大卫·比兹利

    “大卫·比兹利的会议、用户组和培训讲座的档案.”

  • 网址:www.dabeaz.com
  • 推特: @dabeaz
  • 订阅: YouTube
  • 深谋远虑

    “超过 15 年来,Enthought 以科学和工程为核心构建了人工智能解决方案。我们通过让公司及其员工利用人工智能和机器学习的优势来加速数字化转型。”

    此外,Enthought 因早期开发、维护和持续支持 SciPy 而闻名,也是 SciPy US 和 EuroSciPy 会议的主要赞助商。除了公司开发的内容,该频道还提供了 SciPy US 和 EuroScipy(2016 年之前)会议、讲座和教程的所有视频记录,这些视频记录专门关注通过数学、科学和工程领域的开源 Python 软件推进科学计算。

  • 网址:enthought.com
  • 推特:@ entthought
  • 订阅: YouTube
  • 迈坚(谈蟒)

    “关于编程的视频、演示和讲座——尤其是 Python 和 web 主题.”

  • 网址: talkpython.fm
  • 推特: @mkennedy
  • 订阅: YouTube
  • pretty printedT2】

    “我是安东尼。我制作编程视频。”

  • 网址:prettyprinted.com
  • 推特: @pretty_printed
  • 订阅: YouTube
  • PyCon 会议录音

    这些都是 YouTube 上的 PyCon 演讲和会议记录。没有一个单一的渠道将这些结合起来。相反,你可以在一个单独的“PyCon 20…”频道上观看每年的视频。或者,您可以使用 PyVideo.org 的来观看会议录像。

  • 网址:www.pycon.org
  • 推特: @PyCon
  • 订阅:YouTubePyVideo.org⋅
  • PyData

    “PyData 为数据分析工具的国际用户和开发人员提供了一个交流思想和相互学习的论坛。全球 PyData 网络促进对数据管理、处理、分析和可视化的最佳实践、新方法和新兴技术的讨论。PyData 社区使用许多语言来研究数据科学,包括(但不限于)Python、Julia 和 r。

  • 网址:pydata.org
  • 推特: @PyData
  • 订阅: YouTube
  • Remove ads

    真正的巨蟒

    “Python 教程和 Pythonistas 培训视频,超越了基础知识。在这个频道上,你每周都会看到新的 Python 视频和截屏。它们很小,而且很中肯,所以你可以把它们融入你的一天,并顺便学习新的 Python 技能。”

  • 网址:realpython.com⋅dbader.org
  • 推特:@ real python⋅@ dbader _ org
  • 订阅: YouTube ⋅ 播客
  • 鲁文·勒纳T2】

    “我是鲁文·勒纳,我为世界各地的公司教授 Python 和数据科学。在这个 YouTube 频道上,我发布了一些视频,可以帮助你学习 Python 编程。”

  • 网址:lerner.co.il
  • Twitter:@ reuvenmler
  • 订阅: YouTube
  • 森德克斯

    “Python 编程教程,不仅仅是基础知识。了解机器学习、金融、数据分析、机器人、网页开发、游戏开发等等。”

  • 网址:pythonprogramming.net
  • 推特:@ send ex
  • 订阅: YouTube ⋅ Patreon
  • 苏格拉底

    “苏格拉底制作高质量的数学和科学教育视频。每周都有新视频!我们是加州理工的毕业生,相信你应该得到更好的视频。和我们在一起,你会学到更多!”

  • 网址:www.socratica.com
  • Twitter:@ socratica
  • 订阅: YouTube ⋅ Patreon
  • 新波斯顿(Bucky Roberts)

    “大量甜蜜的计算机相关教程和一些其他可怕的视频!”

  • 网址:thenewboston.com
  • 推特: @bucky_roberts
  • 订阅: YouTube ⋅ Patreon
  • 小型 Python 会议

    以下频道提供了世界各地举行的许多小型本地 Python 会议的教程、讲座和会议录音。

    虽然就其本身而言,这些频道中的大多数都不满足 2000 订户的要求,但我们在这里将它们列为荣誉奖,因为它们代表了世界各地不同的 Python 社区。

    请注意,这些会议的一些(可能更早)视频(以及其他非 Python 内容)可以在第二天的视频和工程师那里获得。SG 通道。或者,PyVideo.org 的可以作为一个一站式商店,在那里你可以找到大多数(但不是全部)这些会议录音。

  • 欧洲巨蟒
  • EuroSciPy
  • 猕猴桃果
  • PyCascades
  • PyCon 阿根廷(Ar)
  • PyCon 澳大利亚(AU)
  • 加拿大 PyCon】
  • PyCon 捷克语(CZ)
  • 芬兰 PyCon】
  • PyCon 德国(德)
  • 香港 PyCon(HG)
  • 爱尔兰派康(IE)
  • 意大利派康(Nove)
  • PyCon 日本(JP)
  • 朝鲜半岛(KR)
  • 马来西亚 PyCon(MY)
  • PyCon phillipins(PH)
  • 波兰皮孔
  • 新加坡 PyCon(SG)
  • PyCon 斯洛伐克(SK)
  • PyCon 乌克兰(非盟)
  • PyCon 英国(英国)
  • PyGotham 2017
  • PyOhio
  • 如果你认为这个列表中缺少了什么,或者如果你想看到你自己的 Python YouTube 频道被添加,那么请在下面留下评论,或者发推文给我们。***

    使用 Python zip()函数进行并行迭代

    原文:https://realpython.com/python-zip-function/

    *立即观看**本教程有真实 Python 团队创建的相关视频课程。和书面教程一起看,加深理解: 用 Python 的 zip()函数 并行迭代

    Python 的zip()函数创建了一个迭代器,它将聚合两个或更多可迭代对象的元素。您可以使用结果迭代器快速一致地解决常见的编程问题,比如创建字典。在本教程中,您将发现 Python zip()函数背后的逻辑,以及如何使用它来解决现实世界中的问题。

    本教程结束时,您将学会:

  • zip() 如何在 Python 3 和 Python 2 中工作
  • 如何使用 Python zip()函数进行并行迭代
  • 如何使用zip()动态地创建字典
  • 免费奖励: 掌握 Python 的 5 个想法,这是一个面向 Python 开发者的免费课程,向您展示将 Python 技能提升到下一个水平所需的路线图和心态。

    了解 Python zip()函数

    zip()在内置命名空间中可用。如果您使用dir()来检查__builtins__,那么您会在列表的末尾看到zip():

    >>> dir(__builtins__)
    ['ArithmeticError', 'AssertionError', 'AttributeError', ..., 'zip']
    

    您可以看到'zip'是可用对象列表中的最后一个条目。

    根据官方文档,Python 的zip()函数表现如下:

    返回元组的迭代器,其中第 i 个元组包含来自每个参数序列或可迭代对象的第 i 个元素。当最短的输入 iterable 用尽时,迭代器停止。使用一个可迭代的参数,它返回一个 1 元组的迭代器。如果没有参数,它将返回一个空迭代器。(来源)

    在本教程的剩余部分,您将解开这个定义。在研究代码示例时,您会看到 Python zip 操作的工作方式就像包或牛仔裤上的物理拉链一样。拉链两侧的互锁齿对被拉到一起以闭合开口。事实上,这个直观的类比对于理解zip()来说是完美的,因为这个功能是以物理拉链命名的!

    Remove ads

    在 Python 中使用zip()

    Python 的zip()函数定义为zip(*iterables)。该函数将 iterables 作为参数,并返回一个迭代器。这个迭代器生成一系列元组,其中包含来自每个 iterable 的元素。zip()可以接受任何类型的 iterable,比如文件、列表、元组、字典、集合等等。

    传递n个参数

    如果将zip()n参数一起使用,那么函数将返回一个迭代器,生成长度为n的元组。要了解这一点,请看下面的代码块:

    >>> numbers = [1, 2, 3]
    >>> letters = ['a', 'b', 'c']
    >>> zipped = zip(numbers, letters)
    >>> zipped  # Holds an iterator object
    <zip object at 0x7fa4831153c8>
    >>> type(zipped)
    <class 'zip'>
    >>> list(zipped)
    [(1, 'a'), (2, 'b'), (3, 'c')]
    

    这里,您使用zip(numbers, letters)创建一个迭代器,该迭代器产生形式为(x, y)的元组。在这种情况下,x值取自numbers,而y值取自letters。注意 Python zip()函数是如何返回迭代器的。要检索最终的列表对象,需要使用list()来消耗迭代器。

    如果你正在处理像列表、元组或字符串这样的序列,那么你的 iterables 肯定会从左到右被求值。这意味着元组的结果列表将采用[(numbers[0], letters[0]), (numbers[1], letters[1]),..., (numbers[n], letters[n])]的形式。然而,对于其他类型的可重复项(如集合,您可能会看到一些奇怪的结果:

    >>> s1 = {2, 3, 1}
    >>> s2 = {'b', 'a', 'c'}
    >>> list(zip(s1, s2))
    [(1, 'a'), (2, 'c'), (3, 'b')]
    

    在这个例子中,s1s2set对象,它们的元素没有任何特定的顺序。这意味着zip()返回的元组将包含随机配对的元素。如果你打算将 Python zip()函数用于像集合这样的无序可重复项,那么这一点需要记住。

    不传递参数

    您也可以不带任何参数调用zip()。在这种情况下,您将简单地得到一个空迭代器:

    >>> zipped = zip()
    >>> zipped
    <zip object at 0x7f196294a488>
    >>> list(zipped)
    []
    

    在这里,您调用没有参数的zip(),所以您的zipped 变量持有一个空迭代器。如果您使用list()来使用迭代器,那么您也会看到一个空列表。

    您也可以尝试强制空迭代器直接产生一个元素。在这种情况下,你会得到一个StopIteration 异常:

    >>> zipped = zip()
    >>> next(zipped)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration
    

    当您在zipped上调用 next() 时,Python 会尝试检索下一项。然而,由于zipped持有一个空迭代器,所以没有东西可以取出,所以 Python 引发了一个StopIteration异常。

    传递一个参数

    Python 的zip()函数也可以只接受一个参数。结果将是一个迭代器,产生一系列 1 项元组:

    >>> a = [1, 2, 3]
    >>> zipped = zip(a)
    >>> list(zipped)
    [(1,), (2,), (3,)]
    

    这可能不是那么有用,但它仍然有效。也许你能找到一些zip()这种行为的用例!

    正如您所看到的,您可以调用 Python zip()函数,使用任意多的输入可重复项。结果元组的长度将始终等于作为参数传递的 iterables 的数量。下面是一个包含三个可迭代项的示例:

    >>> integers = [1, 2, 3]
    >>> letters = ['a', 'b', 'c']
    >>> floats = [4.0, 5.0, 6.0]
    >>> zipped = zip(integers, letters, floats)  # Three input iterables
    >>> list(zipped)
    [(1, 'a', 4.0), (2, 'b', 5.0), (3, 'c', 6.0)]
    

    这里,您用三个 iterables 调用 Python zip()函数,所以得到的元组每个都有三个元素。

    Remove ads

    传递长度不等的参数

    当你使用 Python zip()函数时,注意你的 iterables 的长度是很重要的。作为参数传入的 iterables 可能长度不同。

    在这些情况下,zip()输出的元素数量将等于最短的的长度。任何更长的 iterables 中的剩余元素将被zip()完全忽略,正如你在这里看到的:

    >>> list(zip(range(5), range(100)))
    [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]
    

    由于5是第一个(也是最短的) range() 对象的长度,zip()输出一个五元组列表。第二个range()对象仍有 95 个不匹配的元素。这些都被zip()忽略了,因为没有更多来自第一个range()对象的元素来完成配对。

    如果尾随或不匹配的值对你很重要,那么你可以用 itertools.zip_longest() 代替zip()。使用这个函数,丢失的值将被替换为传递给fillvalue参数的值(默认为 None )。迭代将继续,直到最长的可迭代次数用完:

    >>> from itertools import zip_longest
    >>> numbers = [1, 2, 3]
    >>> letters = ['a', 'b', 'c']
    >>> longest = range(5)
    >>> zipped = zip_longest(numbers, letters, longest, fillvalue='?')
    >>> list(zipped)
    [(1, 'a', 0), (2, 'b', 1), (3, 'c', 2), ('?', '?', 3), ('?', '?', 4)]
    

    在这里,您使用itertools.zip_longest()生成五个元组,其中包含来自lettersnumberslongest的元素。只有当longest耗尽时,迭代才会停止。numbersletters中缺失的元素用问号?填充,这是你用fillvalue指定的。

    自从 Python 3.10 ,zip()有了一个新的可选关键字参数叫做 strict ,它是通过 PEP 618 引入的——给 zip 添加可选的长度检查。这个参数的主要目标是提供一种安全的方式来处理长度不等的可重复项。

    strict的缺省值是False,这确保了zip()保持向后兼容,并且具有与它在旧 Python 3 版本中的行为相匹配的缺省行为:

    >>> # Python >= 3.10
    
    >>> list(zip(range(5), range(100)))
    [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]
    

    在 Python >= 3.10 中,调用zip()而不将默认值更改为strict仍然会给出一个五元组列表,忽略第二个range()对象中不匹配的元素。

    或者,如果您将strict设置为True,那么zip()将检查您作为参数提供的输入可重复项是否具有相同的长度,如果不相同,将引发 ValueError :

    >>> # Python >= 3.10
    
    >>> list(zip(range(5), range(100), strict=True))
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: zip() argument 2 is longer than argument 1
    

    当您需要确保函数只接受等长的可重复项时,zip()的这个新特性非常有用。将strict设置为True会使期望等长可重复项的代码更加安全,确保对调用者代码的错误更改不会导致数据无声地丢失。

    比较 Python 3 中的zip()和 2 中的

    Python 的zip()函数在该语言的两个版本中工作方式不同。在 Python 2 中,zip()返回元组的list。产生的list被截断为最短输入 iterable 的长度。如果你调用zip()而没有参数,那么你得到一个空的list作为回报:

    >>> # Python 2
    >>> zipped = zip(range(3), 'ABCD')
    >>> zipped  # Hold a list object
    [(0, 'A'), (1, 'B'), (2, 'C')]
    >>> type(zipped)
    <type 'list'>
    >>> zipped = zip()  # Create an empty list
    >>> zipped
    []
    

    在这种情况下,您对 Python zip()函数的调用返回在值C处截断的元组列表。当你调用没有参数的zip()时,你得到一个空的list

    然而,在 Python 3 中,zip()返回一个迭代器。该对象按需生成元组,并且只能被遍历一次。一旦最短的输入 iterable 用尽,迭代以一个StopIteration异常结束。如果没有给zip()提供参数,那么函数返回一个空迭代器:

    >>> # Python 3
    >>> zipped = zip(range(3), 'ABCD')
    >>> zipped  # Hold an iterator
    <zip object at 0x7f456ccacbc8>
    >>> type(zipped)
    <class 'zip'>
    >>> list(zipped)
    [(0, 'A'), (1, 'B'), (2, 'C')]
    >>> zipped = zip()  # Create an empty iterator
    >>> zipped
    <zip object at 0x7f456cc93ac8>
    >>> next(zipped)
    Traceback (most recent call last):
      File "<input>", line 1, in <module>
        next(zipped)
    StopIteration
    

    这里,您对zip()的调用返回一个迭代器。第一次迭代在C被截断,第二次迭代导致StopIteration异常。在 Python 3 中,您还可以通过将返回的迭代器封装在对list()的调用中来模拟zip()的 Python 2 行为。这将遍历迭代器并返回一个元组列表。

    如果您经常使用 Python 2,那么请注意将zip()与长输入 iterables 一起使用会无意中消耗大量内存。在这些情况下,考虑使用itertools.izip(*iterables)来代替。这个函数创建了一个迭代器,它聚集了每个可迭代对象的元素。它产生了与 Python 3 中的zip()相同的效果:

    >>> # Python 2
    >>> from itertools import izip
    >>> zipped = izip(range(3), 'ABCD')
    >>> zipped
    <itertools.izip object at 0x7f3614b3fdd0>
    >>> list(zipped)
    [(0, 'A'), (1, 'B'), (2, 'C')]
    

    在这个例子中,您调用itertools.izip()来创建一个迭代器。当您用list()消费返回的迭代器时,您会得到一个元组列表,就像您在 Python 3 中使用zip()一样。当最短的输入 iterable 用尽时,迭代停止。

    如果您真的需要编写在 Python 2 和 Python 3 中行为相同的代码,那么您可以使用如下技巧:

    try:
        from itertools import izip as zip
    except ImportError:
        pass
    

    在这里,如果在itertoolsizip()可用,那么您将知道您在 Python 2 中,并且izip()将使用别名zip被导入。否则,你的程序会抛出一个ImportError,你就知道你在 Python 3 中了。(这里的 pass语句只是一个占位符。)

    有了这个技巧,您可以在整个代码中安全地使用 Python zip()函数。运行时,您的程序将自动选择并使用正确的版本。

    到目前为止,您已经了解了 Python 的zip()函数是如何工作的,并了解了它的一些最重要的特性。现在是时候卷起袖子开始编写真实世界的例子了!

    Remove ads

    在多个可迭代对象上循环

    在多个可迭代对象上循环是 Python 的zip()函数最常见的用例之一。如果您需要遍历多个列表、元组或任何其他序列,那么您很可能会求助于zip()。本节将向您展示如何使用zip()来同时迭代多个可迭代对象。

    并行遍历列表

    Python 的zip()函数允许你在两个或更多的可迭代对象上并行迭代。由于zip()生成元组,您可以在 for循环的头中解包这些元组:

    >>> letters = ['a', 'b', 'c']
    >>> numbers = [0, 1, 2]
    >>> for l, n in zip(letters, numbers):
    ...     print(f'Letter: {l}')
    ...     print(f'Number: {n}')
    ...
    Letter: a
    Number: 0
    Letter: b
    Number: 1
    Letter: c
    Number: 2
    

    在这里,您遍历由zip()返回的一系列元组,并将元素解包到ln。当你组合zip()for循环、元组解包时,你可以得到一个有用的python 式习语,用于一次遍历两个或更多的 iterables。

    您也可以在一个for循环中遍历两个以上的 iterables。考虑下面的例子,它有三个输入项:

    >>> letters = ['a', 'b', 'c']
    >>> numbers = [0, 1, 2]
    >>> operators = ['*', '/', '+']
    >>> for l, n, o in zip(letters, numbers, operators):
    ...     print(f'Letter: {l}')
    ...     print(f'Number: {n}')
    ...     print(f'Operator: {o}')
    ...
    Letter: a
    Number: 0
    Operator: *
    Letter: b
    Number: 1
    Operator: /
    Letter: c
    Number: 2
    Operator: +
    

    在这个例子中,您使用带有三个 iterables 的zip()来创建并返回一个迭代器,该迭代器生成 3 项元组。这使您可以一次遍历所有三个可迭代对象。对于 Python 的zip()函数,可以使用的 iterables 的数量没有限制。

    **注意:**如果你想更深入地研究 Python for循环,请查看Python“for”循环(确定迭代)。

    并行遍历字典

    在 Python 3.6 及更高版本中,字典是有序集合,这意味着它们保持其元素被引入的相同顺序。如果您利用了这个特性,那么您可以使用 Python zip()函数以一种安全和一致的方式遍历多个字典:

    >>> dict_one = {'name': 'John', 'last_name': 'Doe', 'job': 'Python Consultant'}
    >>> dict_two = {'name': 'Jane', 'last_name': 'Doe', 'job': 'Community Manager'}
    >>> for (k1, v1), (k2, v2) in zip(dict_one.items(), dict_two.items()):
    ...     print(k1, '->', v1)
    ...     print(k2, '->', v2)
    ...
    name -> John
    name -> Jane
    last_name -> Doe
    last_name -> Doe
    job -> Python Consultant
    job -> Community Manager
    

    这里,您并行迭代dict_onedict_two。在这种情况下,zip()用两个字典中的条目生成元组。然后,您可以解包每个元组并同时访问两个字典的条目。

    **注意:**如果你想更深入地研究字典迭代,请查看如何在 Python 中迭代字典。

    注意,在上面的例子中,从左到右的求值顺序是有保证的。还可以使用 Python 的zip()函数并行遍历集合。然而,你需要考虑到,与 Python 3.6 中的字典不同,集合不会保持它们的元素有序。如果你忘记了这个细节,你的程序的最终结果可能并不完全是你想要的或期望的。

    解压缩序列

    在新 Pythonistas 的论坛中经常出现一个问题:“如果有一个zip()函数,那么为什么没有一个unzip()函数做相反的事情?”

    Python 中之所以没有unzip()函数,是因为zip()的反义词是……嗯,zip()。你还记得 Python zip()函数就像一个真正的拉链一样工作吗?到目前为止,示例已经向您展示了 Python 如何压缩关闭的内容。那么,如何解压 Python 对象呢?

    假设您有一个元组列表,并希望将每个元组的元素分成独立的序列。为此,您可以将zip()与解包操作符* 一起使用,如下所示:

    >>> pairs = [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]
    >>> numbers, letters = zip(*pairs)
    >>> numbers
    (1, 2, 3, 4)
    >>> letters
    ('a', 'b', 'c', 'd')
    

    这里,您有一个包含某种混合数据的元组list。然后,使用解包操作符*解压数据,创建两个不同的列表(numbersletters)。

    Remove ads

    并行排序

    排序是编程中常见的操作。假设你想合并两个列表,同时对它们进行排序。为此,您可以将zip().sort() 一起使用,如下所示:

    >>> letters = ['b', 'a', 'd', 'c']
    >>> numbers = [2, 4, 3, 1]
    >>> data1 = list(zip(letters, numbers))
    >>> data1
    [('b', 2), ('a', 4), ('d', 3), ('c', 1)]
    >>> data1.sort()  # Sort by letters
    >>> data1
    [('a', 4), ('b', 2), ('c', 1), ('d', 3)]
    >>> data2 = list(zip(numbers, letters))
    >>> data2
    [(2, 'b'), (4, 'a'), (3, 'd'), (1, 'c')]
    >>> data2.sort()  # Sort by numbers
    >>> data2
    [(1, 'c'), (2, 'b'), (3, 'd'), (4, 'a')]
    

    在这个例子中,首先用zip()合并两个列表,并对它们进行排序。请注意data1是如何按照letters排序的,而data2是如何按照numbers排序的。

    您也可以同时使用sorted()zip()来获得类似的结果:

    >>> letters = ['b', 'a', 'd', 'c']
    >>> numbers = [2, 4, 3, 1]
    >>> data = sorted(zip(letters, numbers))  # Sort by letters
    >>> data
    [('a', 4), ('b', 2), ('c', 1), ('d', 3)]
    

    在这种情况下,sorted()遍历由zip()生成的迭代器,并通过letters对条目进行排序,这一切都是一气呵成的。这种方法会快一点,因为你只需要两个函数调用:zip()sorted()

    使用sorted(),你还可以编写一段更通用的代码。这将允许你排序任何种类的序列,而不仅仅是列表。

    成对计算

    可以使用 Python zip()函数进行一些快速计算。假设您在电子表格中有以下数据:

    元素/月份 一月 二月 三月
    销售总额 Fifty-two thousand Fifty-one thousand Forty-eight thousand
    生产成本 Forty-six thousand eight hundred Forty-five thousand nine hundred Forty-three thousand two hundred

    你将使用这些数据来计算你的月利润。zip()可以为您提供一种快速的计算方式:

    >>> total_sales = [52000.00, 51000.00, 48000.00]
    >>> prod_cost = [46800.00, 45900.00, 43200.00]
    >>> for sales, costs in zip(total_sales, prod_cost):
    ...     profit = sales - costs
    ...     print(f'Total profit: {profit}')
    ...
    Total profit: 5200.0
    Total profit: 5100.0
    Total profit: 4800.0
    

    在这里,您通过从sales中减去costs来计算每个月的利润。Python 的zip()函数结合正确的数据对进行计算。您可以推广这个逻辑,用zip()返回的对进行任何复杂的计算。

    构建字典

    Python 的字典是一种非常有用的数据结构。有时,您可能需要从两个不同但密切相关的序列中构建一个字典。实现这一点的一个方便方法是同时使用dict()zip()。例如,假设您从表单或数据库中检索一个人的数据。现在,您拥有以下数据列表:

    >>> fields = ['name', 'last_name', 'age', 'job']
    >>> values = ['John', 'Doe', '45', 'Python Developer']
    

    有了这些数据,您需要创建一个字典来进行进一步的处理。在这种情况下,您可以将dict()zip()一起使用,如下所示:

    >>> a_dict = dict(zip(fields, values))
    >>> a_dict
    {'name': 'John', 'last_name': 'Doe', 'age': '45', 'job': 'Python Developer'}
    

    在这里,您创建了一个结合了两个列表的字典。zip(fields, values)返回一个生成 2 项元组的迭代器。如果您在这个迭代器上调用dict(),那么您将构建您需要的字典。fields的元素成为字典的键,values的元素代表字典中的值。

    您也可以通过组合zip()dict.update()来更新现有的字典。假设约翰换了工作,你需要更新字典。您可以执行如下操作:

    >>> new_job = ['Python Consultant']
    >>> field = ['job']
    >>> a_dict.update(zip(field, new_job))
    >>> a_dict
    {'name': 'John', 'last_name': 'Doe', 'age': '45', 'job': 'Python Consultant'}
    

    这里,dict.update()用您使用 Python 的zip()函数创建的键值元组更新字典。使用这种技术,您可以很容易地覆盖job的值。

    Remove ads

    结论

    在本教程中,你已经学会了如何使用 Python 的zip()函数。zip()可以接收多个 iterables 作为输入。它返回一个迭代器,该迭代器可以从每个参数生成带有成对元素的元组。当您需要在一个循环中处理多个可迭代对象并同时对它们的项执行一些操作时,结果迭代器会非常有用。

    现在您可以:

  • 在 Python 3 和 Python 2 中都使用zip()函数
  • 循环遍历多个 iterables 并对它们的项目并行执行不同的操作
  • 通过将两个输入的可重复项压缩在一起,动态创建和更新字典
  • 您还编写了一些例子,可以作为使用 Python 的zip()函数实现自己的解决方案的起点。当您深入探索zip()时,请随意修改这些示例!

    立即观看**本教程有真实 Python 团队创建的相关视频课程。和书面教程一起看,加深理解: 用 Python 的 zip()函数 并行迭代******

    Python Zip 导入:快速分发模块和包

    原文:https://realpython.com/python-zip-import/

    Python 允许你直接通过 Zip imports 从 ZIP 文件导入代码。这个有趣的内置特性使您能够压缩 Python 代码以供分发。如果您经常使用 Zip 文件中的 Python 代码,ZIP 导入也会有所帮助。在这两种情况下,学习创建可导入的 ZIP 文件并从中导入代码将是一项很有价值的技能。

    即使您的日常工作流程不涉及包含 Python 代码的 Zip 文件,您仍然可以通过本教程探索 ZIP 导入来学习一些有趣的新技能。

    在本教程中,您将学习:

  • 什么是 Zip 导入
  • 何时在代码中使用 Zip 导入
  • 如何用zipfile创建可导入的压缩文件
  • 如何使您的 ZIP 文件对导入代码可用
  • 您还将学习如何使用zipimport模块从 ZIP 文件中动态导入代码,而无需将它们添加到 Python 的模块搜索路径中。为此,您将编写一个从 ZIP 文件加载 Python 代码的最小插件系统。

    为了从本教程中获得最大收益,你应该事先了解 Python 的导入系统是如何工作的。你还应该知道用zipfile操作 ZIP 文件的基本知识,用操作文件,使用 with语句。

    免费奖励: 掌握 Python 的 5 个想法,这是一个面向 Python 开发者的免费课程,向您展示将 Python 技能提升到下一个水平所需的路线图和心态。

    了解 Python Zip 导入

    从 Python 2.3 开始,可以从 ZIP 文件里面导入模块和包。这个特性被称为 Zip imports ,当您需要将一个完整的包作为单个文件分发时,这是非常有用的,这是它最常见的用例。

    PEP 273 引入了 Zip 导入作为内置特性。这个特性被 Python 社区广泛认为是必备的,因为分发几个独立的.py.pyc.pyo文件并不总是合适和有效的。

    Zip 导入可以简化共享和分发代码的过程,这样您的同事和最终用户就不必四处摸索,试图将文件提取到正确的位置来让代码工作。

    **注意:**从 Python 3.5 开始,.pyo文件扩展名不再使用。详见 PEP 488 。

    PEP 302 增加了一系列的导入挂钩 ,为 Zip 导入提供内置支持。如果你想从一个 ZIP 文件中导入模块和包,那么你只需要这个文件出现在 Python 的模块搜索路径中。

    模块搜索路径是目录和 ZIP 文件的列表。它住在 sys.path 。当您在代码中运行 import 语句时,Python 会自动搜索列表中的项目。

    在接下来的几节中,您将学习如何使用不同的 Python 工具和技术创建准备导入的 ZIP 文件。您还将了解一些将这些文件添加到当前 Python 的模块搜索路径中的方法。最后,您将深入研究zipimport,它是在幕后支持 Zip 导入特性的模块。

    Remove ads

    创建您自己的可导入 ZIP 文件

    Zip 导入允许您将组织在几个模块和包中的代码作为单个文件快速分发。在创建可导入的 ZIP 文件时,Python 已经帮你搞定了。来自标准库的 zipfile 模块包含一个名为 ZipFile 的类,用于操作 ZIP 文件。它还包含了一个更专业的类,叫做 PyZipFile ,可以方便地创建可导入的 ZIP 文件。

    PyZipFile让您快速高效地将 Python 代码捆绑到 ZIP 文件中。该类继承自ZipFile,因此它共享同一个基本接口。但是,这些类别之间有两个主要区别:

    1. PyZipFile的初始化器带有一个名为optimize的可选参数,它允许你在归档之前通过编译成字节码来优化 Python 代码。
    2. PyZipFile类提供了一个名为 .writepy() 的方法,该方法接受 Python 模块或包作为参数,并将其添加到目标 ZIP 文件中。

    如果optimize是其默认值-1,那么输入的.py文件会自动编译成.pyc文件,然后添加到目标档案中。为什么会这样?通过跳过编译步骤,打包.pyc文件而不是原始的.py文件使得导入过程更加有效。在接下来的章节中,您将了解到关于这个主题的更多信息。

    在接下来的两节中,您将亲自动手创建自己的包含模块和包的可导入 ZIP 文件。

    将 Python 模块捆绑成 ZIP 文件

    在这一节中,您将使用PyZipFile.writepy()将一个.py文件编译成字节码,并将生成的.pyc文件添加到一个 ZIP 存档中。要试用.writepy(),假设您有一个hello.py模块:

    """Print a greeting message."""
    # hello.py
    
    def greet(name="World"):
        print(f"Hello, {name}! Welcome to Real Python!")
    

    这个模块定义了一个名为greet()的函数,它将name作为参数,将友好的问候信息打印到屏幕上。

    现在假设您想将这个模块打包成一个 ZIP 文件,以便以后导入。为此,您可以运行以下代码:

    >>> import zipfile
    
    >>> with zipfile.PyZipFile("hello.zip", mode="w") as zip_module:
    ...     zip_module.writepy("hello.py")
    ...
    
    >>> with zipfile.PyZipFile("hello.zip", mode="r") as zip_module:
    ...     zip_module.printdir()
    ...
    File Name                                             Modified             Size
    hello.pyc                                      2021-10-18 05:40:04          313
    

    运行这段代码后,您将在当前工作目录中拥有一个hello.zip文件。对zip_module上的.writepy()的调用自动将hello.py编译成hello.pyc,并存储在底层的 ZIP 文件hello.zip中。这就是为什么 .printdir() 显示hello.pyc而不是你原来的hello.py文件。这种自动编译确保了高效的导入过程。

    注意:PyZipFile类默认不压缩你的 Python 模块和包。它只是将它们存储在一个 ZIP 文件容器中。如果你想压缩你的源文件,你需要通过PyZipFilecompression参数显式地提供一个压缩方法。目前,Python 支持 Deflate 、 bzip2 和 LZMA 压缩方法。

    在本教程中,您将依赖于默认值compression、、ZIP_STORED、,这意味着您的源文件不会被压缩。压缩源文件会影响导入操作的性能,您将在本教程的后面部分了解到这一点。

    您也可以使用任何常规的文件归档器手动将.py.pyc文件打包成 ZIP 文件。如果生成的档案包含没有相应的.pyc文件的.py文件,那么 Python 将在您第一次从特定的 ZIP 文件导入时编译它们。

    Python 不会修改底层的 ZIP 文件来添加新编译的.pyc文件。所以下次运行导入时,Python 会再次编译代码。这种行为会使导入过程变慢。

    您还可以将一个目录作为第一个参数传递给.writepy()。如果输入目录不是 Python 包,那么该方法扫描它寻找.py文件,将它们编译成.pyc文件,并将这些.pyc文件添加到目标 ZIP 文件的顶层。扫描步骤不是递归的,这意味着不扫描子目录中的源文件。

    您可以通过将PyZipFileoptimize参数设置为以下值之一来进一步调整编译过程:

    价值 最佳化
    0 不执行任何优化
    1 删除 assert语句
    2 删除assert语句和文档字符串

    有了这些值,当.writepy()在归档之前将.py文件编译成.pyc文件时,您可以微调您想要使用的优化级别。

    到目前为止,您已经学习了如何将一个或多个模块捆绑到一个 ZIP 文件中。在日常编码中,您可能还需要压缩一个完整的 Python 包。您将在下一节中学习如何做到这一点。

    Remove ads

    将 Python 包打包成 ZIP 文件

    还可以通过使用PyZipFile及其.writepy()方法将 Python 包捆绑到 ZIP 文件中。正如您已经了解到的,如果您将一个常规目录作为第一个参数传递给.writepy(),那么该方法将扫描目录中的.py文件,编译它们,并将相应的.pyc文件添加到结果 ZIP 文件中。

    另一方面,如果输入目录是一个 Python 包,那么.writepy()编译所有的.py文件,并将它们添加到 ZIP 文件中,保持包的内部结构。

    要使用 Python 包来尝试.writepy(),创建一个新的hello/目录,并将您的hello.py文件复制到其中。然后添加一个空的__init__.py模块,把目录变成一个包。您最终应该得到以下结构:

    hello/
    |
    ├── __init__.py
    └── hello.py
    

    现在假设您想要将这个包打包成一个 ZIP 文件,以便分发。如果是这种情况,那么您可以运行以下代码:

    >>> import zipfile
    
    >>> with zipfile.PyZipFile("hello_pkg.zip", mode="w") as zip_pkg:
    ...     zip_pkg.writepy("hello")
    ...
    
    >>> with zipfile.PyZipFile("hello_pkg.zip", mode="r") as zip_pkg:
    ...     zip_pkg.printdir()
    ...
    File Name                                             Modified             Size
    hello/__init__.pyc                             2021-10-18 05:56:00          110
    hello/hello.pyc                                2021-10-18 05:56:00          319
    

    .writepy()的调用以hello包为参数,在其中搜索.py文件,编译成.pyc文件,最后添加到目标 ZIP 文件中,保持相同的包结构。

    了解 Zip 导入的局限性

    当您使用 Zip 文件分发 Python 代码时,您需要考虑 ZIP 导入的一些限制:

  • 无法加载动态文件,如.pyd.dll.so
    *** 从 .py文件中导入代码意味着性能妥协。* 如果解压缩库不可用,从压缩文件导入代码将会失败。*
  • *您可以在 ZIP 存档中包含任何类型的文件。然而,当您的用户从这些档案中导入代码时,只读取了.py.pyw.pyc.pyo文件。从动态文件中导入代码是不可能的,比如.pyd.dll.so,如果它们存在于 ZIP 文件中。比如,你不能从 ZIP 存档中加载用 C 编写的共享库和扩展模块。

    您可以通过从 ZIP 文件中提取动态模块,将它们写入文件系统,然后加载它们的代码来解决这个限制。然而,这意味着您需要创建临时文件并处理可能的错误和安全风险,这可能会使事情变得复杂。

    正如您在本教程前面所学的,Zip 导入也可能意味着性能的降低。如果您的档案包含.py模块,那么 Python 将编译它们以满足导入。但是,它不会保存相应的.pyc文件。这种行为可能会降低导入操作的性能。

    最后,如果你需要从一个压缩的 ZIP 文件中导入代码,那么 zlib 必须在你的工作环境中可用以进行解压缩。如果这个库不可用,从压缩的归档文件中导入代码会失败,并显示一个丢失的zlib消息。此外,解压缩步骤会给导入过程增加额外的性能开销。出于这些原因,您将在本教程中使用未压缩的 ZIP 文件。

    从 ZIP 文件导入 Python 代码

    到目前为止,您已经学会了如何创建自己的可导入 ZIP 文件以供分发。现在假设您在另一端,并且您正在获得带有 Python 模块和包的 ZIP 文件。如何从它们那里导入代码呢?在本节中,您将获得这个问题的答案,并了解如何使 ZIP 文件可用于导入其内容。

    为了让 Python 从 ZIP 文件导入代码,该文件必须在 Python 的模块搜索路径中可用,该路径存储在sys.path中。这个模块级变量包含一个由指定模块搜索路径的字符串组成的列表。path的内容包括:

  • 包含您正在运行的脚本的目录
  • 当前目录,如果你已经交互式地运行了解释器
  • PYTHONPATH 环境变量中的目录,如果设置的话
  • 取决于您的特定 Python 安装的目录列表
  • 该目录中列出了任意路径的配置文件(.pth文件)
  • 下表指出了几种将 ZIP 文件添加到sys.path的方法:

    [计]选项 目标代码或解释程序
    list.insert()list.append()list.extend()方法 您正在编写和运行的 Python 代码
    PYTHONPATH环境变量 您系统上运行的每个 Python 解释器
    一个 Python 路径配置文件,或.pth文件 包含.pth文件的 Python 解释器

    在接下来的部分中,您将探索这三种向sys.path添加项目的方法,这样您就可以使您的 ZIP 文件可用于导入它们的内容。

    Remove ads

    动态使用sys.path进行 Zip 导入

    因为sys.path是一个list对象,所以可以通过使用常规的list方法从 Python 代码中操纵它。一般来说,要向list对象添加新项目,可以使用.insert()、、或.extend()

    通常,您将使用.insert(0, item)从您的 Python 代码向sys.path添加新项目。以这种方式调用.insert()会在列表的开头插入item,确保新添加的条目优先于已有的条目。在名字冲突可能发生的时候,让item在开头使你能够隐藏现有的模块和包。

    现在假设您需要将包含您的hello.py模块的hello.zip文件添加到您当前 Python 的sys.path中。在这种情况下,您可以运行下面示例中的代码。注意,为了在您的机器上运行这个例子,您需要提供到hello.zip的正确路径:

    >>> import sys
    
    >>> # Insert the hello.zip into sys.path
    >>> sys.path.insert(0, "/path/to/hello.zip")
    
    >>> sys.path[0]
    '/path/to/hello.zip'
    
    >>> # Import and use the code
    >>> import hello
    
    >>> hello.greet("Pythonista")
    Hello, Pythonista! Welcome to Real Python!
    

    一旦你将hello.zip的路径添加到你的sys.path中,那么你就可以从hello.py中导入对象,就像对待任何常规模块一样。

    如果像hello_pkg.zip一样,您的 ZIP 文件包含 Python 包,那么您也可以将它添加到sys.path中。在这种情况下,导入应该是包相关的:

    >>> import sys
    
    >>> sys.path.insert(0, "/path/to/hello_pkg.zip")
    
    >>> from hello import hello
    
    >>> hello.greet("Pythonista")
    Hello, Pythonista! Welcome to Real Python!
    

    因为您的代码现在在一个包中,所以您需要从hello包中导入hello模块。然后您可以像往常一样访问greet()功能。

    sys.path添加项目的另一个选项是使用.append()。此方法将单个对象作为参数,并将其添加到基础列表的末尾。重启您的 Python 交互式会话,并运行提供hello.zip路径的代码:

    >>> import sys
    
    >>> sys.path.append("/path/to/hello.zip")
    
    >>> # The hello.zip file is at the end of sys.path
    >>> sys.path[-1] '/path/to/hello.zip'
    
    >>> from hello import greet
    >>> greet("Pythonista")
    Hello, Pythonista! Welcome to Real Python!
    

    这种技术的工作原理类似于使用.insert()。然而,ZIP 文件的路径现在位于sys.path的末尾。如果列表中前面的任何一项包含一个名为hello.py的模块,那么 Python 将从该模块导入,而不是从您新添加的hello.py模块导入。

    你也可以循环使用.append()来添加几个文件到sys.path,或者你可以只使用.extend()。该方法接受 iterable 项,并将其内容添加到基础列表的末尾。和.append()一样,记住.extend()会把你的文件添加到sys.path的末尾,所以现有的名字可以隐藏你的 ZIP 文件中的模块和包。

    使用PYTHONPATH进行系统范围的 Zip 导入

    在某些情况下,您可能需要一个给定的 ZIP 文件,以便从计算机上运行的任何脚本或程序中导入其内容。在这些情况下,您可以使用PYTHONPATH环境变量让 Python 在您运行解释器时自动将您的档案加载到sys.path中。

    PYTHONPATH使用与 PATH 环境变量相同的格式,由 os.pathsep 分隔的目录路径列表。在 Unix 系统上,比如 Linux 和 macOS,这个函数返回一个冒号(:),而在 Windows 上,它返回一个分号(;)。

    例如,如果您在 Linux 或 macOS 上,那么您可以通过运行以下命令将您的hello.zip文件添加到PYTHONPATH:

    $ export PYTHONPATH="$PYTHONPATH:/path/to/hello.zip"
    

    该命令将/path/to/hello.zip添加到当前的PYTHONPATH中,并导出它,以便它在当前的终端会话中可用。

    **注意:**上面的命令导出了一个定制版本的PYTHONPATH,其中包含了到hello.zip的路径。该变量的自定义版本仅在当前命令行会话中可用,一旦关闭该会话,该版本将会丢失。

    如果您正在运行 Bash 作为您当前的 shell ,那么您可以通过将以下代码添加到您的.bashrc文件中,使这个自定义版本的PYTHONPATH可用于您的所有命令行会话:

    # .bashrc
    
    if [ -f /path/to/hello.zip ]; then
        export PYTHONPATH="$PYTHONPATH:/path/to/hello.zip"
    fi
    

    这段代码检查hello.zip是否存在于您的文件系统中。如果是,那么它将文件添加到PYTHONPATH变量并导出它。因为每次启动新的命令行实例时,Bash 都会运行这个文件,所以定制的PYTHONPATH将在每个会话中可用。

    现在您可以发出python命令来运行解释器。一旦你到达那里,像往常一样检查sys.path的内容:

    >>> import sys
    
    >>> sys.path
    [..., '/path/to/hello.zip', ...]
    

    酷!您的hello.zip文件在列表中。从这一点开始,您将能够像在上一节中一样从hello.py导入对象。来吧,试一试!

    在上面的输出中需要注意的重要一点是,你的hello.zip文件不在sys.path的开头,这意味着根据 Python 如何处理其模块搜索路径,较早出现的同名模块将优先于你的hello模块。

    要在 Windows 系统上向PYTHONPATH添加项目,您可以在cmd.exe窗口中执行命令:

    C:\> set PYTHONPATH=%PYTHONPATH%;C:\path\to\hello.zip
    

    该命令将C:\path\to\hello.zip添加到 Windows 机器上PYTHONPATH变量的当前内容中。要检查它,在同一个命令提示符会话中运行 Python 解释器,并像以前一样查看sys.path的内容。

    **注意:**同样,您用上面的命令设置的PYTHONPATH变量将只在您当前的终端会话中可用。要在 Windows 上永久设置PYTHONPATH变量,学习如何在 Windows 中添加 PYTHONPATH

    将目录和 ZIP 文件添加到PYTHONPATH环境变量中,可以让您在终端会话下运行的任何 Python 解释器都可以使用这些条目。最后,需要注意的是 Python 会忽略PYTHONPATH中列出的不存在的目录和 ZIP 文件,所以请密切关注。

    Remove ads

    使用.pth文件进行首选范围的 zip 导入

    有时,只有在运行特定的 Python 解释器时,您可能希望从给定的 ZIP 文件中导入代码。当您的项目使用该 ZIP 文件中的代码,并且您不希望该代码可用于您的其他项目时,这是非常有用的。

    Python 的路径配置文件允许你用自定义的目录和 ZIP 文件来扩展给定解释器的sys.path

    路径配置文件使用.pth文件扩展名,可以保存目录和 ZIP 文件的路径列表,每行一个。每次运行提供.pth文件的 Python 解释器时,这个路径列表都会被添加到sys.path中。

    Python 的.pth文件有一个简单明了的格式:

  • 每行必须包含一个路径条目。
  • 空行和以数字符号(#)开头的行被跳过。
  • 执行以import开头的行。
  • 一旦你有了一个合适的.pth文件,你需要把它复制到一个站点目录中,这样 Python 就可以找到它并加载它的内容。要获得当前 Python 环境的站点目录,可以从 site 模块中调用 getusersitepackages() 。如果您在当前机器上没有管理员权限,那么您可以使用位于 site.USER_SITE 的用户站点目录。

    **注意:**用户网站目录可能不在您的个人文件夹中。如果这是您的情况,那么请按照所需的路径结构随意创建它。

    例如,以下命令为 Ubuntu 上的全系统 Python 3 解释器创建了一个hello.pth路径配置文件:

    $ sudo nano /usr/lib/python3/dist-packages/hello.pth
    

    该命令创建hello.pth,使用 GNU nano 文本编辑器作为root。在那里,输入你的hello.zip文件的路径。按 Ctrl + X ,然后按 Y ,最后按 Enter 保存文件。现在,当您再次启动系统 Python 解释器时,这个 ZIP 文件将在sys.path中可用:

    >>> import sys
    
    >>> sys.path
    [..., '/path/to/hello.zip', ...]
    

    就是这样!从这一点开始,只要使用系统范围的 Python 解释器,就可以从hello.py导入对象。

    同样,当 Python 读取和加载给定的.pth文件的内容时,不存在的目录和 ZIP 文件不会被添加到sys.path中。最后,.pth文件中的重复条目只添加一次到sys.path

    探索 Python 的zipimport:Zip 导入背后的工具

    你已经在不知不觉中使用了标准库中的 zipimport 模块。在幕后,当一个sys.path项包含一个 ZIP 文件的路径时,Python 的内置导入机制会自动使用这个模块。在这一节中,您将通过一个实际的例子学习zipimport是如何工作的,以及如何在您的代码中显式地使用它。

    了解zipimport的基础知识

    zipimport的主要成分是 zipimporter 。这个类将 ZIP 文件的路径作为参数,并创建一个导入器实例。下面是一个如何使用zipimporter及其一些属性和方法的例子:

    >>> from zipimport import zipimporter
    
    >>> importer = zipimporter("/path/to/hello.zip")
    
    >>> importer.is_package("hello")
    False
    
    >>> importer.get_filename("hello")
    '/path/to/hello.zip/hello.pyc'
    
    >>> hello = importer.load_module("hello")
    >>> hello.__file__
    '/path/to/hello.zip/hello.pyc'
    
    >>> hello.greet("Pythonista")
    Hello, Pythonista! Welcome to Real Python!
    

    在这个例子中,首先从zipimport导入zipimporter。然后您创建一个带有您的hello.zip文件路径的zipimporter实例。

    zipimporter类提供了几个有用的属性和方法。例如,如果输入名称是一个包,则 .is_package() 返回True,否则返回False.get_filename() 方法返回归档文件中给定模块的路径( .__file__ )。

    如果您想将模块的名称放入当前的名称空间,那么您可以使用.load_module(),它返回对输入模块的引用。有了这个引用,您就可以像往常一样从模块中访问任何代码对象。

    Remove ads

    zipimport 构建一个插件系统

    如上所述,Python 内部使用zipimport从 ZIP 文件加载代码。您还了解了本模块提供的工具,您可以在一些实际的编码情况下使用。例如,假设您想要实现一个定制的插件系统,其中每个插件都位于自己的 ZIP 文件中。您的代码应该在给定的文件夹中搜索 ZIP 文件,并自动导入插件的功能。

    要实际体验这个例子,您将实现两个玩具插件,它们接受一条消息和一个标题,并在您的默认 web 浏览器和一个 Tkinter 消息框中显示它们。每个插件都应该在自己的目录中,在一个叫做plugin.py的模块中。这个模块应该实现插件的功能,并提供一个 main() 函数作为插件的入口点。

    继续创建一个名为web_message/的文件夹,其中包含一个plugin.py文件。在您最喜欢的代码编辑器或 IDE 中打开文件,并为 web 浏览器插件键入以下代码:

    """A plugin that displays a message using webbrowser."""
    # web_message/plugin.py
    
    import tempfile
    import webbrowser
    
    def main(text, title="Alert"):
        with tempfile.NamedTemporaryFile(
            mode="w", suffix=".html", delete=False
        ) as home:
            html = f"""
     <html>
     <head>
     <title>{title}</title>
     </head>
     <body>
     <h1>
      {text} </h1>
     </body>
     </html>
     """
            home.write(html)
            path = "file://" + home.name
        webbrowser.open(path)
    

    这段代码中的main()函数接受一条text消息和一个窗口title。然后在一个with语句中创建一个 NamedTemporaryFile 。该文件将包含一个在页面上显示titletext的最小 HTML 文档。要在默认的 web 浏览器中打开这个文件,可以使用webbrowser.open()

    下一个插件提供了类似的功能,但是使用了Tkinter工具包。这个插件的代码也应该存在于一个名为plugin.py的模块中。您可以将该模块放在文件系统中一个名为tk_message/的目录下:

    """A plugin that displays a message using Tkinter."""
    # tk_message/plugin.py
    
    import tkinter
    from tkinter import messagebox
    
    def main(text, title="Alert"):
        root = tkinter.Tk()
        root.withdraw()
        messagebox.showinfo(title, text)
    

    遵循与网络浏览器插件相同的模式,main()使用texttitle。在这种情况下,该函数创建一个 Tk 实例来保存插件的顶层窗口。但是,您不需要显示那个窗口,只需要一个消息框。所以,你使用.withdraw()来隐藏根窗口,然后调用messagebox上的.showinfo()来显示一个带有输入texttitle的对话框。

    现在您需要将每个插件打包到它自己的 ZIP 文件中。为此,在包含web_message/tk_message/文件夹的目录中启动一个 Python 交互会话,并运行以下代码:

    >>> import zipfile
    
    >>> plugins = ("web_message", "tk_message")
    
    >>> for plugin in plugins:
    ...     with zipfile.PyZipFile(f"{plugin}.zip", mode="w") as zip_plugin:
    ...         zip_plugin.writepy(plugin)
    ...
    

    下一步是为你的插件系统创建一个根文件夹。该文件夹必须包含一个plugins/目录,其中包含新创建的 ZIP 文件。您的目录应该是这样的:

    rp_plugins/
    │
    ├── plugins/
    │   │
    │   ├── tk_message.zip
    │   └── web_message.zip
    │
    └── main.py
    

    main.py中,您将为您的插件系统放置客户端代码。继续用下面的代码填充main.py:

     1# main.py
     2
     3import zipimport
     4from pathlib import Path
     5
     6def load_plugins(path):
     7    plugins = []
     8    for zip_plugin in path.glob("*.zip"):
     9        importer = zipimport.zipimporter(zip_plugin)
    10        plugin_module = importer.load_module("plugin")
    11        plugins.append(getattr(plugin_module, "main"))
    12    return plugins
    13
    14if __name__ == "__main__":
    15    path = Path("plugins/")
    16    plugins = load_plugins(path)
    17    for plugin in plugins:
    18        plugin("Hello, World!", "Greeting!")
    

    下面是这段代码的逐行工作方式:

  • 第 3 行导入zipimport从相应的 ZIP 文件中动态加载你的插件。
  • 第 4 行导入 pathlib 来管理系统路径。
  • 第 6 行定义了load_plugins(),它获取包含插件档案的目录的路径。
  • 第 7 行创建一个空列表来保存当前的插件。
  • 第 8 行定义了一个 for循环,它遍历插件目录中的.zip文件。
  • 第 9 行为系统中的每个插件创建一个zipimporter实例。
  • 第 10 行从每个插件的 ZIP 文件中加载plugin模块。
  • 第 11 行将每个插件的main()函数添加到plugins列表中。
  • 第 12 行T3】将plugins列表返回给调用者。
  • 第 14 到 18 行调用load_plugins()来生成当前可用插件列表,并循环执行它们。

    如果您从命令行运行main.py脚本,那么您首先会得到一个 Tkinter 消息框,显示Hello, World!消息和Greeting!标题。关闭该窗口后,您的 web 浏览器将在新页面上显示相同的消息和标题。来吧,试一试!

    结论

    Python 可以直接从 ZIP 文件导入代码,如果它们在模块搜索路径中可用的话。这个特性被称为 Zip 导入。您可以利用 Zip 导入将模块和包捆绑到一个归档文件中,这样您就可以快速有效地将它们分发给最终用户。

    如果您经常将 Python 代码捆绑到 ZIP 文件中,并且需要在日常任务中使用这些代码,那么您也可以利用 Zip 导入。

    在本教程中,您学习了:

  • 什么是 Zip 导入
  • 何时以及如何使用 Zip 导入
  • 如何用zipfile构建可导入的压缩文件
  • 如何使 ZIP 文件对导入机制可用
  • 您还编写了一个关于如何用zipimport构建一个最小插件系统的实践示例。通过这个例子,您学习了如何用 Python 从 ZIP 文件中动态导入代码。*******

    Python 的 zipapp:构建可执行的 Zip 应用程序

    原文:https://realpython.com/python-zipapp/

    Python Zip 应用程序是一个快速而又酷的选择,您可以将可执行的应用程序捆绑并分发到一个单独的准备运行的文件中,这将使您的最终用户体验更加愉快。如果您想了解 Python 应用程序以及如何使用标准库中的zipapp创建它们,那么本教程就是为您准备的。

    您将能够创建 Python Zip 应用程序,作为向最终用户和客户分发您的软件产品的一种快速且可访问的方式。

    在本教程中,您将学习:

  • 什么是 Python Zip 应用程序
  • Zip 应用程序如何工作内部
  • 如何用**zipapp**构建 Python Zip 应用
  • 什么是独立的 Python Zip 应用程序以及如何创建它们
  • 如何使用命令行工具手动创建 Python Zip 应用程序
  • **您还将了解一些用于创建 Zip 应用程序的第三方库,它们克服了zipapp的一些限制。

    为了更好地理解本教程,你需要知道如何构造 Python 应用程序布局、运行 Python 脚本、构建 Python 包,使用 Python 虚拟环境,以及使用 pip 安装和管理依赖关系。您还需要熟练使用命令行或终端。

    免费奖励: 并学习 Python 3 的基础知识,如使用数据类型、字典、列表和 Python 函数。

    Python Zip 应用入门

    Python 生态系统中最具挑战性的问题之一是找到一种有效的方法来分发可执行的应用程序,如图形用户界面(GUI) 和命令行界面(CLI) 程序。

    编译后的编程语言,比如 C 、 C++ 、 Go ,可以生成你可以直接在不同操作系统和架构上运行的可执行文件。这种能力使您可以轻松地向最终用户分发软件。

    然而,Python 不是那样工作的。Python 是一种解释语言,这意味着你需要一个合适的 Python 解释器来运行你的应用程序。没有直接的方法生成一个不需要解释器就能运行的独立的可执行文件。

    有许多解决方案可以解决这个问题。你会发现诸如 PyInstaller 、 py2exe 、 py2app 、 Nuitka 等工具。这些工具允许您创建可分发给最终用户的自包含可执行应用程序。然而,设置这些工具可能是一个复杂且具有挑战性的过程。

    有时候你不需要额外的复杂性。你只需要从一个脚本或者一个小程序中构建一个可执行的应用程序,这样你就可以快速的把它分发给你的终端用户。如果您的应用程序足够小,并且使用纯 Python 代码,那么使用一个 Python Zip 应用程序就足够了。

    Remove ads

    什么是 Python Zip 应用程序?

    PEP 441——改进 Python ZIP 应用程序支持围绕 Python Zip 应用程序形成了概念、术语和规范。这种类型的应用程序由一个使用 ZIP 文件格式的文件组成,其中包含 Python 可以作为程序执行的代码。这些应用程序依靠 Python 从 ZIP 文件中运行代码的能力,这些 ZIP 文件的根目录下有一个 __main__.py 模块,它作为一个入口点脚本工作。

    从版本 2.6 和 3.0 开始,Python 已经能够从 ZIP 文件运行脚本。实现这一目标的步骤非常简单。您只需要一个 ZIP 文件,其根目录下有一个__main__.py模块。然后你可以把那个文件传递给 Python,Python 把它添加到 sys.path 并把__main__.py作为一个程序执行。在sys.path中保存应用程序的档案允许你通过 Python 的导入系统访问它的代码。

    举个简单的例子,假设你在一个类似于 Unix 的操作系统上,比如 Linux 或者 macOS,你运行下面的命令:

    $ echo 'print("Hello, World!")' > __main__.py
    
    $ zip hello.zip __main__.py
     adding: __main__.py (stored 0%)
    
    $ python ./hello.zip
    Hello, World!
    

    您使用 echo命令创建一个包含代码print("Hello, World!")__main__.py文件。然后你使用 zip 命令将__main__.py存档到hello.zip。一旦你完成了这些,你就可以通过将文件名作为参数传递给python命令来运行hello.zip了。

    为了完善 Python Zip 应用程序的内部结构,您需要一种方法来告诉操作系统如何执行它们。ZIP 文件格式允许您在 ZIP 存档文件的开头添加任意数据。Python Zip 应用程序利用该特性在应用程序的归档中包含一个标准的 Unix she bang 行:

    #!/usr/bin/env python3
    

    在 Unix 系统上,这一行告诉操作系统使用哪个程序来执行手头的文件,这样您就可以直接运行文件,而无需使用python命令。在 Windows 系统上,Python 启动器正确理解 shebang 行并为您运行 Zip 应用程序。

    即使使用 shebang 行,也可以通过将应用程序的文件名作为参数传递给python命令来执行 Python Zip 应用程序。

    总之,要构建 Python Zip 应用程序,您需要:

  • 一个使用标准 ZIP 文件格式并在其根包含一个 __main__.py模块的档案
  • 一个可选的 shebang 行,指定适当的 Python 解释器来运行应用程序
  • 除了__main__.py模块,您的应用程序的 ZIP 文件可以包含 Python 模块和包以及任何其他任意文件。但是,只有.py.pyc.pyo文件可以通过导入系统直接使用。换句话说,您可以将.pyd.so.dll文件打包到您的应用程序文件中,但是除非您将它们解压缩到您的文件系统中,否则您将无法使用它们。

    **注意:**无法执行存储在 ZIP 文件中的.pyd.so.dll文件的代码是操作系统的限制。这个限制使得创建运送和使用.pyd.so.dll文件的 Zip 应用程序变得困难。

    Python 生态系统充满了用 C 或 C++编写的有用的库和工具,以保证速度和效率。即使您可以将这些库捆绑到一个 Zip 应用程序的归档文件中,您也不能从那里直接使用它们。您需要将这个库解压缩到您的文件系统中,然后从这个新位置访问它的组件。

    PEP 441 提议将.pyz.pyzw作为 Python Zip 应用的文件扩展名。.pyz扩展标识控制台或命令行应用程序,而.pyzw扩展指窗口或 GUI 应用程序。

    在 Unix 系统上,如果您更喜欢 CLI 应用程序的简单命令名,可以删除.pyz扩展名。在 Windows 上,.pyz.pyzw文件是可执行文件,因为 Python 解释器将它们注册为可执行文件。

    为什么使用 Python Zip 应用程序?

    假设你有一个程序,你的团队在他们的内部工作流程中经常使用它。该程序已经从一个单文件脚本发展成为一个拥有多个包、模块和文件的成熟应用程序。

    此时,一些团队成员努力安装和设置每个新版本。他们不断要求您提供一种更快、更简单的方式来设置和运行程序。在这种情况下,您应该考虑创建一个 Python Zip 应用程序,将您的程序捆绑到一个文件中,并作为一个准备运行的应用程序分发给您的同事。

    Python Zip 应用程序是发布软件的绝佳选择,您必须将这些软件作为单个可执行文件进行分发。这也是一种使用非正式渠道分发软件的便捷方式,例如通过计算机网络发送或托管在 FTP 服务器上。

    Python Zip 应用程序是以现成的格式打包和分发 Python 应用程序的方便快捷的方式,可以让您的最终用户的生活更加愉快。

    Remove ads

    如何构建 Python Zip 应用程序?

    正如您已经了解到的,Python Zip 应用程序由一个标准 Zip 文件组成,该文件包含一个__main__.py模块,该模块作为应用程序的入口点。当您运行应用程序时,Python 会自动将其容器(ZIP 文件本身)添加到sys.path中,这样__main__.py就可以从塑造应用程序的模块和包中导入对象。

    要构建 Python Zip 应用程序,您可以运行以下常规步骤:

    1. 创建包含__main__.py模块的应用程序源目录。
    2. 压缩应用程序的源目录。
    3. 添加一个可选的 Unix shebang 行来定义运行应用程序的解释器。
    4. 使应用程序的 ZIP 文件可执行。此步骤仅适用于类似 Unix 的操作系统。

    这些步骤非常简单,运行起来也很快。有了它们,如果您拥有所需的工具和知识,您可以在几分钟内手动构建一个 Python Zip 应用程序。然而,Python 标准库为您提供了更方便、更快捷的解决方案。

    PEP 441 提议在标准库中增加一个名为 zipapp 的新模块。这个模块方便了 Zip 应用程序的创建,它从 Python 3.5 开始就可用了。

    在本教程中,您将关注使用zipapp创建 Python Zip 应用程序。然而,您还将学习如何使用不同的工具手动运行整个系列的步骤。这些额外的知识可以帮助您更深入地理解创建 Python Zip 应用程序的整个过程。如果您使用的是低于 3.5 的 Python 版本,这也会很有帮助。

    设置 Python Zip 应用程序

    到目前为止,您已经了解了什么是 Python Zip 应用程序,如何构建它们,为什么使用它们,以及创建它们时需要遵循的步骤。你已经准备好开始建造你自己的了。不过,首先,您需要有一个用于 Python Zip 应用程序的功能性应用程序或脚本。

    对于本教程,您将使用一个名为 reader 的示例应用程序,它是一个最小的 web 提要阅读器,从 真实 Python 提要中读取最新的文章和资源。

    接下来,您应该将reader的存储库克隆到您的本地机器上。在您选择的工作目录中打开命令行,并运行以下命令:

    $ git clone https://github.com/realpython/reader.git
    

    该命令将reader存储库的全部内容下载到当前目录下的reader/文件夹中。

    **注意:**如果你不熟悉 Git 和 GitHub ,请查看Git 和 GitHub 介绍给 Python 开发者。

    一旦克隆了存储库,就需要安装应用程序的依赖项。首先,你应该创建一个 Python 虚拟环境。继续运行以下命令:

    $ cd reader/
    $ python3 -m venv ./venv
    $ source venv/bin/activate
    

    这些命令在reader/目录中创建和激活一个新的 Python 虚拟环境,该目录是reader项目的根目录。

    **注意:**要在 Windows 上创建和激活虚拟环境,您可以运行以下命令:

    C:\> python -m venv venv
    C:\> venv\Scripts\activate.bat
    

    如果你在一个不同的平台上,那么你可能需要查看 Python 的官方文档关于创建虚拟环境。

    现在您可以使用 pip 安装reader的依赖项:

    (venv) $ python -m pip install feedparser html2text importlib_resources
    

    运行上面的命令将在您的活动 Python 虚拟环境中安装应用程序的所有依赖项。

    **注:**自 Python 3.7 起, importlib_resources 在标准库中可用为 importlib.resources 。所以,如果你使用的是高于或等于 3.7 的版本,你不需要安装这个库。只需在定义了reader包的__init__.py文件中修改相应的导入。

    下面是一个使用readerReal Python 获取最新文章、课程、播客剧集和其他学习资源的例子:

    (venv) $ python -m reader
    The latest tutorials from Real Python (https://realpython.com/)
     0 The Django Template Language: Tags and Filters
     1 Pass by Reference in Python: Best Practices
     2 Using the "and" Boolean Operator in Python
     ...
    

    由于reader在提要中列出了 30 个最新的学习资源,因此您的输出会有所不同。每个学习资源都有一个 ID 号。要从这些学习资源中获取一个项目的内容,您可以将相应的 ID 号作为命令行参数传递给reader:

    (venv) $ python -m reader 2
    Using the "and" Boolean Operator in Python
    
    Python has three Boolean operators, or **logical operators** : `and`, `or`,
    and `not`. You can use them to check if certain conditions are met before
    deciding the execution path your programs will follow. In this tutorial,
    you'll learn about the `and` operator and how to use it in your code.
     ...
    

    该命令使用 Python 中的“and”布尔运算符将文章的部分内容打印到使用 Markdown 文本格式的屏幕上。您可以通过更改 ID 号来阅读任何可用的内容。

    注意:reader如何工作的细节与本教程无关。如果你对实现感兴趣,那么看看如何向 PyPI 发布开源 Python 包。特别是,你可以阅读名为的部分,快速浏览代码。

    要从reader存储库创建一个 Zip 应用程序,您将主要使用reader/文件夹。该文件夹具有以下结构:

    reader/
    |
    ├── config.cfg
    ├── feed.py
    ├── __init__.py
    ├── __main__.py
    └── viewer.py
    

    reader/目录中要注意的最重要的事实是,它包括一个__main__.py文件。这个文件使您能够像以前一样使用python -m reader命令来执行这个包。

    拥有一个__main__.py文件提供了创建 Python Zip 应用程序所需的入口点脚本。在这个例子中,__main__.py文件在reader包中。如果您使用这个目录结构创建您的 Zip 应用程序,那么您的应用程序将不会运行,因为__main__.py将无法从reader导入对象。

    要解决这个问题,将reader包复制到一个名为realpython/的外部目录,并将__main__.py文件放在其根目录下。然后删除运行python -m reader产生的__pycache__/文件夹,就像你之前做的那样。您最终应该得到以下目录结构:

    realpython/ │
    ├── reader/ │   ├── __init__.py
    │   ├── config.cfg
    │   ├── feed.py
    │   └── viewer.py
    │
    └── __main__.py
    

    有了这个新的目录结构,您就可以用zipapp创建您的第一个 Python Zip 应用程序了。这就是你在下一节要做的。

    Remove ads

    zipapp 构建 Python Zip 应用程序

    要创建您的第一个 Python Zip 应用程序,您将使用zipapp。这个模块实现了一个用户友好的命令行界面,它提供了用一个命令构建一个完整的 Zip 应用程序所需的选项。你也可以通过模块的 Python API 从你的代码中使用zipapp,它主要由一个单一的函数组成。

    在接下来的两节中,您将了解使用zipapp构建 Zip 应用程序的两种方法。

    从命令行使用zipapp

    zipapp的命令行界面简化了将 Python 应用程序打包成 ZIP 文件的过程。在内部,zipapp通过运行您之前学习的步骤,从源代码创建一个 Zip 应用程序。

    要从命令行运行zipapp,您应该使用以下命令语法:

    $ python -m zipapp <source> [OPTIONS]
    

    如果source是一个目录,那么这个命令从该目录的内容创建一个 Zip 应用程序。如果source是一个文件,那么这个文件应该是一个包含应用程序代码的 ZIP 文件。然后,输入 ZIP 文件的内容被复制到目标应用程序档案中。

    下面是zipapp接受的命令行选项的总结:

    选择 描述
    -o <output_filename>--output=<output_filename> 将 Zip 应用程序写入名为output_filename的文件。此选项使用您提供的输出文件名。如果你不提供这个选项,那么zipapp使用带有.pyz扩展名的source的名字。
    -p <interpreter>--python=<interpreter> 将 shebang 行添加到应用程序的存档中。如果你在一个 POSIX 系统上,那么zipapp使应用程序的归档文件可执行。如果您不提供此选项,那么您的应用程序的存档将不会有 shebang,也不会是可执行的。
    -m <main_function>--main=<main_function> 生成并写入一个适当的执行main_function__main__.py文件。main_function参数的形式应该是"package.module:callable"。如果你已经有一个__main__.py模块,你不需要这个选项。
    -c--compress 使用 Deflate 压缩方法压缩source的内容。默认情况下,zipapp只存储source的内容而不压缩它,这可以让你的应用程序运行得更快。

    此表提供了对zipapp命令行选项的简要描述。有关每个选项的具体行为的更多细节,请查看官方文档。

    现在您已经知道了从命令行使用zipapp的基本知识,是时候构建reader Zip 应用程序了。返回终端窗口,运行以下命令:

    (venv) $ python -m zipapp realpython/ \
    -o realpython.pyz \
    -p "/usr/bin/env python3"
    

    在这个命令中,您将realpython/目录设置为 Zip 应用程序的源。使用-o选项,您可以为应用程序的档案提供一个名称realpython.pyz。最后,-p选项让您设置解释器,zipapp将使用它来构建 shebang 行。

    就是这样!现在,您将在当前目录中拥有一个realpython.pyz文件。稍后您将学习如何执行该文件。

    为了展示zipapp-m--main命令行选项,假设您决定更改reader项目布局并将__main__.py重命名为cli.py,同时将文件移回reader包。继续创建您的realpython/目录的副本,并进行建议的更改。之后,realpython/的文案应该是这样的:

    realpython_copy/
    │
    └── reader/
        ├── __init__.py
     ├── cli.py    ├── config.cfg
        ├── feed.py
        └── viewer.py
    

    目前,您的应用程序的源目录没有一个__main__.py模块。zipapp-m命令行选项允许你自动生成:

    $ python -m zipapp realpython_copy/ \
    -o realpython.pyz \
    -p "/usr/bin/env python3" \
    -m "reader.cli:main"
    

    该命令使用带有"reader.cli:main"-m选项作为参数。这个输入值告诉zipappZip 应用程序可调用的入口点是reader包中cli.py模块的 main()

    生成的__main__.py文件包含以下内容:

    # -*- coding: utf-8 -*-
    import reader.cli
    reader.cli.main()
    

    然后,这个__main__.py文件与您的应用程序源代码一起打包成一个名为realpython.pyz的 ZIP 存档文件。

    Remove ads

    使用 Python 代码中的zipapp

    Python 的zipapp也有一个应用编程接口(API) ,你可以从你的 Python 代码中使用它。这个 API 主要由一个名为 create_archive() 的函数组成。使用该函数,您可以快速创建 Python Zip 应用程序:

    >>> import zipapp
    
    >>> zipapp.create_archive(
    ...     source="realpython/",
    ...     target="realpython.pyz",
    ...     interpreter="/usr/bin/env python3",
    ... )
    

    这个对create_archive()的调用需要一个名为source的第一个参数,它代表您的 Zip 应用程序的源代码。第二个参数,target,保存应用程序存档的文件名。最后,interpreter保存解释器来构建并作为 shebang 行添加到应用程序的 ZIP 存档中。

    以下是create_archive()可以提出的论点的总结:

  • source 可以带以下对象:
  • 现有源目录的基于字符串的路径
  • 引用现有源目录的类似于路径的对象
  • 现有 Zip 应用程序归档的基于字符串的路径
  • 引用现有 Zip 应用程序档案的类似路径的对象
  • 一个类似于文件的对象被打开用于读取并指向一个现有的 Zip 应用程序档案
  • target 接受以下对象:
  • 目标 Zip 应用程序文件的基于字符串的路径
  • 目标 Zip 应用程序文件的类似路径的对象
  • interpreter 指定一个 Python 解释器,作为 shebang 行写在生成的应用程序归档文件的开头。省略此参数会导致没有 shebang 行,也没有应用程序的执行权限。
  • main 指定zipapp将用作目标归档入口点的可调用文件的名称。当您没有一个__main__.py文件时,您为main提供一个值。
  • filter 采用一个布尔值函数,如果源目录中的给定文件应该被添加到最终的 Zip 应用程序文件中,该函数应该返回True
  • compressed 接受一个决定是否要压缩源文件的布尔值。
  • 这些参数中的大多数在zipapp的命令行界面中都有等价的选项。上面的例子只使用了前三个参数。根据您的具体需要,您也可以使用其他参数。

    运行 Python Zip 应用程序

    到目前为止,您已经学习了如何从命令行和 Python 代码使用zipapp创建 Python Zip 应用程序。现在是时候运行你的realpython.pyz应用程序了,以确保它能正常工作。

    如果您在一个类似 Unix 的系统上,那么您可以通过执行以下命令来运行您的应用程序:

    (venv) $ ./realpython.pyz
    The latest tutorials from Real Python (https://realpython.com/)
     0 The Django Template Language: Tags and Filters
     1 Pass by Reference in Python: Best Practices
     2 Using the "and" Boolean Operator in Python
     ...
    

    酷!有用!现在,您有了一个可以快速与朋友和同事共享的应用程序文件。

    您不再需要从命令行调用 Python 来运行应用程序。因为您的 Zip 应用程序档案文件在开头有一个 shebang 行,所以操作系统将自动使用您的活动 Python 解释器来运行目标档案文件的内容。

    **注意:**为了让您的应用程序运行,您需要在 Python 环境中安装所有必需的依赖项。否则,你会得到一个ImportError

    如果您在 Windows 上,那么您的 Python 安装应该已经注册了.pyz.pyzw文件,并且应该能够运行它们:

    C:\> .\realpython.pyz
    The latest tutorials from Real Python (https://realpython.com/)
     0 The Django Template Language: Tags and Filters
     1 Pass by Reference in Python: Best Practices
     2 Using the "and" Boolean Operator in Python
     ...
    

    本教程中使用的reader应用程序有一个命令行界面,所以从命令行或终端窗口运行它是有意义的。然而,如果你有一个图形用户界面应用程序,那么你将能够从你最喜欢的文件管理器中运行它,就像你通常运行可执行程序一样。

    同样,您可以通过调用适当的 Python 解释器来执行任何 Zip 应用程序,并将应用程序的文件名作为参数:

    $ python3 realpython.pyz
    The latest tutorials from Real Python (https://realpython.com/)
     0 The Django Template Language: Tags and Filters
     1 Pass by Reference in Python: Best Practices
     2 Using the "and" Boolean Operator in Python
     ...
    

    在这个例子中,您使用系统 Python 3.x 安装来运行realpython.pyz。如果您的系统上有许多 Python 版本,那么您可能需要更具体一些,使用类似于python3.9 realpython.pyz的命令。

    注意,无论您使用什么解释器,您都需要安装应用程序的依赖项。否则,您的应用程序将会失败。不满足依赖关系是 Python Zip 应用程序的常见问题。要解决这种恼人的情况,您可以创建一个独立的应用程序,这是下一节的主题。

    Remove ads

    使用zipapp 创建独立的 Python Zip 应用程序

    您还可以使用zipapp来创建独立的 Python Zip 应用程序。这种类型的应用程序将其所有依赖项捆绑到应用程序的 ZIP 文件中。这样,您的最终用户只需要一个合适的 Python 解释器来运行应用程序。他们不需要担心依赖性。

    要创建一个独立的 Python Zip 应用程序,首先需要使用pip将其依赖项安装到源目录中。继续创建一个名为realpython_sa/realpython/目录的副本。然后运行以下命令来安装应用程序的依赖项:

    (venv) $ python -m pip install feedparser html2text importlib_resources \
    --target realpython_sa/
    

    这个命令使用带有--target选项的pip install来安装reader的所有依赖项。pip的文档说这个选项允许你将包安装到一个目标目录中。在本例中,该目录必须是您的应用程序的源目录,realpython_sa/

    **注意:**如果你的应用程序有一个requirements.txt文件,那么你可以通过一个快捷方式来安装依赖项。

    您可以改为运行以下命令:

    (venv) $ python -m pip install \
    -r requirements.txt \ --target app_directory/
    

    使用这个命令,您可以将应用程序的requirements.txt文件中列出的所有依赖项安装到app_directory/文件夹中。

    一旦将reader的依赖项安装到realpython_sa/中,就可以随意删除pip创建的*.dist-info目录。这些目录包含几个带有元数据的文件,pip用它们来管理相应的包。既然你不再需要这些信息,你可以把它们扔掉。

    这个过程的最后一步是像往常一样使用zipapp构建 Zip 应用程序:

    (venv) $ python -m zipapp realpython_sa/ \
    -p "/usr/bin/env python3" \
    -o realpython_sa.pyz \
    -c
    

    该命令在realpython_sa.pyz中生成一个独立的 Python Zip 应用程序。要运行这个应用程序,您的最终用户只需要在他们的机器上安装一个合适的 Python 3 解释器。与常规的 Zip 应用程序相比,这种应用程序的优势在于您的最终用户不需要安装任何依赖项来运行应用程序。来吧,试一试!

    在上面的例子中,您使用了zipapp-c选项来压缩realpython_sa/的内容。对于具有许多依赖项、占用大量磁盘空间的应用程序来说,这个选项相当方便。

    手动创建 Python Zip 应用程序

    正如您已经了解到的,从 Python 3.5 开始,zipapp就在标准库中可用。如果您使用的是低于这个版本的 Python,那么您仍然可以手动构建您的 Python Zip 应用程序,而不需要zipapp

    在接下来的两节中,您将学习如何使用 Python 标准库中的 zipfile 创建一个 Zip 应用程序。您还将学习如何使用一些命令行工具来完成相同的任务。

    使用 Python 的zipfile

    您已经有了包含reader应用程序源文件的realpython/目录。从该目录手动构建 Python Zip 应用程序的下一步是将其归档到一个 Zip 文件中。为此,你可以使用zipfile。这个模块提供了创建、读取、写入、添加和列出 ZIP 文件内容的便利工具。

    下面的代码展示了如何使用zipfile.ZipFile和一些其他工具创建reader Zip 应用程序。例如,代码依靠 pathlibstat 来读取源目录的内容,并对结果文件设置执行权限:

    # build_app.py
    
    import pathlib
    import stat
    import zipfile
    
    app_source = pathlib.Path("realpython/")
    app_filename = pathlib.Path("realpython.pyz")
    
    with open(app_filename, "wb") as app_file:
     # 1\. Prepend a shebang line    shebang_line = b"#!/usr/bin/env python3\n"
        app_file.write(shebang_line)
    
     # 2\. Zip the app's source    with zipfile.ZipFile(app_file, "w") as zip_app:
            for file in app_source.rglob("*"):
                member_file = file.relative_to(app_source)
                zip_app.write(file, member_file)
    
    # 3\. Make the app executable (POSIX systems only) current_mode = app_filename.stat().st_mode
    exec_mode = stat.S_IEXEC
    app_filename.chmod(current_mode | exec_mode)
    

    这段代码运行所需的三个步骤,最终得到一个成熟的 Python Zip 应用程序。第一步是在应用程序文件中添加一个 shebang 行。它使用 with语句中的 open() 创建一个文件对象(app_file)来处理应用程序。然后调用.write()app_file的开头写 shebang 行。

    **注意:**如果你在 Windows 上,你应该在 UTF-8 中编码 shebang 行。如果你在一个 POSIX 系统上,比如 Linux 和 macOS,你应该用 sys.getfilesystemencoding() 返回的任何文件系统编码对它进行编码。

    第二步使用嵌套的with语句中的 ZipFile 压缩应用程序的源目录内容。for循环使用pathlib.Path.rglob()遍历realpython/中的文件,并将它们写入zip_app。注意.rglob()通过目标文件夹app_source递归搜索文件和目录。

    最终 ZIP 存档中每个文件的文件名member_file需要相对于应用程序的源目录,以确保应用程序 ZIP 文件的内部结构与源文件的结构realpython/相匹配。这就是为什么你在上面的例子中使用pathlib.Path.relative_to()

    最后,第三步使用 pathlib.Path.chmod() 使应用程序的文件可执行。为此,首先使用 pathlib.Path.stat() 获取文件的当前模式,然后使用按位或运算符(|)将该模式与 stat.S_IEXEC 结合起来。注意,这个步骤只对 POSIX 系统有影响。

    运行完这些步骤后,您的realpython.pyz应用程序就可以运行了。请从命令行尝试一下。

    Remove ads

    使用 Unix 命令行工具

    如果您使用的是类 Unix 系统,比如 Linux 和 macOS,那么您也可以在命令行中使用特定的工具来运行上一节中的三个步骤。例如,您可以使用zip命令压缩应用程序源目录的内容:

    $ cd realpython/
    $ zip -r ../realpython.zip *
    

    在这个例子中,你先将 cd 放入realpython/目录。然后使用带有-r选项的zip命令将realpython/的内容压缩到realpython.zip中。该选项递归遍历目标目录。

    **注意:**另一个选择是从命令行使用 Python 的zipfile

    为此,从realpython/目录外运行以下命令:

    $ python -m zipfile --create realpython.zip realpython/*
    

    zipfile--create命令行选项允许您从源目录创建一个 ZIP 文件。追加到realpython/目录的星号(*)告诉zipfile将该目录的内容放在生成的 ZIP 文件的根目录下。

    下一步是将 shebang 行添加到 ZIP 文件realpython.zip,并将其保存为realpython.pyz。为此,您可以在管道中使用echocat 命令:

    $ cd ..
    $ echo '#!/usr/bin/env python3' | cat - realpython.zip > realpython.pyz
    

    cd ..命令让你退出realpython/echo命令将'#!/usr/bin/env python3'发送到标准输出。管道字符(|)将标准输出的内容传递给cat命令。然后cat将标准输出(-)与realpython.zip的内容连接起来。最后,大于号(>)将cat输出重定向到realpython.pyz文件。

    最后,您可能希望使用 chmod 命令使应用程序的文件可执行:

    $ chmod +x realpython.pyz
    

    这里,chmodrealpython.pyz增加了执行权限(+x)。现在,您已经准备好再次运行您的应用程序,这可以像往常一样从命令行完成。

    使用第三方工具创建 Python 应用程序

    在 Python 生态系统中,您会发现一些第三方库的工作方式与zipapp类似。它们提供了更多的特性,对探索这些特性很有帮助。在本节中,您将了解其中两个第三方库: pexshiv

    项目提供了一个创建 PEX 文件的工具。PEX 代表 Python 可执行文件,是一种存储自包含可执行 Python 虚拟环境的文件格式。pex工具用一个 shebang 行和一个__main__.py模块将这些环境打包成 ZIP 文件,这允许您直接执行生成的 PEX 文件。pex工具是对 PEP 441 中概述的思想的扩展。

    要用pex创建一个可执行的应用程序,首先需要安装它:

    (venv) $ python -m pip install pex
    (venv) $ pex --help
    pex [-o OUTPUT.PEX] [options] [-- arg1 arg2 ...]
    
    pex builds a PEX (Python Executable) file based on the given specifications:
    sources, requirements, their dependencies and other options.
    Command-line options can be provided in one or more files by prefixing the
    filenames with an @ symbol. These files must contain one argument per line.
     ...
    

    pex工具提供了丰富的选项,允许你微调你的 PEX 文件。以下命令显示了如何为reader项目创建一个 PEX 文件:

    (venv) $ pex realpython-reader -c realpython -o realpython.pex
    

    这个命令在你的当前目录中创建realpython.pex。这个文件是用于reader的 Python 可执行文件。注意,pex处理reader的安装和所有来自 PyPI 的依赖项。在 PyPI 上可以获得名为realpython-readerreader项目,这就是为什么您使用这个名称作为pex的第一个参数。

    -c选项允许您定义应用程序将使用哪个控制台脚本。在这种情况下,控制台脚本是readersetup.py文件中定义的realpython-o选项指定输出文件。像往常一样,您可以从命令行执行./realpython.pex来运行应用程序。

    由于.pex文件的内容在执行前被解压缩,PEX 应用程序解决了zipapp应用程序的限制,允许您执行来自.pyd.so.dll文件的代码。

    需要注意的最后一个细节是pex在生成的 PEX 文件中创建和打包了一个 Python 虚拟环境。这种行为使您的 Zip 应用程序比用zipapp创建的常规应用程序大很多。

    在本节中,您将学习的第二个工具是shiv。它是一个命令行工具,用于构建自包含的 Python Zip 应用程序,如 PEP 441 中所述。与zipapp相比,shiv的优势在于shiv会自动将应用程序的所有依赖项包含在最终文档中,并使它们在 Python 的模块搜索路径中可用。

    要使用shiv,您需要从 PyPI 安装它:

    (venv) $ python -m pip install shiv
    (venv) $ shiv --help
    Usage: shiv [OPTIONS] [PIP_ARGS]...
    
     Shiv is a command line utility for building fully self-contained Python
     zipapps as outlined in PEP 441, but with all their dependencies included!
     ...
    

    --help选项显示了一个完整的使用信息,您可以通过检查来快速了解shiv是如何工作的。

    要用shiv构建 Python Zip 应用程序,您需要一个可安装的 Python 应用程序,带有一个setup.pypyproject.toml文件。幸运的是,GitHub 最初的reader项目满足了这个要求。回到包含克隆的reader/文件夹的目录,运行以下命令:

    (venv) $ shiv -c realpython \
    -o realpython.pyz reader/ \
    -p "/usr/bin/env python3"
    

    pex工具一样,shiv有一个-c选项来定义应用程序的控制台脚本。-o-p选项允许您分别提供输出文件名和合适的 Python 解释器。

    **注意:**上面的命令按预期工作。然而,shiv (0.5.2)的当前版本让pip显示一条关于它如何构建包的反对消息。由于shiv 直接接受pip参数,所以可以将 --use-feature=in-tree-build 放在命令的末尾,这样shiv就可以安全地使用pip

    zipapp不同,shiv允许您使用存储在应用程序档案中的.pyd.so.dll文件。为此,shiv在归档中包含了一个特殊的引导功能。这个函数将应用程序的依赖项解压到您的主文件夹的.shiv/目录中,并将它们添加到 Python 的sys.path目录中。

    这个特性允许您创建独立的应用程序,其中包括部分用 C 和 C++编写的库,以提高速度和效率,例如 NumPy 。

    Remove ads

    结论

    拥有一种快速有效的方法来分发 Python 可执行应用程序,可以在满足最终用户需求方面发挥重要作用。 Python Zip applications 为您捆绑和分发现成的应用程序提供了一个有效且可访问的解决方案。您可以使用 Python 标准库中的 zipapp 来快速创建自己的可执行 Zip 应用程序,并将它们传递给最终用户。

    在本教程中,您学习了:

  • 什么是 Python Zip 应用程序
  • Zip 应用程序如何工作内部
  • 如何用 zipapp 构建自己的 Python Zip 应用
  • 什么是独立 Zip 应用以及如何使用pipzipapp创建它们
  • 如何使用命令行工具手动创建 Python Zip 应用程序
  • 有了这些知识,您就可以快速创建 Python Zip 应用程序,作为向最终用户分发 Python 程序和脚本的便捷方式。*********

    Python 的 zipfile:有效地操作你的 ZIP 文件

    原文:# t0]https://realython . com/python-zipfile/

    Python 的 zipfile 是一个标准的库模块,用来操作 ZIP 文件。这种文件格式是归档和压缩数字数据时广泛采用的行业标准。你可以用它来打包几个相关的文件。它还允许您减少文件的大小并节省磁盘空间。最重要的是,它促进了计算机网络上的数据交换。

    作为 Python 开发人员或 DevOps 工程师,知道如何使用zipfile模块创建、读取、写入、填充、提取和列出 ZIP 文件是一项有用的技能。

    在本教程中,您将学习如何:

  • 用 Python 的zipfile从 ZIP 文件中读取、写入和提取文件
  • 使用zipfile读取关于 ZIP 文件内容的元数据
  • 使用zipfile操作现有 ZIP 文件中的成员文件
  • 创建新的 ZIP 文件来归档和压缩文件
  • 如果您经常处理 ZIP 文件,那么这些知识可以帮助您简化工作流程,自信地处理您的文件。

    为了从本教程中获得最大收益,你应该知道处理文件,使用 with语句,用 pathlib 处理文件系统路径,以及处理类和面向对象编程的基础知识。

    要获取您将用于编写本教程中的示例的文件和档案,请单击下面的链接:

    获取资料: 单击此处获取文件和档案的副本,您将使用它们来运行本 zipfile 教程中的示例。

    ZIP 文件入门

    ZIP 文件是当今数字世界中众所周知的流行工具。这些文件相当流行,广泛用于计算机网络(尤其是互联网)上的跨平台数据交换。

    您可以使用 ZIP 文件将常规文件打包成一个归档文件,压缩数据以节省磁盘空间,分发您的数字产品,等等。在本教程中,您将学习如何使用 Python 的zipfile模块操作 ZIP 文件。

    因为关于 ZIP 文件的术语有时会令人困惑,所以本教程将遵循以下术语约定:

    学期 意义
    ZIP 文件、ZIP 存档或存档 使用 ZIP 文件格式的物理文件
    文件 一个普通的电脑文件
    成员文件 作为现有 ZIP 文件一部分的文件

    清楚地记住这些术语将有助于你在阅读接下来的章节时避免混淆。现在,您已经准备好继续学习如何在 Python 代码中有效地操作 ZIP 文件了!

    Remove ads

    什么是 ZIP 文件?

    您可能已经遇到并使用过 ZIP 文件。没错,那些带.zip文件扩展名的到处都是!ZIP 文件,又称 ZIP 存档,是使用 ZIP 文件格式的文件。

    PKWARE 是创建并首先实现这种文件格式的公司。该公司制定并维护了当前的格式规范,该规范公开发布,允许创建使用 ZIP 文件格式读写文件的产品、程序和进程。

    ZIP 文件格式是一种跨平台、可互操作的文件存储和传输格式。它结合了无损数据压缩,文件管理,数据加密。

    数据压缩不是将归档文件视为 ZIP 文件的必要条件。因此,您可以在 ZIP 存档中压缩或解压缩成员文件。ZIP 文件格式支持几种压缩算法,尽管最常见的是 Deflate 。该格式还支持用 CRC32 进行信息完整性检查。

    尽管有其他类似的存档格式,如 RAR 文件和 T2 文件,ZIP 文件格式已经迅速成为高效数据存储和计算机网络数据交换的通用标准。

    ZIP 文件到处都是。比如微软 Office 和 Libre Office 等办公套件都依赖 ZIP 文件格式作为它们的文档容器文件。这意味着.docx.xlsx.pptx.odt.ods.odp文件实际上是包含组成每个文档的几个文件和文件夹的 ZIP 存档。其他使用 ZIP 格式的常见文件包括 .jar.war.epub 文件。

    你可能对 GitHub 很熟悉,它使用 Git 为软件开发和版本控制提供 web 托管。当你下载软件到你的本地电脑时,GitHub 使用 ZIP 文件打包软件项目。例如,您可以下载 ZIP 文件形式的练习解决方案 for Python 基础知识:Python 3 实用介绍一书,或者您可以下载您选择的任何其他项目。

    ZIP 文件允许您将文件聚合、压缩和加密到一个可互操作的便携式容器中。您可以传输 ZIP 文件,将它们分割成段,使它们能够自解压,等等。

    为什么使用 ZIP 文件?

    对于使用计算机和数字信息的开发人员和专业人员来说,知道如何创建、读取、写入和提取 ZIP 文件是一项有用的技能。除其他好处外,ZIP 文件允许您:

  • 在不丢失信息的情况下,减小文件的大小及其存储要求
  • 由于尺寸减小和单文件传输,提高了网络传输速度
  • 将几个相关的文件打包在一起到一个归档中,以便高效管理
  • 将您的代码打包到一个单独的归档文件中,以便分发
  • 使用加密技术保护您的数据,这是当今的普遍要求
  • 保证您信息的完整性避免对您数据的意外和恶意更改
  • 如果您正在寻找一种灵活、可移植且可靠的方法来归档您的数字文件,这些功能使 ZIP 文件成为您的 Python 工具箱的有用补充。

    Python 可以操作 ZIP 文件吗?

    是啊!Python 有几个工具可以让你操作 ZIP 文件。其中一些工具在 Python 标准库中可用。它们包括使用特定压缩算法压缩和解压缩数据的低级库,如zlibbz2lzma其他。

    Python 还提供了一个名为zipfile的高级模块,专门用于创建、读取、写入、提取和列出 ZIP 文件的内容。在本教程中,您将了解 Python 的zipfile以及如何有效地使用它。

    用 Python 的zipfile 操作现有的 ZIP 文件

    Python 的zipfile提供了方便的类和函数,允许您创建、读取、写入、提取和列出 ZIP 文件的内容。以下是zipfile支持的一些附加功能:

  • 大于 4 GiB 的 ZIP 文件( ZIP64 文件)
  • 数据解密
  • 几种压缩算法,如 Deflate、 Bzip2 和 LZMA
  • 使用 CRC32 进行信息完整性检查
  • 要知道zipfile确实有一些限制。例如,当前的数据解密功能可能相当慢,因为它使用纯 Python 代码。该模块无法处理加密 ZIP 文件的创建。最后,也不支持使用多磁盘 ZIP 文件。尽管有这些限制,zipfile仍然是一个伟大而有用的工具。继续阅读,探索它的能力。

    Remove ads

    打开 ZIP 文件进行读写

    zipfile模块中,你会找到 ZipFile 类。这个类的工作很像 Python 内置的 open() 函数,允许你使用不同的模式打开你的 ZIP 文件。默认为读取模式("r")。您也可以使用写入("w")、追加("a")和独占("x")模式。稍后,您将了解更多关于这些内容的信息。

    ZipFile实现了上下文管理器协议,这样你就可以在 with语句中使用该类。该功能允许您快速打开并处理 ZIP 文件,而不用担心在完成工作后会关闭文件。

    在编写任何代码之前,请确保您有一份将要使用的文件和归档的副本:

    获取资料: 单击此处获取文件和档案的副本,您将使用它们来运行本 zipfile 教程中的示例。

    为了准备好您的工作环境,将下载的资源放在您的主文件夹中名为python-zipfile/的目录中。将文件放在正确的位置后,移动到新创建的目录,并在那里启动一个 Python 交互式会话。

    为了热身,您将从阅读名为sample.zip的 ZIP 文件开始。为此,您可以在阅读模式下使用ZipFile:

    >>> import zipfile
    
    >>> with zipfile.ZipFile("sample.zip", mode="r") as archive:
    ...     archive.printdir()
    ...
    File Name                                        Modified             Size
    hello.txt                                 2021-09-07 19:50:10           83
    lorem.md                                  2021-09-07 19:50:10         2609
    realpython.md                             2021-09-07 19:50:10          428
    

    ZipFile初始化器的第一个参数可以是一个字符串,代表你需要打开的 ZIP 文件的路径。这个参数也可以接受类似文件的和类似路径的对象。在本例中,您使用基于字符串的路径。

    ZipFile的第二个参数是一个单字母字符串,表示您将用来打开文件的模式。正如您在本节开始时了解到的,ZipFile可以根据您的需要接受四种可能的模式。mode 位置参数默认为"r",如果想打开档案只读可以去掉。

    with语句里面,你在archive上调用 .printdir()archive 变量现在保存了ZipFile本身的实例。这个函数提供了一种在屏幕上显示底层 ZIP 文件内容的快速方法。该函数的输出具有用户友好的表格格式,包含三个信息栏:

  • File Name
  • Modified
  • Size
  • 如果您想在尝试打开一个有效的 ZIP 文件之前确保它,那么您可以将ZipFile包装在一个 tryexcept 语句中,并捕捉任何 BadZipFile 异常:

    >>> import zipfile
    
    >>> try:
    ...     with zipfile.ZipFile("sample.zip") as archive:
    ...         archive.printdir()
    ... except zipfile.BadZipFile as error:
    ...     print(error)
    ...
    File Name                                        Modified             Size
    hello.txt                                 2021-09-07 19:50:10           83
    lorem.md                                  2021-09-07 19:50:10         2609
    realpython.md                             2021-09-07 19:50:10          428
    
    >>> try:
    ...     with zipfile.ZipFile("bad_sample.zip") as archive:
    ...         archive.printdir()
    ... except zipfile.BadZipFile as error:
    ...     print(error)
    ...
    File is not a zip file
    

    第一个例子成功地打开了sample.zip而没有引发BadZipFile异常。这是因为sample.zip有一个有效的 ZIP 格式。另一方面,第二个例子没有成功打开bad_sample.zip,因为这个文件不是一个有效的 ZIP 文件。

    要检查有效的 ZIP 文件,您也可以使用 is_zipfile() 功能:

    >>> import zipfile
    
    >>> if zipfile.is_zipfile("sample.zip"):
    ...     with zipfile.ZipFile("sample.zip", "r") as archive:
    ...         archive.printdir()
    ... else:
    ...     print("File is not a zip file")
    ...
    File Name                                        Modified             Size
    hello.txt                                 2021-09-07 19:50:10           83
    lorem.md                                  2021-09-07 19:50:10         2609
    realpython.md                             2021-09-07 19:50:10          428
    
    >>> if zipfile.is_zipfile("bad_sample.zip"):
    ...     with zipfile.ZipFile("bad_sample.zip", "r") as archive:
    ...         archive.printdir()
    ... else:
    ...     print("File is not a zip file")
    ...
    File is not a zip file
    

    在这些例子中,您使用了一个带有is_zipfile()的条件语句作为条件。这个函数接受一个filename参数,它保存文件系统中 ZIP 文件的路径。该参数可以接受字符串、类似文件或类似路径的对象。如果filename是一个有效的 ZIP 文件,该函数将返回True。否则,它返回False

    现在假设您想使用ZipFilehello.txt添加到hello.zip档案中。为此,您可以使用写入模式("w")。这种模式打开一个 ZIP 文件进行写入。如果目标 ZIP 文件存在,那么"w"模式会截断它,并写入您传入的任何新内容。

    **注意:**如果你对现有文件使用ZipFile,那么你应该小心使用"w"模式。您可以截断您的 ZIP 文件并丢失所有原始内容。

    如果目标 ZIP 文件不存在,那么ZipFile会在您关闭归档时为您创建一个:

    >>> import zipfile
    
    >>> with zipfile.ZipFile("hello.zip", mode="w") as archive:
    ...     archive.write("hello.txt")
    ...
    

    运行这段代码后,在您的python-zipfile/目录中会有一个hello.zip文件。如果您使用.printdir()列出文件内容,那么您会注意到hello.txt会出现在那里。在这个例子中,你在ZipFile对象上调用 .write() 。这种方法允许您将成员文件写入 ZIP 存档。请注意,.write()的参数应该是一个现有的文件。

    注意: ZipFile足够聪明,当你以写模式使用该类并且目标档案不存在时,它可以创建一个新的档案。但是,如果这些目录不存在,该类不会在目标 ZIP 文件的路径中创建新目录。

    这解释了为什么下面的代码不起作用:

    >>> import zipfile
    
    >>> with zipfile.ZipFile("missing/hello.zip", mode="w") as archive:
    ...     archive.write("hello.txt")
    ...
    Traceback (most recent call last):
        ...
    FileNotFoundError: [Errno 2] No such file or directory: 'missing/hello.zip'
    

    因为目标hello.zip文件路径中的missing/目录不存在,你得到一个 FileNotFoundError 异常。

    追加模式("a")允许您向现有的 ZIP 文件追加新成员文件。这种模式不会截断存档,因此其原始内容是安全的。如果目标 ZIP 文件不存在,那么"a"模式会为您创建一个新文件,然后将您作为参数传递的任何输入文件追加到.write()中。

    要尝试"a"模式,继续将new_hello.txt文件添加到您新创建的hello.zip档案中:

    >>> import zipfile
    
    >>> with zipfile.ZipFile("hello.zip", mode="a") as archive:
    ...     archive.write("new_hello.txt")
    ...
    
    >>> with zipfile.ZipFile("hello.zip") as archive:
    ...     archive.printdir()
    ...
    File Name                                        Modified             Size
    hello.txt                                 2021-09-07 19:50:10           83
    new_hello.txt                             2021-08-31 17:13:44           13
    

    这里,您使用 append 模式将new_hello.txt添加到hello.zip文件中。然后运行.printdir()来确认这个新文件存在于 ZIP 文件中。

    ZipFile还支持独占模式("x")。这种模式允许你独占创建新的 ZIP 文件,并向其中写入新的成员文件。当您想要创建一个新的 ZIP 文件而不覆盖现有文件时,您将使用独占模式。如果目标文件已经存在,那么你得到 FileExistsError

    最后,如果您使用"w""a""x"模式创建一个 ZIP 文件,然后关闭归档文件而不添加任何成员文件,那么ZipFile会创建一个具有适当 ZIP 格式的空归档文件。

    Remove ads

    从 ZIP 文件读取元数据

    你已经将.printdir()付诸行动。这是一个非常有用的方法,可以用来快速列出 ZIP 文件的内容。与.printdir()一起,ZipFile类提供了几种从现有 ZIP 文件中提取元数据的简便方法。

    以下是这些方法的总结:

    方法 描述
    T2.getinfo(filename) 返回一个包含由filename提供的成员文件信息的 ZipInfo 对象。注意,filename必须保存底层 ZIP 文件中目标文件的路径。
    T2.infolist() 返回一个列表中的 ZipInfo 对象,每个成员一个文件。
    T2.namelist() 返回包含基础存档中所有成员文件名称的列表。此列表中的名称是.getinfo()的有效参数。

    使用这三个工具,您可以检索大量关于 ZIP 文件内容的有用信息。例如,看看下面的例子,它使用了.getinfo():

    >>> import zipfile
    
    >>> with zipfile.ZipFile("sample.zip", mode="r") as archive:
    ...     info = archive.getinfo("hello.txt")
    ...
    
    >>> info.file_size
    83
    
    >>> info.compress_size
    83
    
    >>> info.filename
    'hello.txt'
    
    >>> info.date_time
    (2021, 9, 7, 19, 50, 10)
    

    正如您在上表中所了解到的,.getinfo()将一个成员文件作为参数,并返回一个带有相关信息的ZipInfo对象。

    注意: ZipInfo不打算直接实例化。当你调用.getinfo().infolist()方法时,它们会自动返回ZipInfo对象。然而,ZipInfo包含了一个名为 .from_file() 的类方法,如果你需要的话,它允许你显式地实例化这个类。

    ZipInfo对象有几个属性,允许你检索关于目标成员文件的有价值的信息。例如, .file_size.compress_size 分别保存原始文件和压缩文件的大小,以字节为单位。该类还有一些其他有用的属性,如 .filename.date_time ,它们返回文件名和上次修改日期。

    **注意:**默认情况下,ZipFile不会对输入文件进行压缩以将其添加到最终的归档文件中。这就是为什么在上面的例子中,大小和压缩后的大小是相同的。在下面的压缩文件和目录部分,你会学到更多关于这个主题的知识。

    使用.infolist(),您可以从给定档案中的所有文件中提取信息。下面是一个使用这种方法生成最小报告的例子,该报告包含关于您的sample.zip档案中所有成员文件的信息:

    >>> import datetime
    >>> import zipfile
    
    >>> with zipfile.ZipFile("sample.zip", mode="r") as archive:
    ...     for info in archive.infolist():
    ...         print(f"Filename: {info.filename}")
    ...         print(f"Modified: {datetime.datetime(*info.date_time)}")
    ...         print(f"Normal size: {info.file_size} bytes")
    ...         print(f"Compressed size: {info.compress_size} bytes")
    ...         print("-" * 20)
    ...
    Filename: hello.txt
    Modified: 2021-09-07 19:50:10
    Normal size: 83 bytes
    Compressed size: 83 bytes
    --------------------
    Filename: lorem.md
    Modified: 2021-09-07 19:50:10
    Normal size: 2609 bytes
    Compressed size: 2609 bytes
    --------------------
    Filename: realpython.md
    Modified: 2021-09-07 19:50:10
    Normal size: 428 bytes
    Compressed size: 428 bytes
    --------------------
    

    for循环遍历来自.infolist()ZipInfo对象,检索每个成员文件的文件名、最后修改日期、正常大小和压缩大小。在本例中,您使用了 datetime 以人类可读的方式格式化日期。

    **注:**上例改编自 zipfile — ZIP 存档访问。

    如果您只需要对一个 ZIP 文件执行快速检查并列出其成员文件的名称,那么您可以使用.namelist():

    >>> import zipfile
    
    >>> with zipfile.ZipFile("sample.zip", mode="r") as archive:
    ...     for filename in archive.namelist():
    ...         print(filename)
    ...
    hello.txt
    lorem.md
    realpython.md
    

    因为这个输出中的文件名是.getinfo()的有效参数,所以可以结合这两种方法来只检索关于所选成员文件的信息。

    例如,您可能有一个 ZIP 文件,其中包含不同类型的成员文件(.docx.xlsx.txt等等)。不需要用.infolist()得到完整的信息,你只需要得到关于.docx文件的信息。然后您可以通过扩展名过滤文件,并只对您的.docx文件调用.getinfo()。来吧,试一试!

    读写成员文件

    有时您有一个 ZIP 文件,需要读取给定成员文件的内容,而不需要提取它。为此,您可以使用 .read() 。这个方法获取一个成员文件的name并将该文件的内容作为字节返回:

    >>> import zipfile
    
    >>> with zipfile.ZipFile("sample.zip", mode="r") as archive:
    ...     for line in archive.read("hello.txt").split(b"\n"):
    ...         print(line)
    ...
    b'Hello, Pythonista!'
    b''
    b'Welcome to Real Python!'
    b''
    b"Ready to try Python's zipfile module?"
    b''
    

    要使用.read(),需要打开 ZIP 文件进行读取或追加。注意.read()以字节流的形式返回目标文件的内容。在这个例子中,您使用.split()将流分成行,使用换行字符"\n"作为分隔符。因为.split()正在对一个字节对象进行操作,所以您需要在用作参数的字符串前添加一个前导b

    ZipFile.read()也接受名为pwd的第二个位置参数。此参数允许您提供读取加密文件的密码。要尝试这个特性,您可以依赖与本教程的材料一起下载的sample_pwd.zip文件:

    >>> import zipfile
    
    >>> with zipfile.ZipFile("sample_pwd.zip", mode="r") as archive:
    ...     for line in archive.read("hello.txt", pwd=b"secret").split(b"\n"):
    ...         print(line)
    ...
    b'Hello, Pythonista!'
    b''
    b'Welcome to Real Python!'
    b''
    b"Ready to try Python's zipfile module?"
    b''
    
    >>> with zipfile.ZipFile("sample_pwd.zip", mode="r") as archive:
    ...     for line in archive.read("hello.txt").split(b"\n"):
    ...         print(line)
    ...
    Traceback (most recent call last):
        ...
    RuntimeError: File 'hello.txt' is encrypted, password required for extraction
    

    在第一个例子中,您提供密码secret来读取您的加密文件。pwd参数接受 bytes 类型的值。如果您在没有提供所需密码的情况下对一个加密文件使用.read(),那么您会得到一个RuntimeError,正如您在第二个示例中注意到的。

    注意: Python 的zipfile支持解密。但是,它不支持加密 ZIP 文件的创建。这就是为什么你需要使用一个外部文件归档来加密你的文件。

    一些流行的文件归档器包括 Windows 的 7z 和 WinRAR ,Linux 的 Ark 和 GNOME Archive Manager ,macOS 的 Archiver 。

    对于大型加密 ZIP 文件,请记住,解密操作可能会非常慢,因为它是在纯 Python 中实现的。在这种情况下,考虑使用一个专门的程序来处理你的档案,而不是使用zipfile

    如果您经常使用加密文件,那么您可能希望避免每次调用.read()或另一个接受pwd参数的方法时都提供解密密码。如果是这种情况,可以使用 ZipFile.setpassword() 来设置全局密码:

    >>> import zipfile
    
    >>> with zipfile.ZipFile("sample_pwd.zip", mode="r") as archive:
    ...     archive.setpassword(b"secret")
    ...     for file in archive.namelist():
    ...         print(file)
    ...         print("-" * 20)
    ...         for line in archive.read(file).split(b"\n"):
    ...             print(line)
    ...
    hello.txt
    --------------------
    b'Hello, Pythonista!'
    b''
    b'Welcome to Real Python!'
    b''
    b"Ready to try Python's zipfile module?"
    b''
    lorem.md
    --------------------
    b'# Lorem Ipsum'
    b''
    b'Lorem ipsum dolor sit amet, consectetur adipiscing elit.
     ...
    

    使用.setpassword(),您只需提供一次密码。ZipFile使用该唯一密码解密所有成员文件。

    相比之下,如果 ZIP 文件的各个成员文件具有不同的密码,那么您需要使用.read()pwd参数为每个文件提供特定的密码:

    >>> import zipfile
    
    >>> with zipfile.ZipFile("sample_file_pwd.zip", mode="r") as archive:
    ...     for line in archive.read("hello.txt", pwd=b"secret1").split(b"\n"):
    ...         print(line)
    ...
    b'Hello, Pythonista!'
    b''
    b'Welcome to Real Python!'
    b''
    b"Ready to try Python's zipfile module?"
    b''
    
    >>> with zipfile.ZipFile("sample_file_pwd.zip", mode="r") as archive:
    ...     for line in archive.read("lorem.md", pwd=b"secret2").split(b"\n"):
    ...         print(line)
    ...
    b'# Lorem Ipsum'
    b''
    b'Lorem ipsum dolor sit amet, consectetur adipiscing elit.
     ...
    

    在这个例子中,您使用secret1作为密码来读取hello.txt,使用secret2来读取lorem.md。要考虑的最后一个细节是,当您使用pwd参数时,您将覆盖您可能已经用.setpassword()设置的任何归档级密码。

    **注意:**在使用不支持的压缩方法的 ZIP 文件上调用.read()会引发一个 NotImplementedError 。如果所需的压缩模块在 Python 安装中不可用,也会出现错误。

    如果您正在寻找一种更灵活的方式来读取成员文件,并创建和添加新的成员文件到一个档案,那么 ZipFile.open() 是为您准备的。像内置的open()函数一样,这个方法实现了上下文管理器协议,因此它支持with语句:

    >>> import zipfile
    
    >>> with zipfile.ZipFile("sample.zip", mode="r") as archive:
    ...     with archive.open("hello.txt", mode="r") as hello:
    ...         for line in hello:
    ...             print(line)
    ...
    b'Hello, Pythonista!\n'
    b'\n'
    b'Welcome to Real Python!\n'
    b'\n'
    b"Ready to try Python's zipfile module?\n"
    

    在这个例子中,你打开hello.txt进行阅读。.open()的第一个参数是name,表示您想要打开的成员文件。第二个参数是模式,照常默认为"r"ZipFile.open()也接受一个pwd参数来打开加密文件。该参数的工作原理与.read()中的等效pwd参数相同。

    您也可以将.open()"w"模式配合使用。此模式允许您创建一个新的成员文件,向其中写入内容,最后将文件追加到底层归档文件中,您应该在追加模式下打开该归档文件:

    >>> import zipfile
    
    >>> with zipfile.ZipFile("sample.zip", mode="a") as archive:
    ...     with archive.open("new_hello.txt", "w") as new_hello:
    ...         new_hello.write(b"Hello, World!")
    ...
    13
    
    >>> with zipfile.ZipFile("sample.zip", mode="r") as archive:
    ...     archive.printdir()
    ...     print("------")
    ...     archive.read("new_hello.txt")
    ...
    File Name                                        Modified             Size
    hello.txt                                 2021-09-07 19:50:10           83
    lorem.md                                  2021-09-07 19:50:10         2609
    realpython.md                             2021-09-07 19:50:10          428
    new_hello.txt                             1980-01-01 00:00:00           13
    ------
    b'Hello, World!'
    

    在第一段代码中,您以追加模式("a")打开sample.zip。然后你通过用"w"模式调用.open()来创建new_hello.txt。这个函数返回一个类似文件的对象,支持 .write() ,允许你向新创建的文件中写入字节。

    **注意:**你需要给.open()提供一个不存在的文件名。如果您使用的文件名已经存在于底层归档文件中,那么您将得到一个重复的文件和一个 UserWarning 异常。

    在这个例子中,你将b'Hello, World!'写入new_hello.txt。当执行流退出内部的with语句时,Python 将输入字节写入成员文件。当外部的with语句退出时,Python 将new_hello.txt写入底层的 ZIP 文件sample.zip

    第二段代码确认了new_hello.txt现在是sample.zip的成员文件。在这个例子的输出中需要注意的一个细节是,.write()将新添加文件的Modified日期设置为1980-01-01 00:00:00,这是一个奇怪的行为,在使用这个方法时应该记住。

    Remove ads

    以文本形式读取成员文件的内容

    正如您在上一节中了解到的,您可以使用.read().write()方法来读取和写入成员文件,而不需要从包含它们的 ZIP 存档中提取它们。这两种方法都专门处理字节。

    但是,当您有一个包含文本文件的 ZIP 存档时,您可能希望将它们的内容作为文本而不是字节来读取。至少有两种方法可以做到这一点。您可以使用:

    1. T2bytes.decode()
    2. T2io.TextIOWrapper

    因为ZipFile.read()以字节的形式返回目标成员文件的内容,.decode()可以直接对这些字节进行操作。.decode()方法使用给定的字符编码格式将bytes对象解码成字符串。

    以下是如何使用.decode()sample.zip档案中的hello.txt文件中读取文本:

    >>> import zipfile
    
    >>>  with zipfile.ZipFile("sample.zip", mode="r") as archive:
    ...     text = archive.read("hello.txt").decode(encoding="utf-8")
    ...
    
    >>> print(text)
    Hello, Pythonista!
    
    Welcome to Real Python!
    
    Ready to try Python's zipfile module?
    

    在这个例子中,你以字节的形式读取hello.txt的内容。然后你调用.decode()将字节解码成一个字符串,使用 UTF-8 作为编码。要设置encoding参数,可以使用"utf-8"字符串。但是,您可以使用任何其他有效的编码,例如 UTF-16 或 cp1252 ,它们可以表示为不区分大小写的字符串。注意,"utf-8"encoding参数对.decode()的默认值。

    记住这一点很重要,您需要预先知道想要使用.decode()处理的任何成员文件的字符编码格式。如果您使用了错误的字符编码,那么您的代码将无法正确地将底层的字节解码成文本,并且您最终会得到大量无法辨认的字符。

    从成员文件中读取文本的第二个选项是使用一个io.TextIOWrapper对象,它提供了一个缓冲的文本流。这次你需要使用.open()而不是.read()。下面是一个使用io.TextIOWrapperhello.txt成员文件的内容作为文本流读取的例子:

    >>> import io
    >>> import zipfile
    
    >>> with zipfile.ZipFile("sample.zip", mode="r") as archive:
    ...     with archive.open("hello.txt", mode="r") as hello:
    ...         for line in io.TextIOWrapper(hello, encoding="utf-8"):
    ...             print(line.strip())
    ...
    Hello, Pythonista!
    
    Welcome to Real Python!
    
    Ready to try Python's zipfile module?
    

    在本例的内部with语句中,您从您的sample.zip档案中打开了hello.txt成员文件。然后将生成的类似二进制文件的对象hello作为参数传递给io.TextIOWrapper。这通过使用 UTF-8 字符编码格式解码hello的内容来创建一个缓冲的文本流。因此,您可以直接从目标成员文件中获得文本流。

    就像使用.encode()一样,io.TextIOWrapper类接受一个encoding参数。您应该始终为该参数指定一个值,因为默认文本编码取决于运行代码的系统,并且可能不是您试图解码的文件的正确值。

    从您的 ZIP 存档中提取成员文件

    提取给定归档文件的内容是对 ZIP 文件最常见的操作之一。根据您的需要,您可能希望一次提取一个文件或一次提取所有文件。

    ZipFile.extract() 让你完成第一个任务。这个方法获取一个member文件的名称,并将其提取到一个由path指示的给定目录中。目的地path默认为当前目录:

    >>> import zipfile
    
    >>> with zipfile.ZipFile("sample.zip", mode="r") as archive:
    ...     archive.extract("new_hello.txt", path="output_dir/")
    ...
    'output_dir/new_hello.txt'
    

    现在new_hello.txt将在你的output_dir/目录中。如果目标文件名已经存在于输出目录中,那么.extract()会覆盖它而不要求确认。如果输出目录不存在,那么.extract()会为您创建一个。注意,.extract()返回解压文件的路径。

    成员文件的名称必须是由.namelist()返回的文件的全名。它也可以是一个包含文件信息的ZipInfo对象。

    您也可以对加密文件使用.extract()。在这种情况下,您需要提供必需的pwd参数或者用.setpassword()设置归档级别的密码。

    当从一个档案中提取所有成员文件时,可以使用 .extractall() 。顾名思义,该方法将所有成员文件提取到目标路径,默认情况下是当前目录:

    >>> import zipfile
    
    >>> with zipfile.ZipFile("sample.zip", mode="r") as archive:
    ...     archive.extractall("output_dir/")
    ...
    

    运行这段代码后,sample.zip的所有当前内容都将在您的output_dir/目录中。如果您将一个不存在的目录传递给.extractall(),那么这个方法会自动创建这个目录。最后,如果任何成员文件已经存在于目标目录中,那么.extractall()将会覆盖它们而不需要你的确认,所以要小心。

    如果您只需要从给定的档案中提取一些成员文件,那么您可以使用members参数。该参数接受一个成员文件列表,该列表应该是手头存档中整个文件列表的子集。最后,和.extract()一样,.extractall()方法也接受一个pwd参数来提取加密文件。

    Remove ads

    使用后关闭 ZIP 文件

    有时,不使用with语句打开给定的 ZIP 文件会很方便。在这些情况下,您需要在使用后手动关闭归档,以完成任何写入操作并释放获得的资源。

    为此,您可以在您的ZipFile对象上调用 .close() :

    >>> import zipfile
    
    >>> archive = zipfile.ZipFile("sample.zip", mode="r")
    
    >>> # Use archive in different parts of your code
    >>> archive.printdir()
    File Name                                        Modified             Size
    hello.txt                                 2021-09-07 19:50:10           83
    lorem.md                                  2021-09-07 19:50:10         2609
    realpython.md                             2021-09-07 19:50:10          428
    new_hello.txt                             1980-01-01 00:00:00           13
    
    >>> # Close the archive when you're done
    >>> archive.close()
    >>> archive
    <zipfile.ZipFile [closed]>
    

    .close()的调用会为您关闭archive。在退出你的程序之前,你必须呼叫.close()。否则,一些写操作可能无法执行。例如,如果您打开一个 ZIP 文件来追加("a")新的成员文件,那么您需要关闭归档文件来写入这些文件。

    创建、填充和解压你自己的 ZIP 文件

    到目前为止,您已经学习了如何使用现有的 ZIP 文件。您已经学会了通过使用不同的ZipFile模式来读取、写入和添加成员文件。您还学习了如何读取相关元数据以及如何提取给定 ZIP 文件的内容。

    在这一节中,您将编写一些实用的例子,帮助您学习如何使用zipfile和其他 Python 工具从几个输入文件和整个目录创建 ZIP 文件。您还将学习如何使用zipfile进行文件压缩等等。

    从多个常规文件创建一个 ZIP 文件

    有时您需要从几个相关的文件创建一个 ZIP 存档。这样,您可以将所有文件放在一个容器中,以便通过计算机网络分发或与朋友或同事共享。为此,您可以创建一个目标文件列表,并使用ZipFile和一个循环将它们写入归档文件:

    >>> import zipfile
    
    >>> filenames = ["hello.txt", "lorem.md", "realpython.md"]
    
    >>> with zipfile.ZipFile("multiple_files.zip", mode="w") as archive:
    ...     for filename in filenames:
    ...         archive.write(filename)
    ...
    

    在这里,您创建了一个ZipFile对象,将所需的档案名称作为它的第一个参数。"w"模式允许您将成员文件写入最终的 ZIP 文件。

    for循环遍历输入文件列表,并使用.write()将它们写入底层 ZIP 文件。一旦执行流退出了with语句,ZipFile会自动关闭存档,为您保存更改。现在您有了一个multiple_files.zip档案,其中包含了您的原始文件列表中的所有文件。

    从目录构建 ZIP 文件

    将一个目录的内容捆绑到一个归档中是 ZIP 文件的另一个日常用例。Python 有几个工具可以和zipfile一起使用来完成这个任务。例如,您可以使用 pathlib 到读取给定目录的内容。有了这些信息,您可以使用ZipFile创建一个容器档案。

    python-zipfile/目录下,有一个名为source_dir/的子目录,内容如下:

    source_dir/
    │
    ├── hello.txt
    ├── lorem.md
    └── realpython.md
    

    source_dir/中,你只有三个常规文件。因为目录不包含子目录,所以可以使用 pathlib.Path.iterdir() 直接迭代其内容。有了这个想法,下面是如何从source_dir/的内容构建一个 ZIP 文件:

    >>> import pathlib
    >>> import zipfile
    
    >>> directory = pathlib.Path("source_dir/")
    
    >>> with zipfile.ZipFile("directory.zip", mode="w") as archive:
    ...    for file_path in directory.iterdir():
    ...        archive.write(file_path, arcname=file_path.name)
    ...
    
    >>> with zipfile.ZipFile("directory.zip", mode="r") as archive:
    ...     archive.printdir()
    ...
    File Name                                        Modified             Size
    realpython.md                             2021-09-07 19:50:10          428
    hello.txt                                 2021-09-07 19:50:10           83
    lorem.md                                  2021-09-07 19:50:10         2609
    

    在这个例子中,你从你的源目录中创建一个 pathlib.Path 对象。第一个with语句创建一个准备写入的ZipFile对象。然后对.iterdir()的调用返回底层目录中条目的迭代器。

    因为在source_dir/中没有任何子目录,所以.iterdir()函数只产生文件。for循环遍历文件并将它们写入存档。

    在这种情况下,您将file_path.name传递给.write()的第二个参数。这个参数被称为arcname,它保存了结果档案中成员文件的名称。到目前为止,您看到的所有示例都依赖于默认值arcname,这是您作为第一个参数传递给.write()的相同文件名。

    如果您没有将file_path.name传递给arcname,那么您的源目录将位于您的 ZIP 文件的根目录,根据您的需要,这也可以是一个有效的结果。

    现在检查工作目录中的root_dir/文件夹。在这种情况下,您会发现以下结构:

    root_dir/
    │
    ├── sub_dir/
    │   └── new_hello.txt
    │
    ├── hello.txt
    ├── lorem.md
    └── realpython.md
    

    您有常用的文件和一个包含单个文件的子目录。如果你想创建一个具有相同内部结构的 ZIP 文件,那么你需要一个工具,让递归地遍历目录树中root_dir/下的。

    下面是如何压缩一个完整的目录树,如上图所示,使用来自pathlib模块的zipfilePath.rglob():

    >>> import pathlib
    >>> import zipfile
    
    >>> directory = pathlib.Path("root_dir/")
    
    >>> with zipfile.ZipFile("directory_tree.zip", mode="w") as archive:
    ...     for file_path in directory.rglob("*"):
    ...         archive.write(
    ...             file_path,
    ...             arcname=file_path.relative_to(directory)
    ...         )
    ...
    
    >>> with zipfile.ZipFile("directory_tree.zip", mode="r") as archive:
    ...     archive.printdir()
    ...
    File Name                                        Modified             Size
    sub_dir/                                  2021-09-09 20:52:14            0
    realpython.md                             2021-09-07 19:50:10          428
    hello.txt                                 2021-09-07 19:50:10           83
    lorem.md                                  2021-09-07 19:50:10         2609
    sub_dir/new_hello.txt                     2021-08-31 17:13:44           13
    

    在这个例子中,您使用 Path.rglob() 递归遍历root_dir/下的目录树。然后,将每个文件和子目录写入目标 ZIP 存档。

    这一次,您使用 Path.relative_to() 获得每个文件的相对路径,然后将结果传递给第二个参数.write()。这样,最终得到的 ZIP 文件的内部结构与原始源目录相同。同样,如果您希望您的源目录位于 ZIP 文件的根目录,您可以去掉这个参数。

    Remove ads

    压缩文件和目录

    如果您的文件占用了太多的磁盘空间,那么您可以考虑压缩它们。Python 的zipfile支持几种流行的压缩方法。但是,默认情况下,该模块不会压缩您的文件。如果您想让您的文件更小,那么您需要显式地为ZipFile提供一个压缩方法。

    通常,您将使用术语存储的来指代未经压缩就写入 ZIP 文件的成员文件。这就是为什么ZipFile的默认压缩方法被称为 ZIP_STORED ,它实际上指的是未压缩的成员文件,它们被简单地存储在包含的归档文件中。

    compression方法是ZipFile初始化器的第三个参数。如果要在将文件写入 ZIP 存档文件时对其进行压缩,可以将该参数设置为下列常量之一:

    常数 压缩法 所需模块
    zipfile.ZIP_DEFLATED 紧缩 zlib
    zipfile.ZIP_BZIP2 Bzip2 bz2
    zipfile.ZIP_LZMA 伊玛 lzma

    这些是您目前可以在ZipFile中使用的压缩方法。不同的方法会养出一个 NotImplementedError 。从 Python 3.10 开始,zipfile没有额外的压缩方法。

    作为附加要求,如果您选择这些方法中的一种,那么 Python 安装中必须有支持它的压缩模块。否则,你会得到一个 RuntimeError 异常,你的代码就会中断。

    当涉及到压缩文件时,ZipFile的另一个相关参数是compresslevel。此参数控制您使用的压缩级别。

    使用 Deflate 方法,compresslevel可以从09取整数。使用 Bzip2 方法,您可以从19传递整数。在这两种情况下,当压缩级别增加时,您将获得更高的压缩和更低的压缩速度。

    **注意:**PNG、JPG、MP3 等二进制文件已经使用了某种压缩方式。因此,将它们添加到 ZIP 文件中可能不会使数据变得更小,因为它已经被压缩到一定程度。

    现在假设您想使用 Deflate 方法归档和压缩给定目录的内容,这是 ZIP 文件中最常用的方法。为此,您可以运行以下代码:

    >>> import pathlib
    >>> from zipfile import ZipFile, ZIP_DEFLATED
    
    >>> directory = pathlib.Path("source_dir/")
    
    >>> with ZipFile("comp_dir.zip", "w", ZIP_DEFLATED, compresslevel=9) as archive:
    ...     for file_path in directory.rglob("*"):
    ...         archive.write(file_path, arcname=file_path.relative_to(directory))
    ...
    

    在这个例子中,您将9传递给compresslevel以获得最大的压缩。为了提供这个参数,您使用了一个关键字参数。你需要这么做,因为compresslevel不是ZipFile初始化器的第四个位置参数。

    注意:ZipFile的初始化器接受第四个参数,称为allowZip64。这是一个布尔参数,告诉ZipFile为大于 4 GB 的文件创建扩展名为.zip64的 ZIP 文件。

    运行这段代码后,您将在当前目录中拥有一个comp_dir.zip文件。如果您将该文件的大小与您原来的sample.zip文件的大小进行比较,那么您会注意到文件大小显著减小。

    按顺序创建 ZIP 文件

    按顺序创建 ZIP 文件可能是日常编程中的另一个常见需求。例如,您可能需要创建一个包含或不包含内容的初始 ZIP 文件,然后在新成员文件可用时立即追加它们。在这种情况下,您需要多次打开和关闭目标 ZIP 文件。

    要解决这个问题,您可以在追加模式("a")中使用ZipFile,就像您已经做的那样。此模式允许您将新成员文件安全地附加到 ZIP 存档,而不会截断其当前内容:

    >>> import zipfile
    
    >>> def append_member(zip_file, member):
    ...     with zipfile.ZipFile(zip_file, mode="a") as archive:
    ...         archive.write(member)
    ...
    
    >>> def get_file_from_stream():
    ...     """Simulate a stream of files."""
    ...     for file in ["hello.txt", "lorem.md", "realpython.md"]:
    ...         yield file
    ...
    
    >>> for filename in get_file_from_stream():
    ...     append_member("incremental.zip", filename)
    ...
    
    >>> with zipfile.ZipFile("incremental.zip", mode="r") as archive:
    ...     archive.printdir()
    ...
    File Name                                        Modified             Size
    hello.txt                                 2021-09-07 19:50:10           83
    lorem.md                                  2021-09-07 19:50:10         2609
    realpython.md                             2021-09-07 19:50:10          428
    

    在这个例子中,append_member()是一个函数,它将一个文件(member)附加到输入 ZIP 存档(zip_file)中。要执行此操作,该函数会在您每次调用它时打开和关闭目标归档。使用一个函数来执行这个任务允许您根据需要多次重用代码。

    get_file_from_stream()函数是一个生成器函数,模拟要处理的文件流。同时,for循环使用append_number()将成员文件依次添加到incremental.zip中。如果您在运行完这段代码后检查您的工作目录,那么您会发现一个incremental.zip档案,包含您传递到循环中的三个文件。

    Remove ads

    提取文件和目录

    对 ZIP 文件执行的最常见的操作之一是将它们的内容提取到文件系统中的给定目录。您已经学习了使用.extract().extractall()从归档中提取一个或所有文件的基础知识。

    作为一个额外的例子,回到你的sample.zip文件。此时,归档包含四个不同类型的文件。你有两个.txt档和两个.md档。现在假设您只想提取.md文件。为此,您可以运行以下代码:

    >>> import zipfile
    
    >>> with zipfile.ZipFile("sample.zip", mode="r") as archive:
    ...     for file in archive.namelist():
    ...         if file.endswith(".md"):
    ...             archive.extract(file, "output_dir/")
    ...
    'output_dir/lorem.md'
    'output_dir/realpython.md'
    

    with语句打开sample.zip进行读取。该循环使用namelist()遍历归档中的每个文件,而条件语句检查文件名是否以扩展名.md结尾。如果是这样,那么使用.extract()将手头的文件提取到目标目录output_dir/

    zipfile到探索附加类

    到目前为止,你已经学习了ZipFileZipInfo,这是zipfile中可用的两个职业。这个模块还提供了另外两个类,在某些情况下会很方便。那些类是 zipfile.Pathzipfile.PyZipFile 。在接下来的两节中,您将学习这些类的基础知识和它们的主要特性。

    在 ZIP 文件中查找Path

    当你用你最喜欢的归档程序打开一个 ZIP 文件时,你会看到归档文件的内部结构。您可能在归档的根目录下有文件。您还可以拥有包含更多文件的子目录。归档文件看起来像文件系统中的一个普通目录,每个文件都位于一个特定的路径中。

    zipfile.Path类允许您构造 path 对象来快速创建和管理给定 ZIP 文件中成员文件和目录的路径。该类有两个参数:

  • root 接受一个 ZIP 文件,作为一个ZipFile对象或者一个物理 ZIP 文件的基于字符串的路径。
  • at 保存着特定成员文件的位置或归档内的目录。它默认为空字符串,代表归档文件的根目录。
  • 以你的老朋友sample.zip为目标,运行下面的代码:

    >>> import zipfile
    
    >>> hello_txt = zipfile.Path("sample.zip", "hello.txt")
    
    >>> hello_txt
    Path('sample.zip', 'hello.txt')
    
    >>> hello_txt.name
    'hello.txt'
    
    >>> hello_txt.is_file()
    True
    
    >>> hello_txt.exists()
    True
    
    >>> print(hello_txt.read_text())
    Hello, Pythonista!
    
    Welcome to Real Python!
    
    Ready to try Python's zipfile module?
    

    这段代码显示了zipfile.Path实现了几个对pathlib.Path对象通用的特性。你可以用.name得到文件的名字。可以用.is_file()检查路径是否指向常规文件。您可以检查给定的文件是否存在于特定的 ZIP 文件中,等等。

    Path还提供了一个.open()方法来使用不同的模式打开一个成员文件。例如,下面的代码打开hello.txt进行阅读:

    >>> import zipfile
    
    >>> hello_txt = zipfile.Path("sample.zip", "hello.txt")
    
    >>> with hello_txt.open(mode="r") as hello:
    ...     for line in hello:
    ...         print(line)
    ...
    Hello, Pythonista!
    
    Welcome to Real Python!
    
    Ready to try Python's zipfile module?
    

    使用Path,您可以在给定的 ZIP 文件中快速创建一个指向特定成员文件的 path 对象,并使用.open()立即访问其内容。

    就像使用pathlib.Path对象一样,您可以通过在zipfile.Path对象上调用 .iterdir() 来列出 ZIP 文件的内容:

    >>> import zipfile
    
    >>> root = zipfile.Path("sample.zip")
    >>> root
    Path('sample.zip', '')
    
    >>> root.is_dir()
    True
    
    >>> list(root.iterdir())
    [
     Path('sample.zip', 'hello.txt'),
     Path('sample.zip', 'lorem.md'),
     Path('sample.zip', 'realpython.md')
    ]
    

    很明显,zipfile.Path提供了许多有用的特性,您可以用它们来管理 ZIP 存档中的成员文件。

    Remove ads

    PyZipFile 构建可导入的 ZIP 文件

    zipfile中另一个有用的类是 PyZipFile 。这个类非常类似于ZipFile,当您需要将 Python 模块和包打包成 ZIP 文件时,它尤其方便。与ZipFile的主要区别在于PyZipFile的初始化器带有一个名为optimize的可选参数,它允许你在归档之前通过编译成字节码来优化 Python 代码。

    PyZipFile提供与ZipFile相同的接口,增加了 .writepy() 。这个方法可以将一个 Python 文件(.py)作为参数,并将其添加到底层的 ZIP 文件中。如果optimize-1(默认),那么.py文件会自动编译成.pyc文件,然后添加到目标档案中。为什么会这样?

    从 2.3 版本开始,Python 解释器支持从 ZIP 文件导入 Python 代码,这一功能被称为 Zip 导入。这个功能相当方便。它允许你创建可导入的 ZIP 文件来分发你的模块和包作为一个单独的存档。

    **注意:**还可以使用 ZIP 文件格式创建和分发 Python 可执行应用程序,也就是俗称的 Python Zip 应用程序。要了解如何创建它们,请查看 Python 的 zipapp:构建可执行的 Zip 应用程序。

    当您需要生成可导入的 ZIP 文件时,PyZipFile非常有用。打包.pyc文件而不是.py文件使得导入过程更加有效,因为它跳过了编译步骤。

    python-zipfile/目录中,有一个包含以下内容的hello.py模块:

    """Print a greeting message."""
    # hello.py
    
    def greet(name="World"):
        print(f"Hello, {name}! Welcome to Real Python!")
    

    这段代码定义了一个名为greet()的函数,它将name作为一个参数,将一条问候消息打印到屏幕上。现在假设您想要将这个模块打包成一个 ZIP 文件,以便于分发。为此,您可以运行以下代码:

    >>> import zipfile
    
    >>> with zipfile.PyZipFile("hello.zip", mode="w") as zip_module:
    ...     zip_module.writepy("hello.py")
    ...
    
    >>> with zipfile.PyZipFile("hello.zip", mode="r") as zip_module:
    ...     zip_module.printdir()
    ...
    File Name                                        Modified             Size
    hello.pyc                                 2021-09-13 13:25:56          311
    

    在这个例子中,对.writepy()的调用自动将hello.py编译成hello.pyc,并存储在hello.zip中。当您使用.printdir()列出档案的内容时,这就变得很清楚了。

    一旦将hello.py打包成一个 ZIP 文件,就可以使用 Python 的 import 系统从其包含的归档文件中导入这个模块:

    >>> import sys
    
    >>> # Insert the archive into sys.path
    >>> sys.path.insert(0, "/home/user/python-zipfile/hello.zip")
    >>> sys.path[0]
    '/home/user/python-zipfile/hello.zip'
    
    >>> # Import and use the code
    >>> import hello
    
    >>> hello.greet("Pythonista")
    Hello, Pythonista! Welcome to Real Python!
    

    从 ZIP 文件导入代码的第一步是使该文件在 sys.path 中可用。这个变量保存了一个字符串列表,该列表指定了 Python 对模块的搜索路径。要向sys.path添加新项目,可以使用.insert()

    为了让这个示例工作,您需要更改占位符路径,并将路径传递给文件系统上的hello.zip。一旦您的可导入 ZIP 文件出现在这个列表中,您就可以像对待常规模块一样导入代码了。

    最后,考虑工作文件夹中的hello/子目录。它包含一个具有以下结构的小 Python 包:

    hello/
    |
    ├── __init__.py
    └── hello.py
    

    __init__.py模块将hello/目录变成一个 Python 包。hello.py模块就是你在上面的例子中使用的那个。现在假设您想将这个包打包成一个 ZIP 文件。如果是这种情况,您可以执行以下操作:

    >>> import zipfile
    
    >>> with zipfile.PyZipFile("hello.zip", mode="w") as zip_pkg:
    ...     zip_pkg.writepy("hello")
    ...
    
    >>> with zipfile.PyZipFile("hello.zip", mode="r") as zip_pkg:
    ...     zip_pkg.printdir()
    ...
    File Name                                        Modified             Size
    hello/__init__.pyc                        2021-09-13 13:39:30          108
    hello/hello.pyc                           2021-09-13 13:39:30          317
    

    .writepy()的调用以hello包为参数,在其中搜索.py文件,编译成.pyc文件,最后添加到目标 ZIP 文件hello.zip。同样,您可以按照之前学习的步骤从这个归档文件中导入您的代码:

    >>> import sys
    
    >>> sys.path.insert(0, "/home/user/python-zipfile/hello.zip")
    
    >>> from hello import hello
    
    >>> hello.greet("Pythonista")
    Hello, Pythonista! Welcome to Real Python!
    

    因为您的代码现在在一个包中,所以您首先需要从hello包中导入hello模块。然后您可以正常访问您的greet()功能。

    Remove ads

    从命令行运行zipfile

    Python 的zipfile还提供了一个最小的命令行接口,允许你快速访问模块的主要功能。例如,您可以使用-l--list选项来列出现有 ZIP 文件的内容:

    $ python -m zipfile --list sample.zip
    File Name                                         Modified             Size
    hello.txt                                  2021-09-07 19:50:10           83
    lorem.md                                   2021-09-07 19:50:10         2609
    realpython.md                              2021-09-07 19:50:10          428
    new_hello.txt                              1980-01-01 00:00:00           13
    

    该命令显示的输出与对sample.zip档案中的.printdir()的等效调用相同。

    现在假设您想要创建一个包含几个输入文件的新 ZIP 文件。在这种情况下,您可以使用-c--create选项:

    $ python -m zipfile --create new_sample.zip hello.txt lorem.md realpython.md
    
    $ python -m zipfile -l new_sample.zip
    File Name                                         Modified             Size
    hello.txt                                  2021-09-07 19:50:10           83
    lorem.md                                   2021-09-07 19:50:10         2609
    realpython.md                              2021-09-07 19:50:10          428
    

    这个命令创建一个包含您的hello.txtlorem.mdrealpython.md文件的new_sample.zip文件。

    如果您需要创建一个 ZIP 文件来归档整个目录,该怎么办?例如,您可能有自己的source_dir/,它包含与上面例子相同的三个文件。您可以使用以下命令从该目录创建一个 ZIP 文件:

    $ python -m zipfile -c source_dir.zip source_dir/
    
    $ python -m zipfile -l source_dir.zip
    File Name                                         Modified             Size
    source_dir/                                2021-08-31 08:55:58            0
    source_dir/hello.txt                       2021-08-31 08:55:58           83
    source_dir/lorem.md                        2021-08-31 09:01:08         2609
    source_dir/realpython.md                   2021-08-31 09:31:22          428
    

    使用这个命令,zipfilesource_dir/放在生成的source_dir.zip文件的根目录下。像往常一样,您可以通过使用-l选项运行zipfile来列出归档内容。

    **注意:**当您从命令行使用zipfile创建归档文件时,库在归档您的文件时隐式地使用Deflate 压缩算法。

    您还可以从命令行使用-e--extract选项提取给定 ZIP 文件的所有内容:

    $ python -m zipfile --extract sample.zip sample/
    

    运行这个命令后,您的工作目录中将会有一个新的sample/文件夹。新文件夹将包含您的sample.zip档案中的当前文件。

    您可以从命令行使用zipfile的最后一个选项是-t--test。此选项允许您测试给定文件是否是有效的 ZIP 文件。来吧,试一试!

    使用其他库管理 ZIP 文件

    Python 标准库中还有一些其他工具,可以用来在较低的级别上归档、压缩和解压缩文件。Python 的zipfile在内部使用了其中一些,主要用于压缩目的。以下是其中一些工具的摘要:

    组件 描述
    T2zlib 允许使用 zlib 库进行压缩和解压缩
    T2bz2 提供使用 Bzip2 压缩算法压缩和解压缩数据的接口
    T2lzma 提供使用 LZMA 压缩算法压缩和解压缩数据的类和函数

    zipfile不同,这些模块中的一些允许你压缩和解压缩来自内存和数据流的数据,而不是常规文件和存档。

    在 Python 标准库中,您还会找到 tarfile ,它支持 TAR 归档格式。还有一个模块叫做 gzip ,它提供了一个压缩和解压缩数据的接口,类似于 GNU Gzip 程序的做法。

    例如,您可以使用gzip创建一个包含一些文本的压缩文件:

    >>> import gzip
    
    >>> with gzip.open("hello.txt.gz", mode="wt") as gz_file:
    ...     gz_file.write("Hello, World!")
    ...
    13
    

    一旦运行了这段代码,在当前目录中就会有一个包含压缩版本的hello.txthello.txt.gz档案。在hello.txt里面,你会找到文本Hello, World!

    不使用zipfile创建 ZIP 文件的一种快速高级方法是使用 shutil 。此模块允许您对文件和文件集合执行一些高级操作。说到归档操作,你有 make_archive() ,可以创建归档,比如 ZIP 或者 TAR 文件:

    >>> import shutil
    
    >>> shutil.make_archive("shutil_sample", format="zip", root_dir="source_dir/")
    '/home/user/sample.zip'
    

    这段代码在您的工作目录中创建一个名为sample.zip的压缩文件。这个 ZIP 文件将包含输入目录source_dir/中的所有文件。当你需要一种快速和高级的方式在 Python 中创建你的 ZIP 文件时,make_archive()函数是很方便的。

    Remove ads

    结论

    当你需要从 ZIP 档案中读取、写入、压缩、解压缩和提取文件时,Python 的 zipfile 是一个方便的工具。ZIP 文件格式已经成为行业标准,允许你打包或者压缩你的数字数据。

    使用 ZIP 文件的好处包括将相关文件归档在一起、节省磁盘空间、便于通过计算机网络传输数据、捆绑 Python 代码以供分发等等。

    在本教程中,您学习了如何:

  • 使用 Python 的zipfile读取、写入和提取现有的 ZIP 文件
  • zipfile阅读元数据关于你的 ZIP 文件的内容
  • 使用zipfile操作现有 ZIP 文件中的成员文件
  • 创建您自己的 ZIP 文件来归档和压缩您的数字数据
  • 您还学习了如何在命令行中使用zipfile来列出、创建和解压缩 ZIP 文件。有了这些知识,您就可以使用 ZIP 文件格式高效地归档、压缩和操作您的数字数据了。**********

    作者:绝不原创的飞龙

    物联沃分享整理
    物联沃-IOTWORD物联网 » RealPython中文系列教程第116课详解

    发表回复