前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >爬虫(107)Python 3.7的超酷新功能(接近一万字,请耐心享用,而且建议收藏)

爬虫(107)Python 3.7的超酷新功能(接近一万字,请耐心享用,而且建议收藏)

作者头像
公众号---人生代码
发布2020-05-18 16:52:47
1.5K0
发布2020-05-18 16:52:47
举报
文章被收录于专栏:人生代码人生代码人生代码

Python 3.7 正式发布!这个新的Python版本自2016年9月开始开发,现在我们所有人都可以享受核心开发人员辛勤工作的成果。

新的Python版本带来了什么?尽管文档很好地概述了这些新功能,但本文将深入探讨一些重大新闻。这些包括:

  • 通过新的breakpoint()内置功能更轻松地访问调试器
  • 使用数据类创建简单的类
  • 定制访问模块属性
  • 改进了对类型提示的支持
  • 高精度计时功能

更重要的是,Python 3.7速度很快

在本文的最后几节中,您将了解有关此速度的更多信息,以及Python 3.7的其他一些出色功能。您还将获得有关升级到新版本的一些建议

内置的 breakpoint() 断点函数

虽然我们可能会努力编写完美的代码,但简单的事实是我们从不这样做。调试是编程的重要组成部分。Python 3.7引入了新的内置函数breakpoint()。这实际上并没有向Python添加任何新功能,但是它使调试器的使用更加灵活和直观。

假设文件中包含以下错误代码bugs.py

def divide(e, f):
    return f / e

a, b = 0, 1
print(divide(a, b))

运行代码会导致函数ZeroDivisionError内部divide()。假设您要中断代码并直接在的顶部放入调试器divide()。您可以通过在代码中设置一个所谓的“断点”来实现:

def divide(e, f):
    # Insert breakpoint here
    return f / e

断点是代码内部的信号,该信号应暂时停止执行,以便您可以查看程序的当前状态。您如何放置断点?在Python 3.6和更低版本中,您使用以下含糊的行:

def divide(e, f):
    import pdb; pdb.set_trace()
    return f / e

pdb是标准库中的Python调试器。在Python 3.7中,可以将新breakpoint()函数调用用作快捷方式:

def divide(e, f):
    breakpoint()
    return f / e

在后台,breakpoint()首先是导入pdb,然后要求pdb.set_trace()您。明显的好处breakpoint()是更容易记住,您只需要键入12个字符而不是27个字符即可。但是,使用的真正好处breakpoint()是其可定制性。

使用以下命令运行bugs.py脚本breakpoint()

$ python bugs.py
> bugs.py(3)divide()
-> return f / e
(Pdb)

该脚本到达时将中断,breakpoint()并使您进入PDB调试会话。您可以键入c并单击Enter以继续执行脚本。如果您想了解有关PDB和调试的更多信息,请参考Nathan Jennings的PDB指南。

现在,假设您认为已修复该错误。您想再次运行脚本,但不停止调试器。您当然可以注释掉该breakpoint()行,但是另一种选择是使用PYTHONBREAKPOINT环境变量。此变量控制的行为breakpoint(),并且set PYTHONBREAKPOINT=0表示将breakpoint()忽略对的任何调用:

$ PYTHONBREAKPOINT=0 python3.7 bugs.py
ZeroDivisionError: division by zero

糟糕,看来您毕竟还没有修正错误…

另一种选择是用于PYTHONBREAKPOINT指定除PDB之外的调试器。例如,要使用PuDB(控制台中的可视调试器),您可以执行以下操作:

$ PYTHONBREAKPOINT=pudb.set_trace python3.7 bugs.py

为此,您需要pudb安装(pip install pudb)。不过,Python会pudb为您进行导入。这样,您还可以设置默认调试器。只需将PYTHONBREAKPOINT环境变量设置为您首选的调试器。请参阅本指南以获取有关如何在系统上设置环境变量的说明。

breakpoint()功能不仅适用于调试器。一种方便的选择是仅在代码内部启动一个交互式外壳。例如,要启动IPython会话,可以使用以下命令:

