python-3.x 打字和品脱

v8wbuo2f  于 2023-05-02  发布在  Python
关注(0)|答案(1)|浏览(104)

我使用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
所以我尝试从我的TimeLength类中删除基类Quantity(即:e:它们现在从object而不是Quantity派生),在这种情况下,mypy正确地管理键入错误。
但是当我尝试类似Length("60km")/Time("1h")的东西时,它又失败了。mypy抱怨Length对象没有实现执行该除法所需的方法(尽管代码在运行时工作正常,因为毕竟LengthTime__new__()方法返回了一个Quantity对象,该对象 * 确实 * 实现了算术运算)。
那么,有没有什么变通方法可以让这个想法在运行时和mypy上都能工作呢?

e4yzc0pl

e4yzc0pl1#

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

讨论

一般来说,python不强制输入提示(参见官方文档here)。如果你想强制执行这样的行为,最好的办法就是使用isinstance()原生方法(在这里找到详细信息)。但是,在您的示例中,您甚至没有创建新类型。TimeLength类都将返回一个pint。util.数量对象。因此,下面的例子将总是引发错误,即使对于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}"

1.如果你需要保留你的类定义,一个更优雅的解决方案是将你的数量类定义为e。例如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}"

1.最好的方法是简单地使用pint提供的 Package 器函数,它可以自动为您检查输入单位!这里是文档。在你的例子中,它看起来像:

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

相关问题