85

我正在寻找一种有效的方法来检查 Python 函数的变量。例如,我想检查参数类型和值。有这个模块吗?或者我应该使用装饰器之类的东西,还是任何特定的成语?

def my_function(a, b, c):
    """An example function I'd like to check the arguments of."""
    # check that a is an int
    # check that 0 < b < 10
    # check that c is not an empty string
4

14 回答 14

121

在这个加长的答案中,我们在不到 275 行纯 Python(其中大部分是解释性文档字符串和注释)中实现了一个基于PEP 484样式类型提示的 Python 3.x 特定类型检查装饰器——针对工业进行了高度优化——强度真实世界的使用完成了一个py.test驱动的测试套件来执行所有可能的边缘情况。

尽情享受熊打字带来的惊喜:

>>> @beartype
... def spirit_bear(kermode: str, gitgaata: (str, int)) -> tuple:
...     return (kermode, gitgaata, "Moksgm'ol", 'Ursus americanus kermodei')
>>> spirit_bear(0xdeadbeef, 'People of the Cane')
AssertionError: parameter kermode=0xdeadbeef not of <class "str">

正如这个例子所暗示的,熊类型显式地支持参数的类型检查,返回值被注释为简单类型或此类类型的元组。发誓!

好吧,这实际上并不令人印象深刻。@beartype类似于所有其他基于PEP 484风格的类型提示的 Python 3.x 特定类型检查装饰器,在不到 275 行的纯 Python 中。那么问题是什么,小伙子?

纯蛮力硬核效率

