有没有办法在Python中通过类型提示来指定函数参数的有效值范围?

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

我非常喜欢 python 中的类型提示,但是我很好奇是否有一种方法可以使用类型提示为给定参数指定有效的值范围。

我的想法是这样的

from typing import *

def function(
        number: Union[float, int],
        fraction: Float[0.0, 1.0] = 0.5 # give a hint that this should be between 0 and 1,
):
    return fraction * number

我可以想象可以通过断言强制执行此操作,或者指定文档字符串中值的有效范围,但感觉像 Float[0.0, 1.0] 这样的东西看起来会更优雅。

python python-3.x type-hinting
2个回答
17
投票

Python 3.9 引入

typing.Annotated
:

In [75]: from typing import *

In [76]: from dataclasses import dataclass

In [77]: @dataclass
    ...: class ValueRange:
    ...:     min: float
    ...:     max: float
    ...:

In [78]: def function(
    ...:         number: Union[float, int],
    ...:         fraction: Annotated[float, ValueRange(0.0, 1.0)] = 0.5
    ...: ):
    ...:     return fraction * number
    ...:

与任何其他类型提示一样,它不执行任何运行时检查:

In [79]: function(1, 2)
Out[79]: 2

但是您可以实现自己的运行时检查。下面的代码只是一个示例,它并没有涵盖所有情况,并且对于您的简单功能来说可能有点过大了:

In [88]: import inspect

In [89]: @dataclass
    ...: class ValueRange:
    ...:     min: float
    ...:     max: float
    ...:
    ...:     def validate_value(self, x):
    ...:         if not (self.min <= x <= self.max):
    ...:             raise ValueError(f'{x} must be in range [{self.min}, {self.max}]')
    ...:

In [90]: def check_annotated(func):
    ...:     hints = get_type_hints(func, include_extras=True)
    ...:     spec = inspect.getfullargspec(func)
    ...:
    ...:     def wrapper(*args, **kwargs):
    ...:         for idx, arg_name in enumerate(spec[0]):
    ...:             hint = hints.get(arg_name)
    ...:             validators = getattr(hint, '__metadata__', None)
    ...:             if not validators:
    ...:                 continue
    ...:             for validator in validators:
    ...:                 validator.validate_value(args[idx])
    ...:
    ...:         return func(*args, **kwargs)
    ...:     return wrapper
    ...:
    ...:

In [91]: @check_annotated
    ...: def function_2(
    ...:         number: Union[float, int],
    ...:         fraction: Annotated[float, ValueRange(0.0, 1.0)] = 0.5
    ...: ):
    ...:     return fraction * number
    ...:
    ...:

In [92]: function_2(1, 2)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-92-c9345023c025> in <module>
----> 1 function_2(1, 2)

<ipython-input-90-01115cb628ba> in wrapper(*args, **kwargs)
     10                 continue
     11             for validator in validators:
---> 12                 validator.validate_value(args[idx])
     13
     14         return func(*args, **kwargs)

<ipython-input-87-7f4ac07379f9> in validate_value(self, x)
      6     def validate_value(self, x):
      7         if not (self.min <= x <= self.max):
----> 8             raise ValueError(f'{x} must be in range [{self.min}, {self.max}]')
      9

ValueError: 2 must be in range [0.0, 1.0]

In [93]: function_2(1, 1)
Out[93]: 1

1
投票

如果可以并且不介意使用第三方软件包,Pydantic提供约束类型。对于您的具体示例,约束类型之一是

confloat
,具有以下参数:

  • ge: float = None
    :强制float大于等于设定值
  • lt: float = None
    :强制float小于设定值
In [35]: from pydantic import confloat

In [36]: def function(
    ...:     number: Union[float, int],
    ...:     fraction: confloat(ge=0.0, le=1.0) = 0.5,
    ...: ) -> float:
    ...:     return fraction * number

如果仅用作类型提示,则不会在运行时强制执行:

In [38]: function(1, 0)
Out[38]: 0

In [39]: function(1, 1.0)
Out[39]: 1.0

In [40]: function(1, 15)
Out[40]: 15

但是你可以使用 Pydantic 的

validate_arguments
装饰器:

允许在调用函数之前使用函数的注释来解析和验证传递给函数的参数

In [41]: from pydantic import confloat, validate_arguments

In [42]: @validate_arguments
    ...: def function(
    ...:     number: Union[float, int],
    ...:     fraction: confloat(ge=0.0, le=1.0) = 0.5,
    ...: ) -> float:
    ...:     return fraction * number
    ...: 

In [43]: function(1, 0)
Out[43]: 0.0

In [44]: function(1, 1.0)
Out[44]: 1.0

In [45]: function(1, 0.37)
Out[45]: 0.37

In [46]: function(1, 15)
---------------------------------------------------------------------------
ValidationError                           Traceback (most recent call last)
Cell In [46], line 1
----> 1 function(1, 15)

...
ValidationError: 1 validation error for Function
fraction
  ensure this value is less than or equal to 1.0 (type=value_error.number.not_le; limit_value=1.0)

请参阅 ConstrainedTypes 部分了解更多

con*
变化。

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