$ PYTHONBREAKPOINT=IPython.embed python3.7 bugs.py
IPython 6.3.1 -- An enhanced Interactive Python. Type '?' for help.

In [1]: print(e / f)
0.0

您也可以创建自己的函数并进行breakpoint()调用。以下代码在本地范围内打印所有变量。将其添加到名为bp_utils.py

from pprint import pprint
import sys

def print_locals():
    caller = sys._getframe(1)  # Caller is 1 frame up.
    pprint(caller.f_locals)

要使用此功能,请PYTHONBREAKPOINT像以前一样设置为<module>.<function>

$ PYTHONBREAKPOINT=bp_utils.print_locals python3.7 bugs.py
{'e': 0, 'f': 1}
ZeroDivisionError: division by zero

通常,breakpoint()将用于调用不需要参数的函数和方法。但是,也可以传递参数。更改线路breakpoint()bugs.py来:

breakpoint(e, f, end="<-END\n")

注意:默认的PDB调试器将TypeError在此行引发a ,因为pdb.set_trace()它不接受任何位置参数。

breakpoint()伪装为print()函数运行以下代码,以查看传递的参数的简单示例:

$ PYTHONBREAKPOINT=print python3.7 bugs.py
0 1<-END
ZeroDivisionError: division by zero

见PEP 553以及对文档breakpoint()sys.breakpointhook()获取更多信息。

dataclasses 类别模块

新的dataclasses模块,使编写自己的类更方便,因为特殊的方法一样.__init__().__repr__().__eq__()被自动添加。使用@dataclass装饰器,您可以编写如下内容:

from dataclasses import dataclass, field

@dataclass(order=True)
class Country:
    name: str
    population: int
    area: float = field(repr=False, compare=False)
    coastline: float = 0

    def beach_per_person(self):
        """Meters of coastline per person"""
        return (self.coastline * 1000) / self.population

这九行代码代表了很多样板代码和最佳实践。考虑一下将其Country作为常规类实现的.__init__()方法:方法repr,,六个不同的比较方法以及.beach_per_person()方法。您可以展开下面的框,以查看Country与数据类大致等效的实现:

class Country:

    def __init__(self, name, population, area, coastline=0):
        self.name = name
        self.population = population
        self.area = area
        self.coastline = coastline

    def __repr__(self):
        return (
            f"Country(name={self.name!r}, population={self.population!r},"
            f" coastline={self.coastline!r})"
        )

    def __eq__(self, other):
        if other.__class__ is self.__class__:
            return (
                (self.name, self.population, self.coastline)
                == (other.name, other.population, other.coastline)
            )
        return NotImplemented

    def __ne__(self, other):
        if other.__class__ is self.__class__:
            return (
                (self.name, self.population, self.coastline)
                != (other.name, other.population, other.coastline)
            )
        return NotImplemented

    def __lt__(self, other):
        if other.__class__ is self.__class__:
            return ((self.name, self.population, self.coastline) < (
                other.name, other.population, other.coastline
            ))
        return NotImplemented

    def __le__(self, other):
        if other.__class__ is self.__class__:
            return ((self.name, self.population, self.coastline) <= (
                other.name, other.population, other.coastline
            ))
        return NotImplemented

    def __gt__(self, other):
        if other.__class__ is self.__class__:
            return ((self.name, self.population, self.coastline) > (
                other.name, other.population, other.coastline
            ))
        return NotImplemented

    def __ge__(self, other):
        if other.__class__ is self.__class__:
            return ((self.name, self.population, self.coastline) >= (
                other.name, other.population, other.coastline
            ))
        return NotImplemented

    def beach_per_person(self):
        """Meters of coastline per person"""
        return (self.coastline * 1000) / self.population

创建后,数据类是普通类。例如,您可以按常规方式从数据类继承。数据类的主要目的是使编写健壮的类(尤其是主要存储数据的小类)变得快速简便。

您可以Country像其他任何类一样使用数据类:

>>> norway = Country("Norway", 5320045, 323802, 58133)
>>> norway
Country(name='Norway', population=5320045, coastline=58133)

>>> norway.area
323802

>>> usa = Country("United States", 326625791, 9833517, 19924)
>>> nepal = Country("Nepal", 29384297, 147181)
>>> nepal
Country(name='Nepal', population=29384297, coastline=0)

>>> usa.beach_per_person()
0.06099946957342386

>>> norway.beach_per_person()
10.927163210085629

请注意,所有字段.name.population.area,和.coastline被初始化类(尽管当使用.coastline是可选的,如示于内陆尼泊尔的例子)。该Country班有一个合理的repr,而定义方法的工作原理一样的普通班。

默认情况下,可以比较数据类的相等性。由于我们order=True是在@dataclass装饰器中指定的,因此Country该类也可以进行排序:

>>> norway == norway
True

>>> nepal == usa
False

>>> sorted((norway, usa, nepal))
[Country(name='Nepal', population=29384297, coastline=0),
 Country(name='Norway', population=5320045, coastline=58133),
 Country(name='United States', population=326625791, coastline=19924)]

排序发生在字段值上,.name然后是.population,依此类推。但是,如果使用field(),则可以自定义比较中要使用的字段。在示例中,该.area字段未包含repr和比较。

数据类的功能与相同namedtuple。但是,他们从该attrs项目中汲取了最大的灵感。有关更多示例和更多信息,请参见我们的数据类完整指南,对于正式说明,请参见PEP 557

定制模块属性

Python中到处都有属性!虽然类属性可能是最著名的,但实际上属性实际上可以放在任何东西上,包括函数和模块。Python的一些基本功能被实现为属性:大多数自省功能,文档字符串和名称空间。模块内部的功能可用作模块属性。

最常使用点符号来检索属性thing.attribute。但是,您还可以使用getattr()以下命令在运行时获取命名的属性:

import random

random_attr = random.choice(("gammavariate", "lognormvariate", "normalvariate"))
random_func = getattr(random, random_attr)

print(f"A {random_attr} random value: {random_func(1, 1)}")

运行此代码将产生类似以下内容:

A gammavariate random value: 2.8017715125270618

对于类,调用thing.attr将首先attr在上查找定义thing。如果找不到,则thing.__getattr__("attr")调用特殊方法。(这是一种简化。有关更多详细信息,请参见本文。)该.__getattr__()方法可用于自定义对对象属性的访问。

在Python 3.7之前,模块属性很难获得相同的自定义。然而,PEP 562引入__getattr__()了模块以及相应的__dir__()功能。该__dir__()特殊功能允许调用的结果的定制dir()模块上。

PEP本身提供了一些有关如何使用这些功能的示例,包括向功能添加弃用警告以及延迟加载繁重的子模块。下面,我们将构建一个简单的插件系统,该系统允许将功能动态添加到模块中。这个例子利用了Python包。如果您需要更新软件包,请参阅本文。

创建一个新目录,plugins并将以下代码添加到文件中plugins/__init__.py

from importlib import import_module
from importlib import resources

PLUGINS = dict()

def register_plugin(func):
    """Decorator to register plug-ins"""
    name = func.__name__
    PLUGINS[name] = func
    return func

def __getattr__(name):
    """Return a named plugin"""
    try:
        return PLUGINS[name]
    except KeyError:
        _import_plugins()
        if name in PLUGINS:
            return PLUGINS[name]
        else:
            raise AttributeError(
                f"module {__name__!r} has no attribute {name!r}"
            ) from None

def __dir__():
    """List available plug-ins"""
    _import_plugins()
    return list(PLUGINS.keys())

def _import_plugins():
    """Import all resources to register plug-ins"""
    for name in resources.contents(__name__):
        if name.endswith(".py"):
            import_module(f"{__name__}.{name[:-3]}")

在我们看一下这段代码的作用之前,在plugins目录中再添加两个文件。首先,让我们看一下plugins/plugin_1.py

from . import register_plugin

@register_plugin
def hello_1():
    print("Hello from Plugin 1")

接下来,在文件中添加类似的代码plugins/plugin_2.py

from . import register_plugin

@register_plugin
def hello_2():
    print("Hello from Plugin 2")

@register_plugin
def goodbye():
    print("Plugin 2 says goodbye")

现在可以按以下方式使用这些插件:

>>> import plugins
>>> plugins.hello_1()
Hello from Plugin 1

>>> dir(plugins)
['goodbye', 'hello_1', 'hello_2']

>>> plugins.goodbye()
Plugin 2 says goodbye

这似乎并不全都是革命性的(也许不是),但让我们看看这里实际发生了什么。通常,为了能够调用plugins.hello_1(),该hello_1()函数必须在plugins模块中定义或__init__.pyplugins包中显式导入。在这里,两者都不是!

而是hello_1()plugins包内的任意文件中定义,并通过使用decorator进行自身注册而hello_1()成为包的一部分。plugins@register_plugin

区别是微妙的。代替了指示哪些功能可用的程序包,各个功能将自身注册为程序包的一部分。这为您提供了一个简单的结构,您可以独立于代码的其余部分添加功能,而不必保留可用功能的集中列表。

让我们快速回顾一下代码__getattr__()内部的功能plugins/__init__.py。当您要求时plugins.hello_1(),Python首先hello_1()plugins/__init__.py文件内寻找一个函数。由于不存在此类函数,因此Python会调用__getattr__("hello_1")。记住该__getattr__()函数的源代码:

def __getattr__(name):
    """Return a named plugin"""
    try:
        return PLUGINS[name]        # 1) Try to return plugin
    except KeyError:
        _import_plugins()           # 2) Import all plugins
        if name in PLUGINS:
            return PLUGINS[name]    # 3) Try to return plugin again
        else:
            raise AttributeError(   # 4) Raise error
                f"module {__name__!r} has no attribute {name!r}"
            ) from None

__getattr__()包含以下步骤。下表中的数字与代码中带注释的数字相对应:

首先,该函数乐观地尝试从PLUGINS字典中返回命名插件。如果名为的插件name存在并且已经导入,则将成功。

如果在PLUGINS字典中找不到指定的插件,则确保所有插件都已导入。

如果导入后该命名插件变得可用,则返回该命名插件。

如果PLUGINS在导入所有插件之后该插件不在词典中,那么我们提出一个AttributeError说法,name它不是当前模块上的属性(插件)。

PLUGINS字典如何填充?该_import_plugins()函数导入plugins包内的所有

Python文件,但似乎没有联系PLUGINS

def _import_plugins():
    """Import all resources to register plug-ins"""
    for name in resources.contents(__name__):
        if name.endswith(".py"):
            import_module(f"{__name__}.{name[:-3]}")

不要忘记,每个插件功能都是由@register_plugin装饰器装饰的。导入插件时将调用此装饰器,它是实际填充PLUGINS字典的装饰器。如果您手动导入插件文件之一,则可以看到以下内容:

>>> import plugins
>>> plugins.PLUGINS
{}

>>> import plugins.plugin_1
>>> plugins.PLUGINS
{'hello_1': <function hello_1 at 0x7f29d4341598>}

继续该示例,请注意,dir()在模块上调用还会导入其余的插件:

>>> dir(plugins)
['goodbye', 'hello_1', 'hello_2']

>>> plugins.PLUGINS
{'hello_1': <function hello_1 at 0x7f29d4341598>,
 'hello_2': <function hello_2 at 0x7f29d4341620>,
 'goodbye': <function goodbye at 0x7f29d43416a8>}

dir()通常列出对象上所有可用的属性。通常,dir()在模块上使用会产生以下结果:

>>> import plugins
>>> dir(plugins)
['PLUGINS', '__builtins__', '__cached__', '__doc__',
 '__file__', '__getattr__', '__loader__', '__name__',
 '__package__', '__path__', '__spec__', '_import_plugins',
 'import_module', 'register_plugin', 'resources']

