Programing

Python에서 함수 인수를 확인하는 가장 좋은 방법

lottogame 2021. 1. 5. 07:42
반응형

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

가장 파이썬적인 관용구는 함수가 기대하는 것을 명확하게 문서화 한 다음 함수에 전달되는 모든 것을 사용하고 예외가 전파되도록하거나 속성 오류를 포착하고 TypeError대신 발생시키는 것입니다. 유형 검사는 덕 타이핑에 반대하므로 가능한 한 피해야합니다. 상황에 따라 가치 테스트는 괜찮을 수 있습니다.

유효성 검사가 실제로 의미가있는 유일한 위치는 웹 양식, 명령 줄 인수 등과 같은 시스템 또는 하위 시스템 진입 점입니다. 다른 모든 곳에서는 함수가 적절하게 문서화되어있는 한 적절한 인수를 전달하는 것은 호출자의 책임입니다.


이 길쭉한 답변에서 우리 는 275 줄 미만의 pure-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">

이 예제에서 알 수 있듯이 베어 타이핑은 매개 변수의 유형 검사를 명시 적으로 지원하고 이러한 유형의 단순 유형 또는 튜플로 주석이 달린 반환 값을 반환합니다. Golly!

좋습니다. 실제로 인상적이지 않습니다. 275 줄 미만의 pure-Python 에서 PEP 484 스타일 유형 힌트를 기반으로하는 다른@beartype 모든 Python 3.x 특정 유형 검사 데코레이터 와 유사합니다 . 그래서 문지름은 무엇입니까?

순수 Bruteforce 하드 코어 효율성

베어 타이핑은 제한된 도메인 지식을 최대한 활용하여 Python에서 기존의 모든 유형 검사 구현보다 공간과 시간 모두에서 훨씬 더 효율적입니다. ( 나중에 자세히 설명합니다. )

그러나 효율성은 일반적으로 Python에서 중요하지 않습니다. 그렇다면 Python을 사용하지 않을 것입니다. 유형 검사가 실제로 Python에서 조기 최적화를 피하는 잘 확립 된 표준에서 벗어 납니까? 예. 네, 그렇습니다.

프로파일 링 된 관심 지표 (예 : 함수 호출, 라인)에 피할 수없는 오버 헤드를 추가하는 프로파일 링을 고려하십시오. 정확한 결과를 보장하기 위해 최적화 되지 않은 순수 Python (예 : 모듈)이 아닌 최적화 된 C 확장 (예 : 모듈에서 _lsprof활용 하는 C 확장)을 활용 하여 이러한 오버 헤드를 완화합니다 . 효율성은 정말 않는 프로파일 링 할 때 문제.cProfileprofile

유형 검사도 다르지 않습니다. 유형 검사는 응용 프로그램에서 검사하는 각 함수 호출 유형에 오버 헤드를 추가 합니다. 이상적으로는 모든 유형 이 있습니다. 선의의 (그러나 슬프게도 작은 마음을 가진) 동료가 지난 금요일 카페인이 가득한 밤새도록 노인의 장고 웹 앱에 추가 한 유형 검사를 자동으로 제거하지 못하도록하려면 유형 검사가 빨라야합니다. 너무 빨라서 아무에게도 말하지 않고 추가하면 아무도 눈치 채지 못합니다. 나는 항상 이것을한다! 동료라면이 글을 그만 읽으십시오.

그러나 어리석은 속도가 당신의 탐욕스러운 애플리케이션에 충분하지 않다면, 파이썬 최적화를 활성화함으로써 (예를 들어, -O파이썬 인터프리터에 옵션을 전달함으로써) 베어 타이핑을 전역 적으로 비활성화 할 수 있습니다 :

$ 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')

으니까. 곰 타이핑에 오신 것을 환영합니다.

뭐야 ...? 왜 "곰"입니까? 당신은 Neckbeard 맞죠?

베어 타이핑은 베어 메탈 유형 검사입니다. 즉, 가능한 한 Python에서 유형 검사의 수동 접근 방식에 가까운 유형 검사입니다. 베어 타이핑은 성능 저하, 호환성 제약 또는 제 3 자 종속성 (어쨌든 수동 접근 방식에 의해 부과되는 것 이상)을 부과 하지 않습니다 . 베어 타이핑은 수정없이 기존 코드베이스 및 테스트 스위트에 원활하게 통합 될 수 있습니다.

누구나 수동 접근 방식에 익숙 할 것입니다. 코드베이스의 모든 함수에 assert전달 된 각 매개 변수 및 / 또는 반환 값을 수동으로 반환합니다 . 어떤 상용구가 더 간단하거나 더 진부 할 수 있습니까? 우리는 모두 googleplex 번에 백 번 보았고 우리가 할 때마다 입에서 약간 구토했습니다. 반복은 빨리 늙어갑니다. DRY , yo.

구토 주머니 준비하기. 간결함을 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

파이썬 101 맞죠? 우리 중 많은 사람들이 그 수업을 통과했습니다.

베어 타이핑은 위의 접근 방식에 의해 수동으로 수행 된 유형 검사를 동적으로 정의 된 래퍼 함수로 추출하여 자동으로 동일한 검사를 수행합니다 . TypeError모호한 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

장황합니다. 그러나 기본적으로이기도 * 수동 방식으로 빨리. * Squinting 제안.

원래 함수와 비슷한 수의 테스트를 포함하는 래퍼 함수에서 함수 검사 또는 반복이 완전히 결여되어 있음을 유의하십시오. 유형 검사 할 매개 변수가 다음으로 전달되는지 여부와 방법을 테스트하는 데 추가 (무시할 수 있음) 비용이 들지만 현재 함수 호출. 모든 전투에서 이길 수는 없습니다.

이러한 래퍼 함수는 실제로 275 줄 미만의 순수 Python에서 임의의 함수를 유형 검사하기 위해 안정적으로 생성 될 수 있습니까? Snake Plisskin이 말합니다. "사실이야. 담배 피워?"

그리고 네. 목 수염이있을 수 있습니다.

아니, Srsly. 왜 "곰"입니까?

곰이 오리를 친다. 오리는 날 수 있지만 곰은 오리에게 연어를 던질 수 있습니다. 캐나다에서는 자연이 당신을 놀라게 할 수 있습니다.

다음 질문.

어쨌든 곰은 뭐가 그렇게 뜨거워?

기존 솔루션은 베어 메탈 유형 검사를 수행 하지 않습니다 . 적어도 저는 전혀 고민하지 않았습니다. 이들은 모두 각 함수 호출 에서 유형 검사 된 함수의 시그니처를 반복적으로 재검사합니다 . 단일 호출에 대해서는 무시할 수 있지만 재검사 오버 헤드는 일반적으로 모든 호출에 대해 집계 될 때 무시할 수 없습니다. 정말, 정말 무시할 수 없습니다.

그러나 단순히 효율성 문제가 아닙니다. 또한 기존 솔루션은 일반적인 경우를 설명하지 못하는 경우가 많습니다. 여기에는 여기와 다른 곳에서 stackoverflow 답변으로 제공되는 대부분의 장난감 데코레이터가 포함됩니다. 고전적인 실패는 다음과 같습니다.

  • 유형 검사 키워드 인수 및 / 또는 반환 값 (예 : sweeneyrod@checkargs데코레이터 )에 실패했습니다 .
  • isinstance()빌트인에서 허용되는 유형의 튜플 (즉, 공용체)을 지원하지 못합니다.
  • 이름, 독 스트링 및 기타 식별 메타 데이터를 원래 함수에서 래퍼 함수로 전파하지 못했습니다.
  • 최소한의 단위 테스트를 제공하지 못했습니다. ( 일종의 비판적입니다. )
  • 실패한 유형 검사에서 AssertionError특정 예외가 아닌 일반 예외를 발생 TypeError시킵니다. 세분성과 온전함을 위해 유형 검사는 일반 예외를 발생 시키지 않아야합니다 .

베어 타이핑은 비 베어가 실패하는 곳에서 성공합니다. 모두 하나, 모두 곰!

Unbared 타이핑 곰

베어 타이핑은 함수 시그니처를 검사하는 데 드는 공간 및 시간 비용을 함수 호출 시간에서 함수 정의 시간 @beartype으로 이동합니다. 즉 , 데코레이터가 반환 한 래퍼 함수 에서 데코레이터 자체로 이동합니다. 데코레이터는 함수 정의당 한 번만 호출되므로이 최적화는 모두에게 기쁨을줍니다.

베어 타이핑은 당신의 타이프를 확인하고 케이크를 먹으려는 시도입니다. 그렇게하려면 @beartype:

  1. 원래 함수의 서명과 주석을 검사합니다.
  2. 원래 함수를 확인하는 래퍼 함수 유형의 본문을 동적으로 구성합니다. Thaaat의 말이 맞습니다. 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

그리고 leycec 은 " @beartype빠르게 유형 검사를 시작 하자"고 말했습니다 .

주의 사항, 저주 및 빈 약속

완벽한 것은 없습니다. 타이핑도 참 아라.

