打字和品脱



我使用pint来使用和转换单位。我想创建将数量限制为"0"的类;[时间]";或";[长度]";尺寸,所以作为第一种方法,我做了以下操作:

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. 为什么mypy没有发现错误
  2. 如何正确操作

对于1。我猜想在pint的Quantity实现中发生了一些可疑的事情。我试过了:

foo = Quantity("3 pounds")
reveal_type(foo)

所揭示的类型是CCD_ 7而不是非常可疑的CCD_。

因此,我尝试从TimeLength类中删除基类Quantity(即:它们现在从object而不是Quantity派生),在这种情况下,mypy正确地管理了键入错误。

但当我尝试类似Length("60km")/Time("1h")的东西时,它又失败了。mypy抱怨Length对象没有实现执行该除法所需的方法(尽管代码在运行时工作正常,因为毕竟LengthTime__new__()方法返回了一个Quantity对象,确实实现了算术运算)。

那么,再一次,有什么变通方法可以让这个想法在运行时和mypy中都有效吗?

TL;DR:Pint有一个包装器函数可以为您做到这一点:@ureg.check('[time]'),它位于函数定义的正上方。

讨论

一般来说,python不强制执行键入提示(请参阅此处的官方文档)。如果您想强制执行这样的行为,最好的方法就是使用isinstance()本机方法(请在此处查找详细信息)。但是,在您的示例中,您甚至没有创建一个新类型。TimeLength类都将返回一个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]")

解决方案

  1. 一个简单的建议是定义一个检查数量类型的函数:

    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}"
    
  2. 如果您需要保留类定义,一个更优雅的解决方案是将数量类额外定义为例如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}"
    
  3. 最棒的是简单地使用pint提供的包装器功能,它可以自动为您检查输入单位!这是文件。在你的情况下,它看起来像:

    @ureg.check('[time]')
    def myfunc(t: Quantity) -> str:
    return f"The duration is {t}"
    

相关内容

  • 没有找到相关文章

最新更新