假设我想用Python编写一个接受任何类型数字的函数,我可以如下注释它:
from numbers import Number
def foo(bar: Number):
print(bar)
更进一步,我编写的函数接受数字类型,即int
、float
或numpy
数据类型作为参数。目前,我正在写:
from typing import Type
def foo(bar: Type):
assert issubclass(bar, Number)
print(bar)
我想我可以用NumberType
(类似于NotImplementedType
和朋友,re-introduced in Python 3.10)来代替Type
,因为所有数字类型都是Number
的子类:
from numbers import Number
import numpy as np
assert issubclass(int, Number)
assert issubclass(np.uint8, Number)
事实证明(或者至少据我所知),Python(3.9)中没有泛型NumberType
:
>>> type(Number)
abc.ABCMeta
是否有一种干净的方法(即不进行运行时检查)来实现所需的注释类型
没有通用的方法可以做到这一点。数字一开始并不严格相关,它们的类型甚至更少
虽然} is explicitly not a ^{} 作为子类、子类型或虚拟子类。特别是对于键入,
numbers.Number
看起来像是“数字的类型”,但它不是通用的。例如,^{numbers.Number
是not endorsed by PEP 484 Type Hints为了有意义地键入提示“数字”,必须明确定义该上下文中的数字。这可能是预先存在的数值类型集,例如}/{},定义运算和代数结构的{},或类似
int
<;:float
<;:complex
,数值类型的{所有这些都表明,“数字的类型”甚至没有什么意义。即使是特定的
numbers.Number
实际上也没有任何特性:它不能转换为具体类型,也不能实例化为有意义的数字相反,使用“数字的类型”sometype of number:
如果
Type
的唯一目标是创建实例,那么最好是请求一个Callable
。这包括类型和工厂功能通常,我们如何提示类,而不是类的实例
通常,如果我们想告诉类型检查器某个类的任何实例(或该类的子类的任何实例)应被接受为函数的参数,我们可以这样做:
另一方面,如果我们有一个类
C
,并且我们想要提示类C
本身(或者C
的子类)应该作为参数传递给函数,那么我们使用type[C]
而不是C
。(在Python<;=3.8中,您需要使用typing.Type
而不是内置的type
函数,但在Python 3.9和PEP 585中,我们可以直接参数化type
。)我们如何注释一个函数来表示某个参数应该接受任何数值类?
int
、float
和所有的numpy
数值类型都是numbers.Number
的子类,所以如果我们想说所有数值类都是允许的,我们应该可以只使用type[Number]
至少,Python说
float
和int
是Number
的子类:如果我们使用运行时类型检查库,比如typeguard,那么使用
type[Number]
似乎可以很好地工作:但是等等!如果我们尝试将
type[Number]
与静态类型检查器一起使用,它似乎不起作用。如果我们通过MyPy运行以下代码段,它会为每个类(除了fractions.Fraction
)引发一个错误:Python肯定不会对我们撒谎说
float
和int
是Number
的子类。发生什么事了为什么
type[Number]
不能作为数值类的静态类型提示虽然} 和^{} 都注册为
issubclass(float, Number)
和issubclass(int, Number)
都计算为True
,但float
和int
实际上都不是numbers.Number
的“严格”子类numbers.Number
是一个抽象基类,^{Number
的“虚拟子类”。这会导致Python在运行时将float
和int
识别为Number
的“子类”,即使Number
不在它们的方法解析顺序中问题是{a5}(和,{a6})
MyPy确实了解标准库中的一些ABC。例如,MyPy knows that ^{} is a subtype of ^{} ,尽管} is only a virtual subclass of ^{}. 只有知道的方法解析顺序
MutableSequence
是ABC,但是^{list
是MutableSequence
的一个亚型,因为我们一直在对MyPy撒谎关于^{MyPy与所有其他主要类型检查器一起,使用typeshed repository中的存根对标准库中的类和模块进行静态分析。如果你看一下the stub for ^{} in typeshed ,你会发现} is written in pure Python, 而^{} is an optimised data structure written in C 。但是对于静态分析,MyPy认为这是真的是有用的。标准库中的其他集合类(例如,^{} 、^{} 和^{} )按typeshed以大致相同的方式进行特殊大小写,但^{} 和^{} 等数字类型则不是
list
是collections.abc.MutableSequence
的一个直接子类。那根本不是真的-^{如果我们在集合类方面对MyPy撒谎,为什么我们不在数值类方面也对MyPy撒谎?
很多人(包括我!)认为我们应该这样做,关于是否应该做出这种改变的讨论已经持续了很长时间(例如,typeshed proposal,MyPy issue)。然而,这样做有各种复杂因素
可能的解决方案:使用duck类型
这里一个可能的解决方案(尽管稍微icky)可能是使用
typing.SupportsFloat
SupportsFloat
是一个运行时可检查的协议,它有一个abstractmethod
,__float__
。这意味着任何具有__float__
方法的类都会被识别为类的子类型,无论是在运行时还是静态类型检查器SupportsFloat
,即使SupportsFloat
不在类的方法解析顺序中注意:尽管用户定义的协议仅在Python中可用>;=3.8,
SupportsFloat
模块自从添加到Python 3.5中的标准库之后就一直在typing
模块中此解决方案的优点
所有主要数字类型的综合支持*所有主要数字类型的综合支持*所有主要数字类型的综合支持*所有主要数字类型的综合支持*所有主要数字类型::^{},{},{},,{},,{>cd19>},,{},,{}},,,,{}},,,,,,{},{},},{},},>{}、{}、{}、{}、{}和{}它们都有一个
__float__
方法如果我们将函数参数注释为
type[SupportsFloat]
,MyPy correctly accepts* the types that conform to the protocol, and correctly rejects the types that do not conform to the protocol.这是一个相当普遍的解决方案-您不需要显式地枚举所有希望接受的可能类型
使用静态类型检查程序和运行时类型检查库,如
typeguard
此解决方案的缺点
这感觉像(而且是)一次黑客攻击。有一个
__float__
方法并不是任何人在抽象中定义“数字”的合理想法Mypy不将}. 中没有} method has in fact been removed from the ^{} class in Python 3.10
complex
识别为SupportsFloat
的亚型complex
实际上在Python中有一个__float__
方法<;=3.9. 但是,它在typeshed stub for ^{__float__
方法,因为MyPy(以及所有其他主要类型检查器)使用类型化存根进行静态分析,这意味着它不知道complex
有此方法complex.__float__
可能从typeshed存根中省略,因为该方法总是引发TypeError
;因此,the ^{任何用户定义的类,即使不是数值类,也可能定义} is here 和the source code for ^{} is here 。)
__float__
。事实上,标准库中甚至有几个非数值类定义了__float__
。例如,尽管Python中的str
类型(用C编写)没有__float__
方法,但是collections.UserString
(用纯Python编写)有。(The source code for ^{示例用法
除了
complex
之外,它通过了我测试它的所有数字类型的MyPy:如果我们希望} ),从理论上讲,创建} itself is an empty class that has no methods -它的存在只是为了为标准库中的其他数值类提供“虚拟基类”
complex
也被接受,那么对这个解决方案的一个简单的调整就是使用以下代码片段,即特殊大小写complex
。对于我能想到的每一种数字类型,这都满足MyPy的要求。我还将type[Number]
抛出到类型提示中,因为它可以捕获一个假设类,该类直接继承自numbers.Number
,没有__float__
方法。我不知道为什么有人会编写这样一个类,但是有一些类直接继承自numbers.Number
(例如^{Number
的直接子类而不使用__float__
方法肯定是可能的^{翻译成英语,
NumberType
这里相当于:我不认为这是}有关的问题说明了这种方法的危险性。例如,在第三方库中可能存在其他不寻常的数值类型,它们不直接子类
complex
问题的“解决方案”——它更多的是一种变通方法。与{numbers.Number
或具有__float__
方法。要事先知道它们可能是什么样子是非常困难的,特别是在所有的情况下附录
为什么} ?
SupportsFloat
而不是^{fractions.Fraction
有__float__
方法(inherited from ^{__int__
方法为什么} ?
SupportsFloat
而不是^{甚至} 对我来说不太像数字。)如果你使用
complex
也有一个__abs__
方法,所以typing.SupportsAbs
乍一看似乎是一个很有前途的选择!但是,标准库中还有其他几个类有__abs__
方法,而没有__float__
方法,如果说它们都是数值类,那就太夸张了。(^{SupportsAbs
而不是SupportsFloat
,你可能会把你的网拉得太宽,并允许各种非数字类为什么} ?
SupportsFloat
而不是^{作为^ {CD57>}的替代,您也可以考虑使用^ {CD148>},它接受所有具有^ {CD149>}方法的类。这与
SupportsFloat
一样全面(它涵盖了除complex
之外的所有主要数字类型)。它的另一个优点是collection.UserString
没有__round__
方法,而如上所述,它确实有__float__
方法。最后,第三方非数值类不太可能包含__round__
方法但是,在我看来,如果您选择
SupportsRound
而不是SupportsFloat
,那么排除有效的第三方数值类的风险会更大,因为任何原因,这些类都没有定义__round__
“Having a
__float__
方法”和“Having a__round__
方法”对于类是“数字”意味着什么都是非常糟糕的定义。然而,前者感觉比后者更接近“真实”的定义。因此,指望第三方数值类具有__float__
方法比指望它们具有__round__
方法更安全如果您希望在确保函数接受有效的第三方数字类型时“格外安全”,我看不出扩展
NumberType
有什么特别的危害,甚至进一步扩展SupportsRound
:然而,考虑到任何具有
__round__
方法的类型也很可能具有__float__
方法,我会质疑是否真的有必要包含SupportsRound
*…除了
complex
相关问题 更多 >
编程相关推荐