python:__class_getitem__使用以及cached_property源码分析

python:__class_getitem__使用以及cached_property源码分析

1 前言

Python中如何模拟泛型类型?

当使用类型标注时,使用 Python 的方括号标记来形参化一个 generic type 往往会很有用处。 例如,list[int] 这样的标注可以被用来表示一个 list 中的所有元素均为 int 类型。

一个类通常只有在定义了特殊的类方法 __class_getitem__() 时才能被形参化。我们知道,一个list对象,可以通过索引下标取值,即形如a[0],是因为有__getitem__方法的实现,而__class_getitem__() 即针对类的,也就是上述的类名[xx]的形式用法,调用类名[xx]时,也就会调用我们自定义的__class_getitem__()方法。

classmethod object.__class_getitem__(cls, key)

按照 key 参数指定的类型返回一个表示泛型类的专门化对象。

当在类上定义时,__class_getitem__() 会自动成为类方法。 因此,当它被定义时没有必要使用 @classmethod 来装饰。

本文基于Python3.9对__class_getitem__()进行使用的讲解和代码演示

官方文档参考:

https://docs.python.org/zh-cn/3.9/contents.html

2 使用

官方文档参考如下:

https://docs.python.org/zh-cn/3.9/reference/datamodel.html#object.__class_getitem__

2.1 __class_getitem__ 的目的

__class_getitem__() 的目的是允许标准库泛型类的运行时形参化以更方便地对这些类应用类型提示

要实现可以在运行时被形参化并可被静态类型检查所理解的自定义泛型类,用户应当从已经实现了 __class_getitem__() 的标准库类继承,或是从 typing.Generic 继承,这个类拥有自己的 __class_getitem__() 实现。

标准库以外的类上的 __class_getitem__() 自定义实现可能无法被第三方类型检查器如 mypy 所理解。 不建议在任何类上出于类型提示以外的目的使用 __class_getitem__()。

2.2 __class_getitem__ 与 __getitem__

通常,使用方括号语法 抽取 一个对象将会调用在该对象的类上定义的 __getitem__() 实例方法。 不过,如果被拟抽取的对象本身是一个类,则可能会调用 __class_getitem__() 类方法。 __class_getitem__() 如果被正确地定义,则应当返回一个 GenericAlias 对象。


下面先来认识GenericAlias 类型:

参考官方文档:

https://docs.python.org/zh-cn/3.9/library/stdtypes.html#types-genericalias

GenericAlias 对象通常是通过 抽取 一个类来创建的。 它们最常被用于容器类,如 list 或 dict。 举例来说,list[int] 这个 GenericAlias 对象是通过附带 int 参数抽取 list 类来创建的。 GenericAlias 对象的主要目的是用于 类型标注

类型标注,意即:关联到某个变量、类属性、函数形参或返回值的标签,被约定作为 类型注解 来使用。局部变量的标注在运行时不可访问,但全局变量、类属性和函数的标注会分别存放模块、类和函数的 __annotations__ 特殊属性中。也就是我们所说的python的annotation注解,如下简单示例python函数使用注解的场景:

def run(x: int, y: int) -> int:
    pass

上述的形参x、y以及返回值的注解都是int,存在于函数的 __annotations__ 特殊属性中。

注意:通常一个类只有在实现了特殊方法 __class_getitem__() 时才支持抽取操作,也就是形如类名A[xx]的抽取操作。


GenericAlias 对象可作为 generic type 的代理,实现了 形参化泛型。

对于一个容器类,提供给类的 抽取 操作的参数可以指明对象所包含的元素类型。 例如,set[bytes] 可在类型标注中用来表示一个 set 中的所有元素均为 bytes 类型。

对于一个定义了 __class_getitem__() 但不属于容器的类,提供给类的抽取操作的参数往往会指明在对象上定义的一个或多个方法的返回值类型。 例如,正则表达式可以被用在 str 数据类型和 bytes 数据类型上:

  • 如果 x = re.search(‘foo’, ‘foo’),则 x 将为一个 re.Match 对象而 x.group(0) 和x[0] 的返回值将均为 str 类型。 我们可以在类型标注中使用 GenericAlias re.Match[str] 来代表这种对象。
  • 如果 y = re.search(b’bar’, b’bar’),(注意 b 表示 bytes),则 y 也将为一个 re.Match的实例,但 y.group(0) 和 y[0] 的返回值将均为 bytes 类型。 在类型标注中,我们将使用 re.Match[bytes] 来代表这种形式的 re.Match 对象。

GenericAlias 对象是 types.GenericAlias 类的实例,该类也可被用来直接创建 GenericAlias 对象。

T[X, Y, Z…]

创建一个代表由类型 X, Y, Z来参数化的类型 T 的 GenericAlias,此类型会更依赖于所使用的 T。 例如,一个接受包含 float 元素的 list 的函数:

def average(values: list[float]) -> float:
    return sum(values) / len(values)


print(average([1.3, 3, 5]))
# 3.1

另一个例子是关于 mapping 对象的,用到了 dict,泛型的两个类型参数分别代表了键类型和值类型。本例中的函数需要一个 dict,其键的类型为 str,值的类型为 int:。

def send_post_request(url: str, body: dict[str, int]) -> None:
    ...

内置函数 isinstance() 和 issubclass() 不接受第二个参数为 GenericAlias 类型:

isinstance([1, 2], list[str])

执行报错:

在这里插入图片描述

Python运行时不会强制执行类型标注。 这种行为扩展到了泛型及其类型形参。 当由 GenericAlias创建容器对象时,并不会检查容器中为元素指定的类型。 例如,以下代码虽然不被鼓励,但运行时并不会报错:

t = list[str]
print(t([1, 2, 3]))

结果:

[1, 2, 3]

或者使用GenericAlias,如下有官方文档参考:

参考官方文档,GenericAlias 对象的特殊属性:

https://docs.python.org/zh-cn/3.9/library/stdtypes.html#special-attributes-of-genericalias-objects

genericalias.__origin__:本属性指向未应用参数之前的泛型类

print(list[int].__origin__)
# <class 'list'>

genericalias.__args__:该属性是传给泛型类的原始 __class_getitem__() 的泛型所组成的 tuple (长度可能为 1):

print(dict[str, list[int]].__args__)
# (<class 'str'>, list[int])

genericalias.__parameters__:该属性是延迟计算出来的一个元组(可能为空),包含了 __args__ 中的类型变量。

from typing import TypeVar

T = TypeVar('T')
print(list[T].__parameters__)
# (~T,)

对于GenericAlias对象的特殊属性,应用参数后的泛型都实现了一些特殊的只读属性,简单示例如下:

from types import GenericAlias

print(GenericAlias)
alias = GenericAlias(list[str], [1, 4, 9])
print(alias)
print(type(alias))

# 本属性指向未应用参数之前的泛型类
print(alias.__origin__)
# list[str]

print(type(alias.__origin__))
# <class 'types.GenericAlias'>

# 该属性是传给泛型类的原始 __class_getitem__()
# 的泛型所组成的 tuple (长度可能为 1):
print(alias.__args__)
# ([1, 4, 9],)


# 该属性是延迟计算出来的一个元组(可能为空),
# 包含了 __args__ 中的类型变量。
print(alias.__parameters__)
# ()

结果如下:

在这里插入图片描述

除了上述对于GenericAlias的使用,我们再来举个栗子:

使用非数据描述器协议,纯Python版本的 classmethod() 实现如下:

from types import MethodType


class ClassMethod:
    "Emulate PyClassMethod_Type() in Objects/funcobject.c"

    def __init__(self, f):
        self.f = f

    def __get__(self, obj, cls=None):
        if cls is None:
            cls = type(obj)
        if hasattr(type(self.f), '__get__'):
            print("执行hasattr:")
            return self.f.__get__(cls)
        return MethodType(self.f, cls)


class A:

    @ClassMethod
    def test(cls):
        print("A ClassMethod:", cls)
        print(type(cls))


def MyFunc(cls):
    print("MyFunc:", cls)
    print(type(cls))


class B:
    pass


if __name__ == '__main__':
    A.test()
    print("\n********************\n")
    MyFunc.__get__(B)()

执行结果如下:

在这里插入图片描述

上述演示了使用纯Python实现classmethod的方式,classmethod()一般作为装饰器使用,作用是将类中实例方法,绑定为类方法(即类中方法使用classmethod装饰后,第一个参数是cls,代表class对象,而非self实例对象)

有了上述的说明,我们再来看下Python源码中常见的对于GenericAlias的使用

如下是functools中的cached_property源码片段:

class cached_property:
	...
	
    __class_getitem__ = classmethod(GenericAlias)

上述对于类cached_property的__class_getitem__,将其赋值为classmethod方法修饰了GenericAlias后的对象,其巧妙之处如下可见:

from types import GenericAlias
from types import MethodType


class ClassMethod:
    "Emulate PyClassMethod_Type() in Objects/funcobject.c"

    def __init__(self, f):
        self.f = f

    def __get__(self, obj, cls=None):
        if cls is None:
            cls = type(obj)
        if hasattr(type(self.f), '__get__'):
            print("执行hasattr:")
            return self.f.__get__(cls)
        return MethodType(self.f, cls)


class Xiaoxu:
    __class_getitem__ = classmethod(GenericAlias)

    # def __class_getitem__(cls, item):
    #     pass


print(Xiaoxu[int])
print(GenericAlias(Xiaoxu, (int,)))

执行结果如下:

在这里插入图片描述

分析如下,上述的Xiaoxu[int],是通过Python的方括号实现类的泛型化(注意这里是类名+方括号,而不是实例对象+方括号),会自动调用Xiaoxu.__class_getitem__()方法,同时方括号中的int作为方法调用的参数,实际调用形式是:Xiaoxu.__class_getitem__(int)。因为__class_getitem__方法的完整调用形式为:def __class_getitem__(cls, item),所以我们传入的泛型int,就是该方法的item参数,而cls自然需要为类自身,这里为Xiaoxu类,那么Python底层源码为类定义__class_getitem__ = classmethod(GenericAlias)是如何巧妙的实现这种调用形式的呢?

参考官方文档,调用描述器:

https://docs.python.org/zh-cn/3.9/reference/datamodel.html#object.__get__

由此我们知道,描述器的__get__的执行逻辑是

总的说来,描述器就是具有“绑定行为”的对象属性,其属性访问已被描述器协议中的方法所重载: __get__(), __set__() 和 __delete__()。 如果一个对象定义了以上方法中的任意一个,它就被称为描述器。

属性访问的默认行为是从一个对象的字典中获取、设置或删除属性。例如,a.x 的查找顺序会从 a.__dict__[‘x’] 开始,然后是 type(a).__dict__[‘x’],接下来依次查找 type(a) 的上级基类,不包括元类。

但是,如果找到的值是定义了某个描述器方法的对象,则 Python 可能会重载默认行为并转而发起调用描述器方法。这具体发生在优先级链的哪个环节则要根据所定义的描述器方法及其被调用的方式来决定。

描述器发起调用的开始点是一个绑定 a.x。参数的组合方式依 a 而定:

直接调用

  • 最简单但最不常见的调用方式是用户代码直接发起调用一个描述器方法: x.__get__(a)。

实例绑定

  • 如果绑定到一个对象实例,a.x 会被转换为调用: type(a).__dict__[‘x’].__get__(a,
    type(a))。

类绑定

  • 如果绑定到一个类,A.x 会被转换为调用: A.__dict__[‘x’].__get__(None, A)。

超绑定

  • 如果 a 是 super 的一个实例,则绑定 super(B, obj).m() 会在
    obj.__class__.__mro__ 中搜索 B 的直接上级基类 A 然后通过以下调用来发起调用描述器:
    A.__dict__[‘m’].__get__(obj, obj.__class__)。

所以我们将__class_getitem__设置为classmethod(GenericAlias)后,Xiaoxu.__class_getitem__(int)实际执行为类绑定发起的描述器的get,故而转换成Xiaoxu.__dict__[‘__class_getitem__’].__get__(None, Xiaoxu)(int),也就是:

classmethod(GenericAlias).__get__(None, Xiaoxu)(int)

所以本质是调用classmethod的__get__方法,obj是None,cls是class对象Xiaoxu,返回MethodType(self.f, cls),也就是绑定了第一个参数是class对象Xiaoxu的GenericAlias对象,对该GenericAlias对象再通过(int)调用,即传入GenericAlias的第二个参数cls为int,前面我们说过:

  • GenericAlias的第一个属性指向未应用参数之前的泛型类,可以通过GenericAlias对象.__origin__获取,这里是绑定的Xiaoxu类对象;
  • GenericAlias的第二个属性是传给泛型类的原始__class_getitem__()的泛型(也就是__class_getitem__()的item参数)所组成的tuple (长度可能为1),可以通过GenericAlias对象.__args__获取,这里是我们调用Xiaoxu[int]时传入的int,为元组形式(int, )。

