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
送人玫瑰,手有余香!