你应该知道的Python3.6、3.7、3.8新特性小结
很多人在学习了基本的Python语言知识后,就转入应用阶段了,后期很少对语言本身的新变化、新内容进行跟踪学习和知识更新,甚至连已经发布了好几年的Python3.6的新特性都缺乏了解。
本文列举了Python3.6、3.7、3.8三个版本的新特性,学习它们有助于提高对Python的了解,跟上最新的潮流。
** 一、Python3.6新特性 **
1、新的格式化字符串方式
新的格式化字符串方式,即在普通字符串前添加 f 或 F 前缀,其效果类似于str.format()。比如
name = "red"
print(f"He said his name is {name}.")
# 'He said his name is red.'
相当于:
print("He said his name is {name}.".format(**locals()))
此外,此特性还支持嵌套字段,比如:
import decimal
width = 10
precision = 4
value = decimal.Decimal("12.34567")
print(f"result: {value:{width}.{precision}}")
#'result: 12.35'
2、变量声明语法
可以像下面一样声明一个变量并指定类型:
from typing import List, Dict
primes: List[int] = []
captain: str # 此时没有初始值
class Starship:
stats: Dict[str, int] = {}
3、数字的下划线写法
允许在数字中使用下划线,以提高多位数字的可读性。
a = 1_000_000_000_000_000 # 1000000000000000
b = 0x_FF_FF_FF_FF # 4294967295
除此之外,字符串格式化也支持_选项,以打印出更易读的数字字符串:
'{:_}'.format(1000000) # '1_000_000'
'{:_x}'.format(0xFFFFFFFF) # 'ffff_ffff'
4、异步生成器
在Python3.5中,引入了新的语法 async 和 await 来实现协同程序。但是有个限制,不能在同一个函数体内同时使用 yield 和
await。Python3.6中,这个限制被放开了,允许定义异步生成器:
async def ticker(delay, to):
"""Yield numbers from 0 to *to* every *delay* seconds."""
for i in range(to):
yield i
await asyncio.sleep(delay)
5、异步解析器
允许在列表list、集合set 和字典dict 解析器中使用 async 或 await 语法。
result = [i async for i in aiter() if i % 2]
result = [await fun() for fun in funcs if await condition()]
6、新增加模块
标准库(The Standard Library)中增加了一个新的模块:secrets。该模块用来生成一些安全性更高的随机数,用于管理passwords,
account authentication, security tokens, 以及related secrets等数据。
7、其他新特性
- 新的 PYTHONMALLOC 环境变量允许开发者设置内存分配器,以及注册debug钩子等。
- asyncio模块更加稳定、高效,并且不再是临时模块,其中的API也都是稳定版的了。
- typing模块也有了一定改进,并且不再是临时模块。
- datetime.strftime 和 date.strftime 开始支持ISO 8601的时间标识符%G, %u, %V。
- hashlib 和 ssl 模块开始支持OpenSSL1.1.0。
- hashlib模块开始支持新的hash算法,比如BLAKE2, SHA-3 和 SHAKE。
- Windows上的 filesystem 和 console 默认编码改为UTF-8。
- json模块中的 json.load() 和 json.loads() 函数开始支持 binary 类型输入。
更多内容参考官方文档: [ What's New In Python 3.6
](https://docs.python.org/3.6/whatsnew/3.6.html)
二、Python3.7新特性
Python 3.7于2018年6月27日发布,
包含许多新特性和优化,增添了众多新的类,可用于数据处理、针对脚本编译和垃圾收集的优化以及更快的异步I/O,主要如下:
- 用类处理数据时减少样板代码的数据类。
- 一处可能无法向后兼容的变更涉及处理生成器中的异常。
- 面向解释器的“开发模式”。
- 具有纳秒分辨率的时间对象。
- 环境中默认使用UTF-8编码的UTF-8模式。
- 触发调试器的一个新的内置函数。
1、新增内置函数breakpoint()
使用该内置函数,相当于通过代码的方式设置了断点,会自动进入Pbd调试模式。
如果在环境变量中设置PYTHONBREAKPOINT=0会忽略此函数。并且,pdb 只是众多可用调试器之一,你可以通过设置新的
PYTHONBREAKPOINT 环境变量来配置想要使用的调试器。
下面有一个简单例子,用户需要输入一个数字,判断它是否和目标数字一样:
"""猜数字游戏"""
def guess(target):
user_guess = input("请输入你猜的数 >>> ")
if user_guess == target:
return "你猜对了!"
else:
return "猜错了"
if __name__ == '__main__':
a = 100
print(guess(a))
不幸的是,即使猜的数和目标数一样,打印的结果也是‘猜错了',并且没有任何异常或错误信息。
为了弄清楚发生了什么,我们可以插入一个断点,来调试一下。以往一般通过print大法或者IDE的调试工具,但现在我们可以使用 breakpoint()。
"""猜数字游戏"""
def guess(target):
user_guess = input("请输入你猜的数 >>> ")
breakpoint() //加入这一行
if user_guess == target:
return "你猜对了!"
else:
return "猜错了"
if __name__ == '__main__':
a = 100
print(guess(a))
在 pdb 提示符下,我们可以调用 locals() 来查看当前的本地作用域的所有变量。(pdb 有大量的命令,你也可以在其中运行正常的Python 语句)
请输入你猜的数 >>> 100
> d:\work\for_test\py3_test\test.py(7)guess()
-> if user_guess == target:
(Pdb) locals()
{'target': 100, 'user_guess': '100'}
(Pdb) type(user_guess)
<class 'str'>
搞明白了,target是一个整数,而user_guess 是一个字符串,这里发生了类型对比错误。
2、类型和注解
从 Python 3.5 开始,类型注解就越来越受欢迎。对于那些不熟悉类型提示的人来说,这是一种完全可选的注释代码的方式,以指定变量的类型。
什么是注解?它们是关联元数据与变量的语法支持,可以是任意表达式,在运行时被 Python 计算但被忽略。注解可以是任何有效的 Python 表达式。
下面是个对比的例子:
# 不带类型注解
def foo(bar, baz):
# 带类型注解
def foo(bar: 'Describe the bar', baz: print('random')) -> 'return thingy':
上面的做法,其实是Python对自身弱类型语言的强化,希望获得一定的类型可靠和健壮度,向Java等语言靠拢。
在 Python 3.5 中,注解的语法获得标准化,此后,Python 社区广泛使用了注解类型提示。
但是,注解仅仅是一种开发工具,可以使用 PyCharm 等 IDE 或 Mypy 等第三方工具进行检查,并不是语法层面的限制。
我们前面的猜数程序如果添加类型注解,它应该是这样的:
"""猜数字游戏"""
def guess(target:str):
user_guess:str = input("请输入你猜的数 >>> ")
breakpoint()
if user_guess == target:
return "你猜对了!"
else:
return "猜错了"
if __name__ == '__main__':
a:int = 100
print(guess(a))
PyCharm会给我们灰色的规范错误提醒,但不会给红色的语法错误提示。
用注解作为类型提示时,有两个主要问题:启动性能和前向引用。
- 在定义时计算大量任意表达式相当影响启动性能,而且 typing 模块非常慢
- 你不能用尚未声明的类型来注解
typing 模块如此缓慢的部分原因是,最初的设计目标是在不修改核心 CPython 解释器的情况下实现 typing
模块。随着类型提示变得越来越流行,这一限制已经被移除,这意味着现在有了对 typing 的核心支持。
而对于向前引用,看下面的例子:
class User:
def __init__(self, name: str, prev_user: User) -> None:
pass
错误在于 User类型还没有被声明,此时的 prev_user 不能定义为 User 类型。
为了解决这个问题,Python3.7 将注解的评估进行了推迟。并且,这项改动向后不兼容,需要先导入annotations,只有到Python
4.0后才会成为默认行为。
from __future__ import annotations
class User:
def __init__(self, name: str, prev_user: User) -> None:
pass
或者如下面的例子:
class C:
def validate_b(self, obj: B) -> bool:
...
class B:
...
3、新增dataclasses模块
这个特性可能是 Python3.7以后比较常用的,它有什么作用呢?
假如我们需要编写一个下面的类:
from datetime import datetime
import dateutil
class Article(object):
def __init__(self, _id, author_id, title, text, tags=None,
created=datetime.now(), edited=datetime.now()):
self._id = _id
self.author_id = author_id
self.title = title
self.text = text
self.tags = list() if tags is None else tags
self.created = created
self.edited = edited
if type(self.created) is str:
self.created = dateutil.parser.parse(self.created)
if type(self.edited) is str:
self.edited = dateutil.parser.parse(self.edited)
def __eq__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return (self._id, self.author_id) == (other._id, other.author_id)
def __lt__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return (self._id, self.author_id) < (other._id, other.author_id)
def __repr__(self):
return '{}(id={}, author_id={}, title={})'.format(
self.__class__.__name__, self._id, self.author_id, self.title)
大量的初始化属性要定义默认值,可能还需要重写一堆魔法方法,来实现类实例的打印、比较、排序和去重等功能。
如果使用dataclasses进行改造,可以写成这个样子:
from dataclasses import dataclass, field
from typing import List
from datetime import datetime
import dateutil
@dataclass(order=True) //注意这里
class Article(object):
_id: int
author_id: int
title: str = field(compare=False)
text: str = field(repr=False, compare=False)
tags: List[str] = field(default=list(), repr=False, compare=False)
created: datetime = field(default=datetime.now(), repr=False, compare=False)
edited: datetime = field(default=datetime.now(), repr=False, compare=False)
def __post_init__(self):
if type(self.created) is str:
self.created = dateutil.parser.parse(self.created)
if type(self.edited) is str:
self.edited = dateutil.parser.parse(self.edited)
这使得类不仅容易设置,而且当我们创建一个实例并打印出来时,它还可以自动生成优美的字符串。在与其他类实例进行比较时,它也会有适当的行为。这是因为dataclasses除了帮我们自动生成
init 方法外,还生成了一些其他特殊方法,如 repr、eq 和 hash 等。
Dataclasses 使用字段 field来完提供默认值,手动构造一个 field() 函数能够访问其他选项,从而更改默认值。例如,这里将 field
中的 default_factory 设置为一个 lambda 函数,该函数提示用户输入其名称。
from dataclasses import dataclass, field
class User:
name: str = field(default_factory=lambda: input("enter name"))
4、生成器异常处理
在Python
3.7中,生成器引发StopIteration异常后,StopIteration异常将被转换成RuntimeError异常,那样它不会悄悄一路影响应用程序的堆栈框架。这意味着如何处理生成器的行为方面不太敏锐的一些程序会在Python
3.7中抛出RuntimeError。在Python 3.6中,这种行为生成一个弃用警告;在Python 3.7中,它将生成一个完整的错误。