尽管这可能是有用的信息,但我们对公开可用的插件更感兴趣。在Python 3.7中,您可以dir()通过添加__dir__()特殊功能来自定义调用模块的结果。对于plugins/__init__.py,此函数首先确保已导入所有插件,然后列出其名称:

def __dir__():
    """List available plug-ins"""
    _import_plugins()
    return list(PLUGINS.keys())

在离开此示例之前,请注意,我们还使用了Python 3.7的另一个很酷的新功能。要导入plugins目录中的所有模块,我们使用了新importlib.resources模块。通过此模块,可以访问模块和软件包中的文件和资源,而无需进行__file__黑客攻击(不一定总是有效)或pkg_resources(很慢)。的其他功能importlib.resources将在稍后强调。

增强模块

在整个Python 3系列发行版中,类型提示和注释一直在不断发展。Python的打字系统现在非常稳定。尽管如此,Python 3.7对该表进行了一些增强:更好的性能,核心支持和正向引用。

Python在运行时不会进行任何类型检查(除非您显式使用诸如之类的包enforce)。因此,在代码中添加类型提示不应影响其性能。

不幸的是,这并非完全正确,因为大多数类型提示都需要该typing模块。该typing模块是标准库中最慢的模块之一。PEP 560增加了一些在Python 3.7中键入的核心支持,这大大加快了typing模块的速度。通常不需要了解其详细信息。只需向后倾斜即可享受更高的性能。

尽管Python的类型系统具有合理的表现力,但引起麻烦的一个问题是前向引用。导入模块时将评估类型提示(或更一般而言,注释)。因此,所有名称在使用之前都必须已经定义。以下是不可能的:

class Tree:
    def __init__(self, left: Tree, right: Tree) -> None:
        self.left = left
        self.right = right

运行代码会引发a,NameError因为该类Tree尚未(完全)在.__init__()方法的定义中定义:

Traceback (most recent call last):
  File "tree.py", line 1, in <module>
    class Tree:
  File "tree.py", line 2, in Tree
    def __init__(self, left: Tree, right: Tree) -> None:
NameError: name 'Tree' is not defined

为了克服这个问题,您将需要编写"Tree"为字符串文字,而不是:

class Tree:
    def __init__(self, left: "Tree", right: "Tree") -> None:
        self.left = left
        self.right = right

有关原始讨论,请参见PEP 484。

在未来的Python 4.0中,将允许使用所谓的前向引用。除非明确要求,否则不评估注释来处理此问题。PEP 563描述了该提议的细节。在Python 3.7中,前向引用已作为__future__import提供。您现在可以编写以下内容:

from __future__ import annotations

class Tree:
    def __init__(self, left: Tree, right: Tree) -> None:
        self.left = left
        self.right = right

请注意,除了避免"Tree"语法过于笨拙之外,推迟注释的评估也将加快代码的速度,因为不会执行类型提示。前向引用已受mypy

到目前为止,注释最常见的用法是类型提示。尽管如此,您仍可以在运行时完全访问注释,并可以根据需要使用它们。如果直接处理批注,则需要显式处理可能的前向引用。

让我们创建一些公认的愚蠢示例,它们显示何时评估注释。首先,我们以旧样式进行操作,因此注释将在导入时进行评估。让我们anno.py包含以下代码:

def greet(name: print("Now!")):
    print(f"Hello {name}")

请注意,注释nameprint()。这只是为了准确查看注释的时间。导入新模块:

>>> import anno
Now!

>>> anno.greet.__annotations__
{'name': None}

>>> anno.greet("Alice")
Hello Alice

如您所见,注释是在导入时评估的。请注意,name最终以注释,None因为这是的返回值print()

添加__future__导入以启用注释的延迟评估:

from __future__ import annotations

def greet(name: print("Now!")):
    print(f"Hello {name}")

导入此更新的代码将不会评估注释:

>>> import anno

>>> anno.greet.__annotations__
{'name': "print('Now!')"}

>>> anno.greet("Marty")
Hello Marty

请注意,Now!它永远不会被打印,并且注释将作为字符串文字保留在__annotations__字典中。为了评估注释,请使用typing.get_type_hints()eval()

>>> import typing
>>> typing.get_type_hints(anno.greet)
Now!
{'name': <class 'NoneType'>}

>>> eval(anno.greet.__annotations__["name"])
Now!

>>> anno.greet.__annotations__
{'name': "print('Now!')"}

请注意,该__annotations__字典永远不会更新,因此您每次使用该注释时都需要对其进行评估。

计时精度

在Python 3.7中,该time模块获得了一些新功能,如PEP 564中所述。特别是,添加了以下六个功能:

clock_gettime_ns():返回指定时钟的时间

clock_settime_ns():设置指定时钟的时间

monotonic_ns():返回不能倒退的相对时钟的时间(例如,由于夏令时)

perf_counter_ns():返回性能计数器的值-一种专门用于测量短间隔的时钟

process_time_ns():返回当前进程的系统和用户CPU时间的总和(不包括睡眠时间)

time_ns():返回自1970年1月1日以来的纳秒数

从某种意义上说,没有添加任何新功能。每个功能类似于不带_ns后缀的现有功能。不同之处在于新函数返回int的秒数为n而不是a的秒数float

对于大多数应用而言,这些新的纳秒级功能与旧的纳秒级功能之间的差异将不明显。但是,新功能更容易推论,因为它们依赖int而不是float。浮点数本质上是不准确的:

>>> 0.1 + 0.1 + 0.1
0.30000000000000004

>>> 0.1 + 0.1 + 0.1 == 0.3
False

这不是Python的问题,而是计算机需要使用有限数量的位表示无限十进制数的结果。

Python float遵循IEEE 754标准,并使用53个有效位。结果是任何大于约104天(2×3或约9万亿纳秒)的时间都不能表示为具有纳秒精度的浮点数。相反,Python int是无限制的,因此整数纳秒将始终具有纳秒精度,而与时间值无关。

例如,time.time()返回自1970年1月1日以来的秒数。此数字已经很大,因此此数字的精度为微秒级别。此功能是其_ns版本中最大的改进。的分辨率time.time_ns()大约是更好的3倍比time.time()

顺便说一下,纳秒是多少?从技术上讲,它是十亿分之一秒,1e-9如果您更喜欢科学计数法,则为秒。这些只是数字,并不能提供任何直觉。有关更好的视觉帮助,请参见Grace Hopper的纳秒级精彩演示。

顺便说一句,如果您需要使用纳秒精度的日期时间,则datetime标准库将不会削减它。它显式只处理微秒:

>>> from datetime import datetime, timedelta
>>> datetime(2018, 6, 27) + timedelta(seconds=1e-6)
datetime.datetime(2018, 6, 27, 0, 0, 0, 1)

>>> datetime(2018, 6, 27) + timedelta(seconds=1e-9)
datetime.datetime(2018, 6, 27, 0, 0)

相反,您可以使用astropyproject。它的astropy.time程序包使用两个float对象表示日期时间,从而保证“在整个宇宙时代的整个时间范围内,亚纳秒精度”。

>>> from astropy.time import Time, TimeDelta
>>> Time("2018-06-27")
<Time object: scale='utc' format='iso' value=2018-06-27 00:00:00.000>

>>> t = Time("2018-06-27") + TimeDelta(1e-9, format="sec")
>>> (t - Time("2018-06-27")).sec
9.976020010071807e-10

其他很酷的功能

到目前为止,您已经看到了有关Python 3.7新增功能的头条新闻。但是,还有许多其他变化也很酷。在本节中,我们将简要介绍其中一些。

保证字典顺序

Python 3.6的CPython实现对字典进行了排序。(PyPy也有此功能。)这意味着字典中的项目将按照插入时的顺序进行迭代。第一个示例使用Python 3.5,第二个示例使用Python 3.6:

>>> {"one": 1, "two": 2, "three": 3}  # Python <= 3.5
{'three': 3, 'one': 1, 'two': 2}

>>> {"one": 1, "two": 2, "three": 3}  # Python >= 3.6
{'one': 1, 'two': 2, 'three': 3}

在Python 3.6中,这种排序只是实现的一个很好的结果dict。但是,在Python 3.7中,保留其插入顺序的字典是语言规范的一部分。因此,现在只能在仅支持Python> = 3.7(或CPython> = 3.6)的项目中依赖它。

async”和“ await”是关键字

Python 3.5引入了带有asyncawait语法的协程。为了避免向后兼容的问题,async并且await未将其添加到保留关键字列表中。换句话说,它仍然可以定义变量或命名功能asyncawait

在Python 3.7中,这不再可行:

>>> async = 1
  File "<stdin>", line 1
    async = 1
          ^
SyntaxError: invalid syntax

>>> def await():
  File "<stdin>", line 1
    def await():
            ^
SyntaxError: invalid syntax

asyncio”瘦脸

asyncio标准库在Python 3.4最初推出使用事件循环,协同程序和期货以现代的方式来处理并发。这里是一个温柔的介绍。

在Python 3.7中,该asyncio模块进行了重大改进,包括许多新功能,对上下文变量的支持(请参见下文)和性能改进。特别值得注意的是asyncio.run(),它简化了从同步代码中调用协程的过程。使用asyncio.run(),您无需显式创建事件循环。现在可以编写异步Hello World程序:

import asyncio

async def hello_world():
    print("Hello World!")

asyncio.run(hello_world())

上下文变量

上下文变量是根据上下文可以具有不同值的变量。它们类似于线程本地存储,其中每个执行线程的变量值可能不同。但是,使用上下文变量,一个执行线程中可能有多个上下文。上下文变量的主要用例是跟踪并发异步任务中的变量。

以下示例构造了三个上下文,每个上下文都有自己的value值name。该greet()函数以后可以使用name每个上下文内部的值:

import contextvars

name = contextvars.ContextVar("name")
contexts = list()

def greet():
    print(f"Hello {name.get()}")

# Construct contexts and set the context variable name
for first_name in ["Steve", "Dina", "Harry"]:
    ctx = contextvars.copy_context()
    ctx.run(name.set, first_name)
    contexts.append(ctx)

# Run greet function inside each context
for ctx in reversed(contexts):
    ctx.run(greet)

运行此脚本以相反的顺序向Steve,Dina和Harry致意:

$ python context_demo.py
Hello Harry
Hello Dina
Hello Steve

用“ importlib.resources” 导入数据文件

打包Python项目时的一个挑战是决定如何处理项目资源,例如项目所需的数据文件。通常使用一些选项:

  • 硬编码数据文件的路径。
  • 将数据文件放入包装中,并使用进行定位__file__
  • 使用setuptools.pkg_resources访问数据文件资源。

这些都有各自的缺点。第一种选择是不可移植的。使用__file__更具可移植性,但是如果安装了Python项目,则它可能最终位于zip内并且没有__file__属性。第三种选择解决了这个问题,但是很慢。

更好的解决方案是importlib.resources标准库中的新模块。它使用Python现有的导入功能来导入数据文件。假设您在Python包中有这样的资源:

data/
│
├── alice_in_wonderland.txt
└── __init__.py

请注意,该文件data必须是Python软件包。也就是说,该目录需要包含一个__init__.py文件(可能为空)。然后,您可以alice_in_wonderland.txt按以下方式读取文件:

>>> from importlib import resources
>>> with resources.open_text("data", "alice_in_wonderland.txt") as fid:
...     alice = fid.readlines()
... 
>>> print("".join(alice[:7]))
CHAPTER I. Down the Rabbit-Hole

Alice was beginning to get very tired of sitting by her sister on the
bank, and of having nothing to do: once or twice she had peeped into the
book her sister was reading, but it had no pictures or conversations in
it, "and what is the use of a book," thought Alice "without pictures or
conversations?"

