Python 语言旨在使复杂任务变得简单,所以更新迭代的速度比较快,需要我们紧跟其步伐!
新版本的发布,总是会伴随着新特性和新功能的产生,我们在升级版本之前首先就是需要了解和属性这些要点,才可能会在之后的编程中灵活的使用到。迫不及待,蓄势待发,那现在就让我们开始吧!

1. PEP 498
新的语法特性:格式化的字符串文字
新的格式化字符串方式,即在普通字符串前添加 f 或 F 前缀,其效果类似于 str.format()。
# 基本使用方式
>>> name = "Fred"
>>> f"He said his name is {name}."
'He said his name is Fred.'
# 支持嵌套字段
>>> width = 10
>>> precision = 4
>>> value = decimal.Decimal("12.34567")
>>> f"result: {value:{width}.{precision}}"
'result: 12.35'
# 支持变量属性
>>> from datetime import *
>>> date = datetime.now().date()
>>> f'{date} was on a {date:%A}'
'2020-06-12 was on a Friday'
2. PEP 515
新的语法特性:数字文字中的下划线
允许在数字中使用下划线,以提高多位数字的可读性。
>>> 1_000_000_000_000_000
1000000000000000
>>> 0x_FF_FF_FF_FF
4294967295
除此之外,字符串格式化也支持 _ 选项,以打印出更易读的数字字符串。
>>> '{:_}'.format(1000000)
'1_000_000'
>>> '{:_x}'.format(0xFFFFFFFF)
'ffff_ffff'
3. PEP 526
新的语法特性:变量注释的语法
在 Python3.7 中,才开始真正开始变的可用。
primes: List[int] = []
captain: str
class Starship:
stats: Dict[str, int] = {}
4. PEP 525
新的语法特性:异步生成器
在 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)
async def coro():
for item in range(10):
yield item
async def main():
async for item in coro():
...
5. PEP 530
新的语法特性:异步推导
允许在列表(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. PEP 506
新的库模块:Secret 模块被加入 Python 标准库
标准库中增加的 secrets 模块,用来生成一些安全性更高的随机数,便于管理密码、tokens、认证等数据。
>>> import secrets
>>> 'https://www.escapelife.site/reset=' + secrets.token_urlsafe()
https://www.escapelife.site/reset=OWitz10VOR_8Y84FYsDjGYQjGVrNruGuRTIxRYm2a7E
7. PEP 487
标准库中的重大改进:定制类的创建使用新协议进行了简化
在 PEP 487 中提供了一种可以在不使用元类的情况下自定义子类的方法,即每当创建一个新的子类时,新的 __init_subclass__ 类方法会被调用,这样自定义类就变得简单多了。
class PluginBase:
subclasses = []
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
cls.subclasses.append(cls)
class Plugin1(PluginBase):
pass
class Plugin2(PluginBase):
pass
标准库中的重大改进:描述符协议增强
描述符是一个具有绑定行为的对象属性,由于它的优先级高会改变一个属性的基本的获取、设置和删除方式。下面这个示例,相当于把 score 和 amount 这 2 个属性都绑定上 Integer 的对象上了。
# class_test.py
class IntField:
def __init__(self, name):
self.name = name
def __get__(self, instance, owner):
print('__get__', instance, owner)
return instance.__dict__[self.name]
def __set__(self, instance, value):
print('__set__', instance, value)
if not isinstance(value, int):
raise ValueError(f'expecting integer in {self.name}')
instance.__dict__[self.name] = value
class Model:
score = IntField('score')
amount = IntField('amount')
movie = Model()
print('-------------------------------')
movie.score = 9
print(movie.score)
❯ python3 class_test.py
-------------------------------
__set__ <__main__.Model object at 0x106f637f0> 9
__get__ <__main__.Model object at 0x106f637f0> <class '__main__.Model'>
9
上面的用法有个问题,就是初始化的时候都明确让属性的值绑定在 Integer 上的 name 属性上,而无法获知所有者类的属性名。使用在 PEP487 上提供的可选的 __set_name__() 可以获得这个属性名字,并且可以自定义这部分内容。即我们可以看到,添加了一个名字和属性名一样。
# class_test.py
class IntField:
def __get__(self, instance, owner):
print('__get__', instance, owner)
return instance.__dict__[self.name]
def __set__(self, instance, value):
print('__set__', instance, value)
if not isinstance(value, int):
raise ValueError(f'expecting integer in {self.name}')
instance.__dict__[self.name] = value
# this is the new initializer:
def __set_name__(self, owner, name):
print(f'> __set_name__: name is {name}')
self.name = name
class Model:
score = IntField()
amount = IntField()
movie = Model()
print('-------------------------------')
movie.score = 9
print(movie.score)
❯ python3 class_test.py
> __set_name__: name is score
> __set_name__: name is amount
-------------------------------
__set__ <__main__.Model object at 0x10de77790> 9
__get__ <__main__.Model object at 0x10de77790> <class '__main__.Model'>
9
描述符指的是实现了描述符协议的特殊的类,三个描述符协议分别指的是 __get__、__set__、__delete__ 以及在 Python 3.6 中新增的 __set_name__ 方法。
- 非数据描述符(
Non-Data descriptor) => 只读- 只实现了
__get__()方法
- 只实现了
- 数据描述符(
Data descriptors) => 可读写- 实现了
__get__()以及__set__()、__delete__()和__set_name__()方法
- 实现了
那么,它们有什么区别呢?我们如果调用一个属性,那么其顺序是优先从实例的 __dict__ 里查找,如果没有查找到的话,那么依次查询类字典,父类字典,直到彻底查不到为止。但是,这里没有考虑描述符的因素进去。如果将描述符因素考虑进去,那么正确的表述应该是我们如果调用一个属性,那么其顺序是优先从实例的 __dict__ 里查找,如果没有查找到的话,那么依次查询类字典,父类字典,直到彻底查不到为止。
其中,如果在类实例字典中的该属性是一个 Data descriptors 的话,那么无论实例字典中是否存在该属性,都会无条件的走描述符协议进行调用;如果在类实例字典中的该属性是一个 Non-Data descriptors 的话,那么优先调用实例字典中的属性值而不触发描述符协议,如果实例字典中不存在该属性值的话,那么才会触发 Non-Data descriptor 的描述符协议。
那么现在我们来回答一下上面提到的那个问题,我们即使在 __set__ 将具体的属性写入实例字典中,但是由于类字典中存在着 Data descriptors,因此,我们在调用 name 属性时,依旧会触发描述符协议。
8. PEP 519
标准库中的重大改进:添加文件系统路径协议
pathlib 模块是 Python3 新增的模块,可以让我们更方便的处理路径相关的工作。
>>> import pathlib
>>> with open(pathlib.Path("README")) as f:
... contents = f.read()
...
>>> import os.path
>>> os.path.splitext(pathlib.Path("some_file.txt"))
('some_file', '.txt')
>>> os.path.join("/a/b", pathlib.Path("c"))
'/a/b/c'
>>> import os
>>> os.fspath(pathlib.Path("some_file.txt"))
'some_file.txt'
In [1]: from pathlib import Path
In [2]: Path.home()
Out[2]: PosixPath('/Users/escape')
In [3]: path = Path('/user')
In [4]: path/'local'
Out[4]: PosixPath('/user/local')
In [5]: str(path/'local')
Out[5]: '/user/local'
In [6]: p = Path('touched')
In [7]: p.exists()
Out[7]: False
In [8]: p.with_suffix('.jpg')
Out[8]: PosixPath('touched.jpg')
In [9]: p.is_dir()
Out[9]: False
In [10]: p.joinpath('a', 'b')
Out[10]: PosixPath('touched/a/b')
9. PEP 495
标准库中的重大改进:消除本地时间的歧义
>>> u0 = datetime(2016, 11, 6, 4, tzinfo=timezone.utc)
>>> for i in range(4):
... u = u0 + i*HOUR
... t = u.astimezone(Eastern)
... print(u.time(), 'UTC =', t.time(), t.tzname(), t.fold)
...
04:00:00 UTC = 00:00:00 EDT 0
05:00:00 UTC = 01:00:00 EDT 0
06:00:00 UTC = 01:00:00 EST 1
07:00:00 UTC = 02:00:00 EST 0
10. Note
主要记录语法等的变更新和说明
- [1] CPython 实现的改进
新的 PYTHONMALLOC 环境变量允许开发者设置内存分配器,以及注册 debug 钩子等。
- [2] 标准库中的重大改进
dict 能保存键值顺序,OrderdDict 要失业了。typing 模块也有了一定改进,并且不再是临时模块。asyncio 模块更加稳定、高效,并且不再是临时模块,其中的 API 也都是稳定版的了。json 模块中的 json.load() 和 json.loads() 函数开始支持 binary 类型输入。
当对大量小对象进行反序列化时,pickle.load() 和 pickle.loads() 的速度可提高 10%。
通过 os.scandir 对 glob 模块中的 glob() 及 iglob() 进行优化,使得它们现在大概快了 3-6 倍。
# 在Python2中不能直接通配递归的目录(只能这样)
found_images = glob.glob('/path/*.jpg') \
+ glob.glob('/path/*/*.jpg') \
+ glob.glob('/path/*/*/*.jpg') \
+ glob.glob('/path/*/*/*/*.jpg') \
+ glob.glob('/path/*/*/*/*/*.jpg')
# 在Python3中写法要清爽的多
found_images = glob.glob('/path/**/*.jpg', recursive=True)
# 更好的用法是使用pathlib库
found_images = pathlib.Path('/path/').glob('**/*.jpg')
- [3] 安全改进
hashlib 和 ssl 模块开始支持 OpenSSL1.1.0。hashlib 模块开始支持新的 hash 算法,比如 BLAKE2, SHA-3 和 SHAKE。
11. Links
送人玫瑰,手有余香!