python 3.7 的一些新特性 | python优质外文翻译 | python 技术论坛-金年会app官方网

python 3.7 发布 啦! 新版本的python 从 开发至今,现在我们终于能享受核心开发者辛勤劳动的成果了。

新版本的 python 将带来什么? 这篇  可以让我们很好的了解python3.7 中出现的新特性,也会深入带我们了解以下一些有价值的内容:

  • 通过 breakpoint()  可以更容易的访问调试器
  • 使用data classes模块创建一个简单类
  • 支持模块属性的定制化访问
  • 改进了对类型提示的支持
  • 更高精度的时间表示

更重要的是,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))

运行这段代码会导致  divide() 函数产生 zerodivisionerror 错误。假设你想在divide()的开头中断代码并直接进入  。你可以在代码中打上一个所谓的“断点”:

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

断点(breakpoint)是一个让程序执行暂时中止的信号,好让你可以观察程序当前的状态。那么如何添加断点呢?在 python 3.6 及之前的版本中是通过一行神秘的代码来实现的:

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

在这里, 是 python 标准库中的调试器。在 python 3.7 中,你可以调用新的 breakpoint() 函数作为快捷方法:

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

breakpoint() 会在后台首先导入 pdb 然后帮你调用 pdb.set_trace()。显而易见的好处是 breakpoint() 更容易记住,只需要打 12 个字符,而原来需要 27个。 而它真正的优势是 breakpoint() 可自定义。

运行包含 breakpoint()bugs.py 脚本:

$ python3.7 bugs.py
> /home/gahjelle/bugs.py(3)divide()
-> return f / e
(pdb)

当脚本运行到 breakpoint() 的位置时会中断, 进入一个 pdb 的调试会话。你可以敲 c 然后回车使脚本继续。如果你想学习更多 pdb和调试的知识,可参阅

现在假设你已经修正了这个 bug,你希望再跑一遍这个脚本但不会中止并进入调试模式。你当然可以注释掉 breakpoint() 这一行,但另一种方法是使用 pythonbreakpoint 环境变量。这个变量控制breakpoint() 的行为, 把 pythonbreakpoint 置成 0 会忽略所有 breakpoint() 的调用:

$ pythonbreakpoint=0 python3.7 bugs.py
zerodivisionerror: division by zero

哎呀,好像你还没修好这个 bug。

还有个方法是用 pythonbreakpoint 来指定一个 pdb 以外的调试器。比如要使用 (一个终端中的可视化调试器),你可以这样:

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

要让这个工作,你要先安装好 pudbpip 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 设成 . 的表示:

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

一般 breakpoint() 用来调用不需要参数的函数或方法,但其实也可以传入参数。 把 bugs.pybreakpoint() 那行改为:

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

注意:默认的 pdb 调试器会在这行抛出 typeerror, 因为 pdb.set_trace() 不接受任何参数。

运行这个把 breakpoint() 伪装成了 print() 函数的代码,作为一个给函数传入参数的示例:

$ pythonbreakpoint=print python3.7 bugs.py
0 1<-end
zerodivisionerror: division by zero

参阅  与 的文档,以及  来获取更多信息。

数据类

新的  模块让你在编写你自己的类时更加方便,如 .__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):
        """每人平均海岸线长度"""
        return (self.coastline * 1000) / self.population

这9行代码代表了相当多的样板代码和最佳实践。想想要把 country 类作为一个常规类来实现需要哪些工作:__init__() 方法,一个 repr,6个不同的比较方法还有 beach_per_person 方法。你可以扩展下方的代码块来证明 country 的实现大致相当于数据类:

“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 是可选的, 如 landlocked nepal 例子所示)。  在定义与其他常规类方法功能相同的函数时,country 类有一个合理的 。

默认,数据类能做相等性比较。一旦我们在 @dataclass 装饰器中指定 order=truecountry 类就可以被排序:

>>> 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 被省略。

