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 TypeVarT = TypeVar('T')
print(list[T].__parameters__)
# (~T,)

對于GenericAlias對象的特殊屬性,應用參數后的泛型都實現了一些特殊的只讀屬性,簡單示例如下:

from types import GenericAliasprint(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 MethodTypeclass ClassMethod:"Emulate PyClassMethod_Type() in Objects/funcobject.c"def __init__(self, f):self.f = fdef __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:@ClassMethoddef test(cls):print("A ClassMethod:", cls)print(type(cls))def MyFunc(cls):print("MyFunc:", cls)print(type(cls))class B:passif __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 MethodTypeclass ClassMethod:"Emulate PyClassMethod_Type() in Objects/funcobject.c"def __init__(self, f):self.f = fdef __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):#     passprint(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 MethodTypeclass ClassMethod:"Emulate PyClassMethod_Type() in Objects/funcobject.c"def __init__(self, f):self.f = fdef __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):#     passprint("\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):#     passclass Xiaoxu2(Xiaoxu):passprint(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 TypeVarX = TypeVar('X')
print(dict[str, X][int])
# dict[str, int]

到此,上述對GenericAlias對象和泛型的一些說明介紹已經完畢,下面回到__class_getitem__和__getitem__

使用表達式obj[x]來呈現,Python 解釋器會遵循下面這樣的過程來確定應當調用 __getitem__() 還是 __class_getitem__():

from inspect import isclassdef 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 exceptionelse: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 Enumclass 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.SPAMprint(type(XiaoxuMenu['SPAM']))
# <enum 'XiaoxuMenu'>print(XiaoxuMenu['BACON'])
# XiaoxuMenu.BACONprint(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 Enumclass XiaoxuMenu(Enum):"""A breakfast menu"""# 午餐肉SPAM = 'xiaoxu_spam'# 培根BACON = 'xiaoxu_bacon'@classmethoddef getMenuByName(cls, menu_name: str) -> Enum:for name, member in cls.__members__.items():if member.value.__eq__(menu_name):return memberelse: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 GenericAliasalias = GenericAlias(cls, item)print("獲取泛型類原類型:", alias.__origin__)print("獲取泛型類泛型類型:", alias.__args__)return aliasprint(XiaoxuMenu.getMenuByName("xiaoxu_bacon"))
# XiaoxuMenu.BACONprint(XiaoxuMenu['SPAM'])
# XiaoxuMenu.SPAMprint(XiaoxuMenu[int])
# Error,KeyError: <class 'int'>

結果如下:

在這里插入圖片描述


2.3 __class_getitem__的使用的其它栗子

栗子1:

from typing import ClassVar, Generic, TypeVarT = 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__)# _GenericAliasreturn getitem__def __init__(self, arg: T):self.arg = argx = 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]@classmethoddef do_iterable(cls, iterable):return cls(*iterable)@staticmethoddef get_data(x):return xdef __class_getitem__(cls, item):if isinstance(item, (tuple,)):raise TypeError(f"unsupported item found: {item}.")X, = (item,)class NewXiaoxuGeneric(cls):@classmethoddef do_iterable(cls, iterable):return cls(*(X(x) for x in iterable))@staticmethoddef get_data(x):return X(x)return NewXiaoxuGenericdata = (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.pswp.cn/bicheng/14638.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/14638.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/14638.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

深入 OpenFeign:探索緩存、QueryMap、MatrixVariable 和 CollectionFormat 的高級用法以實現優雅的遠程調用

免費多模型AI網站,支持豆包、GPT-4o、谷歌Gemini等AI模型&#xff0c;無限制使用&#xff0c;快去白嫖&#x1f449;海鯨AI 一、OpenFeign簡介 OpenFeign 是一個聲明式的 HTTP 客戶端&#xff0c;它使得我們可以通過簡單的注解和接口定義來調用遠程 HTTP 服務。與傳統的 HTTP …

K8S集群再搭建

前述&#xff1a;總體是非常簡單的&#xff0c;就是過程繁瑣&#xff0c;不過都是些重復的操作 master成員: [controller-manager, scheduler, api-server, etcd, proxy,kubelet] node成員: [kubelet, proxy] master要修改的配置文件有 1. vi /etc/etcd/etcd.conf # 數…

Mokito的一些API

Mockito是一個Java單元測試框架&#xff0c;它允許開發者創建和配置模擬對象&#xff08;mock objects&#xff09;&#xff0c;以便在隔離的環境中測試代碼&#xff0c;尤其是當實際對象難以構造或其行為不確定時。下面是一些核心的Mockito API及其使用場景和代碼示例。 基礎…

wordpress教程視頻 wordpress教程網盤 wordpress教程推薦wordpress教程網

WordPress&#xff0c;作為一款強大且靈活的開源內容管理系統&#xff0c;已成為許多網站開發者與運營者的首選。其強大的功能、豐富的插件以及易于上手的特點&#xff0c;使得無論是初學者還是專業開發者都能輕松構建出個性化的網站。然而&#xff0c;對于初學者來說&#xff…

JUnit5標記測試用例

使用場景&#xff1a; 通過Tag對用例分組&#xff1a; 環境分組&#xff1a;測試環境、預發布環境階段分組&#xff1a;冒煙用例版本分組&#xff1a;V1.1、V1.2 Tag標記用例&#xff1a; 設置標簽根據標簽執行 結合Maven執行結合測試套件執行 設置標簽&#xff1a; 通過T…

NER 數據集格式轉換

NER 數據集格式 格式一 某些地方的數據和標簽拆成兩個文件了 sentences.txt 如 何 解 決 足 球 界 長 期 存 在 的 諸 多 矛 盾 &#xff0c; 重 振 昔 日 津 門 足 球 的 雄 風 &#xff0c; 成 為 天 津 足 壇 上 下 內 外 到 處 議 論 的 話 題 。 該 縣 一 手 抓 農 業…

【Spring Cloud】全面解析服務容錯中間件 Sentinel 持久化兩種模式

文章目錄 推送模式本地文件持久化&#xff08;拉模式&#xff09;配置yml編寫處理類添加配置演示 配置中心持久化&#xff08;推模式&#xff09;修改nacos在sentinel中生效引入依賴配置文件 修改sentinel在nacos中生效下載源碼更改代碼演示 總結 推送模式 Sentinel 規則的推送…

allegro 無法刪除Xnet

allegro 無法刪除Xnet Orcad中打開Constraint Manager之后&#xff0c;再生成網表&#xff0c;導入PCB后就會出現一堆Xnet網絡。無法去除Xnet。 解決辦法 在原理圖ORCAD中&#xff0c; 1、打開Edit Object properties 2、選擇Filter by:Capture 3、點擊New Property 4、設置…

火山引擎邊緣云亮相 Force 原動力大會,探索 AI 應用新范式

5月15日&#xff0c;2024 春季火山引擎 FORCE 原動力大會在北京正式舉辦。大會聚焦 AI 主題&#xff0c;以大模型應用為核心、以 AI 落地為導向&#xff0c;展示了火山引擎在大模型、云計算領域的實踐應用&#xff0c;攜手汽車、手機終端、金融、消費、互聯網等領域的專家和企業…

2024042102-array-list

數組 Array 一、前言 數組是數據結構還是數據類型&#xff1f; 數組只是個名稱&#xff0c;它可以描述一組操作&#xff0c;也可以命名這組操作。數組的數據操作&#xff0c;是通過 idx->val 的方式來處理。它不是具體要求內存上要存儲著連續的數據才叫數據&#xff0c;而…

js積累三(web頁面一段時間未操作,退出登錄)

//核心代碼&#xff0c;已封裝function CountDownLogout() {/* if 30 seconds no operation then logout */var maxTime 30; // seconds&#xff0c;可自行修改時長var time_time maxTime;/* 鼠標點擊事件 */$(document).mousedown(function(){time_time maxTime; //…

