打字和品脱

问题描述 投票:0回答:1

我正在使用

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

对于 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
都有效?

python-3.x mypy typing pint
1个回答
0
投票

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]")

解决方案

  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. 最棒的是简单地使用品脱提供的包装器功能,它可以自动为您检查输入单位! 这里是文档。在您的情况下,它看起来像:

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

希望对您有所帮助!

© www.soinside.com 2019 - 2024. All rights reserved.