注:国家数据来自于 2017 年 7 月  的人口估计。

在你们预订挪威的下一个海滩假期之前,这里有关于  的factbook:“温带沿海,随着北大西洋海流改边;内陆较冷,降水增加,夏季较冷;西海岸常年有雨。”

数据类与 做了一些相同的事情。然而,他们从  中吸取了最大的灵感。有关更多示例和更多信息,请参阅我们的 ,以及  的官方描述。

自定义模块属性

python 中处处都有属性!虽然类属性很常见,但是事实上,属性可以被任意对象所拥有,包括函数和模块。 python 的一些基本特性使用属性来实现,包括大部分的内置函数,文档字符串以及命名空间等。模块内的函数可以被视作模块属性。

检索对象的属性时经常使用点来表示: 对象.属性 。但是,你也可以使用 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

对于类而言,当调用 对象.属性 时,解释器将会第一时间查找该对象 是否有定义该 属性 。如果没有找到,则会调用特定的方法 对象.__getattr__("attr") (以上时简化说明,详细请参考 )。 __getattr__() 方法可以用于自定义访问对象的属性。

在 python 3.7 之前,要实现相同效果的模块属性定制并非这么简单。但是, 文档中介绍了如何使用 __getattr__()__dir__() 函数达到相同效果。 __dir__() 可以自定义 时返回的结果。

pep 上有几个样例展示以上函数的用法。包括如何给函数添加警告,以及如何实现子模块的延迟加载等。以下我们介绍一个简单的插件系统,可以实现动态地向模块添加函数。该样例需要导入 python 库。如果你需要更新这些库,请参考 。

创建一个新的 plugins 目录,然后在该目录下创建 plugins/__init__.py 文件:

from importlib import import_module
from importlib import resources
plugins = dict()
def register_plugin(func):
    """注册插件的装饰器"""
    name = func.__name__
    plugins[name] = func
    return func
def __getattr__(name):
    """返回对应名称的插件"""
    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__():
    """返回可用插件列表"""
    _import_plugins()
    return list(plugins.keys())
def _import_plugins():
    """导入所有资源来注册插件"""
    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 模块或者显示地导入到 plugins 包的 __init__.py 文件中。但是这里再也不是这样了。

而这里,hello_1() 被定义在 plugins 包的任意一个文件中,通过使用  @register_plugin 将它自己注册成为 plugins 包的一部分。

差异很微妙,各个函数将自己注册成为包的一部分,而不是由包来检测哪些函数是可用的。这将给你提供了一个简单的结构,可以独立于其余代码添加函数,而不用在包里面集中保留一个可用函数列表。

让我们快速看下  plugins/__init__.py 文件中的  __getattr__() 做了什么。当你请求访问 plugins.hello_1() 时,python 首先在  plugins/__init__.py  文件中查找一个名为 hello_1() 的函数。由于这个函数不存在,python 调用了  __getattr__("hello_1") 。回顾下  __getattr__() 函数的源代码:

def __getattr__(name):
    """return a named plugin"""
    try:
        return plugins[name]        # 1) 尝试返回插件
    except keyerror:
        _import_plugins()           # 2)  导入所有的插件
        if name in plugins:
            return plugins[name]    # 3) 在此尝试返回插件
        else:
            raise attributeerror(   # 4) 引发错误
                f"module {__name__!r} has no attribute {name!r}"
            ) from none

__getattr__()  包含下列的步骤。以下列表中的编号对应代码注释中的编号:

  1. 首先,该函数乐观地从  plugins 字典中返回命名的插件。如果名为 name 的插件存并且已经导入,这将成功返回。
  2. 如果这个插件在  plugins 字典中没有找到,我们将确保导入所有插件。
  3. 如果在导入之后该插件变得可用了,那就成功返回。
  4. 如果在导入之后还是没有找到这个插件,我们将引发 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': }