就我有限的领域知识而言,熊打字在空间和时间上都比 Python 中所有现有的类型检查实现更有效。(稍后会详细介绍。

然而,效率在 Python 中通常并不重要。如果是这样,您将不会使用 Python。类型检查实际上是否偏离了 Python 中避免过早优化的公认规范?是的。是的,它确实。

考虑分析,这会为每个感兴趣的分析度量(例如,函数调用、行)增加不可避免的开销。为了确保准确的结果,通过利用优化的 C 扩展(例如,模块_lsprof利用的 C 扩展cProfile)而不是未优化的纯 Python(例如,profile模块)来减轻这种开销。分析时效率确实很重要

类型检查也不例外。类型检查会为您的应用程序检查的每个函数调用类型增加开销——理想情况下,所有这些函数调用类型。为了防止好心(但可悲的是心胸狭隘)的同事删除您在上周五对您的老年病遗留 Django Web 应用程序的咖啡因沉迷的通宵后默默添加的类型检查,类型检查必须快速。速度如此之快,以至于当您在不告诉任何人的情况下添加它时,没有人注意到它的存在。我一直这样做!如果您是同事,请停止阅读本文。

但是,如果对于你贪吃的应用程序来说,即使是可笑的速度还不够,可以通过启用 Python 优化(例如,通过将-O选项传递给 Python 解释器)来全局禁用熊输入:

$ python3 -O
# This succeeds only when type checking is optimized away. See above!
>>> spirit_bear(0xdeadbeef, 'People of the Cane')
(0xdeadbeef, 'People of the Cane', "Moksgm'ol", 'Ursus americanus kermodei')

只是因为。欢迎熊打字。

什么...?为什么是“熊”?你是颈须,对吧?

熊打字是裸机类型检查——也就是说,类型检查尽可能接近 Python 中类型检查的手动方法。Bear 类型的目的是不施加性能损失、兼容性限制或第三方依赖项(无论如何,超出手动方法所施加的范围)。熊打字可以无缝集成到现有的代码库和测试套件中,无需修改。

每个人可能都熟悉手动方法。您手动传递给代码库中每个assert函数的每个参数和/或返回值。什么样板可以更简单或更平庸?我们都在 googleplex 上看过一百次,每次都在嘴里吐一点。重复会很快变老。,哟。

准备好你的呕吐袋。为简洁起见,我们假设一个简化的easy_spirit_bear()函数只接受一个str参数。手动方法如下所示:

def easy_spirit_bear(kermode: str) -> str:
    assert isinstance(kermode, str), 'easy_spirit_bear() parameter kermode={} not of <class "str">'.format(kermode)
    return_value = (kermode, "Moksgm'ol", 'Ursus americanus kermodei')
    assert isinstance(return_value, str), 'easy_spirit_bear() return value {} not of <class "str">'.format(return_value)
    return return_value

Python 101,对吧?我们很多人都通过了那门课。

TypeErrorBear 类型将上述方法手动执行的类型检查提取到一个动态定义的包装函数中,自动执行相同的检查——增加了提高粒度而不是模棱两可的AssertionError异常的好处。自动化方法如下所示:

def easy_spirit_bear_wrapper(*args, __beartype_func=easy_spirit_bear, **kwargs):
    if not (
        isinstance(args[0], __beartype_func.__annotations__['kermode'])
        if 0 < len(args) else
        isinstance(kwargs['kermode'], __beartype_func.__annotations__['kermode'])
        if 'kermode' in kwargs else True):
            raise TypeError(
                'easy_spirit_bear() parameter kermode={} not of {!r}'.format(
                args[0] if 0 < len(args) else kwargs['kermode'],
                __beartype_func.__annotations__['kermode']))

    return_value = __beartype_func(*args, **kwargs)

    if not isinstance(return_value, __beartype_func.__annotations__['return']):
        raise TypeError(
            'easy_spirit_bear() return value {} not of {!r}'.format(
                return_value, __beartype_func.__annotations__['return']))

    return return_value

这是长篇大论。但它也基本上*与手动方法一样快。*建议眯眼。

请注意,包装函数中完全缺乏函数检查或迭代,它包含与原始函数相似数量的测试——尽管测试是否以及如何将要进行类型检查的参数传递给当前函数调用。你不可能赢得每一场战斗。

这样的包装函数实际上可以可靠地生成以在少于 275 行的纯 Python 中对任意函数进行类型检查吗?Snake Plisskin说:“真实的故事。有烟吗?”

是的。我可能有胡须。

不,Srsly。为什么是“熊”?

熊打鸭。鸭子可能会飞,但熊可能会向鸭子扔鲑鱼。在加拿大,大自然会给你惊喜。

下一个问题。

无论如何,熊有什么火爆的?

现有的解决方案执行裸机类型检查——至少,我没有遇到过。他们都在每个函数调用上迭代地重新检查类型检查函数的签名。虽然对于单个调用可以忽略不计,但重新检查开销在汇总所有调用时通常是不可忽略的。真的,真的不容小觑。

然而,这不仅仅是效率问题。现有的解决方案也经常无法解决常见的边缘情况。这包括在这里和其他地方作为 stackoverflow 答案提供的大多数(如果不是全部)玩具装饰器。经典失败包括:

  • 未能键入检查关键字参数和/或返回值(例如,sweeneyrod@checkargs装饰器)。
  • isinstance()无法支持内置函数接受的类型的元组(即联合) 。
  • 未能将名称、文档字符串和其他标识元数据从原始函数传播到包装函数。
  • 未能提供至少类似的单元测试。(有点批评。
  • 在类型检查失败时引发通用AssertionError异常而不是特定异常。TypeError为了粒度和完整性,类型检查永远不应该引发通用异常。

熊打字在非熊打字失败的地方成功。都一个,所有的熊!

熊打字裸露

熊类型将检查函数签名的空间和时间成本从函数调用时间转移到函数定义时间——也就是说,从装饰器返回的包装函数@beartype到装饰器本身。由于每个函数定义只调用一次装饰器,因此这种优化会为所有人带来欢乐。

熊打字是一种尝试让你的类型检查蛋糕并吃掉它。为此,@beartype

  1. 检查原始函数的签名和注释。
  2. 动态构造包装函数类型的主体,检查原始函数。塔亚特是对的。Python 代码生成 Python 代码。
  3. exec()通过内置动态声明这个包装函数。
  4. 返回此包装函数。

我们可以?让我们深入了解一下。

# If the active Python interpreter is *NOT* optimized (e.g., option "-O" was
# *NOT* passed to this interpreter), enable type checking.
if __debug__:
    import inspect
    from functools import wraps
    from inspect import Parameter, Signature

    def beartype(func: callable) -> callable:
        '''
        Decorate the passed **callable** (e.g., function, method) to validate
        both all annotated parameters passed to this callable _and_ the
        annotated value returned by this callable if any.

        This decorator performs rudimentary type checking based on Python 3.x
        function annotations, as officially documented by PEP 484 ("Type
        Hints"). While PEP 484 supports arbitrarily complex type composition,
        this decorator requires _all_ parameter and return value annotations to
        be either:

        * Classes (e.g., `int`, `OrderedDict`).
        * Tuples of classes (e.g., `(int, OrderedDict)`).

        If optimizations are enabled by the active Python interpreter (e.g., due
        to option `-O` passed to this interpreter), this decorator is a noop.

        Raises
        ----------
        NameError
            If any parameter has the reserved name `__beartype_func`.
        TypeError
            If either:
            * Any parameter or return value annotation is neither:
              * A type.
              * A tuple of types.
            * The kind of any parameter is unrecognized. This should _never_
              happen, assuming no significant changes to Python semantics.
        '''

        # Raw string of Python statements comprising the body of this wrapper,
        # including (in order):
        #
        # * A "@wraps" decorator propagating the name, docstring, and other
        #   identifying metadata of the original function to this wrapper.
        # * A private "__beartype_func" parameter initialized to this function.
        #   In theory, the "func" parameter passed to this decorator should be
        #   accessible as a closure-style local in this wrapper. For unknown
        #   reasons (presumably, a subtle bug in the exec() builtin), this is
        #   not the case. Instead, a closure-style local must be simulated by
        #   passing the "func" parameter to this function at function
        #   definition time as the default value of an arbitrary parameter. To
        #   ensure this default is *NOT* overwritten by a function accepting a
        #   parameter of the same name, this edge case is tested for below.
        # * Assert statements type checking parameters passed to this callable.
        # * A call to this callable.
        # * An assert statement type checking the value returned by this
        #   callable.
        #
        # While there exist numerous alternatives (e.g., appending to a list or
        # bytearray before joining the elements of that iterable into a string),
        # these alternatives are either slower (as in the case of a list, due to
        # the high up-front cost of list construction) or substantially more
        # cumbersome (as in the case of a bytearray). Since string concatenation
        # is heavily optimized by the official CPython interpreter, the simplest
        # approach is (curiously) the most ideal.
        func_body = '''
@wraps(__beartype_func)
def func_beartyped(*args, __beartype_func=__beartype_func, **kwargs):
'''

        # "inspect.Signature" instance encapsulating this callable's signature.
        func_sig = inspect.signature(func)

        # Human-readable name of this function for use in exceptions.
        func_name = func.__name__ + '()'

        # For the name of each parameter passed to this callable and the
        # "inspect.Parameter" instance encapsulating this parameter (in the
        # passed order)...
        for func_arg_index, func_arg in enumerate(func_sig.parameters.values()):
            # If this callable redefines a parameter initialized to a default
            # value by this wrapper, raise an exception. Permitting this
            # unlikely edge case would permit unsuspecting users to
            # "accidentally" override these defaults.
            if func_arg.name == '__beartype_func':
                raise NameError(
                    'Parameter {} reserved for use by @beartype.'.format(
                        func_arg.name))

            # If this parameter is both annotated and non-ignorable for purposes
            # of type checking, type check this parameter.
            if (func_arg.annotation is not Parameter.empty and
                func_arg.kind not in _PARAMETER_KIND_IGNORED):
                # Validate this annotation.
                _check_type_annotation(
                    annotation=func_arg.annotation,
                    label='{} parameter {} type'.format(
                        func_name, func_arg.name))

                # String evaluating to this parameter's annotated type.
                func_arg_type_expr = (
                    '__beartype_func.__annotations__[{!r}]'.format(
                        func_arg.name))

                # String evaluating to this parameter's current value when
                # passed as a keyword.
                func_arg_value_key_expr = 'kwargs[{!r}]'.format(func_arg.name)

                # If this parameter is keyword-only, type check this parameter
                # only by lookup in the variadic "**kwargs" dictionary.
                if func_arg.kind is Parameter.KEYWORD_ONLY:
                    func_body += '''
    if {arg_name!r} in kwargs and not isinstance(
        {arg_value_key_expr}, {arg_type_expr}):
        raise TypeError(
            '{func_name} keyword-only parameter '
            '{arg_name}={{}} not a {{!r}}'.format(
                {arg_value_key_expr}, {arg_type_expr}))
'''.format(
                        func_name=func_name,
                        arg_name=func_arg.name,
                        arg_type_expr=func_arg_type_expr,
                        arg_value_key_expr=func_arg_value_key_expr,
                    )
                # Else, this parameter may be passed either positionally or as
                # a keyword. Type check this parameter both by lookup in the
                # variadic "**kwargs" dictionary *AND* by index into the
                # variadic "*args" tuple.
                else:
                    # String evaluating to this parameter's current value when
                    # passed positionally.
                    func_arg_value_pos_expr = 'args[{!r}]'.format(
                        func_arg_index)

                    func_body += '''
    if not (
        isinstance({arg_value_pos_expr}, {arg_type_expr})
        if {arg_index} < len(args) else
        isinstance({arg_value_key_expr}, {arg_type_expr})
        if {arg_name!r} in kwargs else True):
            raise TypeError(
                '{func_name} parameter {arg_name}={{}} not of {{!r}}'.format(
                {arg_value_pos_expr} if {arg_index} < len(args) else {arg_value_key_expr},
                {arg_type_expr}))
'''.format(
                    func_name=func_name,
                    arg_name=func_arg.name,
                    arg_index=func_arg_index,
                    arg_type_expr=func_arg_type_expr,
                    arg_value_key_expr=func_arg_value_key_expr,
                    arg_value_pos_expr=func_arg_value_pos_expr,
                )

        # If this callable's return value is both annotated and non-ignorable
        # for purposes of type checking, type check this value.
        if func_sig.return_annotation not in _RETURN_ANNOTATION_IGNORED:
            # Validate this annotation.
            _check_type_annotation(
                annotation=func_sig.return_annotation,
                label='{} return type'.format(func_name))

            # Strings evaluating to this parameter's annotated type and
            # currently passed value, as above.
            func_return_type_expr = (
                "__beartype_func.__annotations__['return']")

            # Call this callable, type check the returned value, and return this
            # value from this wrapper.
            func_body += '''
    return_value = __beartype_func(*args, **kwargs)
    if not isinstance(return_value, {return_type}):
        raise TypeError(
            '{func_name} return value {{}} not of {{!r}}'.format(
                return_value, {return_type}))
    return return_value
'''.format(func_name=func_name, return_type=func_return_type_expr)
        # Else, call this callable and return this value from this wrapper.
        else:
            func_body += '''
    return __beartype_func(*args, **kwargs)
'''

        # Dictionary mapping from local attribute name to value. For efficiency,
        # only those local attributes explicitly required in the body of this
        # wrapper are copied from the current namespace. (See below.)
        local_attrs = {'__beartype_func': func}

        # Dynamically define this wrapper as a closure of this decorator. For
        # obscure and presumably uninteresting reasons, Python fails to locally
        # declare this closure when the locals() dictionary is passed; to
        # capture this closure, a local dictionary must be passed instead.
        exec(func_body, globals(), local_attrs)

        # Return this wrapper.
        return local_attrs['func_beartyped']

    _PARAMETER_KIND_IGNORED = {
        Parameter.POSITIONAL_ONLY, Parameter.VAR_POSITIONAL, Parameter.VAR_KEYWORD,
    }
    '''
    Set of all `inspect.Parameter.kind` constants to be ignored during
    annotation- based type checking in the `@beartype` decorator.

    This includes:

    * Constants specific to variadic parameters (e.g., `*args`, `**kwargs`).
      Variadic parameters cannot be annotated and hence cannot be type checked.
    * Constants specific to positional-only parameters, which apply to non-pure-
      Python callables (e.g., defined by C extensions). The `@beartype`
      decorator applies _only_ to pure-Python callables, which provide no
      syntactic means of specifying positional-only parameters.
    '''

    _RETURN_ANNOTATION_IGNORED = {Signature.empty, None}
    '''
    Set of all annotations for return values to be ignored during annotation-
    based type checking in the `@beartype` decorator.

    This includes:

    * `Signature.empty`, signifying a callable whose return value is _not_
      annotated.
    * `None`, signifying a callable returning no value. By convention, callables
      returning no value are typically annotated to return `None`. Technically,
      callables whose return values are annotated as `None` _could_ be
      explicitly checked to return `None` rather than a none-`None` value. Since
      return values are safely ignorable by callers, however, there appears to
      be little real-world utility in enforcing this constraint.
    '''

    def _check_type_annotation(annotation: object, label: str) -> None:
        '''
        Validate the passed annotation to be a valid type supported by the
        `@beartype` decorator.

        Parameters
        ----------
        annotation : object
            Annotation to be validated.
        label : str
            Human-readable label describing this annotation, interpolated into
            exceptions raised by this function.

        Raises
        ----------
        TypeError
            If this annotation is neither a new-style class nor a tuple of
            new-style classes.
        '''

        # If this annotation is a tuple, raise an exception if any member of
        # this tuple is not a new-style class. Note that the "__name__"
        # attribute tested below is not defined by old-style classes and hence
        # serves as a helpful means of identifying new-style classes.
        if isinstance(annotation, tuple):
            for member in annotation:
                if not (
                    isinstance(member, type) and hasattr(member, '__name__')):
                    raise TypeError(
                        '{} tuple member {} not a new-style class'.format(
                            label, member))
        # Else if this annotation is not a new-style class, raise an exception.
        elif not (
            isinstance(annotation, type) and hasattr(annotation, '__name__')):
            raise TypeError(
                '{} {} neither a new-style class nor '
                'tuple of such classes'.format(label, annotation))

# Else, the active Python interpreter is optimized. In this case, disable type
# checking by reducing this decorator to the identity decorator.
else:
    def beartype(func: callable) -> callable:
        return func

莱切克说,让我们 快速@beartype进行类型检查:结果就是这样。

警告、诅咒和空洞的承诺

没有什么是完美的。甚至不忍打字。

警告 I:未选中默认值

熊打字不检查未通过的参数分配的默认值。理论上是可以的。但不是 275 行或更少,当然也不是 stackoverflow 的答案。

安全(...可能完全不安全)假设是函数实现者声称他们在定义默认值时知道自己在做什么。由于默认值通常是常量(......最好是!),重新检查在每次分配一个或多个默认值的函数调用上永远不会改变的常量类型将违反熊打字的基本原则:“不要重复你自己一遍又一遍,又一遍又一遍。”

告诉我错了,我会给你点赞。

警告二:没有 PEP 484

PEP 484“类型提示”)形式化了函数注释的使用,最初由PEP 3107“函数注释”)引入。Python 3.5 表面上通过一个新的顶级typing模块支持这种形式化,这是一个标准 API,用于从更简单的类型组合任意复杂的类型(例如,Callable[[Arg1Type, Arg2Type], ReturnType]一种描述接受两个类型参数Arg1TypeArg2Type返回一个类型值的函数的类型ReturnType)。

