刚才看到 Learn Python the Hard Way 第四版都开始使用 Python 3.6😝,。想起当时某些人还吐槽我的书竟然是使用 Python 2 的,好吧,我也来列一下 Python 3.6 中非常值得一提的变化(最详细的当然还是看官方的 What’s New )。

Literal String Interpolation

新增了 格式化字符串变量 语法,通过字符串的前缀 f,实现类似于 Scala/Swift 等语言的字符串插值:

>>> name = 'Fred'
>>> f'My name is {name}'
'My name is Fred'
>>> date = datetime.datetime.now().date()
>>> f'{date} was on a {date:%A}'
'2017-02-25 was on a Saturday'
>>> f'{"quoted string"}'
'quoted string'
>>> def foo():
...     return 20
...
>>> f'result={foo()}'
'result=20'

Asynchronous Generators

新增 异步生成器 语法,可以直接在协程中用生成器:

async def coro():
    for item in range(10):
        yield item

async def main():
    async for item in coro():
        ...

Async comprehensions

新增 异步解析 语法:

result = [await fun() for fun in funcs]
result = {await fun() for fun in funcs}
result = {fun: await fun() for fun in funcs}
result = [func async for fun in funcs]
result = {func async for fun in funcs}
result = {func async for fun in funcs}

Variable annotations

在 Python 3.5 的时候已经添加了 类型标注 语法:

primes = []  # type: List[int]
primes = 1

不过得通过 mypy 做类型检查:

❯ mypy prime.py
prime.py:2: error: Incompatible types in assignment (expression has type "int", variable has type List[int])

现在又新增 给变量添加注释 语法:

>>> from typing import List
>>> primes: List[int] = []
>>> __annotations__
{'primes': typing.List[int]}

Underscores in Numeric Literals

新增 数字变量使用下划线 语法:

>>> '{:_}'.format(1000000)
'1_000_000'
>>> 10_000_000.0
10000000.0

新模块 secrets

用来以简化使用于管理密码,比如账号认证,令牌等的密码的随机数的生成:

>>> import secrets
>>> 'https://mydomain.com/reset=' + secrets.token_urlsafe()
'https://mydomain.com/reset=3-dr2ZQLSDO1lOOgEG4Pd518iwNS-FuNNTptzYdI7VA'

重新实现了 dict。

之前的大量实验证实,Python 3 一直比较慢,这也是我一直观望的原因。目前看,Python 2.7 还是最快的 Python 解释器。官方一直致力于速度的提升,所以 Python 3.4< Python 3.5 < Python 3.6。

所以,3.6 中参考 PyPy 重新实现了字典 dict,使其更加紧凑。此次重新实现的 dict 比 Python3.5 中的字典内存使用减少了 20%-25%。

PS: 看目前正在开发的 3.7, 也会有一些显著的改善。

定制类的创建使用新协议进行了简化

Simpler customisation of class creation 提供了一种可以在不使用元类的情况下自定义子类的方法。每当创建一个新的子类时,新的__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

>>> PluginBase.subclasses
[__main__.Plugin1, __main__.Plugin2]

这样让自定义类的变得更简单了。

描述符协议增强

描述符是一个具有绑定行为的对象属性,由于它的优先级高会改变一个属性的基本的获取、设置和删除方式,我们先看一个例子:

class Integer(object):
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, owner):
       return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if value < 0:
            raise ValueError('Negative value not allowed')
        instance.__dict__[self.name] = value


class Movie(object):
    score = Integer('score')
    amount = Integer('amount')


movie = Movie()
movie.score = 9
print(movie.score)

相当于把 score 和 amount 这个 2 个属性都绑定上 Integer 的对象上了,结果会是:

❯ python3 movie.py
9

上面的用法有个问题,就是初始化的时候都明确让属性的值绑定在 Integer 上的 name 属性上,而无法获知所有者类的属性名。使用在 PEP487 上提供的可选的__set_name__() 可以获得这个属性名字,并且可以自定义这部分内容:

class Integer(object):
    def __get__(self, instance, owner):
       return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if value < 0:
            raise ValueError('Negative value not allowed')
        instance.__dict__[self.name] = value

    def __set_name__(self, owner, name):
        self.name = name


class Movie(object):
    score = Integer()
    amount = Integer()


movie = Movie()
movie.score = 9
print(movie.score)

Preserving Class Attribute Definition Order

我们知道 Python 2 中 dict 是不能保存键值顺序的:

 python2
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> d
{'a': 1, 'c': 3, 'b': 2}

现在则会 保存类属性定义顺序 。也就是按照源码中属性名出现的顺序存储在\\dict__的属性中。

而且我发现 dict 的实现也保留了顺序:

 python3
Python 3.6.0 (default, Dec 24 2016, 00:01:50)
...
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> d
{'a': 1, 'b': 2, 'c': 3}
>>>

看来 OrderdDict 要失业了~

Preserving Keyword Argument Order

现在也会 保存关键字参数顺序 了:

>>> def test(**kw):
...     print(kw)
...
>>> test(a=1, b=2, c=3)
{'a': 1, 'b': 2, 'c': 3}

asyncio 可用于生产环境

asyncio 模板添加了很多新的功能、重要的可用性、性能改进以及大量的 bug,现在 asyncio 模块的 API 已经很稳定,可用于生产环境了。其中:

  1. 有了一个更快的 asyncio.Future 的 C 的实现。
  2. 有了一个更快的 asyncio.Task 的 C 的实现。

使用这 2 个实现可以让效率提高 15% 左右。而使用第三方的 uvloop 还能让速度提升 5%-10%。

re 模块

  1. 在正则表达式中,增加对 spans 修饰符的支持。示例: '(?i:p) ython' 匹配 'python' 和 'Python', 但不匹配 'PYTHON'; '(?i) g (?-i:v) r' 匹配 'GvR' 和 'gvr', 但不匹配 'GVR'。
  2. 匹配对象组可通过__getitem__访问,它就等价于 group ()。因此, 现在 mo ['name'] 就等价于 mo.group ('name')。
  3. Match 对象支持 index-like 对象一样的组索引。

glob 优化

通过 os.scandir 对 glob 模块中的 glob () 及 iglob () 进行优化,使得它们现在大概快了 3-6 倍。😔 唉,我当时咋没想到呢。有兴趣的可以看 Issue 25596 。如果你正常也有这种目录扫描的需求,请参看实现。

pickle 优化

当对大量小对象进行反序列化时,pickle.load () 和 pickle.loads () 的速度可提高 10%。