类似的resources.open_binary()功能可用于以二进制模式打开文件。在前面的“作为模块属性的插件”示例中,我们曾经使用importlib.resources来发现可用的插件resources.contents()。有关更多信息,请参见Barry Warsaw的PyCon 2018演讲。

importlib.resources通过backport可以在Python 2.7和Python 3.4+中使用。提供了从pkg_resources到迁移importlib.resources的指南。

开发人员技巧

Python 3.7添加了一些针对您作为开发人员的功能。您已经看到了新的breakpoint()内置程序。此外,Python解释器中添加了一些新的-X命令行选项。

您可以使用以下命令轻松了解脚本中的导入所花费的时间-X importtime

$ python  -X importtime my_script.py
import time: self [us] | cumulative | imported package
import time:      2607 |       2607 | _frozen_importlib_external
...
import time:       844 |      28866 |   importlib.resources
import time:       404 |      30434 | plugins

cumulative列显示了累计的导入时间(以微秒为单位)。在此示例中,导入plugins耗时约0.03秒,其中大部分时间花费在import上importlib.resources。该self列显示导入时间,不包括嵌套导入。

现在,您可以-X dev用来激活“开发模式”。开发模式将添加某些调试功能和运行时检查,这些功能和运行时检查被认为太慢而无法默认启用。这些措施包括能够faulthandler在严重崩溃时显示回溯,以及更多警告和调试挂钩。

最后,-X utf8启用UTF-8模式。(请参阅PEP540。)在此模式下,UTF-8无论当前语言环境如何,都将用于文本编码。

最佳化

Python的每个新发行版都带有一组优化。在Python 3.7中,有一些显着的提速,包括:

在标准库中调用许多方法的开销较小。

通常,方法调用速度最多可提高20%。

Python本身的启动时间减少了10-30%。

导入typing速度提高了7倍。

此外,还包括许多更专业的优化。请参阅此列表以获取详细概述。

所有这些优化的结果是Python 3.7速度很快。它只是到目前为止发布的最快的CPython版本。

那么,我应该升级吗?

让我们从简单的答案开始。如果您想尝试这里看到的任何新功能,那么您确实需要能够使用Python 3.7。使用pyenv或Anaconda之类的工具可以轻松地并排安装多个版本的Python。安装Python 3.7并尝试它没有任何缺点。

现在,对于更复杂的问题。您是否应该将生产环境升级到Python 3.7?您是否应该使自己的项目依赖于Python 3.7来利用这些新功能?

具有明显的警告,你总是应该在升级生产环境之前做彻底的测试,在Python 3.7非常几件事情,将打破前面的代码(asyncawait成为关键词就是一个例子,虽然)。如果您已经在使用现代Python,则升级到3.7应该很顺利。如果您想稍微保守一些,则可能要等待第一个维护版本(Python 3.7.1)的发布,暂定在2018年7月的某个时候发布。

争论只应使项目3.7困难。Python 3.7中的许多新功能都可以作为向Python 3.6的反向移植(数据类,importlib.resources)或便利性(更快的启动和方法调用,更容易的调试和-X选项)。后者,您可以通过自己运行Python 3.7来利用,同时保持代码与Python 3.6(或更低版本)兼容。

将代码锁定到Python 3.7的主要功能是__getattr__()在模块上,类型提示中的正向引用以及纳秒级time函数。如果您确实需要任何这些,则应继续改进您的要求。否则,如果可以在Python 3.6上运行更长的时间,您的项目可能会对其他人更有用。

有关升级时要注意的详细信息,请参见《移植到Python 3.7指南》。

引用自

https://realpython.com/python37-new-features/

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-04-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 CryptoCode 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 保证字典顺序
  • “ async”和“ await”是关键字
  • “ asyncio”瘦脸
  • 上下文变量
  • 用“ importlib.resources” 导入数据文件
  • 开发人员技巧
  • 最佳化
  • 那么,我应该升级吗?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档