熊打字不支持它们。理论上是可以的。但不是 275 行或更少,当然也不是 stackoverflow 的答案。

但是,熊类型确实支持类型的联合,就像isinstance()内置支持类型的联合一样:作为元组。这表面上对应于typing.Union类型——明显的警告是typing.Union支持任意复杂的类型,而接受的元组@beartype支持简单的类。在我看来,275 行。

测试或没有发生

这是它的要点明白了吗,要点?我现在就停下来。

@beartype装饰器本身一样,这些py.test测试可以无缝集成到现有的测试套件中而无需修改。珍贵,不是吗?

现在没有人要求强制性的胡须咆哮。

API 暴力的历史

Python 3.5 不提供对使用 PEP 484 类型的实际支持。笏?

这是真的:没有类型检查,没有类型推断,没有类型。相反,开发人员应该通过实现此类支持的传真(例如mypy)的重量级第三方 CPython 解释器包装器定期运行他们的整个代码库。当然,这些包装器强加:

  • 兼容性惩罚。正如官方 mypy 常见问题解答在回答常见问题“我可以使用 mypy 类型检查我现有的 Python 代码吗?”时承认的那样:“这取决于。兼容性很好,但一些 Python 功能尚未实现或完全支持。” 随后的常见问题解答通过说明:
    • “...您的代码必须明确属性并使用明确的协议表示。” 语法警察看到你的“一个明确的”,并给你一个含蓄的皱眉。
    • “Mypy 将支持模块化、高效的类型检查,这似乎排除了对某些语言特性进行类型检查的可能性,例如任意运行时添加方法。但是,这些特性中的许多可能会以受限的形式得到支持(例如,运行时修改仅支持注册为动态或“可修补”的类或方法)。”
    • 有关语法不兼容性的完整列表,请参阅“处理常见问题”。这漂亮。你只需要类型检查,现在你重构了整个代码库,并在候选人发布两天后破坏了每个人的构建,而穿着休闲商务装的漂亮 HR 侏儒从你的隔间兼人形洞穴的裂缝中滑出一条粉红色的幻灯片。非常感谢,mypy。
  • 尽管解释了静态类型的代码,但性能损失。40 年的硬核计算机科学告诉我们(……在其他条件相同的情况下)解释静态类型的代码应该比解释动态类型的代码更快,而不是更慢。在 Python 中,向上是新的向下。
  • 额外的重要依赖项,增加:
    • 项目部署的漏洞百出,尤其是跨平台。
    • 项目开发的维护负担。
    • 可能的攻击面。