同时,上述的MethodType(self.f, cls),实际就是通过猴子补丁,将函数f,这里为GenericAlias,绑定cls,也就是Xiaoxu类,然后再通过int参数调用绑定cls后的GenericAlias,具体猴子补丁可以参考如下:

python猴子补丁:修改类的__new__

到此,我们将Python源码中如何巧妙使用__class_getitem__()的方式分析完毕了,这也提示了我们,如果需要使用__class_getitem__方法,可以通过在类中定义__class_getitem__ = classmethod(GenericAlias)的形式来自定义实现__class_getitem__方法并获取对应的泛型类型GenericAlias,下述的代码也可以印证我们上述分析所得:

from types import GenericAlias
from types import MethodType


class ClassMethod:
    "Emulate PyClassMethod_Type() in Objects/funcobject.c"

    def __init__(self, f):
        self.f = f

    def __get__(self, obj, cls=None):
        print(f"self:{self}, obj:{obj}, cls:{cls}")
        if cls is None:
            cls = type(obj)
        if hasattr(type(self.f), '__get__'):
            print("执行hasattr:")
            return self.f.__get__(cls)

        print("返回MethodType", MethodType(self.f, cls))
        return MethodType(self.f, cls)


class Xiaoxu:
    # __class_getitem__ = classmethod(GenericAlias)
    __class_getitem__ = ClassMethod(GenericAlias)

    # def __class_getitem__(cls, item):
    #     pass


print("\n" + "(0)" + "*" * 30)
print(Xiaoxu.__dict__['__class_getitem__'].__get__(None, Xiaoxu)(int))

print("\n" + "(1)" + "*" * 30)
print(Xiaoxu.__class_getitem__(int))

print("\n" + "(2)" + "*" * 30)
print(Xiaoxu[int])

print("\n" + "(3)" + "*" * 30)
print(GenericAlias(Xiaoxu, (int,)))

# print(type(GenericAlias))
# <class 'type'>

结果:


(0)******************************
self:<__main__.ClassMethod object at 0x0000021C7ED89070>, obj:None, cls:<class '__main__.Xiaoxu'>
返回MethodType <bound method GenericAlias of <class '__main__.Xiaoxu'>>
__main__.Xiaoxu[int]

(1)******************************
self:<__main__.ClassMethod object at 0x0000021C7ED89070>, obj:None, cls:<class '__main__.Xiaoxu'>
返回MethodType <bound method GenericAlias of <class '__main__.Xiaoxu'>>
__main__.Xiaoxu[int]

(2)******************************
self:<__main__.ClassMethod object at 0x0000021C7ED89070>, obj:None, cls:<class '__main__.Xiaoxu'>
返回MethodType <bound method GenericAlias of <class '__main__.Xiaoxu'>>
__main__.Xiaoxu[int]

(3)******************************
__main__.Xiaoxu[int]

可以看到,最终返回的就是bound method:<bound method GenericAlias of <class ‘__main__.Xiaoxu’>>,GenericAlias绑定了class对象Xiaoxu,然后执行item为int的方法调用,最终并打印GenericAlias泛型对象,且4种方式的结果完全一致:

在这里插入图片描述

如果增加GenericAlias的特殊属性打印,如下代码:

print("\n" + "(0)" + "*" * 30)
print(Xiaoxu.__dict__['__class_getitem__'].__get__(None, Xiaoxu)(int))

print("\n" + "(1)" + "*" * 30)
print(Xiaoxu.__class_getitem__(int))

print("\n" + "(2)" + "*" * 30)
print(Xiaoxu[int])

print("\n" + "(3)" + "*" * 30)
print(GenericAlias(Xiaoxu, (int,)))

# print(type(GenericAlias))
# <class 'type'>

print(GenericAlias(Xiaoxu, (int,)).__origin__)
print(GenericAlias(Xiaoxu, (int,)).__args__)
print(Xiaoxu[int].__origin__)
print(Xiaoxu[int].__args__)

结果如下:

在这里插入图片描述

那么我们在自已实现泛型类,并需要判断泛型类本身和其泛型时,可以通过下述的部分方式:

print(Xiaoxu[int].__origin__ is Xiaoxu)
print(Xiaoxu[int].__origin__ == Xiaoxu)
print(Xiaoxu[int].__args__ is (int,))
print(Xiaoxu[int].__args__ == (int,))
# True
# True
# False
# True

另外还有一个点需要注意,就是GenericAlias的第一个和第二个参数都是可以为None的,注意泛型类和泛型类型在使用时,需要判空处理等等:

print(GenericAlias(None, None).__origin__)
print(GenericAlias(None, None).__args__)
print(GenericAlias(Xiaoxu, None).__origin__)
print(GenericAlias(Xiaoxu, None).__args__)
print(Xiaoxu[None].__origin__)
print(Xiaoxu[None].__args__)

结果如下:

在这里插入图片描述

当然,如果是None[None],那么直接抛出异常(None对象或者说没有自定义实现__class_getitem__方法或定义有误的类对象,使用中括号,抛错一般为:is not subscriptable,当然不考虑优先调用__getitem__方法的情况):

TypeError: 'NoneType' object is not subscriptable

最后,当然地,继承也是可以直接使用__class_getitem__方法的:

class Xiaoxu:
    # __class_getitem__ = classmethod(GenericAlias)
    __class_getitem__ = ClassMethod(GenericAlias)

    # def __class_getitem__(cls, item):
    #     pass


class Xiaoxu2(Xiaoxu):
    pass

print(Xiaoxu2[int].__origin__)
print(Xiaoxu2[int].__args__)
# <class '__main__.Xiaoxu2'>
# (<class 'int'>,)

执行的效果和上述一致。


上述对GenericAlias对象有了一个较为详尽的使用分析,下面再来看下泛型的一些其他使用说明:

在创建对象的过程中,应用了参数后的泛型还会抹除类型参数:

t = list[str]
print(type(t))
# <class 'types.GenericAlias'>

l = t()
print(type(l))
# <class 'list'>

在泛型上调用 repr() 或 str() 会显示应用参数之后的类型:

print(repr(list[int]))
# list[int]

print(str(list[int]))
# list[int]

调用泛型容器的 __getitem__() 方法将引发异常以防出现 dict[str][str] 之类的错误:

dict[str][str]

报错如下:

在这里插入图片描述

不过,当使用了 类型变量 时这种表达式是无效的。 索引必须有与 GenericAlias 对象的 __args__ 中的类型变量条目数量相当的元素。

from typing import TypeVar

X = TypeVar('X')
print(dict[str, X][int])
# dict[str, int]

到此,上述对GenericAlias对象和泛型的一些说明介绍已经完毕,下面回到__class_getitem__和__getitem__

使用表达式obj[x]来呈现,Python 解释器会遵循下面这样的过程来确定应当调用 __getitem__() 还是 __class_getitem__():

from inspect import isclass

def subscribe(obj, x):
    """Return the result of the expression `obj[x]`"""

    class_of_obj = type(obj)

    # If the class of obj defines __getitem__,
    # call class_of_obj.__getitem__(obj, x)
    if hasattr(class_of_obj, '__getitem__'):
        return class_of_obj.__getitem__(obj, x)

    # Else, if obj is a class and defines __class_getitem__,
    # call obj.__class_getitem__(x)
    elif isclass(obj) and hasattr(obj, '__class_getitem__'):
        return obj.__class_getitem__(x)

    # Else, raise an exception
    else:
        raise TypeError(
            f"'{class_of_obj.__name__}' object is not subscriptable"
        )

在 Python 中,所有的类自身也是其他类的实例。 一个类所属的类被称为该类的 metaclass,并且大多数类都将 type 类作为它们的元类。 type 没有定义 __getitem__(),这意味着 list[int], dict[str, float] 和 tuple[str, bytes] 这样的表达式都将导致 __class_getitem__() 被调用:

# list has class "type" as its metaclass,
# like most classes:
print(type(list))
# <class 'type'>

print(type(dict) == type(list)
      == type(tuple) == type(str)
      == type(bytes))
# True


# "list[int]" calls "list.__class_getitem__(int)"
print(list[int])
# list[int]

# list.__class_getitem__ returns a GenericAlias object:
print(type(list[int]))
# <class 'types.GenericAlias'>

然而,如果一个类属于定义了 __getitem__() 的自定义元类,则抽取该类可能导致不同的行为。 这方面的一个例子可以在 enum 模块中找到(Python的枚举类Enum,自定义了__getitem__()方法):