주의 사항 I : 기본값 선택 취소

베어 타이핑은 기본값이 할당 된 통과 되지 않은 매개 변수를 입력하지 않습니다 . 이론 상으로는 가능합니다. 그러나 275 줄 이하가 아니며 확실히 stackoverflow 답변이 아닙니다.

안전한 (... 아마도 완전히 안전하지 않은 ) 가정은 함수 구현자가 기본값을 정의 할 때 자신이 무엇을하고 있는지 알고 있다고 주장한다는 것입니다. 기본값은 일반적으로 상수이기 때문에 (... 그것이 더 좋을 것입니다! ), 하나 이상의 기본값이 할당 된 각 함수 호출에서 변경되지 않는 상수 유형을 다시 확인하면 베어 타이핑의 기본 원칙에 위배됩니다. "반복하지 마십시오. 너 자신과 oooover 그리고 oooo-oooover . "

잘못 보여 주시면 찬성표를 보내 드리겠습니다.

경고 II : PEP 484 없음

PEP 484 ( "Type Hints" )는 PEP 3107 ( "Function Annotations" )에 의해 처음 도입 된 함수 주석의 사용을 공식화했습니다 . 파이썬 3.5 표면적 새로운 최상위이 공식화 지원 typing모듈 간단한 형식에서 임의의 복잡한 유형을 구성하는 표준 API를 (예를 들어 Callable[[Arg1Type, Arg2Type], ReturnType], 타입 유형의 두 인자를 접수하는 기능을 설명 Arg1Type하고 Arg2Type입력 값을 리턴 ReturnType).

베어 타이핑은 지원하지 않습니다. 이론 상으로는 가능합니다. 그러나 275 줄 이하가 아니며 확실히 stackoverflow 답변이 아닙니다.

그러나 베어 타이핑은 isinstance()내장이 유형 통합을 지원 하는 것과 같은 방식 으로 유형 통합을 지원합니다 . 이것은 피상적으로 typing.Union유형에 해당 typing.Union합니다. 임의적으로 복잡한 유형 지원 하는 명백한 경고와 함께 튜플은 단순한 클래스 @beartype 지원 합니다 . 내 수비에서 275 라인.

테스트 또는 발생하지 않음

여기 에 그것 요점 이 있습니다. 이해, 요점 ? 이제 그만하겠습니다.

@beartype데코레이터 자체 와 마찬가지로 이러한 py.test테스트는 수정없이 기존 테스트 스위트에 원활하게 통합 될 수 있습니다. 귀중하지 않습니까?

이제는 아무도 요구하지 않는 의무적 인 목 수염 호언.

API 폭력의 역사

Python 3.5는 PEP 484 유형 사용에 대한 실제 지원을 제공하지 않습니다. 와트?

사실입니다 : 유형 검사, 유형 추론, 유형이 없습니다. 대신 개발자는 이러한 지원의 팩시밀리 (예 : mypy )를 구현하는 무거운 타사 CPython 인터프리터 래퍼를 통해 전체 코드베이스를 일상적으로 실행해야 합니다. 물론 이러한 래퍼는 다음을 부과합니다.

  • 호환성 처벌. 는 AS 공식 mypy 자주 묻는 질문이 자주 제기되는 질문 "? 기존 파이썬 코드를 확인 입력 할 수 있습니까 사용 mypy"에 대한 응답으로 인정한다 " . 그것은 따라 호환성이 꽤 좋은,하지만 일부 파이썬 기능은 아직 구현 또는 완전히 지원되지 않습니다." 이후 자주 묻는 질문 응답은 그 진술하여이 비 호환성을 명확히 :
    • "... 코드는 속성을 명시 적으로 만들고 명시적인 프로토콜 표현을 사용해야합니다." 문법 경찰은 당신의 "노골적인"것을보고 당신에게 암시적인 찡그린 얼굴을하게합니다.
    • "Mypy는 모듈 식의 효율적인 유형 검사를 지원할 것이며, 이는 임의의 런타임 메소드 추가와 같은 일부 언어 기능의 유형 검사를 배제하는 것처럼 보입니다. 그러나 이러한 기능 중 상당수가 제한된 형식으로 지원 될 가능성이 높습니다 (예 : , 런타임 수정은 동적 또는 '패치 가능'으로 등록 된 클래스 또는 메서드에 대해서만 지원됩니다. "
    • 구문 비 호환성의 전체 목록은 "일반적인 문제 처리"를 참조하십시오 . 그것은입니다 하지 꽤. 유형 검사를 원했고 이제 전체 코드베이스를 리팩토링하고 후보 릴리스로부터 이틀 동안 모든 사람의 빌드를 깨뜨 렸고 캐주얼 비즈니스 복장의 멋진 HR 난쟁이가 큐비클 겸 맨 케이브의 균열을 통해 분홍색 슬립을 미끄러졌습니다. 고마워요, mypy.
  • 성능 저하, 정적으로 입력 된 코드를 해석에도 불구하고. 하드 삶은 컴퓨터 과학의 위로 fourty 년은 우리에게 그 (... 이야기 동일 모든 다른 존재 ) 동적으로 입력 된 코드를 해석하는 것보다,하지 느리게, 빠르게 처리 될 수 정적으로 입력 된 코드를 해석합니다. Python에서 up은 새로운 down입니다.
  • 추가 중요하지 않은 종속성, 증가 :
    • 프로젝트 배포, 특히 크로스 플랫폼의 버그로 인한 취약성.
    • 프로젝트 개발의 유지 관리 부담.
    • 가능한 공격 표면.

