Python 3.10 有什么新变化 - 新的特性

新的特性

带圆括号的上下文管理器

现在已支持使用外层圆括号来使多个上下文管理器可以连续多行地书写。 这允许将过长的上下文管理器集能够以与之前 import 语句类似的方式格式化为多行的形式。 例如,以下这些示例写法现在都是有效的:

with (CtxManager() as example):
    ...

with (
    CtxManager1(),
    CtxManager2()
):
    ...

with (CtxManager1() as example,
      CtxManager2()):
    ...

with (CtxManager1(),
      CtxManager2() as example):
    ...

with (
    CtxManager1() as example1,
    CtxManager2() as example2
):
    ...

在被包含的分组末尾过可以使用一个逗号作为结束:

with (
    CtxManager1() as example1,
    CtxManager2() as example2,
    CtxManager3() as example3,
):
    ...

更清楚的错误消息

SyntaxError

现在当解析包含有未关闭括号的代码时解释器会包括未关闭括号的位置而不是显示 SyntaxError: unexpected EOF while parsing 并指向某个不正确的位置。 例如,考虑以下代码(注意未关闭的 “ { ”):

expected = {9: 1, 18: 2, 19: 2, 27: 3, 28: 3, 29: 3, 36: 4, 37: 4,
            38: 4, 39: 4, 45: 5, 46: 5, 47: 5, 48: 5, 49: 5, 54: 6,
some_other_code = foo()

之前版本的解释器会报告令人迷惑的语法错误位置:

File "example.py", line 3
    some_other_code = foo()
                    ^

SyntaxError: invalid syntax

但在 Python 3.10 中则会发出信息量更多的错误提示:

File "example.py", line 1
    expected = {9: 1, 18: 2, 19: 2, 27: 3, 28: 3, 29: 3, 36: 4, 37: 4,
               ^

SyntaxError: '{' was never closed

类似地,涉及未关闭字符串字面值 (单重引号和三重引号) 的错误现在会指向字符串的开头而不是报告 EOF/EOL。

这些改进的灵感来自 PyPy 解释器之前所进行的工作。

(由 Pablo Galindo 在 bpo-42864 以及 Batuhan Taskaya 在 bpo-40176 中贡献。)

解释器所引发的 SyntaxError 异常现在将高亮构成语法错误本身的完整异常错误内容,而不是仅提示检测到问题的位置。 这样,不再(同 Python 3.10 之前那样)仅显示:

>>>
>>> foo(x, z for z in range(10), t, w)
  File "", line 1
    foo(x, z for z in range(10), t, w)
           ^
SyntaxError: Generator expression must be parenthesized

现在 Python 3.10 将这样显示异常:

>>>
>>> foo(x, z for z in range(10), t, w)
  File "", line 1
    foo(x, z for z in range(10), t, w)
           ^^^^^^^^^^^^^^^^^^^^
SyntaxError: Generator expression must be parenthesized

这个改进是由 Pablo Galindo 在 bpo-43914 中贡献的。

大量新增的专门化 SyntaxError 异常消息已被添加。 其中最主要的一些如下所示:

在代码块之前缺失 ::

>>>
>>> if rocket.position > event_horizon
  File "", line 1
    if rocket.position > event_horizon
                                      ^
SyntaxError: expected ':'

(Contributed by Pablo Galindo in bpo-42997.)

在推导式的目标中有不带圆括号的元组:

>>>
>>> {x,y for x,y in zip('abcd', '1234')}
  File "", line 1
    {x,y for x,y in zip('abcd', '1234')}
     ^
SyntaxError: did you forget parentheses around the comprehension target?

(Contributed by Pablo Galindo in bpo-43017.)

在多项集字面值中和表达式之间缺失逗号:

>>>
>>> items = {
... x: 1,
... y: 2
... z: 3,
  File "", line 3
    y: 2
       ^
SyntaxError: invalid syntax. Perhaps you forgot a comma?

(Contributed by Pablo Galindo in bpo-43822.)

多个异常类型不带圆括号:

>>>
>>> try:
...     build_dyson_sphere()
... except NotEnoughScienceError, NotEnoughResourcesError:
  File "", line 3
    except NotEnoughScienceError, NotEnoughResourcesError:
           ^
SyntaxError: multiple exception types must be parenthesized

(Contributed by Pablo Galindo in bpo-43149.)

字典字面值中缺失 : 和值:

>>>
>>> values = {
... x: 1,
... y: 2,
... z:
... }
  File "", line 4
    z:
     ^
SyntaxError: expression expected after dictionary key and ':'

>>> values = {x:1, y:2, z w:3}
  File "", line 1
    values = {x:1, y:2, z w:3}
                        ^
SyntaxError: ':' expected after dictionary key

(Contributed by Pablo Galindo in bpo-43823.)

try 代码块不带 except 或 finally 代码块:

>>>
>>> try:
...     x = 2
... something = 3
  File "", line 3
    something  = 3
    ^^^^^^^^^
SyntaxError: expected 'except' or 'finally' block

(Contributed by Pablo Galindo in bpo-44305.)

在比较中使用 = 而不是 ==:

>>>
>>> if rocket.position = event_horizon:
  File "", line 1
    if rocket.position = event_horizon:
                       ^
SyntaxError: cannot assign to attribute here. Maybe you meant '==' instead of '='?

(Contributed by Pablo Galindo in bpo-43797.)

在 f-字符串中使用 *:

>>>
>>> f"Black holes {*all_black_holes} and revelations"
  File "", line 1
    (*all_black_holes)
     ^
SyntaxError: f-string: cannot use starred expression here

(Contributed by Pablo Galindo in bpo-41064.)

IndentationError

许多 IndentationError 异常现在具有更多上下文来提示是何种代码块需要缩进,包括语句的位置:

>>>
>>> def foo():
...    if lel:
...    x = 2
  File "", line 3
    x = 2
    ^
IndentationError: expected an indented block after 'if' statement in line 2

AttributeError

当打印 AttributeError 时,PyErr_Display() 将提供引发异常的对象中类似属性名称的建议:

>>>
>>> collections.namedtoplo
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: module 'collections' has no attribute 'namedtoplo'. Did you mean: namedtuple?

(由 Pablo Galindo 在 bpo-38530 中贡献。)

警告 请注意如果未调用 PyErr_Display() 来显示错误则此特性将没有效果,这可能发生在使用了某些其他自定义错误显示函数的时候。 这在某些 REPL 例如 IPython 上是一种常见的情况。

NameError

当打印解释器所引发的 NameError 时,PyErr_Display() 将提供引发异常的函数中类似变量名称的建议:

>>>
>>> schwarzschild_black_hole = None
>>> schwarschild_black_hole
Traceback (most recent call last):
  File "", line 1, in 
NameError: name 'schwarschild_black_hole' is not defined. Did you mean: schwarzschild_bl

PEP 626:在调试和其他工具中使用精确的行号

PEP 626 带来了更精确可靠的行号用于调试、性能分析和测试工具。 所有被执行的代码行都会并且只有被执行的代码行才会生成带有正确行号的追踪事件。

帧对象的 f_lineno 属性将总是包含预期的行号。

代码对象的 co_lnotab 属性已被弃用并将在 3.12 中被移除。 需要从偏移量转换为行号的代码应改用新的 co_lines() 方法。

PEP 634:结构化模式匹配

增加了采用模式加上相应动作的 match 语句 和 case 语句 的形式的结构化模式匹配。 模式由序列、映射、基本数据类型以及类实例构成。 模式匹配使得程序能够从复杂的数据类型中提取信息、根据数据结构实现分支,并基于不同的数据形式应用特定的动作。

语法与操作

模式匹配的通用语法如下:

match subject:
    case :
        
    case :
        
    case :
        
    case _:
        

match 语句接受一个表达式并将其值与以一个或多个 case 语句块形式给出的一系列模式进行比较。 具体来说,模式匹配的操作如下:

1 使用具有特定类型和形状的数据 (subject)

2 针对 subject 在 match 语句中求值

3 从上到下对 subject 与 case 语句中的每个模式进行比较直到确认匹配到一个模式。

4 执行与被确认匹配的模式相关联的动作。

5 如果没有确认到一个完全的匹配,则如果提供了使用通配符 _ 的最后一个 case 语句,则它将被用作已匹配模式。 如果没有确认到一个完全的匹配并且不存在使用通配符的 case 语句,则整个 match 代码块不执行任何操作。

语句方法

读者可能是通过 C, Java 或 JavaScript (以及其他许多语言) 中的 switch 语句将一个目标 (数据对象) 与一个字面值 (模式) 进行匹配的简单例子了解到模式匹配的概念的。 switch 语句常常被用来将一个对象/表达式与包含在 case 语句中的字面值进行比较。

更强大的模式匹配例子可以在 Scala 和 Elixir 等语言中找到。 这种结构化模式匹配方式是“声明性”的并且会显式地为所要匹配的数据指定条件(模式)。

虽然使用嵌套的“if”语句的“命令性”系列指令可以被用来完成类似结构化模式匹配的效果,但它没有“声明性”方式那样清晰。 相反地,“声明性”方式指定了一个匹配所要满足的条件,并且通过其显式的模式使之更为易读。 虽然结构化模式匹配可以采取将一个变量与一个 case 语句中的字面值进行比较的最简单形式来使用,但它对于 Python 的真正价值在于其针对目标类型和形状的处理操作。

简单模式:匹配一个字面值

让我们把这个例子看作是模式匹配的最简单形式:一个值,即主词,被匹配到几个字面值,即模式。在下面的例子中,status 是匹配语句的主词。模式是每个 case 语句,字面值代表请求状态代码。匹配后,将执行与该 case 相关的动作:

def http_error(status):
    match status:
        case 400:
            return "Bad request"
        case 404:
            return "Not found"
        case 418:
            return "I'm a teapot"
        case _:
            return "Something's wrong with the internet"

如果传给上述函数的 status 为 418,则会返回 “I’m a teapot”。 如果传给上述函数的 status 为 500,则带有 _ 的 case 语句将作为通配符匹配,并会返回 “Something’s wrong with the internet”。 请注意最后一个代码块:变量名 _ 将作为 通配符 并确保目标将总是被匹配。 _ 的使用是可选的。

你可以使用 | (“ or ”)在一个模式中组合几个字面值:

case 401 | 403 | 404:
    return "Not allowed"

无通配符的行为

如果我们修改上面的例子,去掉最后一个 case 块,这个例子就变成:

def http_error(status):
    match status:
        case 400:
            return "Bad request"
        case 404:
            return "Not found"
        case 418:
            return "I'm a teapot"

如果不在 case 语句中使用 _,可能会出现不存在匹配的情况。如果不存在匹配,则行为是一个 no-op。例如,如果传入了值为 500 的 status ,就会发生 no-op。

带有字面值和变量的模式

模式可以看起来像解包形式,而且模式可以用来绑定变量。在这个例子中,一个数据点可以被解包为它的 x 坐标和 y 坐标:

# point is an (x, y) tuple
match point:
    case (0, 0):
        print("Origin")
    case (0, y):
        print(f"Y={y}")
    case (x, 0):
        print(f"X={x}")
    case (x, y):
        print(f"X={x}, Y={y}")
    case _:
        raise ValueError("Not a point")

第一个模式有两个字面值 (0, 0) ,可以看作是上面所示字面值模式的扩展。接下来的两个模式结合了一个字面值和一个变量,而变量 绑定 了一个来自主词的值(point)。 第四种模式捕获了两个值,这使得它在概念上类似于解包赋值 (x, y) = point 。

模式和类

如果你使用类来结构化你的数据,你可以使用类的名字,后面跟一个类似构造函数的参数列表,作为一种模式。这种模式可以将类的属性捕捉到变量中:

class Point:
    x: int
    y: int

def location(point):
    match point:
        case Point(x=0, y=0):
            print("Origin is the point's location.")
        case Point(x=0, y=y):
            print(f"Y={y} and the point is on the y-axis.")
        case Point(x=x, y=0):
            print(f"X={x} and the point is on the x-axis.")
        case Point():
            print("The point is located somewhere else on the plane.")
        case _:
            print("Not a point")

带有位置参数的模式

你可以在某些为其属性提供了排序的内置类(例如 dataclass)中使用位置参数。 你也可以通过在你的类中设置 match_args 特殊属性来为模式中的属性定义一个专门的位置。 如果它被设为 (“x”, “y”),则以下模式均为等价的(并且都是将 y 属性绑定到 var 变量):

Point(1, var)
Point(1, y=var)
Point(x=1, y=var)
Point(y=var, x=1)

嵌套模式

模式可以任意地嵌套。 例如,如果我们的数据是由点组成的短列表,则它可以这样被匹配:

match points:
    case []:
        print("No points in the list.")
    case [Point(0, 0)]:
        print("The origin is the only point in the list.")
    case [Point(x, y)]:
        print(f"A single point {x}, {y} is in the list.")
    case [Point(0, y1), Point(0, y2)]:
        print(f"Two points on the Y axis at {y1}, {y2} are in the list.")
    case _:
        print("Something else is found in the list.")

复杂模式和通配符

到目前为止,这些例子仅在最后一个 case 语句中使用了 _。 但通配符可以被用在更复杂的模式中,例如 (‘error’, code, _)。 举例来说:

match test_variable:
    case ('warning', code, 40):
        print("A warning has been received.")
    case ('error', code, _):
        print(f"An error {code} occurred.")

在上述情况下,test_variable 将可匹配 (‘error’, code, 100) 和 (‘error’, code, 800)。

约束项

我们可以向一个模式添加 if 子句,称为“约束项”。 如果约束项为假值,则 match 将继续尝试下一个 case 语句块。 请注意值的捕获发生在约束项被求值之前。:

match point:
    case Point(x, y) if x == y:
        print(f"The point is located on the diagonal Y=X at {x}.")
    case Point(x, y):
        print(f"Point is not on the diagonal.")

其他关键特性

一些其他关键特性:

类似于解包赋值,元组和列表模式具有完全相同的含义,而且实际上能匹配任意序列。 从技术上说,目标必须为一个序列。 因而,一个重要的例外是模式不能匹配迭代器。 而且,为了避免一个常见的错误,序列模式不能匹配字符串。

序列模式支持通配符: 和 的作用类似于解包赋值中的通配符。 在 之后的名称也可以为 ,因此 可以匹配包含两个条目的序列而不必绑定其余的条目。[x, y, *rest](x, y, rest)(x, y, *)

映射模式: 会从一个字典中捕获 和 值。 与序列模式不同,额外的键会被忽略。 也支持通配符 。 (但 是冗余的,因而不被允许。 ){“bandwidth”: b, “latency”: l}“bandwidth”"latency"rest_

子模式可使用 关键字来捕获:as

case (Point(x1, y1), Point(x2, y2) as p2): …
x1, y1, x2, y2 等绑定就如你在没有 子句的情况下所期望的,而 p2 会绑定目标的整个第二项。as

大多数字面值是按相等性比较的。 但是,单例对象 , 和 则是按标识号比较的。TrueFalseNone

命名常量也可以在模式中使用。 这些命名常量必须为带点号的名称以防止常量被解读为捕获变量:

from enum import Enum
class Color(Enum):
RED = 0
GREEN = 1
BLUE = 2

match color:
case Color.RED:
print(“I see red!”)
case Color.GREEN:
print(“Grass is green”)
case Color.BLUE:
print(“I’m feeling the blues ”)
完整规格说明参见 PEP 634。 动机与理由参见 PEP 635,更详细的教程参见 PEP 636。

可选的 和 选项EncodingWarningencoding=“locale”

TextIOWrapper 和 open() 的默认编码格式取决于具体的平台和语言区域设置。 由于 UTF-8 被用于大多数 Unix 平台,当打开 UTF-8 文件 (例如 JSON, YAML, TOML, Markdown) 时省略 选项是一个非常常见的错误。 例如:encoding

# BUG: "rb" mode or encoding="utf-8" should be used.
with open("data.json") as f:
    data = json.load(f)

为了便于查找此类错误,增加了可选的 。 它会在 sys.flags.warn_default_encoding 为真值并使用了语言区域指定的默认编码格式时被发出。EncodingWarning

增加了 选项和 PYTHONWARNDEFAULTENCODING 来启用相应警告。-X warn_default_encoding

请参阅 文本编码格式 了解更多信息。

你可能感兴趣的:(Python,python,开发语言)