Python 3.7 is officially released! This new Python version has been in development since September 2016, and now we all get to enjoy the results of the core developers’ hard work.
Python 3.7正式发布! 这个新的Python版本自2016年9月开始开发,现在我们所有人都可以享受核心开发人员辛勤工作的成果。
What does the new Python version bring? While the documentation gives a good overview of the new features, this article will take a deep dive into some of the biggest pieces of news. These include:
新的Python版本带来了什么? 尽管文档很好地概述了这些新功能,但本文将深入探讨一些重大新闻。 这些包括:
breakpoint()
built-inbreakpoint()
更轻松地访问调试器 More importantly, Python 3.7 is fast.
更重要的是,Python 3.7速度很快。
In the final sections of this article, you’ll read more about this speed, as well as some of the other cool features of Python 3.7. You will also get some advice on upgrading to the new version.
在本文的最后几节中,您将了解有关此速度的更多信息,以及Python 3.7的一些其他出色功能。 您还将获得有关升级到新版本的一些建议。
breakpoint()
(The breakpoint()
Built-In)While we might strive to write perfect code, the simple truth is that we never do. Debugging is an important part of programming. Python 3.7 introduces the new built-in function breakpoint()
. This does not really add any new functionality to Python, but it makes using debuggers more flexible and intuitive.
虽然我们可能会努力编写完美的代码,但简单的事实是我们从不这样做。 调试是编程的重要组成部分。 Python 3.7引入了新的内置函数breakpoint()
。 这实际上并没有为Python添加任何新功能,但是它使调试器的使用更加灵活和直观。
Assume that you have the following buggy code in the file bugs.py
:
假设您在bugs.py
文件中有以下错误代码:
def def dividedivide (( ee , , ff ):
):
return return f f / / e
e
aa , , b b = = 00 , , 1
1
printprint (( dividedivide (( aa , , bb ))
))
Running the code causes a ZeroDivisionError
inside the divide()
function. Let’s say that you want to interrupt your code and drop into a debugger right at the top of divide()
. You can do so by setting a so called “breakpoint” in your code:
运行代码会在ZeroDivisionError
divide()
函数内部导致ZeroDivisionError
。 假设您要中断代码并直接在divide()
顶部放入调试器 。 您可以通过在代码中设置一个所谓的“断点”来实现:
A breakpoint is a signal inside your code that execution should temporarily stop, so that you can look around at the current state of the program. How do you place the breakpoint? In Python 3.6 and below, you use this somewhat cryptic line:
断点是代码内部的信号,该信号应暂时停止执行,以便您可以查看程序的当前状态。 您如何放置断点? 在Python 3.6及以下版本中,您可以使用以下含糊的代码行:
def def dividedivide (( ee , , ff ):
):
import import pdbpdb ; ; pdbpdb .. set_traceset_trace ()
()
return return f f / / e
e
Here, pdb
is the Python Debugger from the standard library. In Python 3.7, you can use the new breakpoint()
function call as a shortcut instead:
在这里, pdb
是标准库中的Python调试器。 在Python 3.7中,可以将新的breakpoint()
函数调用用作快捷方式:
In the background, breakpoint()
is first importing pdb
and then calling pdb.set_trace()
for you. The obvious benefits are that breakpoint()
is easier to remember and that you only need to type 12 characters instead of 27. However, the real bonus of using breakpoint()
is its customizability.
在后台, breakpoint()
首先导入pdb
,然后为您调用pdb.set_trace()
。 明显的好处是breakpoint()
更容易记住,只需要键入12个字符而不是27个字符即可。但是,使用breakpoint()
的真正好处是它的可定制性。
Run your bugs.py
script with breakpoint()
:
使用breakpoint()
运行您的bugs.py
脚本:
$ python3.7 bugs.py
$ python3.7 bugs.py
> /home/gahjelle/bugs.py> /home/gahjelle/bugs.py (( 33 )divide) divide ()
()
-> return f / e
-> return f / e
(Pdb)
(Pdb)
The script will break when it reaches breakpoint()
and drop you into a PDB debugging session. You can type c
and hit Enter to continue the script. Refer to Nathan Jennings’ PDB guide if you want to learn more about PDB and debugging.
该脚本在到达breakpoint()
时将中断,并使您进入PDB调试会话。 您可以键入c
并按Enter键继续执行脚本。 如果您想了解有关PDB和调试的更多信息,请参考Nathan Jennings的PDB指南 。
Now, say that you think you’ve fixed the bug. You would like to run the script again but without stopping in the debugger. You could, of course, comment out the breakpoint()
line, but another option is to use the PYTHONBREAKPOINT
environment variable. This variable controls the behavior of breakpoint()
, and setting PYTHONBREAKPOINT=0
means that any call to breakpoint()
is ignored:
现在,假设您认为已修复该错误。 您想再次运行脚本,但不停止调试器。 当然,您可以注释掉breakpoint()
行,但是另一种选择是使用PYTHONBREAKPOINT
环境变量。 此变量控制breakpoint()
的行为,设置PYTHONBREAKPOINT=0
意味着对breakpoint()
任何调用都将被忽略:
Oops, it seems as if you haven’t fixed the bug after all…
糟糕,看来您毕竟还没有修复错误…
Another option is to use PYTHONBREAKPOINT
to specify a debugger other than PDB. For instance, to use PuDB (a visual debugger in the console) you can do:
另一种选择是使用PYTHONBREAKPOINT
指定除PDB之外的其他调试器。 例如,要使用PuDB (控制台中的可视调试器),您可以执行以下操作:
$ $ PYTHONBREAKPOINTPYTHONBREAKPOINT =pudb.set_trace python3.7 bugs.py
= pudb.set_trace python3.7 bugs.py
For this to work, you need to have pudb
installed (pip install pudb
). Python will take care of importing pudb
for you though. This way you can also set your default debugger. Simply set the PYTHONBREAKPOINT
environment variable to your preferred debugger. See this guide for instructions on how to set an environment variable on your system.
对于这个工作,你需要有pudb
安装( pip install pudb
)。 Python会pudb
您导入pudb
。 这样,您还可以设置默认调试器。 只需将PYTHONBREAKPOINT
环境变量设置为您首选的调试器。 请参阅本指南以获取有关如何在系统上设置环境变量的说明。
The new breakpoint()
function does not only work with debuggers. One convenient option could be to simply start an interactive shell inside your code. For instance, to start an IPython session, you can use the following:
新的breakpoint()
函数不仅适用于调试器。 一种方便的选择是仅在代码内部启动一个交互式外壳。 例如,要启动IPython会话,可以使用以下命令:
You can also create your own function and have breakpoint()
call that. The following code prints all variables in the local scope. Add it to a file called bp_utils.py
:
您也可以创建自己的函数,并使用breakpoint()
调用。 以下代码在本地范围内打印所有变量。 将其添加到名为bp_utils.py
的文件中:
from from pprint pprint import import pprint
pprint
import import sys
sys
def def print_localsprint_locals ():
():
caller caller = = syssys .. _getframe_getframe (( 11 ) ) # Caller is 1 frame up.
# Caller is 1 frame up.
pprintpprint (( callercaller .. f_localsf_locals )
)
To use this function, set PYTHONBREAKPOINT
as before, with the
notation:
要使用此功能, PYTHONBREAKPOINT
像以前一样使用
表示法设置PYTHONBREAKPOINT
:
Normally, breakpoint()
will be used to call functions and methods that do not need arguments. However, it is possible to pass arguments as well. Change the line breakpoint()
in bugs.py
to:
通常, breakpoint()
将用于调用不需要参数的函数和方法。 但是,也可以传递参数。 将bugs.py
的line breakpoint()
bugs.py
为:
breakpointbreakpoint (( ee , , ff , , endend == "<-END"<-END nn "" )
)
Note: The default PDB debugger will raise a TypeError
at this line because pdb.set_trace()
does not take any positional arguments.
注意:默认的PDB调试器将在此行TypeError
,因为pdb.set_trace()
不接受任何位置参数。
Run this code with breakpoint()
masquerading as the print()
function to see a simple example of the arguments being passed through:
伪装有breakpoint()
作为print()
函数运行以下代码,以查看传递的参数的简单示例:
See PEP 553 as well as the documentation for breakpoint()
and sys.breakpointhook()
for more information.
有关更多信息,请参见PEP 553以及breakpoint()
和sys.breakpointhook()
的文档。
The new dataclasses
module makes it more convenient to write your own classes, as special methods like .__init__()
, .__repr__()
, and .__eq__()
are added automatically. Using the @dataclass
decorator, you can write something like:
新的dataclasses
模块使编写自己的类更加方便,因为会自动添加.__init__()
, .__repr__()
和.__eq__()
类的特殊方法。 使用@dataclass
装饰器,您可以编写如下内容:
from from dataclasses dataclasses import import dataclassdataclass , , field
field
@dataclass@dataclass (( orderorder == TrueTrue )
)
class class CountryCountry :
:
namename : : str
str
populationpopulation : : int
int
areaarea : : float float = = fieldfield (( reprrepr == FalseFalse , , comparecompare == FalseFalse )
)
coastlinecoastline : : float float = = 0
0
def def beach_per_personbeach_per_person (( selfself ):
):
"""Meters of coastline per person"""
"""Meters of coastline per person"""
return return (( selfself .. coastline coastline * * 10001000 ) ) / / selfself .. population
population
These nine lines of code stand in for quite a bit of boilerplate code and best practices. Think about what it would take to implement Country
as a regular class: the .__init__()
method, a repr
, six different comparison methods as well as the .beach_per_person()
method. You can expand the box below to see an implementation of Country
that is roughly equivalent to the data class:
这九行代码代表了很多样板代码和最佳实践。 考虑将Country
用作常规类将需要执行什么: .__init__()
方法,一个repr
,六个不同的比较方法以及.beach_per_person()
方法。 您可以展开下面的框,以查看与数据类大致等效的Country
的实现:
Alternate implementation of the “Country” class Show/Hide
“国家 / 地区”类 显示/隐藏的 替代实现
After creation, a data class is a normal class. You can, for instance, inherit from a data class in the normal way. The main purpose of data classes is to make it quick and easy to write robust classes, in particular small classes that mainly store data.
创建后,数据类是普通类。 例如,您可以按常规方式从数据类继承。 数据类的主要目的是使编写健壮的类(尤其是主要存储数据的小类)变得快速便捷。
You can use the Country
data class like any other class:
您可以像其他任何类别一样使用Country
数据类别:
>>> >>> norway norway = = CountryCountry (( "Norway""Norway" , , 53200455320045 , , 323802323802 , , 5813358133 )
)
>>> >>> norway
norway
Country(name='Norway', population=5320045, coastline=58133)
Country(name='Norway', population=5320045, coastline=58133)
>>> >>> norwaynorway .. area
area
323802
323802
>>> >>> usa usa = = CountryCountry (( "United States""United States" , , 326625791326625791 , , 98335179833517 , , 1992419924 )
)
>>> >>> nepal nepal = = CountryCountry (( "Nepal""Nepal" , , 2938429729384297 , , 147181147181 )
)
>>> >>> nepal
nepal
Country(name='Nepal', population=29384297, coastline=0)
Country(name='Nepal', population=29384297, coastline=0)
>>> >>> usausa .. beach_per_personbeach_per_person ()
()
0.06099946957342386
0.06099946957342386
>>> >>> norwaynorway .. beach_per_personbeach_per_person ()
()
10.927163210085629
10.927163210085629
Note that all the fields .name
, .population
, .area
, and .coastline
are used when initializing the class (although .coastline
is optional, as is shown in the example of landlocked Nepal). The Country
class has a reasonable repr
, while defining methods works the same as for regular classes.
请注意,初始化类时,将使用所有字段.name
, .population
, .area
和.coastline
(尽管.coastline
是可选的,如内陆尼泊尔的示例所示)。 Country
类具有合理的repr
,而定义方法的作用与常规类相同。
By default, data classes can be compared for equality. Since we specified order=True
in the @dataclass
decorator, the Country
class can also be sorted:
默认情况下,可以比较数据类的相等性。 由于我们在@dataclass
装饰器中指定了order=True
,因此Country
类也可以排序:
The sorting happens on the field values, first .name
then .population
, and so on. However, if you use field()
, you can customize which fields will be used in the comparison. In the example, the .area
field was left out of the repr
and the comparisons.
排序发生在字段值上,首先是.name
然后是.population
,依此类推。 但是,如果使用field()
,则可以自定义将在比较中使用的字段。 在示例中, .area
字段未包含在repr
和比较中。
Note: The country data are from the CIA World Factbook with population numbers estimated for July 2017.
注意:国家/地区数据来自CIA世界概况 ,估计2017年7月的人口数量。
Before you all go book your next beach holidays in Norway, here is what the Factbook says about the Norwegian climate: “temperate along coast, modified by North Atlantic Current; colder interior with increased precipitation and colder summers; rainy year-round on west coast.”
在大家预定下一个在挪威的海滩假期之前,这是《概况》对挪威气候的评价 :“沿海温带,经北大西洋洋流改造; 内部较冷,降水增加,夏天较冷; 西海岸终年降雨。”
Data classes do some of the same things as namedtuple
. Yet, they draw their biggest inspiration from the attrs
project. See our full guide to data classes for more examples and further information, as well as PEP 557 for the official description.
数据类与namedtuple
做一些相同的事情。 但是,他们从attrs
项目中获得了最大的启发。 有关更多示例和更多信息,请参见我们的数据类完整指南,对于正式说明,请参见PEP 557 。
Attributes are everywhere in Python! While class attributes are probably the most famous, attributes can actually be put on essentially anything—including functions and modules. Several of Python’s basic features are implemented as attributes: most of the introspection functionality, doc-strings, and name spaces. Functions inside a module are made available as module attributes.
Python中到处都有属性! 虽然类属性可能是最著名的,但实际上属性实际上可以放在任何东西上,包括函数和模块。 Python的一些基本功能被实现为属性:大多数自省功能,文档字符串和名称空间。 模块内部的功能可用作模块属性。
Attributes are most often retrieved using the dot notation: thing.attribute
. However, you can also get attributes that are named at runtime using getattr()
:
最常使用点表示法来检索属性: thing.attribute
。 但是,您还可以使用getattr()
获得在运行时命名的属性:
import import random
random
random_attr random_attr = = randomrandom .. choicechoice (((( "gammavariate""gammavariate" , , "lognormvariate""lognormvariate" , , "normalvariate""normalvariate" ))
))
random_func random_func = = getattrgetattr (( randomrandom , , random_attrrandom_attr )
)
printprint (( ff "A "A {random_attr}{random_attr} random value: {random_func(1, 1)}" random value: {random_func(1, 1)}" )
)
Running this code will produce something like:
运行此代码将产生类似以下内容:
For classes, calling thing.attr
will first look for attr
defined on thing
. If it is not found, then the special method thing.__getattr__("attr")
is called. (This is a simplification. See this article for more details.) The .__getattr__()
method can be used to customize access to attributes on objects.
对于类,调用thing.attr
将首先查找在thing
定义的attr
。 如果找不到,则调用特殊方法thing.__getattr__("attr")
。 (这是一种简化。有关更多详细信息,请参见本文 。) .__getattr__()
方法可用于自定义对对象属性的访问。
Until Python 3.7, the same customization was not easily available for module attributes. However, PEP 562 introduces __getattr__()
on modules, together with a corresponding __dir__()
function. The __dir__()
special function allows customization of the result of calling dir()
on a module.
在Python 3.7之前,模块属性很难获得相同的自定义。 但是, PEP 562在模块上引入了__getattr__()
以及相应的__dir__()
函数。 __dir__()
特殊功能允许自定义在模块上调用dir()
的结果。
The PEP itself gives a few examples of how these functions can be used, including adding deprecation warnings to functions and lazy loading of heavy submodules. Below, we will build a simple plugin system that allows functions to be added to a module dynamically. This example takes advantage of Python packages. See this article if you need a refresher on packages.
PEP本身提供了一些如何使用这些功能的示例,包括向功能添加弃用警告以及延迟加载繁重的子模块。 下面,我们将构建一个简单的插件系统,该系统允许将功能动态添加到模块中。 这个例子利用了Python包。 如果您需要更新软件包,请参阅本文 。
Create a new directory, plugins
, and add the following code to a file, plugins/__init__.py
:
创建一个新目录plugins
,并将以下代码添加到一个文件plugins/__init__.py
:
from from importlib importlib import import import_module
import_module
from from importlib importlib import import resources
resources
PLUGINS PLUGINS = = dictdict ()
()
def def register_pluginregister_plugin (( funcfunc ):
):
"""Decorator to register plug-ins"""
"""Decorator to register plug-ins"""
name name = = funcfunc .. __name__
__name__
PLUGINSPLUGINS [[ namename ] ] = = func
func
return return func
func
def def __getattr____getattr__ (( namename ):
):
"""Return a named plugin"""
"""Return a named plugin"""
trytry :
:
return return PLUGINSPLUGINS [[ namename ]
]
except except KeyErrorKeyError :
:
_import_plugins_import_plugins ()
()
if if name name in in PLUGINSPLUGINS :
:
return return PLUGINSPLUGINS [[ namename ]
]
elseelse :
:
raise raise AttributeErrorAttributeError (
(
ff "module "module {__name__!r}{__name__!r} has no attribute has no attribute {name!r}{name!r} "
"
) ) from from None
None
def def __dir____dir__ ():
():
"""List available plug-ins"""
"""List available plug-ins"""
_import_plugins_import_plugins ()
()
return return listlist (( PLUGINSPLUGINS .. keyskeys ())
())
def def _import_plugins_import_plugins ():
():
"""Import all resources to register plug-ins"""
"""Import all resources to register plug-ins"""
for for name name in in resourcesresources .. contentscontents (( __name____name__ ):
):
if if namename .. endswithendswith (( ".py"".py" ):
):
import_moduleimport_module (( ff "" {__name__}{__name__} .. {name[:-3]}{name[:-3]} "" )
)
Before we look at what this code does, add two more files inside the plugins
directory. First, let’s see plugins/plugin_1.py
:
在我们看一下这段代码的作用之前,请在plugins
目录中再添加两个文件。 首先,让我们看看plugins/plugin_1.py
:
Next, add similar code in the file plugins/plugin_2.py
:
接下来,在文件plugins/plugin_2.py
添加类似的代码:
from from . . import import register_plugin
register_plugin
@register_plugin
@register_plugin
def def hello_2hello_2 ():
():
printprint (( "Hello from Plugin 2""Hello from Plugin 2" )
)
@register_plugin
@register_plugin
def def goodbyegoodbye ():
():
printprint (( "Plugin 2 says goodbye""Plugin 2 says goodbye" )
)
These plugins can now be used as follows:
现在可以按以下方式使用这些插件:
This may not all seem that revolutionary (and it probably isn’t), but let’s look at what actually happened here. Normally, to be able to call plugins.hello_1()
, the hello_1()
function must be defined in a plugins
module or explicitly imported inside __init__.py
in a plugins
package. Here, it is neither!
这似乎并不全都是革命性的(也许不是),但让我们看看这里实际发生了什么。 通常,要能够调用plugins.hello_1()
,必须在plugins
模块中定义hello_1()
函数,或在plugins
包中的__init__.py
中显式导入hello_1()
函数。 在这里,两者都不是!
Instead, hello_1()
is defined in an arbitrary file inside the plugins
package, and hello_1()
becomes a part of the plugins
package by registering itself using the @register_plugin
decorator.
相反, hello_1()
是在plugins
包内的任意文件中定义的,并且hello_1()
是通过使用@register_plugin
装饰器进行自身注册而成为plugins
包的一部分。
The difference is subtle. Instead of the package dictating which functions are available, the individual functions register themselves as part of the package. This gives you a simple structure where you can add functions independently of the rest of the code without having to keep a centralized list of which functions are available.
区别是微妙的。 代替了指示哪些功能可用的程序包,各个功能将自身注册为程序包的一部分。 这为您提供了一个简单的结构,您可以独立于其余代码添加功能,而不必保留可用功能的集中列表。
Let us do a quick review of what __getattr__()
does inside the plugins/__init__.py
code. When you asked for plugins.hello_1()
, Python first looks for a hello_1()
function inside the plugins/__init__.py
file. As no such function exists, Python calls __getattr__("hello_1")
instead. Remember the source code of the __getattr__()
function:
让我们快速回顾一下plugins/__init__.py
代码中__getattr__()
功能。 当您要求plugins.hello_1()
,Python首先在plugins/__init__.py
文件中寻找hello_1()
函数。 由于不存在这样的函数,Python会改为调用__getattr__("hello_1")
。 记住__getattr__()
函数的源代码:
def def __getattr____getattr__ (( namename ):
):
"""Return a named plugin"""
"""Return a named plugin"""
trytry :
:
return return PLUGINSPLUGINS [[ namename ] ] # 1) Try to return plugin
# 1) Try to return plugin
except except KeyErrorKeyError :
:
_import_plugins_import_plugins () () # 2) Import all plugins
# 2) Import all plugins
if if name name in in PLUGINSPLUGINS :
:
return return PLUGINSPLUGINS [[ namename ] ] # 3) Try to return plugin again
# 3) Try to return plugin again
elseelse :
:
raise raise AttributeErrorAttributeError ( ( # 4) Raise error
# 4) Raise error
ff "module "module {__name__!r}{__name__!r} has no attribute has no attribute {name!r}{name!r} "
"
) ) from from None
None
__getattr__()
contains the following steps. The numbers in the following list correspond to the numbered comments in the code:
__getattr__()
包含以下步骤。 下表中的数字与代码中带注释的数字相对应:
PLUGINS
dictionary. This will succeed if a plugin named name
exists and has already been imported.PLUGINS
dictionary, we make sure all plugins are imported.PLUGINS
dictionary after importing all plugins, we raise an AttributeError
saying that name
is not an attribute (plugin) on the current module.PLUGINS
字典返回命名的插件。 如果名为name
的插件存在并且已经导入,则将成功。 PLUGINS
词典中找不到指定的插件,则确保所有插件都已导入。 PLUGINS
词典中,则我们引发AttributeError
说name
不是当前模块上的属性(插件)。 How is the PLUGINS
dictionary populated though? The _import_plugins()
function imports all Python files inside the plugins
package, but does not seem to touch PLUGINS
:
PLUGINS
词典如何填充? _import_plugins()
函数导入了plugins
包内的所有Python文件,但似乎没有碰到PLUGINS
:
Don’t forget that each plugin function is decorated by the @register_plugin
decorator. This decorator is called when the plugins are imported and is the one actually populating the PLUGINS
dictionary. You can see this if you manually import one of the plugin files:
不要忘记,每个插件功能都由@register_plugin
装饰器装饰。 导入插件时将调用此装饰器,它实际上是填充PLUGINS
字典的装饰器。 如果您手动导入插件文件之一,则可以看到以下内容:
>>> >>> import import plugins
plugins
>>> >>> pluginsplugins .. PLUGINS
PLUGINS
{}
{}
>>> >>> import import plugins.plugin_1
plugins.plugin_1
>>> >>> pluginsplugins .. PLUGINS
PLUGINS
{'hello_1': }
{'hello_1': }
Continuing the example, note that calling dir()
on the module also imports the remaining plugins:
继续该示例,请注意,在模块上调用dir()
也会导入其余的插件:
dir()
usually lists all available attributes on an object. Normally, using dir()
on a module results in something like this:
dir()
通常列出对象上所有可用的属性。 通常,在模块上使用dir()
导致类似以下情况:
>>> >>> import import plugins
plugins
>>> >>> dirdir (( pluginsplugins )
)
['PLUGINS', '__builtins__', '__cached__', '__doc__',
['PLUGINS', '__builtins__', '__cached__', '__doc__',
'__file__', '__getattr__', '__loader__', '__name__',
'__file__', '__getattr__', '__loader__', '__name__',
'__package__', '__path__', '__spec__', '_import_plugins',
'__package__', '__path__', '__spec__', '_import_plugins',
'import_module', 'register_plugin', 'resources']
'import_module', 'register_plugin', 'resources']
While this might be useful information, we are more interested in exposing the available plugins. In Python 3.7, you can customize the result of calling dir()
on a module by adding a __dir__()
special function. For plugins/__init__.py
, this function first makes sure all plugins have been imported and then lists their names:
尽管这可能是有用的信息,但我们对公开可用的插件更感兴趣。 在Python 3.7中,您可以通过添加__dir__()
特殊函数来自定义在模块上调用dir()
的结果。 对于plugins/__init__.py
,此函数首先确保已导入所有插件,然后列出其名称:
Before leaving this example, please note that we also used another cool new feature of Python 3.7. To import all modules inside the plugins
directory, we used the new importlib.resources
module. This module gives access to files and resources inside modules and packages without the need for __file__
hacks (which do not always work) or pkg_resources
(which is slow). Other features of importlib.resources
will be highlighted later.
在离开此示例之前,请注意,我们还使用了Python 3.7的另一个很酷的新功能。 要导入plugins
目录内的所有模块,我们使用了新的importlib.resources
模块。 通过此模块,可以访问模块和软件包中的文件和资源,而无需__file__
hacks(不一定总是有效)或pkg_resources
(很慢)。 importlib.resources
其他功能将在稍后突出显示 。
Type hinting and annotations have been in constant development throughout the Python 3 series of releases. Python’s typing system is now quite stable. Still, Python 3.7 brings some enhancements to the table: better performance, core support, and forward references.
在整个Python 3系列发行版中,类型提示和注释一直在不断发展。 Python的打字系统现在非常稳定。 尽管如此,Python 3.7对该表进行了一些增强:更好的性能,核心支持和正向引用。
Python does not do any type checking at runtime (unless you are explicitly using packages like enforce
). Therefore, adding type hints to your code should not affect its performance.
Python在运行时不进行任何类型检查(除非您显式使用诸如enforce
包)。 因此,在代码中添加类型提示不会影响其性能。
Unfortunately, this is not completely true as most type hints need the typing
module. The typing
module is one of the slowest modules in the standard library. PEP 560 adds some core support for typing in Python 3.7, which significantly speeds up the typing
module. The details of this are in general not necessary to know about. Simply lean back and enjoy the increased performance.
不幸的是,这并非完全正确,因为大多数类型提示都需要typing
模块。 typing
模块是标准库中最慢的模块之一。 PEP 560为Python 3.7中的键入添加了一些核心支持,从而大大加快了typing
模块的速度。 通常不需要了解其详细信息。 只需向后倾斜即可享受更高的性能。
While Python’s type system is reasonably expressive, one issue that causes some pain is forward references. Type hints—or more generally annotations—are evaluated while the module is imported. Therefore, all names must already be defined before they are used. The following is not possible:
尽管Python的类型系统具有合理的表现力,但引起麻烦的一个问题是前向引用。 导入模块时会评估类型提示(或更一般地说是注释)。 因此,在使用所有名称之前,必须已经定义了所有名称。 以下是不可能的:
class class TreeTree :
:
def def __init____init__ (( selfself , , leftleft : : TreeTree , , rightright : : TreeTree ) ) -> -> NoneNone :
:
selfself .. left left = = left
left
selfself .. right right = = right
right
Running the code raises a NameError
because the class Tree
is not yet (completely) defined in the definition of the .__init__()
method:
运行代码会引发NameError
因为尚未在.__init__()
方法的定义中完全定义类Tree
:
To overcome this, you would have needed to write "Tree"
as a string literal instead:
为了克服这个问题,您将需要编写"Tree"
作为字符串文字:
class class TreeTree :
:
def def __init____init__ (( selfself , , leftleft : : "Tree""Tree" , , rightright : : "Tree""Tree" ) ) -> -> NoneNone :
:
selfself .. left left = = left
left
selfself .. right right = = right
right
See PEP 484 for the original discussion.
有关原始讨论,请参见PEP 484 。
In a future Python 4.0, such so called forward references will be allowed. This will be handled by not evaluating annotations until that is explicitly asked for. PEP 563 describes the details of this proposal. In Python 3.7, forward references are already available as a __future__
import. You can now write the following:
在未来的Python 4.0中 ,将允许使用所谓的前向引用。 除非明确要求,否则不评估注释来解决此问题。 PEP 563描述了该提议的细节。 在Python 3.7中,前向引用已作为__future__
import提供 。 您现在可以编写以下内容:
Note that in addition to avoiding the somewhat clumsy "Tree"
syntax, the postponed evaluation of annotations will also speed up your code, since type hints are not executed. Forward references are already supported by mypy
.
请注意,除了避免使用笨拙的"Tree"
语法外,对注释的延迟评估也将加快代码的速度,因为不会执行类型提示。 mypy
已经支持前向引用。
By far, the most common use of annotations is type hinting. Still, you have full access to the annotations at runtime and can use them as you see fit. If you are handling annotations directly, you need to deal with the possible forward references explicitly.
到目前为止,注释最常见的用法是类型提示。 尽管如此,您仍可以在运行时完全访问注释,并可以根据需要使用它们。 如果直接处理批注,则需要显式处理可能的正向引用。
Let us create some admittedly silly examples that show when annotations are evaluated. First we do it old-style, so annotations are evaluated at import time. Let anno.py
contain the following code:
让我们创建一些公认的愚蠢示例,这些示例显示何时评估注释。 首先,我们采用旧样式,因此在导入时会评估注释。 让anno.py
包含以下代码:
def def greetgreet (( namename : : printprint (( "Now!""Now!" )):
)):
printprint (( ff "Hello "Hello {name}{name} "" )
)
Note that the annotation of name
is print()
. This is only to see exactly when the annotation is evaluated. Import the new module:
注意, name
的注释是print()
。 这只是为了准确查看注释的时间。 导入新模块:
As you can see, the annotation was evaluated at import time. Note that name
ends up annotated with None
because that is the return value of print()
.
如您所见,注释是在导入时评估的。 注意, name
最终以None
注释,因为那是print()
的返回值。
Add the __future__
import to enable postponed evaluation of annotations:
添加__future__
导入以启用注释的延迟评估:
from from __future__ __future__ import import annotations
annotations
def def greetgreet (( namename : : printprint (( "Now!""Now!" )):
)):
printprint (( ff "Hello "Hello {name}{name} "" )
)
Importing this updated code will not evaluate the annotation:
导入此更新的代码将不会评估注释:
Note that Now!
is never printed and the annotation is kept as a string literal in the __annotations__
dictionary. In order to evaluate the annotation, use typing.get_type_hints()
or eval()
:
注意, Now!
永远不会打印出来,并且注释会在__annotations__
字典中保留为字符串文字。 为了评估注释,请使用typing.get_type_hints()
或eval()
:
>>> >>> import import typing
typing
>>> >>> typingtyping .. get_type_hintsget_type_hints (( annoanno .. greetgreet )
)
Now!
Now!
{'name': }
{'name': }
>>> >>> evaleval (( annoanno .. greetgreet .. __annotations____annotations__ [[ "name""name" ])
])
Now!
Now!
>>> >>> annoanno .. greetgreet .. __annotations__
__annotations__
{'name': "print('Now!')"}
{'name': "print('Now!')"}
Observe that the __annotations__
dictionary is never updated, so you need to evaluate the annotation every time you use it.
请注意, __annotations__
字典从未更新,因此您每次使用注释时都需要对其进行评估。
In Python 3.7, the time
module gains some new functions as described in PEP 564. In particular, the following six functions are added:
在Python 3.7中, time
模块获得了一些新功能,如PEP 564中所述 。 特别是,添加了以下六个功能:
clock_gettime_ns()
: Returns the time of a specified clockclock_settime_ns()
: Sets the time of a specified clockmonotonic_ns()
: Returns the time of a relative clock that cannot go backwards (for instance due to daylight savings)perf_counter_ns()
: Returns the value of a performance counter—a clock specifically designed to measure short intervalsprocess_time_ns()
: Returns the sum of the system and user CPU time of the current process (not including sleep time)time_ns()
: Returns the number of nanoseconds since January 1st 1970clock_gettime_ns()
:返回指定时钟的时间 clock_settime_ns()
:设置指定时钟的时间 monotonic_ns()
:返回不能倒退的相对时钟的时间(例如,由于夏令时) perf_counter_ns()
:返回性能计数器的值,该时钟专门用于测量短间隔 process_time_ns()
:返回当前进程的系统和用户CPU时间的总和(不包括睡眠时间) time_ns()
:返回自1970年1月1日以来的纳秒数 In a sense, there is no new functionality added. Each function is similar to an already existing function without the _ns
suffix. The difference being that the new functions return a number of nanoseconds as an int
instead of a number of seconds as a float
.
从某种意义上说,没有添加任何新功能。 每个函数都类似于不带_ns
后缀的现有函数。 不同之处在于,新函数以int
返回的时间为纳秒,而不是float
的秒数。
For most applications, the difference between these new nanosecond functions and their old counterpart will not be appreciable. However, the new functions are easier to reason about because they rely on int
instead of float
. Floating point numbers are by nature inaccurate:
对于大多数应用程序,这些新的纳秒级功能与旧的纳秒级功能之间的差异将不明显。 但是,新函数更容易推论,因为它们依赖于int
而不是float
。 浮点数本质上是不准确的 :
This is not an issue with Python but rather a consequence of computers needing to represent infinite decimal numbers using a finite number of bits.
这不是Python的问题,而是计算机需要使用有限数量的位表示无限十进制数的结果。
A Python float
follows the IEEE 754 standard and uses 53 significant bits. The result is that any time greater than about 104 days (2⁵³ or approximately 9 quadrillion nanoseconds) cannot be expressed as a float with nanosecond precision. In contrast, a Python int
is unlimited, so an integer number of nanoseconds will always have nanosecond precision independent of the time value.
Python float
遵循IEEE 754标准,并使用53个有效位。 结果是,任何大于约104天(2·3³或约9万亿纳秒 )的时间都不能表示为具有纳秒精度的浮点数。 相反,Python int
是无限制的 ,因此整数纳秒将始终具有纳秒精度,而与时间值无关。
As an example, time.time()
returns the number of seconds since January 1st 1970. This number is already quite big, so the precision of this number is at the microsecond level. This function is the one showing the biggest improvement in its _ns
version. The resolution of time.time_ns()
is about 3 times better than for time.time()
.
例如, time.time()
返回自1970年1月1日以来的秒数。此数字已经很大,因此此数字的精度为微秒级别。 此函数是_ns
版本中显示最大改进的_ns
。 的分辨率time.time_ns()
约3倍的比time.time()
What is a nanosecond by the way? Technically, it is one billionth of a second, or 1e-9
second if you prefer scientific notation. These are just numbers though and do not really provide any intuition. For a better visual aid, see Grace Hopper’s wonderful demonstration of the nanosecond.
顺便说一下,纳秒是多少? 从技术上讲,如果您更喜欢科学计数法,则为十亿分之一秒,即1e-9
秒。 这些只是数字,并不能提供任何直觉。 有关更好的视觉帮助,请参见Grace Hopper的 纳秒级精彩演示 。
As an aside, if you need to work with datetimes with nanosecond precision, the datetime
standard library will not cut it. It explicitly only handles microseconds:
顺便说一句,如果您需要使用纳秒精度的datetime
,则datetime
标准库将不会削减它。 它显式只处理微秒:
>>> >>> from from datetime datetime import import datetimedatetime , , timedelta
timedelta
>>> >>> datetimedatetime (( 20182018 , , 66 , , 2727 ) ) + + timedeltatimedelta (( secondsseconds == 1e-61e-6 )
)
datetime.datetime(2018, 6, 27, 0, 0, 0, 1)
datetime.datetime(2018, 6, 27, 0, 0, 0, 1)
>>> >>> datetimedatetime (( 20182018 , , 66 , , 2727 ) ) + + timedeltatimedelta (( secondsseconds == 1e-91e-9 )
)
datetime.datetime(2018, 6, 27, 0, 0)
datetime.datetime(2018, 6, 27, 0, 0)
Instead, you can use the astropy
project. Its astropy.time
package represents datetimes using two float
objects which guarantees “sub-nanosecond precision over times spanning the age of the universe.”
相反,您可以使用astropy
项目 。 它的astropy.time
包使用两个float
对象表示日期时间,从而保证“在整个宇宙时代的时间范围内,亚纳秒精度”。
The latest version of astropy
is available in Python 3.5 and later.
astropy
的最新版本在Python 3.5及更高版本中可用。
So far, you have seen the headline news regarding what’s new in Python 3.7. However, there are many other changes that are also pretty cool. In this section, we will look briefly at some of them.
到目前为止,您已经看到有关Python 3.7新增功能的头条新闻。 但是,还有许多其他变化也很酷。 在本节中,我们将简要介绍其中的一些。
The CPython implementation of Python 3.6 has ordered dictionaries. (PyPy also has this.) This means that items in dictionaries are iterated over in the same order they were inserted. The first example is using Python 3.5, and the second is using Python 3.6:
Python 3.6的CPython实现对字典进行了排序。 ( PyPy也有此功能。)这意味着字典中的项目将按照插入时的相同顺序进行迭代。 第一个示例使用Python 3.5,第二个示例使用Python 3.6:
>>> >>> {
{
"one""one" : : 11 , , "two""two" : : 22 , , "three""three" : : 33 } } # Python <= 3.5
# Python <= 3.5
{'three': 3, 'one': 1, 'two': 2}
{'three': 3, 'one': 1, 'two': 2}
>>> >>> {
{
"one""one" : : 11 , , "two""two" : : 22 , , "three""three" : : 33 } } # Python >= 3.6
# Python >= 3.6
{'one': 1, 'two': 2, 'three': 3}
{'one': 1, 'two': 2, 'three': 3}
In Python 3.6, this ordering was just a nice consequence of that implementation of dict
. In Python 3.7, however, dictionaries preserving their insert order is part of the language specification. As such, it may now be relied on in projects that support only Python >= 3.7 (or CPython >= 3.6).
在Python 3.6中,这种排序只是dict
实现的一个不错的结果。 但是,在Python 3.7中,保留其插入顺序的字典是语言规范的一部分。 因此,现在只能在仅支持Python> = 3.7(或CPython> = 3.6)的项目中使用它。
async
”和“ await
”是关键字 (“async
” and “await
” Are Keywords)Python 3.5 introduced coroutines with async
and await
syntax. To avoid issues of backwards compatibility, async
and await
were not added to the list of reserved keywords. In other words, it was still possible to define variables or functions named async
and await
.
Python 3.5引入了带有async
和await
语法的协程 。 为了避免向后兼容的问题,没有将async
和await
添加到保留关键字列表中。 换句话说,仍然可以定义名为async
和await
变量或函数。
In Python 3.7, this is no longer possible:
在Python 3.7中,这不再可行:
asyncio
”瘦脸 (“asyncio
” Face Lift)The asyncio
standard library was originally introduced in Python 3.4 to handle concurrency in a modern way using event loops, coroutines and futures. Here is a gentle introduction.
asyncio
标准库最初是在Python 3.4中引入的,它使用事件循环,协程和期货以现代方式处理并发。 这是一个简短的介绍 。
In Python 3.7, the asyncio
module is getting a major face lift, including many new functions, support for the context variables mentioned above, and performance improvements. Of particular note is asyncio.run()
, which simplifies calling coroutines from synchronous code. Using asyncio.run()
, you do not need to explicitly create the event loop. An asynchronous Hello World program can now be written:
在Python 3.7中, asyncio
模块得到了重大改进 ,包括许多新功能,对上述上下文变量的支持以及性能改进。 特别要注意的是asyncio.run()
,它简化了从同步代码中调用协程的过程。 使用asyncio.run()
,您无需显式创建事件循环。 现在可以编写异步Hello World程序:
import import asyncio
asyncio
async async def def hello_worldhello_world ():
():
printprint (( "Hello World!""Hello World!" )
)
asyncioasyncio .. runrun (( hello_worldhello_world ())
())
Context variables are variables that can have different values depending on their context. They are similar to Thread-Local Storage in which each execution thread may have a different value for a variable. However, with context variables, there may be several contexts in one execution thread. The main use case for context variables is keeping track of variables in concurrent asynchronous tasks.
上下文变量是根据上下文可以具有不同值的变量。 它们类似于“线程本地存储”,其中每个执行线程的变量值可能不同。 但是,使用上下文变量,一个执行线程中可能有多个上下文。 上下文变量的主要用例是跟踪并发异步任务中的变量。
The following example constructs three contexts, each with their own value for the value name
. The greet()
function is later able to use the value of name
inside each context:
以下示例构造了三个上下文,每个上下文都有其自己的值name
值。 greet()
函数以后可以在每个上下文中使用name
的值:
Running this script greets Steve, Dina, and Harry in reverse order:
运行此脚本以相反的顺序向Steve,Dina和Harry致意:
$ python3.7 context_demo.py
$ python3.7 context_demo.py
Hello Harry
Hello Harry
Hello Dina
Hello Dina
Hello Steve
Hello Steve
importlib.resources
”导入数据文件 (Importing Data Files With “importlib.resources
“)One challenge when packaging a Python project is deciding what to do with project resources like data files needed by the project. A few options have commonly been used:
打包Python项目时的一个挑战是决定如何处理项目资源,例如项目所需的数据文件。 通常使用一些选项:
__file__
.setuptools.pkg_resources
to access the data file resource.__file__
定位。 setuptools.pkg_resources
访问数据文件资源。 Each of these have their shortcomings. The first option is not portable. Using __file__
is more portable, but if the Python project is installed it might end up inside a zip and not have a __file__
attribute. The third option solves this problem, but is unfortunately very slow.
这些都有各自的缺点。 第一种选择是不可移植的。 使用__file__
更具可移植性,但是如果安装了Python项目,则它可能最终位于zip内并且没有__file__
属性。 第三种选择解决了这个问题,但是很慢。
A better solution is the new importlib.resources
module in the standard library. It uses Python’s existing import functionality to also import data files. Assume you have a resource inside a Python package like this:
更好的解决方案是标准库中新的importlib.resources
模块。 它使用Python现有的导入功能来导入数据文件。 假设您在Python包中有这样的资源:
Note that data
needs to be a Python package. That is, the directory needs to contain an __init__.py
file (which may be empty). You can then read the alice_in_wonderland.txt
file as follows:
请注意, data
必须是Python包 。 也就是说,该目录需要包含__init__.py
文件(该文件可能为空)。 然后,您可以阅读alice_in_wonderland.txt
文件,如下所示:
>>> >>> from from importlib importlib import import resources
resources
>>> >>> with with resourcesresources .. open_textopen_text (( "data""data" , , "alice_in_wonderland.txt""alice_in_wonderland.txt" ) ) as as fidfid :
:
... ... alice alice = = fidfid .. readlinesreadlines ()
()
...
...
>>> >>> printprint (( """" .. joinjoin (( alicealice [:[: 77 ]))
]))
CHAPTER I. Down the Rabbit-Hole
CHAPTER I. Down the Rabbit-Hole
Alice was beginning to get very tired of sitting by her sister on the
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
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
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
it, ‘and what is the use of a book,’ thought Alice ‘without pictures or
conversations?’
conversations?’
A similar resources.open_binary()
function is available for opening files in binary mode. In the earlier “plugins as module attributes” example, we used importlib.resources
to discover the available plugins using resources.contents()
. See Barry Warsaw’s PyCon 2018 talk for more information.
类似的resources.open_binary()
函数可用于以二进制模式打开文件。 在前面的“作为模块属性的插件”示例中 ,我们使用importlib.resources
使用resources.contents()
发现可用的插件。 有关更多信息,请参见Barry Warsaw的PyCon 2018演讲 。
It is possible to use importlib.resources
in Python 2.7 and Python 3.4+ through a backport. A guide on migrating from pkg_resources
to importlib.resources
is available.
通过backport可以在Python 2.7和Python 3.4+中使用importlib.resources
。 一个从迁移指南pkg_resources
到importlib.resources
可用。
Python 3.7 has added several features aimed at you as a developer. You have already seen the new breakpoint()
built-in. In addition, a few new -X
command line options have been added to the Python interpreter.
Python 3.7添加了一些针对您作为开发人员的功能。 您已经看到了内置的新breakpoint()
。 另外,一些新的-X
命令行选项已添加到Python解释器中。
You can easily get an idea of how much time the imports in your script takes, using -X importtime
:
您可以使用-X importtime
轻松了解脚本中的导入需要花费多少时间:
The cumulative
column shows the cumulative time of import (in microseconds). In this example, importing plugins
took about 0.03 seconds, most of which was spent importing importlib.resources
. The self
column shows the import time excluding nested imports.
cumulative
列显示了累积的导入时间(以微秒为单位)。 在此示例中,导入plugins
花费了大约0.03秒,其中大部分时间用于导入importlib.resources
。 self
列显示导入时间,不包括嵌套导入。
You can now use -X dev
to activate “development mode.” The development mode will add certain debug features and runtime checks that are considered too slow to be enabled by default. These include enabling faulthandler
to show a traceback on serious crashes, as well as more warnings and debug hooks.
现在,您可以使用-X dev
激活“开发模式”。 开发模式将添加某些调试功能和运行时检查,这些功能和运行时检查被认为太慢而无法默认启用。 其中包括使faulthandler
在严重崩溃时显示回溯,以及更多警告和调试挂钩。
Finally, -X utf8
enables UTF-8 mode. (See PEP 540.) In this mode, UTF-8
will be used for text encoding regardless of the current locale.
最后, -X utf8
启用UTF-8模式 。 (请参阅PEP540 。)在此模式下,无论当前语言环境如何, UTF-8
都将用于文本编码。
Each new release of Python comes with a set of optimizations. In Python 3.7, there are some significant speed-ups, including:
Python的每个新发行版都有一组优化。 在Python 3.7中,有一些显着的提速,包括:
typing
is 7 times faster.typing
速度快7倍。 In addition, many more specialized optimizations are included. See this list for a detailed overview.
此外,还包括许多更专业的优化。 请参阅此列表以获取详细概述。
The upshot of all these optimizations is that Python 3.7 is fast. It is simply the fastest version of CPython released so far.
所有这些优化的结果是Python 3.7速度很快 。 它只是到目前为止发布的最快的CPython版本 。
Let’s start with the simple answer. If you want to try out any of the new features you have seen here, then you do need to be able to use Python 3.7. Using tools such as pyenv
or Anaconda makes it easy to have several versions of Python installed side by side. There is no downside to installing Python 3.7 and trying it out.
让我们从简单的答案开始。 如果您想尝试这里看到的任何新功能,那么您确实需要能够使用Python 3.7。 使用pyenv
或Anaconda之类的工具可以轻松地并排安装多个版本的Python。 安装Python 3.7并尝试它没有任何缺点。
Now, for the more complicated questions. Should you upgrade your production environment to Python 3.7? Should you make your own project dependent on Python 3.7 to take advantage of the new features?
现在,对于更复杂的问题。 您是否应该将生产环境升级到Python 3.7? 您是否应该使自己的项目依赖于Python 3.7来利用这些新功能?
With the obvious caveat that you should always do thorough testing before upgrading your production environment, there are very few things in Python 3.7 that will break earlier code (async
and await
becoming keywords is one example though). If you are already using a modern Python, upgrading to 3.7 should be quite smooth. If you want to be a little conservative, you might want to wait for the release of the first maintenance release—Python 3.7.1—tentatively expected some time in July 2018.
显而易见的警告是,在升级生产环境之前,应始终进行全面的测试,Python 3.7中很少有东西会破坏早期的代码( async
和await
成为关键字是一个示例)。 如果您已经在使用现代Python,则升级到3.7应该很顺利。 如果您想稍微保守一些,则可能要等待第一个维护版本(Python 3.7.1)的发布, 暂定在2018年7月的某个时候发布。
Arguing that you should make your project 3.7 only is harder. Many of the new features in Python 3.7 are either available as backports to Python 3.6 (data classes, importlib.resources
) or conveniences (faster startup and method calls, easier debugging, and -X
options). The latter, you can take advantage of by running Python 3.7 yourself while keeping your code compatible with Python 3.6 (or lower).
认为只应使项目3.7困难。 Python 3.7中的许多新功能都可以作为Python 3.6的importlib.resources
移植(数据类, importlib.resources
)或便利性(更快的启动和方法调用,更容易的调试以及-X
选项)。 后者,您可以通过自己运行Python 3.7来利用,同时保持您的代码与Python 3.6(或更低版本)兼容。
The big features that will lock your code to Python 3.7 are __getattr__()
on modules, forward references in type hints, and the nanosecond time
functions. If you really need any of these, you should go ahead and bump your requirements. Otherwise, your project will probably be more useful to others if it can be run on Python 3.6 for a while longer.
将代码锁定到Python 3.7的主要功能是模块上的__getattr__()
,类型提示中的正向引用以及纳秒级time
函数 。 如果您确实需要其中任何一个,则应继续改进您的要求。 否则,如果可以在Python 3.6上运行更长的时间,您的项目可能会对其他人更有用。
See the Porting to Python 3.7 guide for details to be aware of when upgrading.
有关升级时要注意的详细信息,请参见《 移植到Python 3.7指南 》。
翻译自: https://www.pybloggers.com/2018/06/cool-new-features-in-python-3-7/