我们继续看这个例子,在模块上调用 dir() 也将导入剩余的插件:

>>> dir(plugins)
['goodbye', 'hello_1', 'hello_2']
>>> plugins.plugins
{'hello_1': ,
'hello_2': ,
'goodbye': }

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 目录所有的模块,我们可以用新的    模块。这个模块可以访问模块和包内所有的文件和资源,而不用 __file__ 这种 hack 方式(并不总是很有效)或者 pkg_resources (太慢)。importlib.resources 其他功能将被 。

类型提示的强化

在整个 python 3 系列的发行版中,类型提示和注解都一直在不断发展。python 的 现在十分健壮。尽管如此,python 3.7 仍然带来了一些增强功能,更好的性能,核心支持以及前向引用。

python 在运行时没有做任何类型检查(除非你故意使用了像 这样的包)。因此,添加类型不会影响代码性能。

不幸的是,并不总是这样,因为大多数类型提示需要 typing 模块。而typing 模块是标准库中   之一。在 python 3.7 中,  为类型添加了核心支持,它能够有效地提升 typing 模块的速度。一般来说这个的详细细节是没必要去了解的。只需要简单地回顾下并且享受这个性能提升。

虽然 python 的类型系统具有很强的表现力,但是导致非常痛苦的一个问题是前向引用。类型提示,或者更通用的注解,它们是在模块被导入时进行计算。因此所有的名称必须已经在他们使用之前被定义。下面这段代码就是不对的:

class tree:
    def __init__(self, left: tree, right: tree) -> none:
        self.left = left
        self.right = right

运行这段代码将会引发 nameerror,因为类 tree.__init__() 方法定义的时候还没完成定义:

traceback (most recent call last):
  file "tree.py", line 1, in 
    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

可以在  中看最初的讨论。

在未来的 中,这种所谓的前向引用将被允许。这将通过在明确要求之前不计算注解来处理, 对这个目的做了详细的描述。python 3.7 中,前向引用已经可以通过 使用。现在你可以这样写了:

from __future__ import annotations
class tree:
    def __init__(self, left: tree, right: tree) -> none:
        self.left = left
        self.right = right

记着这里除了避免了看起来有点怪怪的 "tree" 语法之外,对注解的延迟计算也会加快代码的速度,因为类型提示不会被执行。前向引用已经被   支持。

到目前为止,注解通常用作类型提示。尽管如此,您仍可以在运行时完全访问注解,并可以根据需要使用它们。如果直接处理注解,则需要明确处理可能的前向引用。

让我们来看一个有点傻的例子,它展示了注解是何时被求值的。首先我们用经典的风格,这时注解在导入时求值。anno.py包含以下代码:

def greet(name: print("now!")):
    print(f"hello {name}")

注意,name 的注解是print()。这只是为了确切地看到注解何时被求值。这时导入此模块:

>>> import anno
now!
>>> anno.greet.__annotations__
{'name': none}
>>> anno.greet("alice")
hello alice

正如您所看到的,注解在导入时进行了求值。注意到 name 最终的注解是 none,这是因为 print() 的返回值为 none

增加 __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': }
>>> eval(anno.greet.__annotations__["name"])
now!
>>> anno.greet.__annotations__
{'name': "print('now!')"}

我们可以看到 __annotations__ 字典不会更新,因此,每次使用时都需要对注解求值。

时间精度

python 3.7 中, 模块增加了一些 描述的新函数。具体上来说,添加了下面6个函数:

  • clock_gettime_ns(): 返回指定时钟时间
  • clock_settime_ns(): 设置指定时钟时间
  • monotonic_ns(): 返回不能倒退的相对时钟的时间(例如由于夏令时)
  • perf_counter_ns(): 返回性能计数器的值,专门用于测量短间隔的时钟
  • process_time_ns(): 返回当前进程系统和用户 cpu 时间的总和(不包括休眠时间)
  • time_ns(): 返回自1970年1月1日以来的纳秒数

