Python3.6有什么新变化


Python 语言旨在使复杂任务变得简单,所以更新迭代的速度比较快,需要我们紧跟其步伐!

新版本的发布,总是会伴随着新特性和新功能的产生,我们在升级版本之前首先就是需要了解和属性这些要点,才可能会在之后的编程中灵活的使用到。迫不及待,蓄势待发,那现在就让我们开始吧!

Python3.6于2016年12月23日发布


1. PEP 498

新的语法特性:格式化的字符串文字

新的格式化字符串方式,即在普通字符串前添加 fF 前缀,其效果类似于 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 中,引入了新的语法 asyncawait 来实现协同程序。但是有个限制,不能在同一个函数体内同时使用 yieldawait。但在 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) 解析器中使用 asyncawait 语法。

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

标准库中的重大改进:描述符协议增强

描述符是一个具有绑定行为的对象属性,由于它的优先级高会改变一个属性的基本的获取、设置和删除方式。下面这个示例,相当于把 scoreamount2 个属性都绑定上 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.scandirglob 模块中的 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] 安全改进

hashlibssl 模块开始支持 OpenSSL1.1.0
hashlib 模块开始支持新的 hash 算法,比如 BLAKE2, SHA-3SHAKE


送人玫瑰,手有余香!


文章作者: Escape
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Escape !
  目录