python最新版311正式发布有哪些新特色?45

python最新版311正式发布有哪些新特色?45

本文介绍python最新版 3.11在变量类型方面的改进。

python是一种可以动态确定类型变量的编程语言,输入时会给出可选的类型提示,只支持静态输入。python官方在2015年指定的PEP 48 文档中确定了基本的静态输入机制。从python3.5版以来发布的每一个新版本,在输入方面都引入了一些新的改进。

在python 3.11版中,在以下5个方面做了改进,这也是迄今为止针对输入方面改进最多的一次:

  • PEP 646 :可变泛型(Variadic generics)
  • PEP 655 :按照需要或可能缺失的情况下,对各个 TyedDict 项做出标识
  • PEP 673 :Self 类型
  • PEP 675 :主动认定文字字符串类型
  • PEP 681 :数据类型转换

本文重点介绍可变泛型和Self类型。类型变量(Type variables)一直都是python静态输入变量机制的组成部分,使用类型变量可以将常用类型参数化,例如有一个列表,那么可以使用类型变量检查列表内部的元素的类型。示例代码如下:

from typing import Sequence, TypeVar

T = TypeVar("T")

def first(sequence: Sequence[T]) -> T:
    return sequence[0]

以上代码中,first() 函数从一个序列类型中挑出第一个元素,如从一个列表中挑出该列表的第一个元素,这段代码运行与所在序列的元素的类型没有直接关系,需要跟踪序列中的元素的类型,所以需要知道函数 first() 返回值的类型,这段代码简单明确地说明了类型变量的作用。如果要把整数列表传递给函数 first(),那么在类型检查时 T 就被认定为 int 类型,所以类型检查机制就可以推断出函数 first() 的返回值类型是 int 类型。在上面这个例子中所指的列表按照基本类型处理,因为该列表可以作为其他类型变量的参数。

python一直以来都在尝试解决类型提示的问题,对当前类型能给出具体准确的提示。在python 3.11之前的版本中,调用 Person 类的方法如以下代码所示:

# programmers.py

from dataclasses import dataclass

# ...

@dataclass
class Person:
    name: str
    life_span: tuple[int, int]

    @classmethod
    def from_dict(cls, info):
        return cls(
            name=f"{info['name']['first']} {info['name']['last']}",
            life_span=(info["birth"]["year"], info["death"]["year"]),
        )

其中的构建方法 .from_dict() 返回一个Person对象,但是不能用 -> Person 这样的方式给出类型输入的提示,类型提示给出的就是 .from_dict() 返回值的类型,因为在代码中并没有在这一点上做出完整的定义。另外,如果可以用 -> Person 这样的提示,执行类继承时可能会出错;如果创建 Person的一个子类,那么 .from_dict() 就返回子类型,而不是返回 Person 对象。要解决这个问题,可以使用类型变量,从而达到确定所创建类的类型,示例代码如下:

# programmers.py

# ...

from typing import Any, Type, TypeVar

TPerson = TypeVar("TPerson", bound="Person")

@dataclass
class Person:
    name: str
    life_span: tuple[int, int]

    @classmethod
    def from_dict(cls: Type[TPerson], info: dict[str, Any]) -> TPerson:
        return cls(
            name=f"{info['name']['first']} {info['name']['last']}",
            life_span=(info["birth"]["year"], info["death"]["year"]),
        )

以上代码中,专门对 TPerson 做了限定,确保 TPerson 的类型只能是 Person类或者Person类的子类。虽然这种处理方式没有错,但是代码的可读性很差;这种方式对self 或者 cls 类型做了强制声明,但是通常来说没有必要这么做,因此可以直接使用self,指向封装类型,而无需人工定义类型变量,看看以下代码,可以达到与上面代码相同的效果:

# programmers.py

# ...

from typing import Any, Self

