你应该知道的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中,它将生成一个完整的错误。

在这里插入图片描述

posted @ 2021-06-16 16:27  老酱  阅读(504)  评论(0编辑  收藏  举报