나는 Guido에게 묻습니다. "왜? 실제로 그 추상화로 무언가를하는 구체적인 API를 조롱하지 않는다면 왜 추상 API를 발명해야합니까?" 백만 명의 Pythonistas의 운명을 무료 오픈 소스 시장의 관절염에 맡기는 이유는 무엇입니까? 공식 Python stdlib에서 270 줄 데코레이터로 사소하게 해결할 수있는 또 다른 기술 문제를 만드는 이유는 무엇입니까?

나는 파이썬이없고 비명을 지른다.


편집 : 2019 년부터 Python에서 유형 주석 및 정적 검사 사용에 대한 더 많은 지원이 있습니다. 입력 모듈과 mypy를 확인하십시오 . 2013 년 답변은 다음과 같습니다.


유형 검사는 일반적으로 Pythonic이 아닙니다. 파이썬에서는 덕 타이핑 을 사용하는 것이 더 일반적 입니다. 예:

당신의 코드에서, (당신의 예에서 인수가 가정 a)을 같이 산책 int과 같은 꽥꽥 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))

한 가지 방법은 다음을 사용하는 것입니다 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'

PythonDecoratorLibrary 에서 Type Enforcement accept / returns 데코레이터를 사용할 수 있습니다. 매우 쉽고 읽기 쉽습니다.

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

Python에서 변수가 무엇인지 확인하는 방법에는 여러 가지가 있습니다. 따라서 몇 가지를 나열하십시오.

  • isinstance(obj, type)기능은 변수를합니다, obj당신이 제공 True가 동일한 유형입니다 type나열된.

  • issubclass(obj, class)변수를 받아들이고이 하위 클래스 인 경우 obj제공 하는 함수 입니다 . 예를 들어 당신에게 가치를 줄 것입니다Trueobjclassissubclass(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오리 타이핑에 더 기울이고 일반적으로 더 비단뱀 이지만 그 용어는 의견이 있습니다.

참고로 assert진술은 일반적으로 테스트에 사용되며 그렇지 않으면 if/else진술을 사용하십시오 .


일반적으로 다음과 같이합니다.

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

내가 찾은 많은 도서관에 만족하지 않았기 때문에 최근에 그 주제에 대해 꽤 많은 조사를했습니다 .

이 문제를 해결하기 위해 라이브러리를 개발하게 되었고 이름은 valid8 입니다. 문서에 설명 된대로 (가 단순 유형 유효성 검사 기능도 번들로 제공 있지만) 그것은 주로 값 확인을 위해, 당신은 같은 PEP484 기반 형 검사기와 연결 할 수있는 시행 또는 pytypes .

다음은 귀하의 경우 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].

함수 호출시 입력 인수의 유형을 확인합니다.

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(어설 션 오류를 제공해야 함)


검사 할 경우 **kwargs, *args정상적인 인수 한 번에뿐만 아니라, 당신은 사용할 수있는 locals()인수의 사전을 얻으려면 함수 정의의 첫 번째 문 등의 기능을.

그런 다음을 사용 type()하여 예를 들어 dict를 반복하는 동안 인수를 조사합니다.

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...

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

locals () 에 대한 추가 정보


If you want to do the validation for several functions you can add the logic inside a decorator like this:

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

and use it:

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

Hope this helps!


This is not the solution to you, but if you want to restrict the function calls to some specific parameter types then you must use the PROATOR { The Python Function prototype validator }. you can refer the following link. https://github.com/mohit-thakur-721/proator


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

ReferenceURL : https://stackoverflow.com/questions/19684434/best-way-to-check-function-arguments-in-python

반응형