@dataclass
class Person:
    name: str
    life_span: tuple[int, int]

    @classmethod
    def from_dict(cls, info: dict[str, Any]) -> Self:
        return cls(
            name=f"{info['name']['first']} {info['name']['last']}",
            life_span=(info["birth"]["year"], info["death"]["year"]),
        )

以上代码中,从 typing 工具包导入 Self,不需要创建类型变量或强制声明 cls 的类型,这样做要注意 返回 self 的方法,该方法指向 Person。

特别说明的是,类型变量也有一个局限,就是一次只能代表一种类型。看看下面的例子,定义了一个函数,用于反转一个元组的顺序,该元组包含两个元素:

# pair_order.py

def flip(pair):
    first, second = pair
    return (second, first)

其中的 pair 为元组,其每一项包含两个元素;元素的类型可以不同,所以需要两个类型变量来限定该函数的类型,示例代码如下:

# pair_order.py

from typing import TypeVar

T0 = TypeVar("T0")
T1 = TypeVar("T1")

def flip(pair: tuple[T0, T1]) -> tuple[T1, T0]:
    first, second = pair
    return (second, first)

以上代码写得有点繁琐,但写法是正确的,明确给出了强制类型声明。但是如果要标识代码中的元组变量且元组中每项信息的元素的数量是随意的,就会遇到困难。看看下面的代码:

# tuple_order.py

def cycle(elements):
    first, *rest = elements
    return (*rest, first)

代码中,用cycle()函数把元组的第一个元素移动到元组的末尾,而组成元组的每一条内容的元素是没有数量限制的;如果传递一对元素,就相当于 前面代码中的 flip() 函数。 问题在于如何标识函数cycle()的类型,如果元组由 n 个元素,那就需要 n 个类型变量,但是元素的变量数量是没有限制的,那就无法知道需要多少类型变量。

python 3.11 针对这种情况,引入了 TypeVarTuple,用于代表类型的数量, 可以用来标识可变泛型参数的基本类型。以下示例代码中,给函数 cycle() 增加了类型提示:

# tuple_order.py

from typing import TypeVar, TypeVarTuple

T0 = TypeVar("T0")
Ts = TypeVarTuple("Ts")

def cycle(elements: tuple[T0, *Ts]) -> tuple[*Ts, T0]:
    first, *rest = elements
    return (*rest, first)

TypeVarTuple可以替代任何数量的类型,元组中的数量不受数量的限制;代码中 Ts 前面的 (*) 是语法的必要组成部分,Ts 代表任意的类型数量,这也意味着引入类型变量元组的方法肯定也要使用多维数组。

最后,再说说类型提示!调用静态类型同时使用了两个不同的工具:python语言和 类型检查方法,这在python 3.11版之前是不支持的。许多确定类型的特性(如self 和 TypeVarTuple)在python 3.11版之前,有一个扩展工具包 typing_extensions 提供了相关的方法,在python 3.10版本下可以把这个扩展工具包安装在虚拟环境中,可以用这个扩展包修改上面的代码段:

# tuple_order.py

from typing_extensions import TypeVar, TypeVarTuple, Unpack

T0 = TypeVar("T0")
Ts = TypeVarTuple("Ts")

def cycle(elements: tuple[T0, Unpack[Ts]]) -> tuple[Unpack[Ts], T0]:
    first, *rest = elements
    return (*rest, first)

其中的 *Ts 语法只有python 3.11版 支持,在3.11之前的版本下的用法是 Unpack[Ts],但要注意,即使这段代码在 python 3.11版之前的环境中能运行,也不支持 TypeVarTuple 。

发布者:股市刺客,转载请注明出处:https://www.95sca.cn/archives/76265
站内所有文章皆来自网络转载或读者投稿,请勿用于商业用途。如有侵权、不妥之处,请联系站长并出示版权证明以便删除。敬请谅解!

(0)
股市刺客的头像股市刺客
上一篇 2024 年 7 月 11 日
下一篇 2024 年 7 月 11 日

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注