EnumMeta源码部分片段如下,自定义的__getitem__()方法:

def __getitem__(cls, name):
    return cls._member_map_[name]

栗子:

from enum import Enum


class XiaoxuMenu(Enum):
    """A breakfast menu"""
    # 午餐肉
    SPAM = 'xiaoxu_spam'
    # 培根
    BACON = 'xiaoxu_bacon'


# Enum classes have a custom metaclass:
print(type(XiaoxuMenu))
# <class 'enum.EnumMeta'>


# EnumMeta defines __getitem__,
# so __class_getitem__ is not called,
# and the result is not a GenericAlias object:
print(XiaoxuMenu['SPAM'])
# XiaoxuMenu.SPAM


print(type(XiaoxuMenu['SPAM']))
# <enum 'XiaoxuMenu'>

print(XiaoxuMenu['BACON'])
# XiaoxuMenu.BACON

print(XiaoxuMenu[int])
# 报错:return cls._member_map_[name]  KeyError: <class 'int'>
# 这个报错是因为cls._member_map_是字典dict,cls._member_map_[name]
# 会自动调用dict的__get__方法,若Key不存在,
# 则抛出KeyError: <class 'int'>

执行结果:

在这里插入图片描述

或者我们修改如下的方式执行:

from enum import Enum


class XiaoxuMenu(Enum):
    """A breakfast menu"""
    # 午餐肉
    SPAM = 'xiaoxu_spam'
    # 培根
    BACON = 'xiaoxu_bacon'

    @classmethod
    def getMenuByName(cls, menu_name: str) -> Enum:
        for name, member in cls.__members__.items():
            if member.value.__eq__(menu_name):
                return member
        else:
            raise ValueError(f"name {menu_name} do't exists in XiaoxuMenu.")

    def __class_getitem__(cls, item):
        if item is not Enum:
        	raise TypeError("Enum suffix allowed")
        from types import GenericAlias
        alias = GenericAlias(cls, item)
        print("获取泛型类原类型:", alias.__origin__)
        print("获取泛型类泛型类型:", alias.__args__)
        return alias


print(XiaoxuMenu.getMenuByName("xiaoxu_bacon"))
# XiaoxuMenu.BACON

print(XiaoxuMenu['SPAM'])
# XiaoxuMenu.SPAM

print(XiaoxuMenu[int])
# Error,KeyError: <class 'int'>

结果如下:

在这里插入图片描述


2.3 __class_getitem__的使用的其它栗子

栗子1:

from typing import ClassVar, Generic, TypeVar

T = TypeVar("T")


class Xiaoxu(Generic[T]):
    cls_attr: ClassVar[int]

    def __class_getitem__(cls, item: tuple[int, T]):
        print("开始调用__class_getitem__", cls, item)
        cls.cls_attr = item[0]
        getitem__ = super().__class_getitem__(item[1])
        print(getitem__)
        print(type(getitem__))
        print("origin:", getitem__.__origin__)
        print("args:", getitem__.__args__)
        # _GenericAlias
        return getitem__

    def __init__(self, arg: T):
        self.arg = arg


x = Xiaoxu[99, bool](arg=True)
print(x.cls_attr)
print(x.arg)
# 开始调用__class_getitem__ <class '__main__.Xiaoxu'> (99, <class 'bool'>)
# __main__.Xiaoxu[bool]
# <class 'typing._GenericAlias'>
# origin: <class '__main__.Xiaoxu'>
# args: (<class 'bool'>,)
# 99
# True

执行结果:

在这里插入图片描述

栗子2:

class XiaoxuGeneric:

    def __init__(self, *args):
        self.list_data = [*args]

    @classmethod
    def do_iterable(cls, iterable):
        return cls(*iterable)

    @staticmethod
    def get_data(x):
        return x

    def __class_getitem__(cls, item):
        if isinstance(item, (tuple,)):
            raise TypeError(f"unsupported item found: {item}.")
        X, = (item,)

        class NewXiaoxuGeneric(cls):
            @classmethod
            def do_iterable(cls, iterable):
                return cls(*(X(x) for x in iterable))

            @staticmethod
            def get_data(x):
                return X(x)

        return NewXiaoxuGeneric


data = (1, 3, 8.8, 9.9)

print(XiaoxuGeneric.do_iterable(data).list_data)
# [1, 3, 8.8, 9.9]

