我正在使用
pint
来使用和转换单位。我想创建将数量限制为“[time]”或“[length]”维度的类,因此作为第一种方法,我执行了以下操作:
from pint import Quantity, DimensionalityError
class Time(Quantity):
def __new__(cls, v: str | Quantity) -> Quantity:
obj = Quantity(v)
if not obj.check("[time]"):
raise DimensionalityError(v, "[time]")
return obj
class Length(Quantity):
def __new__(cls, v: str | Quantity) -> Quantity:
obj = Quantity(v)
if not obj.check("[length]"):
raise DimensionalityError(v, "[length]")
return obj
在运行时它按预期工作,即:我可以执行以下操作:
1hour = Time("1h") # Works ok, variable 1hour contains `<Quantity(1, 'hour')>`
bad = Time("1meter") # As expected, raises pint.errors.DimensionalityError: Cannot convert from '1meter' to '[time]'
1meter = Length("1meter") # Ok
bad_again = Length("1h") # Ok, raises DimensionalityError
但是,从打字的角度来看,有些地方是错误的:
def myfunc(t: Time) -> str:
return f"The duration is {t}"
print(myfunc(Time("1h"))) # Ok
print(myfunc(Length("1m"))) # Type error?
第二次调用
myfunc()
是类型错误,因为我传递的是 Length
而不是 Time
。然而mypy
对代码很满意。所以我有一些问题:
对于 1。我猜想在品脱的
Quantity
实施中发生了一些可疑的事情。我试过:
foo = Quantity("3 pounds")
reveal_type(foo)
并且显示的类型是
Any
而不是Quantity
,这非常可疑。
所以我尝试从我的
Quantity
和Time
类中删除基类Length
(即:它们现在从object
而不是Quantity
派生),在这种情况下,mypy
正确管理打字错误。
但是一旦我尝试像
Length("60km")/Time("1h")
这样的东西,它就会再次失败。 mypy
抱怨 Length
对象没有实现执行该除法所需的方法(尽管代码在运行时工作正常,因为毕竟 Length
和 Time
__new__()
方法返回一个 Quantity
对象确实实现了算术运算)。
那么,是否有任何解决方法可以使这个想法在运行时和
mypy
都有效?
TLDR:Pint 有一个包装函数可以为你做这件事:
@ureg.check('[time]')
就在你的函数定义之上。
一般情况下,python 不强制输入提示(参见官方文档here)。如果你想强制执行这种行为,你能做的最好的事情就是使用
isinstance()
本机方法(在 here 中找到详细信息)。
但是,在您的示例中,您甚至没有创建新类型。 Time
和 Length
类都将返回一个 pint.util.Quantity 对象。因此,以下示例将总是引发错误,即使对于Time
定义的对象也是如此:
def myfunc(t: Time) -> str: # does not work!
if isinstance(t, Time):
return f"The duration is {t}"
else:
raise DimensionalityError(t, "[time]")
一个简单的建议是定义一个检查数量类型的函数:
def qtype_check(obj: Quantity, q_type: str):
qtype_str = f"[{q_type}]"
if not obj.check(qtype_str):
raise DimensionalityError(obj, qtype_str)
然后简单地在您选择的方法中调用它:
def myfunc(t: Quantity) -> str:
qtype_check(t, 'time')
return f"The duration is {t}"
如果您需要保留类定义,一个更优雅的解决方案是另外定义您的数量类,例如
MyQuantity
,具有您在每个类型定义中更改的类属性 _qtype
。这样,您就可以避免多次编写相同的错误代码片段。此外,您可以将 qtype_check 合并到类中,并通过您选择的类型类(例如Time.qtype_check(value)
)调用它,避免每次都要记住如何定义类类型字符串的麻烦。以下是定义:
from pint import Quantity, DimensionalityError
class MyQuantity(Quantity):
_qtype: str = "[]"
def __new__(cls, v: str | Quantity) -> Quantity:
obj = Quantity(v)
cls.qtype_check(obj)
return obj
@classmethod
def qtype_check(cls, obj: Quantity):
if not obj.check(cls._qtype):
raise DimensionalityError(obj, cls._qtype)
class Time(MyQuantity):
_qtype = "[time]"
class Length(MyQuantity):
_qtype = "[length]"
def myfunc(t: Quantity) -> str:
Time.qtype_check(t)
return f"The duration is {t}"
最棒的是简单地使用品脱提供的包装器功能,它可以自动为您检查输入单位! 这里是文档。在您的情况下,它看起来像:
@ureg.check('[time]')
def myfunc(t: Quantity) -> str:
return f"The duration is {t}"
希望对您有所帮助!