Spring Aop對本地事務的影響

1.Transactional聲明式事物也是基于aop實現的&#xff0c;public方法加了Transactional注解后&#xff0c;已經成功的創建了事務&#xff0c;但是當前方法仍在方法攔截器中 2.業務方法發生異常之后的處理 判斷回滾條件&#xff1a; 如果自定義了RollbackRuleAttribute列表&am…

EI會議的最佳論文獎是什么?如何申請?

EI會議的最佳論文獎通常是指在EI&#xff08;工程索引&#xff0c;Engineering Index&#xff09;收錄的學術會議中&#xff0c;評選出的表現最優秀的論文獎項。以下是關于該獎項的一些基本信息及申請步驟&#xff1a; 最佳論文獎的含義 評選標準&#xff1a;最佳論文獎通常基…

多線程、進程、線程五種狀態、synchronized、volatile、Lock、CAS、死鎖、ThreadLocal

1、并發編程 并發編程三要素 原子性&#xff1a;只一個操作要么全部成功&#xff0c;要么全部失敗可見性&#xff1a;一個線程對共享變量的修改&#xff0c;其他線程能夠立刻看到有序性&#xff1a;程序執行的順序按照代碼的先后順序執行 synchronized&#xff0c;Lock解決原…

前端vue 動態加載ts文件,動態調用ts內的方法

業務場景: 在某個業務場景中, 我們需要在數據庫配置ts文件路徑,和需要調用的函數名稱, 前端需要再指定的場景下,觸發對應的函數, 并執行處理邏輯,返回結果. 實現: 這是一個數據庫配置生成的動態表單 動態校驗的例子, 需要引用動態的函數校驗 任意一個js文件, common1.ts c…

大模型日報|今日必讀的 13 篇大模型論文

大家好&#xff0c;今日必讀的大模型論文來啦&#xff01; 1.MIT新研究&#xff1a;并非所有語言模型特征都是線性的 最近的研究提出了線性表征假說&#xff1a;語言模型通過操作激活空間中概念&#xff08;“特征”&#xff09;的一維表征來執行計算。與此相反&#xff0c;來…

CHI dataless 傳輸——CHI(4)

上篇介紹了read的操作類型&#xff0c;本篇我們來介紹一下dataless 目錄 一、dataless操作概覽 二、Non-CMO (Non-Cache Maintenance Operation) 1、CleanUnique 2、StashOnce and StashOnceSep 3、Evict 三、CMO (Cache Maintenance Operation) 一、dataless操作概覽 名…

C# 中的 Dictionary<TKey, TValue> 類

Dictionary<TKey, TValue> 是 C# 中的一個泛型集合類,它提供了一種鍵值對的存儲結構,可以用來存儲和快速訪問數據。它的主要特點如下: 鍵值對結構: Dictionary 中的每個元素都是一個鍵值對,鍵必須是唯一的,值可以重復。 快速訪問: Dictionary 基于哈希表實現,可以提供 O…

大白話聊聊MySQL查詢之五子句(知識簡單但重要)

前言&#xff1a; 在日常開發中&#xff0c;查詢數據占很大的比重&#xff0c;在使用 MySQL 數據庫進行查詢時&#xff0c;我們經常需要通過各種條件和規則來篩選和排序數據。要實現這些功能&#xff0c;就不得不使用以下這些子句&#xff1a;WHERE、ORDER BY、GROUP BY、HAVI…

物聯網層次架構設計

物聯網可以分為三個層次&#xff0c;底層是用來感知數據的感知層&#xff0c;即利用傳感器、二維碼、RFID等設備隨時隨地獲取物體的信息。第二層是數據傳輸處理的網絡層&#xff0c;即通過各種傳感網絡與互聯網的融合&#xff0c;將對象當前的信息實時準確地傳遞出去。第三層則…