print(XiaoxuGeneric[int].do_iterable(data).list_data)
# [1, 3, 8, 9]

print(XiaoxuGeneric[str].do_iterable(data).list_data)
# ['1', '3', '8.8', '9.9']

print(XiaoxuGeneric[lambda x: x ** 2].do_iterable(data).list_data)
# [1, 9, 77.44000000000001, 98.01]

print(XiaoxuGeneric[int, float, complex].do_iterable(data).list_data)
# TypeError: unsupported item found:
# (<class 'int'>, <class 'float'>, <class 'complex'>).

执行结果如下:

在这里插入图片描述

上述的栗子也说明了,__class_getitem__ 方法可以让开发者在泛型类型中实现类型参数的协变或逆变,从而更加灵活地处理类型。它通常用于实现一些高级的泛型类型,例如函数式编程中的 Functor、Monad 等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/638559.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PointCloudLib 点云半径滤波实现 C++版本

0.展示效果 滤波之前 1.算法原理 半径滤波原理非常直观,主要用于平滑三维点云数据并去除离群点。 设定搜索半径:首先,为每个点设定一个搜索半径r。这个半径定义了该点周围的一个球形区域。计算邻域点数:接着,计算每个点在其搜索半径r内的邻近点的数量。判断与过滤:根据…

MyBatisPlus使用流程

引入依赖 <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.4</version> </dependency> 版本号根据需要选取 在实体类上加注解声明&#xff0c;表信息 根据数…

YOLOv8_seg的训练、验证、预测及导出[实例分割实践篇]

实例分割数据集链接,还是和目标检测篇一样,从coco2017val数据集中挑出来person和surfboard两类:链接:百度网盘 请输入提取码 提取码:3xmm 1.实例分割数据划分及配置 1.1实例分割数据划分 从上面得到的数据还不能够直接训练,需要按照一定的比例划分训练集和验证集,并按…

【教学类-58-01】黑白三角拼图01(2*2宫格)固定256种+随机抽取10张

背景需求&#xff1a; 中班益智区素材&#xff1a;三角形变魔术 - 小红书自制益智区教玩具&#xff1a;三角形变魔术&#xff0c;共24题 玩法一&#xff1a;根据图示在空白格子里摆放。 玩法二&#xff1a;根据图示在空白格子里用黑笔涂。##自制玩具益智区 #幼儿园益智区 #中班…

SpringBootWeb 篇-深入了解 Mybatis 概念、数据库连接池、环境配置和 Lombok 工具包

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文件目录 1.0 Mybatis 概述 2.0 数据库连接池 2.1 数据库连接池的主要作用包括 2.2 如何切换数据库连接池&#xff1f; 3.0 配置环境 4.0 Lombok 工具包 4.1 如何导入到项目中呢…

虹科Pico汽车示波器 | 免拆诊断案例 | 2017款奔驰E300L车行驶中发动机偶尔无法加速

故障现象 一辆2017款奔驰E300L车&#xff0c;搭载274 920发动机&#xff0c;累计行驶里程约为21万km。车主反映&#xff0c;该车行驶中发动机偶尔无法加速&#xff0c;且车辆发闯。 故障诊断 用故障检测仪检测&#xff0c;发动机控制单元&#xff08;N3/10&#xff09;中存储…

2024“电工杯”数学建模A题《园区微电网风光储协调优化配置》思路和代码分享

A 题&#xff1a;园区微电网风光储协调优化配置 这个题目整体就是一个优化问题&#xff0c;可以采用MatlabYalmipGurobi求解器进行求解&#xff0c;持续更新中&#xff0c;敬请关注&#xff01;&#xff01; 园区微电网由风光发电和主电网联合为负荷供电&#xff0c;为了尽量提…

在未来你将何去何从?

在数字化的浪潮中&#xff0c;信息技术行业无疑是推动全球经济和社会发展的重要动力。随着科技的不断迭代与进步&#xff0c;云计算、大数据、人工智能&#xff08;AI&#xff09;、物联网&#xff08;IoT&#xff09;、5G通信和区块链等技术已经深入到我们生活的每一个角落&am…

大模型日报|今日必读的 13 篇大模型论文

大家好&#xff0c;今日必读的大模型论文来啦&#xff01; 1.MIT新研究&#xff1a;并非所有语言模型特征都是线性的 最近的研究提出了线性表征假说&#xff1a;语言模型通过操作激活空间中概念&#xff08;“特征”&#xff09;的一维表征来执行计算。与此相反&#xff0c;来…

