我使用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
在运行时,它按预期工作,i。E:我可以做以下事情:
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
然而,从打字的Angular 来看,有些事情是错误的:
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.为什么我的程序不能捕获错误?
1.如何正确地做?
我猜在pint的Quantity
实现中发生了一些可疑的事情。我试过:
foo = Quantity("3 pounds")
reveal_type(foo)
并且所揭示的类型是Any
而不是非常可疑的Quantity
。
所以我尝试从我的Time
和Length
类中删除基类Quantity
(即:e:它们现在从object
而不是Quantity
派生),在这种情况下,mypy
正确地管理键入错误。
但是当我尝试类似Length("60km")/Time("1h")
的东西时,它又失败了。mypy
抱怨Length
对象没有实现执行该除法所需的方法(尽管代码在运行时工作正常,因为毕竟Length
和Time
__new__()
方法返回了一个Quantity
对象,该对象 * 确实 * 实现了算术运算)。
那么,有没有什么变通方法可以让这个想法在运行时和mypy
上都能工作呢?
1条答案
按热度按时间e4yzc0pl1#
**TL;DR:**Pint有一个 Package 器函数可以帮你做到这一点:
@ureg.check('[time]')
,它位于函数定义的正上方。讨论
一般来说,python不强制输入提示(参见官方文档here)。如果你想强制执行这样的行为,最好的办法就是使用
isinstance()
原生方法(在这里找到详细信息)。但是,在您的示例中,您甚至没有创建新类型。Time
和Length
类都将返回一个pint。util.数量对象。因此,下面的例子将总是引发错误,即使对于Time
定义的对象也是如此:解决方案
1.一个简单的建议是定义一个检查数量类型的函数:
然后简单地在你选择的方法中调用它:
1.如果你需要保留你的类定义,一个更优雅的解决方案是将你的数量类定义为e。例如
MyQuantity
,带有一个类属性_qtype
,您可以在每个类型定义中更改该属性。这样,就可以避免多次编写相同的错误代码片段。此外,您可以将qtype_check合并到类中,并通过您选择的类型类(例如:例如Time.qtype_check(value)
),避免了每次都要记住如何定义类类型字符串的麻烦。以下是定义:1.最好的方法是简单地使用pint提供的 Package 器函数,它可以自动为您检查输入单位!这里是文档。在你的例子中,它看起来像: