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

news2024/11/26 1:42:21

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 等。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【openlayers系统学习】1.1渲染GeoJSON,添加link交互

一、渲染GeoJSON 在进入编辑之前&#xff0c;我们将看一下使用矢量源和图层进行基本要素渲染。Workshop在 data​ 目录中包含一个 countries.json​ GeoJSON文件。我们首先加载该数据并将其渲染在地图上。 首先&#xff0c;编辑 index.html​ 以便向地图添加深色背景&#xf…

开源RAG,本地mac启动 dify源码服务

一、Dify文档 参考官方文档来操作&#xff0c;基本没太大的问题。一些细节&#xff0c;我在本篇文章中补充了出来。 这篇文章主要讲以源码的方式启动后端服务&#xff0c;前端服务使用容器启动。 dify 文档地址 欢迎使用 Dify | 中文 | Dify Dify 本地源码部署文档&#xff…

Redis系统架构中各个处理模块是干什么的?no.19

Redis 系统架构 通过前面的学习&#xff0c;相信你已经掌握了 Redis 的原理、数据类型及访问协议等内容。本课时&#xff0c;我将进一步分析 Redis 的系统架构&#xff0c;重点讲解 Redis 系统架构的事件处理机制、数据管理、功能扩展、系统扩展等内容。 事件处理机制 Redis…

Linxu 系统中 修改 docker 镜像存放目录 修改docker默认路径。亲测有效。

1、关闭docker 服务 systemctl stop docker 2、创建新的存放路径&#xff08;-p 父级目录不存在一起创建&#xff09; mkdir /home/service/docker -p 3、移动默认路径中的镜像文件到新目录 mv /var/lib/docker/* /home/service/docker/ 4、修改docker.service 将新的路…

实操专区-第14周-课堂练习专区-饼图和圆环图、玫瑰图

实操专区-第14周-课堂练习专区-饼图和圆环图、玫瑰图 下载安装ECharts&#xff0c;完成如下样式图形。 代码和截图上传 基本要求&#xff1a;下图3选1&#xff0c;完成代码和截图 完成 3.1.3.13 饼图和圆环图、玫瑰图 中的任务点 基本要求&#xff1a;3个选一个完成&#xff0c…

全网最简洁Java实现多线程安全的令牌桶限流算法

在许多应用中&#xff0c;我们需要限制某些操作的频率&#xff0c;例如&#xff0c;限制API调用的速率&#xff0c;防止系统被过度使用&#xff0c;这种需求就需要一个限流算法来满足 令牌桶算法是一种常用的限流算法 它的基本思想是&#xff1a;系统以恒定的速率向桶中添加令…

Isaac Sim仿真平台学习(1)认识Isaac Sim

0.前言 上一个教程中我们下载好了Isaac Sim&#xff0c;这一章我们将来简单了解一下Isaac Sim平台。 isaac Sim仿真平台安装-CSDN博客 1.Isaac Sim是啥&#xff1f; What Is Isaac Sim? — Omniverse IsaacSim latest documentation Isaac Sim是NVDIA Omniverse平台的机器…

基础3 探索JAVA图形编程桌面:逻辑图形组件实现

在一个宽敞明亮的培训教室里&#xff0c;阳光透过窗户柔和地洒在地上&#xff0c;教室里摆放着整齐的桌椅。卧龙站在讲台上&#xff0c;面带微笑&#xff0c;手里拿着激光笔&#xff0c;他的眼神中充满了热情和期待。他的声音清晰而洪亮&#xff0c;传遍了整个教室&#xff1a;…

深度学习-Softmax回归+损失函数+图像分类数据集

目录 Softmax回归回归 VS 分类Kaggle上的分类问题 从回归到多类分类回归分类从回归到多类分类-均方损失从回归到多类分类-无校验比例从回归到多类分类-校验比例 Softmax和交叉熵损失总结损失函数均方损失绝对值损失函数鲁棒损失 图像分类数据集通过框架中内置函数将FashionMNIS…

前端请求超时截断,axios timeout设置未生效情况记录

问题描述 前端请求超时截断&#xff0c;axios timeout设置未生效情况记录 timeout设置方式&#xff1a; 表现&#xff08;前端超过5min报错500&#xff0c;直接访问接口超过5min能够正常响应&#xff09;&#xff1a; 问题原因 上面的配置设置时间为1000min&#xff0c;明显…

