在Python中检查函数参数的最佳方法

Best way to check function arguments in Python

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

1
2
3
4
5
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

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

熊的打字意外真棒的盛宴:

1
2
3
4
5
>>> @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">

如本示例所示,熊类型明确支持参数的类型检查,并返回注释为此类型的简单类型或元组的值。天哪!

O.K.,这实际上并不那么令人印象深刻。 @beartype类似于基于PEP 484样式类型提示的所有其他Python 3.x特定类型检查装饰器,其中包含少于275行的纯Python。什么是擦,泡沫?

Pure Bruteforce Hardcore效率

在我的有限领域知识中,熊类型在空间和时间方面都比使用Python中的所有现有类型检查实现更有效。 (稍后会详细介绍。)

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

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

类型检查也不例外。类型检查会增加应用程序检查的每个函数调用类型的开销 - 理想情况下,所有函数调用类型。为了防止善意(但可悲的是心胸狭隘)的同事在上周五将咖啡因添加的allnighter添加到您的老年传统Django网络应用程序之后删除您默认添加的类型检查,类型检查必须快速。如此之快,以至于当你在不告诉任何人的情况下添加它时,没有人注意到它就在那里。我一直这样做!如果你是同事,请停止阅读。

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

1
2
3
4
$ 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中类型检查的手动方法。熊类型旨在不施加性能损失,兼容性约束或第三方依赖性(无论如何都超过了手动方法所强加的)。熊类型可以无缝地集成到现有的代码库和测试套件中而无需修改。

每个人都可能熟悉手动方法。您手动assert传递给每个参数的每个参数和/或返回代码库中每个函数返回的值。什么样板可能更简单或更平庸?我们都已经看过它一次了googleplex次,并且每当我们做的时候在我们嘴里吐了一点。重复变老了。干,哟。

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

1
2
3
4
5
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,对吗?我们许多人都通过了这门课。

Bear输入将上述方法手动执行的类型检查提取到动态定义的包装函数中,自动执行相同的检查 - 增加了粒度TypeError而不是模糊的AssertionError异常。以下是自动化方法的样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

  • 检查原始功能的签名和注释。
  • 动态构造包装函数类型的主体,检查原始函数。 Thaaat是对的。 Python代码生成Python代码。
  • 通过exec()内置函数动态声明此包装函数。
  • 返回此包装函数。
  • 我们可以?让我们深入了解深渊。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    # 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

    而leycec说,让@beartype快速进行类型检查:就是这样。

    警告,诅咒和空洞的承诺

    没有什么是完美的。即使是熊打字。

    警告I:未选中默认值

    Bear输入不会键入检查未分配参数分配的默认值。从理论上讲,它可以。但不是275行或更少,当然不是作为stackoverflow的答案。

    安全(可能完全不安全)的假设是功能实现者声称他们在定义默认值时知道他们在做什么。由于默认值通常是常量(...它们最好是!),重新检查在每个函数调用中永不改变的常量类型,分配一个或多个默认值将违反熊类型的基本原则:"不要重复你自己过来了,oooover再次oooo-oooover。"

    告诉我错了,我会用upvotes淋浴你。

    警告II:没有PEP 484

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

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

    然而,熊类型确实支持类型的联合,就像isinstance()内置支持类型的联合一样:作为元组。这表面上对应于typing.Union类型 - 明显需要注意typing.Union支持任意复杂类型,而@beartype接受的元组仅支持简单类。在我的辩护中,275线。

    测试或它没有发生

    这是它的要点。得到它,要点?我现在就停下来。

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

    现在强制性的脖子咆哮没有人要求。

    API暴力史

    Python 3.5没有为使用PEP 484类型提供实际支持。笏?

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

  • 兼容性惩罚。正如官方mypy FAQ在回答常见问题时所承认的那样:"我可以使用mypy来键入检查我现有的Python代码吗?":"这取决于。兼容性非常好,但某些Python功能尚未实现或完全支持。"随后的FAQ响应通过声明:

  • "......你的代码必须使属性显式化,并使用显式的协议表示。"语法警察看到你的"一个明确的",并提出你一个隐含的皱眉。
  • "Mypy将支持模块化,高效的类型检查,这似乎排除了类型检查某些语言功能,例如方法的任意运行时添加。但是,很可能许多这些功能将以受限形式支持(例如,运行时修改仅支持注册为动态或"patchable"的类或方法。"
  • 有关语法不兼容性的完整列表,请参阅"处理常见问题"。它不漂亮。你只是想要进行类型检查,现在你重新构建了整个代码库,并在候选版本发布后的两天内打破了每个人的构建,穿着休闲商务服装的人力资源侏儒在你的隔间和手铐中滑出一条粉红色的滑动。非常感谢,mypy。
  • 尽管解释了静态类型代码,但性能损失仍然存在。四十年来顽固的计算机科学告诉我们(......其他条件相同)解释静态类型代码应该比解释动态类型代码更快,而不是更慢。在Python中,up是新的下降。
  • 其他非平凡的依赖关系,增加:

  • 项目部署中充满漏洞的脆弱性,特别是跨平台。
  • 项目开发的维护负担。
  • 可能的攻击面。
  • 我问Guido:"为什么?如果你不愿意用一个具体的API来实现那个抽象的东西,为什么还要发明一个抽象的API呢?"为什么要把一百万Pythonistas的命运留给自由开源市场的关节炎手?为什么要在官方Python stdlib中创建另一个可以通过275行装饰器轻松解决的技术问题?

    我没有Python,我必须尖叫。

    好。


    最Pythonic的习惯用法是清楚地记录函数所期望的内容,然后尝试使用传递给函数的任何内容,并让异常传播或者只是捕获属性错误并引发TypeError。应尽可能避免类型检查,因为它违反鸭子类型。价值测试可以 - 根据具体情况而定。

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


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

    类型检查通常不是Pythonic。在Python中,更常见的是使用duck typing。例:

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

    1
    2
    def my_function(a):
        return a + 7

    这意味着您的函数不仅可以使用整数,还可以使用浮点数和任何定义了__add__方法的用户定义类,因此如果您或其他人想要扩展,则必须进行更少(有时无需)你的功能与其他东西一起工作。但是,在某些情况下,您可能需要int,那么您可以执行以下操作:

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

    并且该函数仍适用于定义__int__方法的任何a

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

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

    要么

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    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))


    一种方法是使用assert

    1
    2
    3
    4
    5
    6
    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'


    您可以使用Type Enforcement接受/返回装饰器
    PythonDecoratorLibrary
    它非常简单易读:

    1
    2
    3
    @accepts(int, int, float)
    def myfunc(i1, i2, i3):
        pass


    有多种方法可以检查Python中的变量。所以,列举一些:

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

    • issubclass(obj, class)接受变量obj的函数,如果objclass的子类,则为True。所以例如issubclass(Rabbit, Animal)会给你一个True

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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语句。


    我最近对这个话题做了很多调查,因为我对那里发现的很多图书馆都不满意。

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

    这是您在单独使用valid8执行验证的方法(实际上,mini_lambda,以定义验证逻辑 - 但不是必需的)在您的情况下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    # 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的相同示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    # 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].

    通常,你做这样的事情:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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(它必须给出断言错误)


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


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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

    并使用它:

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

    希望这可以帮助!


    1
    2
    3
    4
    def someFunc(a, b, c):
        params = locals()
        for _item in params:
            print type(params[_item]), _item, params[_item]

    演示:

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

    更多关于当地人()


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

    然后使用type()检查参数,例如在迭代dict时。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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...

    1
    2
    3
    4
    5
    6
    7
    8
    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