我问 Guido:“为什么?如果你不愿意花钱买一个具体的 API,实际上用那个抽象做一些事情,为什么还要发明一个抽象的 API?” 为什么要将一百万 Pythonista 的命运留给自由开源市场的关节炎之手?为什么要创建另一个可以用官方 Python 标准库中的 275 行装饰器轻松解决的技术问题?

我没有 Python,我必须尖叫。

于 2016-06-22T07:21:23.743 回答
90

最 Pythonic 的习惯用法是清楚地记录函数期望什么,然后尝试使用传递给函数的任何内容,要么让异常传播,要么只捕获属性错误并引发 a TypeError。应尽可能避免类型检查,因为它与鸭子类型相违背。价值测试是可以的——取决于上下文。

验证真正有意义的唯一地方是系统或子系统入口点,例如 Web 表单、命令行参数等。在其他任何地方,只要您的函数被正确记录,调用者有责任传递适当的参数。

于 2013-10-30T14:16:03.103 回答
31

编辑:截至 2019 年,更多支持在 Python 中使用类型注释和静态检查;查看打字模块和mypy。2013年的答案如下:


类型检查通常不是 Pythonic。在 Python 中,使用鸭子类型更为常见。例子:

在您的代码中,假设参数(在您的示例中a)像 an 一样走路int并且像 an 一样嘎嘎作响int。例如:

def my_function(a):
    return a + 7

这意味着您的函数不仅适用于整数,它还适用于浮点数和任何定义了__add__方法的用户定义类,因此如果您或其他人想要将您的函数扩展到与其他东西一起工作。但是,在某些情况下,您可能需要一个int,因此您可以执行以下操作:

def my_function(a):
    b = int(a) + 7
    c = (5, 6, 3, 123541)[b]
    return c

并且该功能仍然适用于任何a定义该__int__方法的人。

在回答您的其他问题时,我认为这是最好的(正如其他答案所说的那样:

def my_function(a, b, c):
    assert 0 < b < 10
    assert c        # A non-empty string has the Boolean value True

或者

def my_function(a, b, c):
    if 0 < b < 10:
        # Do stuff with b
    else:
        raise ValueError
    if c:
        # Do stuff with c
    else:
        raise ValueError

我制作的一些类型检查装饰器:

import inspect

def checkargs(function):
    def _f(*arguments):
        for index, argument in enumerate(inspect.getfullargspec(function)[0]):
            if not isinstance(arguments[index], function.__annotations__[argument]):
                raise TypeError("{} is not of type {}".format(arguments[index], function.__annotations__[argument]))
        return function(*arguments)
    _f.__doc__ = function.__doc__
    return _f

def coerceargs(function):
    def _f(*arguments):
        new_arguments = []
        for index, argument in enumerate(inspect.getfullargspec(function)[0]):
            new_arguments.append(function.__annotations__[argument](arguments[index]))
        return function(*new_arguments)
    _f.__doc__ = function.__doc__
    return _f

if __name__ == "__main__":
    @checkargs
    def f(x: int, y: int):
        """
        A doc string!
        """
        return x, y

    @coerceargs
    def g(a: int, b: int):
        """
        Another doc string!
        """
        return a + b

    print(f(1, 2))
    try:
        print(f(3, 4.0))
    except TypeError as e:
        print(e)

    print(g(1, 2))
    print(g(3, 4.0))
于 2013-10-30T14:24:28.313 回答
18

一种方法是使用assert

def myFunction(a,b,c):
    "This is an example function I'd like to check arguments of"
    assert isinstance(a, int), 'a should be an int'
    # or if you want to allow whole number floats: assert int(a) == a
    assert b > 0 and b < 10, 'b should be betwen 0 and 10'
    assert isinstance(c, str) and c, 'c should be a non-empty string'
于 2013-10-30T14:06:11.837 回答
7

您可以使用 PythonDecoratorLibrary中 的类型强制接受/返回装饰器,它非常简单易读:

@accepts(int, int, float)
def myfunc(i1, i2, i3):
    pass
于 2014-03-25T03:08:45.247 回答
5

有不同的方法可以检查 Python 中的变量是什么。所以,列举几个:

  • isinstance(obj, type)函数接受您的变量,obj并为您提供与您列出True的相同类型。type

  • issubclass(obj, class)函数接受一个变量obj,并为您提供Trueifobjclass. 所以例如issubclass(Rabbit, Animal)会给你一个True价值

  • hasattr是另一个例子,由这个函数演示,super_len


def super_len(o):
    if hasattr(o, '__len__'):
        return len(o)

    if hasattr(o, 'len'):
        return o.len

    if hasattr(o, 'fileno'):
        try:
            fileno = o.fileno()
        except io.UnsupportedOperation:
            pass
        else:
            return os.fstat(fileno).st_size

    if hasattr(o, 'getvalue'):
        # e.g. BytesIO, cStringIO.StringI
        return len(o.getvalue())

hasattr更倾向于鸭式打字,以及通常更pythonic但该术语自以为是的东西。

就像一个注释,assert语句通常用于测试,否则,只需使用if/else语句。

于 2013-10-30T14:26:52.890 回答
5

我最近对该主题进行了相当多的调查,因为我对在那里找到的许多并不满意。

我最终开发了一个库来解决这个问题,它被命名为valid8。如文档中所述,它主要用于值验证(尽管它也捆绑了简单的类型验证功能),您可能希望将其与基于 PEP484 的类型检查器相关联,例如enforcepytypes

在您的情况下,这就是您将如何valid8单独执行验证(mini_lambda实际上是定义验证逻辑 - 但它不是强制性的):

# for type validation
from numbers import Integral
from valid8 import instance_of

# for value validation
from valid8 import validate_arg
from mini_lambda import x, s, Len

@validate_arg('a', instance_of(Integral))
@validate_arg('b', (0 < x) & (x < 10))
@validate_arg('c', instance_of(str), Len(s) > 0)
def my_function(a: Integral, b, c: str):
    """an example function I'd like to check the arguments of."""
    # check that a is an int
    # check that 0 < b < 10
    # check that c is not an empty string

# check that it works
my_function(0.2, 1, 'r')  # InputValidationError for 'a' HasWrongType: Value should be an instance of <class 'numbers.Integral'>. Wrong value: [0.2].
my_function(0, 0, 'r')    # InputValidationError for 'b' [(x > 0) & (x < 10)] returned [False]
my_function(0, 1, 0)      # InputValidationError for 'c' Successes: [] / Failures: {"instance_of_<class 'str'>": "HasWrongType: Value should be an instance of <class 'str'>. Wrong value: [0]", 'len(s) > 0': "TypeError: object of type 'int' has no len()"}.
my_function(0, 1, '')     # InputValidationError for 'c' Successes: ["instance_of_<class 'str'>"] / Failures: {'len(s) > 0': 'False'}

这是利用 PEP484 类型提示并将类型检查委托给的相同示例enforce

# for type validation
from numbers import Integral
from enforce import runtime_validation, config
config(dict(mode='covariant'))  # type validation will accept subclasses too

# for value validation
from valid8 import validate_arg
from mini_lambda import x, s, Len

@runtime_validation
@validate_arg('b', (0 < x) & (x < 10))
@validate_arg('c', Len(s) > 0)
def my_function(a: Integral, b, c: str):
    """an example function I'd like to check the arguments of."""
    # check that a is an int
    # check that 0 < b < 10
    # check that c is not an empty string

# check that it works
my_function(0.2, 1, 'r')  # RuntimeTypeError 'a' was not of type <class 'numbers.Integral'>
my_function(0, 0, 'r')    # InputValidationError for 'b' [(x > 0) & (x < 10)] returned [False]
my_function(0, 1, 0)      # RuntimeTypeError 'c' was not of type <class 'str'>
my_function(0, 1, '')     # InputValidationError for 'c' [len(s) > 0] returned [False].
于 2017-12-21T14:52:34.863 回答
4

这会在调用函数时检查输入参数的类型:

def func(inp1:int=0,inp2:str="*"):

    for item in func.__annotations__.keys():
        assert isinstance(locals()[item],func.__annotations__[item])

    return (something)

first=7
second="$"
print(func(first,second))

还要检查second=9(它必须给出断言错误)

于 2017-09-19T22:07:15.370 回答
2

通常,您会执行以下操作:

def myFunction(a,b,c):
   if not isinstance(a, int):
      raise TypeError("Expected int, got %s" % (type(a),))
   if b <= 0 or b >= 10:
      raise ValueError("Value %d out of range" % (b,))
   if not c:
      raise ValueError("String was empty")

   # Rest of function
于 2013-10-30T14:07:53.717 回答
1
def someFunc(a, b, c):
    params = locals()
    for _item in params:
        print type(params[_item]), _item, params[_item]

演示:

>> someFunc(1, 'asd', 1.0)
>> <type 'int'> a 1
>> <type 'float'> c 1.0
>> <type 'str'> b asd

更多关于本地人()

于 2013-10-30T14:08:52.643 回答
0

如果您想一次性检查**kwargs,*args以及普通参数,您可以使用locals()函数作为函数定义中的第一条语句来获取参数字典。

然后用于type()检查参数,例如在迭代字典时。

def myfunc(my, args, to, this, function, **kwargs):
    d = locals()
    assert(type(d.get('x')) == str)
    for x in d:
        if x != 'x':
            assert(type(d[x]) == x
    for x in ['a','b','c']:
        assert(x in d)

    whatever more...
于 2013-10-30T14:06:39.207 回答
0

如果要对多个函数进行验证,可以在装饰器中添加逻辑,如下所示:

def deco(func):
     def wrapper(a,b,c):
         if not isinstance(a, int)\
            or not isinstance(b, int)\
            or not isinstance(c, str):
             raise TypeError
         if not 0 < b < 10:
             raise ValueError
         if c == '':
             raise ValueError
         return func(a,b,c)
     return wrapper

并使用它:

@deco
def foo(a,b,c):
    print 'ok!'

希望这可以帮助!

于 2013-10-30T14:11:07.673 回答
0

这不是您的解决方案,但是如果您想将函数调用限制为某些特定的参数类型,那么您必须使用 PROATOR { Python 函数原型验证器}。您可以参考以下链接。https://github.com/mohit-thakur-721/proator

于 2017-09-25T09:29:16.083 回答
-1
def myFunction(a,b,c):
"This is an example function I'd like to check arguments of"
    if type( a ) == int:
       #dostuff
    if 0 < b < 10:
       #dostuff
    if type( C ) == str and c != "":
       #dostuff
于 2013-10-30T14:08:56.910 回答