Servlet的response对象

目录 HTTP响应报文协议 reponse继承体系 reponse的方法 响应行 public void setStatus(int sc) 响应头 public void setHeader(String name, String value) 响应体 public java.io.PrintWriter getWriter() public ServletOutputStream getOutputStream() 请求重定…

P2P服务端模型配合 Tool.net P2pServerAsync 类使用

Tool.Net 支持的 P2P 服务器模型实例 说明服务器部分相关代码相关调用实例Tcp版本Udp版本 最后附一张思维图 说明 当前文章&#xff0c;仅是Tool.Net 开源库的一个缩影。本次更新V5.0版本以上提供支持。可以提供简单实现P2P功能用于业务开发。 服务器部分相关代码 完整代码&…

基于Docker部署GitLab环境搭建

文件在D:\E\学习文档子目录压缩\专项进阶&#xff0c;如ngnix,webservice,linux,redis等\docker 建议虚拟机内存2G以上 1.下载镜像文件 docker pull beginor/gitlab-ce:11.0.1-ce.0 注意&#xff1a;一定要配置阿里云的加速镜像 创建GitLab 的配置 (etc) 、 日志 (log) 、数…

MQTT 异常断开(一)

分析问题总结&#xff1a; 前提&#xff1a;MQTT是基于TCP层再次封装&#xff0c;MQTT是不关心TCP层的实现与传输&#xff0c;但是如果TCP链路出现异常&#xff08;丢失TCP ACK&#xff0c;网络延时TCP ACK等&#xff09;一定会导致MQTT断开连接。 MQTT代理服务器存在如下问题&…

服务器没有图形界面没有显示器怎么办

可以用vnc。 vnc是开元的。什么是vnc&#xff1f; 使用vnc 下载vnc和vncserver命令。 每生成一个图形界面就叫做开启session会话。 vnc相关命令&#xff1a; start a new session: vncserver。 如果没有会话&#xff0c;一般从:1开始 端口5901 vncserver :2 #指定会话为:2 端…

如何让社区版IDEA变得好用

如何让社区版IDEA变得好用 背景 收费版的idea功能非常强大&#xff0c;但是费用高。社区版的免费&#xff0c;但是功能被阉割了。如何才能让社区版Idea变得好用&#xff0c;就需要各种插件支持了。经过全局配置编码&#xff0c;maven&#xff0c;jdk版本&#xff0c;在加上各…

MSI U盘重装系统

MSI U盘重装系统 1. 准备一块U盘 首先需要将U盘格式化&#xff0c;这个格式化并不是在文件管理中将U盘里面的所有东西都删干净就可以了&#xff0c;需要在磁盘管理中&#xff0c;将这块U盘格式化&#xff0c;如果这块U盘有分区的话&#xff0c;那将所有的分区都格式化并且删除…

大模型最新黑书:大模型应用解决方案: 基于GPT-3、ChatGPT、GPT-4等Transformer架构的自然语言处理 PDF

今天给大家推荐一本丹尼斯罗斯曼(Denis Rothman)编写的关于大语言模型&#xff08;LLM&#xff09;权威教程<<大模型应用解决方案> 基于GPT-3、ChatGPT、GPT-4等Transformer架构的自然语言处理>&#xff01;Google工程总监Antonio Gulli作序&#xff0c;这含金量不…

开源软件 | 一文彻底搞懂许可证的定义、起源、分类及八大主流许可证,让你选型不再头疼

为什么开源软件会存在许可证&#xff0c;许可证的起源与产生目的是为了解决什么问题&#xff1f;许可证的定义又是怎样的&#xff1f;什么是Copyleft&#xff0c;与Copyright有何区别&#xff1f;开源软件常见的许可证有哪些&#xff1f;这些许可证都有什么特点&#xff1f;接下…

RAC11G删除节点

删除节点步骤&#xff1a;删除实例、删除 DB 软件、删除 GI 软件 删除节点发生的场景 1、被删除节点一切保留&#xff0c;需要从RAC中剔除&#xff0c;例如因为要更换服务器。 2、被删除节点关于RAC的部分文件丢失&#xff0c;如GI、库软件误删除&#xff0c;需要重新安装GI…