计算机如何将输入文字显示出来的?渲染Image rendering

1.文字渲染的简单理解 渲染图像&#xff0c;可以理解为用cpu/gpu构造出原本不存在的图像。比如输入计算机的英文字符都是ASCII码&#xff0c;而我们在屏幕上看到显示的字符对应的应该是RGB/YUV的像素。计算机把ASCII字符转化成像素的过程就是文字渲染。又比如我们GPU用多个2D图…

BioMistral 7B——医疗领域的新方法,专为医疗领域设计的大规模语言模型

1. 概述 自然语言处理领域正在以惊人的速度发展&#xff0c;ChatGPT 和 Vicuna 等大型语言模型正在从根本上改变我们与计算机交互的方式。从简单的文本理解到复杂的问题解决&#xff0c;这些先进的模型展示了类似人类的推理能力。 特别是&#xff0c;BLOOM 和 LLaMA 等开源模…

【简单介绍下近邻算法】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

案例题(第一版)

案例题目 软件架构设计考点&#xff08;历年必考&#xff09; 软件架构设计通常在每年的第一题&#xff0c;该题必考 必备概念 必备概念即考试必须要默写出来的概念 概念描述软件架构风格是指描述特定软件系统组织方式和惯用模式。组织方式描述了系统的组成构件和这些构件的组…

力扣刷题---返回word中所有不重复的单词

当需要从一个数据集合中去除重复元素时&#xff0c;set是一个很好的选择。由于其不允许存储重复的元素&#xff0c;因此可以很容易地实现去重功能。这在处理原始数据或进行数据分析时特别有用。 题目&#xff1a; 给定一个字符串数组 words&#xff0c;请返回一个由 words 中所…

表现层框架设计之表现层设计模式_2.MVP模式

1.MVP模式 MVP&#xff08;Model-View-Presenter&#xff09;模式提供数据&#xff0c;View负责显示&#xff0c;Controller/Presenter负责逻辑的处理。MVP是从经典的模式MVC演变而来&#xff0c;它们的基本思想有相通的地方&#xff1a;Controller/Presenter负责逻辑的处理&am…

构建健壮的机器学习大数据平台:任务实现与数据治理的关键

随着数据驱动决策成为现代企业的核心&#xff0c;构建安全、可靠且可扩展的大数据平台变得至关重要。这样的平台不仅需要支持复杂的机器学习任务&#xff0c;还需要在数据质量、合规性和分发方面提供严格的控制。本文旨在探讨构建大型企业机器学习大数据平台时需要考虑的关键要…

【软件设计师】2018年的上午题总结

2018 2018上半年2018下半年 2018上半年 1.小阶向大阶对齐 2.吞吐率是最长流水段操作时间的倒数 3.ssh的端口号是22 4.s所发送的信息使用s的私钥进行数字签名&#xff0c;t收到后使用s的公钥验证消息的真实性 5.数据流分析是被动攻击方式 6.《计算机软件保护条例》是国务院颁布…

OSPF问题

.ospf 选路 域内 --- 1类&#xff0c;2类LSA 域间 --- 3类LSA 域外 --- 5类&#xff0c;7类LSA --- 根据开销值的计算规则不同&#xff0c;还分为类型1和类型2 ospf 防环机制 区域内防环&#xff1a;在同一OSPF区域内&#xff0c;所有路由器通过交换链路状态通告&#xff…

操作视频号小店,新手最关心的问题,一篇给你讲解清楚!

大家好&#xff0c;我是电商小V 新手去做视频号小店的时候&#xff0c;心里面一定是有很多疑问的&#xff0c;会反复咨询一些最关心的问题&#xff0c;因为他们要做好准备&#xff0c;以防后续做店过程中出现问题&#xff0c;其实新手关心的问题就那几个&#xff0c;咱们今天就…

第2天 搭建安全拓展_小迪网络安全笔记

1.常见搭建平台脚本使用: 例如 phpstudy IIS Nginx(俗称中间件): 什么是中间件: 中间件是介于应用系统和系统软件之间的一类软件&#xff0c;它使用系统软件所提供的基础服务&#xff08;功能&#xff09;&#xff0c;衔接网络上应用系统的各个部分或不同的应用&#…