从某种意义上来说,没有新函数添加。每个函数都与已经存在的没有 _ns 后缀的函数相似。不同的是,新函数返回的是 int 类型的纳秒数而不是 float 类型的秒数。

对于大多数应用来说,新的纳秒函数和旧的同功能函数之间的差异性不是很明显。然而,新函数更容易应用起来更合理,因为返回 int 而不是 float。浮点数  :

>>> 0.1   0.1   0.1
0.30000000000000004
>>> 0.1   0.1   0.1 == 0.3
false

这不是 python 的问题,而是由于计算机需要使用有限位数表示无限十进制数的后果。

python 的 float 遵循 使用了53个有效位。结果是,任何超过104天(2⁵³ 或者超过)不能表示为具有纳秒精度的浮点数。相比之下,python 的 ,所以纳秒整数将始终有纳秒精度而不用考虑时间值。

例如,time.time() 返回了自1970年1月1日以来的秒数。这个数字已经非常巨大,它的精度已经处于微妙级别。这个函数显示了 _ns 版本的最大改, time.time_ns()  的比 time.time()

顺便说一下,什么是纳秒?技术上来说,它是十亿分之一秒,如果你更新欢科学技术,那就是 1e-9。这些仅仅是数字,实际上并没有提供任何直觉。为了更好地视觉辅助,请参与 精彩的 。

顺便说一句,如果你想处理纳秒精度的日期时间, 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)

然而,你可以用  。它的 使用两个 float 对象表示日期时间,保证「跨越宇宙时代的亚纳秒精度」。

>>> from astropy.time import time, timedelta
>>> time("2018-06-27")

 astropy 最新的版本在 python 3.5 以及后续版本中都可用。

其他酷炫的功能

到目前为止,你已经看到了 python 3.7 中新功能头条新闻。然而,这儿还有一些新的酷炫功能。这节,我们将简单的了解下它们。

字典顺序得到保证

python 3.6 的 cpython 实现了有序字典( 也有这个)。这意味着字典中元素被迭代顺序同它们被插入的顺序相同。第一个例子是使用 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 中介绍了 。为了避免向后兼容问题,async  和 await 并未添加到保留关键字列表。换句话说,仍然可以定义名为 asyncawait 的变量或者函数。

python 3.7 中,再也不可能那样了:

>>> async = 1
  file "", line 1
    async = 1
          ^
syntaxerror: invalid syntax
>>> def await():
  file "", line 1
    def await():
            ^
syntaxerror: invalid syntax

"asyncio" 重大改进

asyncio 模块被从 python 3.4 中引入去用事件循环,协程和 futures 的现代化方式处理并发。这里有个。

python 3.7 中,asyncio 模块取得了,包括许多新的函数,支持上下文变量(看)以及性能改进。特别值得注意的是 asyncio.run(),它简化了同步代码调用协程。使用 你不必再去显示地创建事件循环。一个异步的 hello world 程序可以这样写了:

import asyncio
async def hello_world():
    print("hello world!")
asyncio.run(hello_world())

上下文变量

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

下面的示例构造了三个上下文,每个上下文都有自己的 name 值。 greet() 函数在之后的每一个上下文中都可以使用 name 的值:

import contextvars
name = contextvars.contextvar("name")
contexts = list()
def greet():
    print(f"hello {name.get()}")
# 构造上下文并设置上下文变量名称
for first_name in ["steve", "dina", "harry"]:
    ctx = contextvars.copy_context()
    ctx.run(name.set, first_name)
    contexts.append(ctx)
# 在每个上下文中运行 greet 函数
for ctx in reversed(contexts):
    ctx.run(greet)

运行此脚本,以相反的顺序与 steve,dina和harry 打招呼:

$ python3.7 context_demo.py
hello harry
hello dina
hello steve

使用 "importlib.resources" 导入数据文件

打包 python 项目的一个挑战是如何处理项目资源文件,例如项目所需的数据文件。通用的一些处理方式:

  • 硬编码数据文件路径。
  • 把数据文件放在包里面并通过 __file__ 定位。
  • 使用  访问数据文件资源。

这三个方式都有缺点。第一个方式不便于移植。使用 __file__ 具备了可移植性,但是如果 python 项目被以一个 zip 文件安装,它就没有 __file__ 属性了。第三个方式解决了这写问题,不幸的是太慢了。

更好的金年会app官方网的解决方案是标准库中的新模块   。它使用 python 现有的导入功能导入数据文件。假设你在一个 python 包里有像下面这样的资源:

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

注意的是 data 需要是一个  。也就是说,这个目录需要包含一个 __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?'

一个相似的函数   用于以二进制模式打开文件。在前面  ,我们用  importlib.resources 中的  resources.contents() 去发现可用的插件。阅读   查看更多信息。

通过 在 python 2.7 和 python 3.4 中使用  importlib.resources  已成为可能。 也已经可用了。

开发者技巧

python 3.7 添加了几个针对您作为开发人员的功能。你。此外,python 解释器中添加了一些新的  。

您可以使用-x importtime 轻松了解脚本中的导入时间:

$ python3.7 -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 秒,其中大部分用于导入 importlib.resourcesself 列显示不包括嵌套导入的导入时间。

你现在可以使用 -x dev 来激活“开发者模式”。开发者模式将增加一节 debug 特性和运行时检查,这些功能被认为太慢而无法在默认情况下启用。这些包括启用 显示严重崩溃的追溯,以及更多警告和调试钩子。

最后,-x utf8 启用 。 (参阅 .) 在这个模式下,无论当前的语言环境如何,utf-8 都将用于文本编码。

优化

每个 python 新版本都会带来一些优化。python 3.7 中,这里有一些有意义的加速,包括:

  • 调用标准库中的许多函数将会有更少的开销。
  • 一般而言,方法调用快了 20%。
  • python 解释器的启动时间减少了 10-30%。
  • 导入 typing 比以前快了 7 倍。

除此之外,还有更多专业的优化。有关详细概述,请看  。

这些优化的结果是 。它只是截至目前  。

所以,我该升级吗?

让我们从简单的答案开始吧,如果你想用这里任何的新功能,你需要去使用 python 3.7。使用 或者 这样的工具可以很容易同时安装多个 python 版本。安装 python 3.7 并且使用它你并不吃亏。

现在,对于更复杂的问题,你是否应该将你的生产环境升级到 python 3.7 ?你是否应该使用 python 3.7 开发自己的项目而使用它的新功能?

显而易见的警告是,你应该在升级你的生产环境之前对你的代码进行彻底的测试,python 3.7 很少会破坏之前的代码(async 和 await 成为关键字是一个例子)。如果你已经在使用现代化 python,升级到 3.7 应该十分平滑。如果你比较保守一点,你应该等一个维护版本的发布--python 3.7.1--。

去争论是否应该在你的项目中使用 3.7 很难。python 3.7 中许多新功能 (数据类,importlib.resources)或者便利性(快速的启动和方法调用,更容易的调试以及 -x 选项)可以反向移植到 python 3.6。再往后,你可以通过自己运行 python 3.7,同时保持代码与 python 3.6(或更低版本)兼容来利用新版本的优势。

将代码锁定到 python 3.7 的重要功能是  , 和 。如果你真需要这些,你应该考虑升级。否则,如果你的项目在 python 3.6 再运行一段时间对于其他人可能更有用。

有关升级需要的详细信息,请阅读  。

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 cc 协议,如果我们的工作有侵犯到您的权益,请及时联系金年会app官方网。

原文地址:

译文地址:https://learnku.com/python/t/22994/some-...

本帖已被设为精华帖!
本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
讨论数量: 2

怎么分享到朋友圈

6年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
网站地图