Python3의“함수 주석”에 대한 유용한 사용법


159

함수 주석 : PEP-3107

Python3의 함수 주석을 보여주는 코드 스 니펫을 살펴 보았습니다. 개념은 간단하지만 왜 이것이 Python3으로 구현되었는지 또는 그것들을 잘 사용하는지 생각할 수 없습니다. 아마도 그렇게 나를 밝게 할 수 있습니까?

작동 방식 :

def foo(a: 'x', b: 5 + 6, c: list) -> max(2, 9):
    ... function body ...

인수 후 콜론 다음에 오는 모든 것은 '주석'이며 -> 는 함수의 반환 값에 대한 주석입니다.

foo.func_annotations는 사전을 반환합니다 :

{'a': 'x',
 'b': 11,
 'c': list,
 'return': 9}

이것을 사용할 수 있다는 것은 어떤 의미입니까?



6
@SilentGhost : 불행히도 실제 사용 사례와의 많은 링크가 손상되었습니다. 컨텐츠가 저장되었거나 영원히 사라진 곳이 있습니까?
최대

16
foo.func_annotations foo.__annotations__python3에?
zhangxaochen 2019

2
주석에는 특별한 의미가 없습니다. 파이썬이하는 유일한 일은 주석 사전 에 넣는 것입니다 . 다른 행동은 당신에게 달려 있습니다.
N Randhawa

무슨 def foo(a: 'x', b: 5 + 6, c: list) -> max(2, 9):뜻입니까?
Ali SH

답변:


90

나는 이것이 실제로 훌륭하다고 생각합니다.

학문적 배경에서 온 주석은 Java와 같은 언어에 대해 스마트 정적 분석기를 활성화하는 데 귀중한 것으로 판명되었습니다. 예를 들어 상태 제한, 액세스 가능한 스레드, 아키텍처 제한 등과 같은 의미를 정의 할 수 있으며이를 읽고 처리하여 컴파일러에서 얻는 것 이상의 보증을 제공 할 수있는 도구가 많이 있습니다. 전제 조건 / 사후 조건을 확인하는 것을 작성할 수도 있습니다.

필자는 타이핑이 약하기 때문에 특히 파이썬에서 이와 같은 것이 필요하다고 생각하지만 실제로이 구문을 공식적이고 간단하게 만드는 구문은 없었습니다.

보증 이외의 주석에는 다른 용도가 있습니다. Java 기반 도구를 Python에 적용하는 방법을 알 수 있습니다. 예를 들어, 메소드에 특별한 경고를 할당하고 호출 할 때 문서를 읽어야한다는 표시를 제공하는 도구가 있습니다 (예 : 음수 값으로 호출해서는 안되는 메소드가 있다고 가정하십시오). 이름에서 직관적이지 않음). 주석을 사용하면 기술적으로 Python에 이와 같은 것을 작성할 수 있습니다. 마찬가지로 공식 구문이있는 경우 태그를 기반으로 큰 클래스에서 메소드를 구성하는 도구를 작성할 수 있습니다.


34
ISTM은 표준 라이브러리 및 타사 모듈이 모두 함수 주석을 사용하고 일관된 의미로 사용하고 잘 알려진 주석 시스템을 사용하는 경우에만 실현 될 수있는 이론적 인 이점입니다. 그 날까지 (절대로 오지 않을), 파이썬의 함수 주석의 주요 용도는 다른 답변에서 설명한 일회성 용도입니다. 당분간은 스마트 정적 분석기, 컴파일러 보증, Java 기반 툴 체인 등을 잊을 수 있습니다.
Raymond Hettinger

4
함수 주석을 사용하는 모든 것이 없어도 입력에 주석이 있고 비슷한 주석이 달린 다른 코드를 호출하는 코드 내에서 정적 분석에 주석을 계속 사용할 수 있습니다. 더 큰 프로젝트 또는 코드베이스 내에서 주석 기반 정적 분석을 수행하는 데 여전히 유용한 코드가 될 수 있습니다.
gps

1
AFAICT,이 모든 것을 데코레이터로 할 수 있습니다. 따라서 여전히 이점을 볼 수 없습니다. 이 질문에 약간의 차이가 있습니다. stackoverflow.com/questions/13784713/…
allyourcode

9
2015 년으로 넘어 가면 python.org/dev/peps/pep-0484mypy-lang.org 가 모든 해설자를 잘못 입증하기 시작했습니다.
Mauricio Scheffer

1
또한 Swift에 대한 Python의 영향을 훨씬 더 많이 보여줍니다.
uchuugaka

92

함수 주석은 당신이 만드는 것입니다.

문서화에 사용할 수 있습니다.

def kinetic_energy(mass: 'in kilograms', velocity: 'in meters per second'):
     ...

사전 조건 확인에 사용할 수 있습니다.

def validate(func, locals):
    for var, test in func.__annotations__.items():
        value = locals[var]
        msg = 'Var: {0}\tValue: {1}\tTest: {2.__name__}'.format(var, value, test)
        assert test(value), msg


def is_int(x):
    return isinstance(x, int)

def between(lo, hi):
    def _between(x):
            return lo <= x <= hi
    return _between

def f(x: between(3, 10), y: is_int):
    validate(f, locals())
    print(x, y)


>>> f(0, 31.1)
Traceback (most recent call last):
   ... 
AssertionError: Var: y  Value: 31.1 Test: is_int

형식 검사를 구현하는 방법 은 http://www.python.org/dev/peps/pep-0362/ 를 참조 하십시오 .


18
문서화 또는 함수의 명시 적 유형 검사를 위해 docstring보다 나은 점은 무엇입니까? 아무 이유없이 언어를 복잡하게 만드는 것 같습니다.
endolith

10
@endolith 함수 주석없이 확실히 할 수 있습니다. 주석에 액세스하는 표준 방법 만 제공합니다. 이를 통해 help () 및 툴팁에 액세스 할 수 있으며 내부 조사가 가능합니다.
Raymond Hettinger

4
오히려 당신이 유형을 만들 수있는 숫자의 주위에 통과하는 것보다 Mass하고 Velocity대신.
rightfold

1
이것을 완전히 증명 def kinetic_energy(mass: 'in kilograms', velocity: 'in meters per second') -> float:하려면 반환 유형도 표시해야합니다. 이것은 내가 가장 좋아하는 대답입니다.
Tommy

코드를 사용하여 return주석 을 확인하는 방법이 있습니까? 에 표시되지 않는 것 같습니다locals
user189728

46

이것은 답변이 늦었지만, 현재 가장 효과적으로 함수 주석을 사용하는 AFAICT는 PEP- 0484MyPy 입니다.

Mypy는 Python의 선택적 정적 유형 검사기입니다. Python 3.5 베타 1 (PEP 484)에 도입 된 형식 주석에 대한 다음 표준을 사용하여 Python 프로그램에 형식 힌트를 추가하고 mypy를 사용하여 정적으로 형식을 검사 할 수 있습니다.

이렇게 사용 :

from typing import Iterator

def fib(n: int) -> Iterator[int]:
    a, b = 0, 1
    while a < n:
        yield a
        a, b = b, a + b


PEP-0484를 염두에두고 구축 된 다른 정적 분석기 인 pytype 도 참조하십시오 .
gps

불행히도 유형이 적용되지 않습니다. list(fib('a'))예제 함수로 입력하면 Python 3.7은 인수를 행복하게 받아들이고 문자열과 int를 비교할 방법이 없다고 불평합니다.
Denis de Bernardy

@DenisdeBernardy PEP-484는 파이썬이 타입 주석만을 제공한다고 설명합니다. 유형을 적용하려면 mypy를 사용해야합니다.
Dustin Wyatt

23

그냥 내 대답에서 좋은 사용의 구체적인 예를 추가하는 여기 multimethods에 대한 간단한 메커니즘을 수행 할 수 있습니다 장식과 함께.

# This is in the 'mm' module

registry = {}
import inspect

class MultiMethod(object):
    def __init__(self, name):
        self.name = name
        self.typemap = {}
    def __call__(self, *args):
        types = tuple(arg.__class__ for arg in args) # a generator expression!
        function = self.typemap.get(types)
        if function is None:
            raise TypeError("no match")
        return function(*args)
    def register(self, types, function):
        if types in self.typemap:
            raise TypeError("duplicate registration")
        self.typemap[types] = function

def multimethod(function):
    name = function.__name__
    mm = registry.get(name)
    if mm is None:
        mm = registry[name] = MultiMethod(name)
    spec = inspect.getfullargspec(function)
    types = tuple(spec.annotations[x] for x in spec.args)
    mm.register(types, function)
    return mm

사용 예 :

from mm import multimethod

@multimethod
def foo(a: int):
    return "an int"

@multimethod
def foo(a: int, b: str):
    return "an int and a string"

if __name__ == '__main__':
    print("foo(1,'a') = {}".format(foo(1,'a')))
    print("foo(7) = {}".format(foo(7)))

이것은 Guido의 원래 포스트 쇼 처럼 데코레이터에 유형을 추가하여 수행 할 수 있지만 매개 변수와 유형이 잘못 일치하지 않도록 매개 변수 자체에 주석을 추가하는 것이 좋습니다.

참고 : Python에서는 Python 3 에서 스타일이 제거 function.__annotations__되지 않고 주석에 액세스 할 수 있습니다 .function.func_annotationsfunc_*


2
function = self.typemap.get(types)서브 클래스가 관여 할 때 흥미로운 응용 프로그램 이 작동하지 않습니다. 이 경우 아마도을 typemap사용하여 반복해야 할 것입니다 isinnstance. @overload이 문제를 올바르게 처리 하는지 궁금 합니다
Tobias Kienzler

함수가 리턴 타입을 가지고 있다면 이것이 깨 졌다고 생각합니다
zenna

1
__annotations__ A는 dict이 조각은 때때로 실패 할 수 있도록, 인수의 순서를 보장하지 않는다. 나는 변경 추천 types = tuple(...)spec = inspect.getfullargspec(function)다음 types = tuple([spec.annotations[x] for x in spec.args]).
xoolive

당신은 꽤 정확합니다, @xoolive. 수정 사항을 추가하기 위해 답변을 편집하지 않는 이유는 무엇입니까?
Muhammad Alkarouri

@ xoolive : 나는 알아 차렸다. 때때로 편집자들은 편집 관리에 많은 노력을 기울입니다. 수정 사항을 포함하도록 질문을 편집했습니다. 실제로, 나는 이것에 대해 토론 했지만, 수정을 거부 할 수있는 방법은 없습니다. 그런데 도움을 주셔서 감사합니다.
Muhammad Alkarouri

22

Uri는 이미 정답을 제시 했으므로 덜 심각합니다. 따라서 문서 문자열을 더 짧게 만들 수 있습니다.


2
그것을 사랑하십시오. +1. 그러나 결국 docstrings를 작성하는 것은 여전히 ​​코드를 읽을 수있는 가장 좋은 방법입니다. 그러나 정적 또는 동적 검사를 구현하려면이 기능을 사용하는 것이 좋습니다. 아마도 나는 그것을 사용할 수 있습니다.
워렌 P

8
문서 문자열에서 Args : 섹션 또는 @param 줄 또는 이와 유사한 형식으로 주석을 사용하지 않는 것이 좋습니다 (어떤 형식을 사용하든 선택). 문서 주석은 예를 들어 설명하지만 다른 강력한 용도로 방해가 될 수 있으므로 주석의 잠재적 인 힘을 손상시킵니다. 또한 docstrings 및 assert 문에서와 같이 런타임시 주석을 생략하여 메모리 소비 (python -OO)를 줄일 수 없습니다.
gps

2
@gps : 내가 말했듯이 덜 심각한 답변이었습니다.
JAB

2
진지하게도 DuckTyping을 준수하면서 예상되는 유형을 문서화하는 것이 훨씬 더 좋습니다.
Marc

1
@gps 나는 docstrings의 메모리 소비가 99.999 %의 경우에 걱정할 것이 확실하지 않다.
Tommy

13

처음으로 주석을 보았을 때 "좋아요! 마지막으로 유형 확인을 선택할 수 있습니다!"라고 생각했습니다. 물론 주석이 실제로 적용되지 않았다는 것을 알지 못했습니다.

그래서 나는 간단한 함수 데코레이터를 작성하여 그것들을 시행 하기로 결정 했습니다 .

def ensure_annotations(f):
    from functools import wraps
    from inspect import getcallargs
    @wraps(f)
    def wrapper(*args, **kwargs):
        for arg, val in getcallargs(f, *args, **kwargs).items():
            if arg in f.__annotations__:
                templ = f.__annotations__[arg]
                msg = "Argument {arg} to {f} does not match annotation type {t}"
                Check(val).is_a(templ).or_raise(EnsureError, msg.format(arg=arg, f=f, t=templ))
        return_val = f(*args, **kwargs)
        if 'return' in f.__annotations__:
            templ = f.__annotations__['return']
            msg = "Return value of {f} does not match annotation type {t}"
            Check(return_val).is_a(templ).or_raise(EnsureError, msg.format(f=f, t=templ))
        return return_val
    return wrapper

@ensure_annotations
def f(x: int, y: float) -> float:
    return x+y

print(f(1, y=2.2))

>>> 3.2

print(f(1, y=2))

>>> ensure.EnsureError: Argument y to <function f at 0x109b7c710> does not match annotation type <class 'float'>

확인 라이브러리에 추가했습니다 .


마지막으로 파이썬이 마지막으로 유형 검사를한다고 믿었을 때도 실망했습니다. 마침내 집에서 만든 유형 검사 구현을 시작해야합니다.
Hibou57

3

이것이 요청 된 이후 오랜 시간이 지났지 만 질문에 주어진 예제 스 니펫은 PEP 3107에서 (PWP의 끝 부분에서) PEP 예제의 끝 부분에 있습니다. 보기;)

다음은 PEP3107에서 인용 한 것입니다

사용 사례

주석을 논의하는 과정에서 많은 사용 사례가 제기되었습니다. 이 중 일부는 여기에 어떤 종류의 정보가 전달되는지에 따라 분류되어 있습니다. 주석을 사용할 수있는 기존 제품 및 패키지의 예도 포함되어 있습니다.

  • 타이핑 정보 제공
    • 유형 확인 ([3], [4])
    • IDE가 함수가 기대하고 반환하는 타입을 보여 주도록하라 ([17])
    • 함수 오버로딩 / 일반 함수 ([22])
    • 외국어 교량 ([18], [19])
    • 적응 ([21], [20])
    • 술어 논리 함수
    • 데이터베이스 쿼리 매핑
    • RPC 매개 변수 마샬링 ([23])
  • 기타 정보
    • 매개 변수 및 리턴 값에 대한 문서 ([24])

특정 요점 (및 참조)에 대한 자세한 내용은 PEP 를 참조하십시오.


downvoter가 downvote의 원인에 대해 최소한 짧은 의견을 남기면 정말 감사하겠습니다. 이것은 실제로 개선하는 데 많은 도움이 될 것입니다.
klaas

2

Python 3.X (전용)는 또한 함수 정의를 일반화 하여 확장에서 사용 하기 위해 인수 및 반환 값에 객체 값으로 주석을 달 수 있습니다 .

함수 값에 대해 더 명확하게 설명 할 메타 데이터입니다.

주석은 :value인수 이름 뒤에 그리고 기본값 앞에 그리고 ->value인수 목록 뒤에 코드화됩니다 .

그것들은 __annotations__함수 의 속성으로 수집 되지만 파이썬 자체에서 특별하게 취급되지는 않습니다.

>>> def f(a:99, b:'spam'=None) -> float:
... print(a, b)
...
>>> f(88)
88 None
>>> f.__annotations__
{'a': 99, 'b': 'spam', 'return': <class 'float'>}

출처 : Python Pocket Reference, 5 판

예:

typeannotations모듈은 파이썬 코드의 타입 검사 및 타입 추론을위한 툴 세트를 제공합니다. 또한 함수 및 객체에 주석을 달 때 유용한 유형 세트를 제공합니다.

이 도구는 주로 린터, 코드 완성 라이브러리 및 IDE와 같은 정적 분석기에서 사용하도록 설계되었습니다. 또한 런타임 검사를위한 데코레이터가 제공됩니다. 런타임 타입 검사는 파이썬에서 항상 좋은 생각은 아니지만 어떤 경우에는 매우 유용 할 수 있습니다.

https://github.com/ceronman/typeannotations

타이핑으로 더 나은 코드를 작성하는 방법

입력하면 정적 코드 분석을 수행하여 코드를 프로덕션으로 보내기 전에 유형 오류를 포착하고 명백한 버그를 방지 할 수 있습니다. 소프트웨어 수명주기의 일부로 도구 상자에 추가 할 수있는 mypy와 같은 도구가 있습니다. mypy는 코드베이스를 부분적으로 또는 완전히 실행하여 올바른 유형을 확인할 수 있습니다. mypy는 또한 함수에서 값이 반환 될 때 None 유형 확인과 같은 버그를 감지하는 데 도움이됩니다. 입력하면 코드가 더 깨끗해집니다. 문서 문자열에 유형을 지정하는 주석을 사용하여 코드를 문서화하는 대신 성능 비용없이 유형을 사용할 수 있습니다.

클린 파이썬 : 파이썬의 우아한 코딩 ISBN : ISBN-13 (pbk) : 978-1-4842-4877-5

PEP 526-변수 주석 구문

https://www.python.org/dev/peps/pep-0526/

https://www.attrs.org/en/stable/types.html


@BlackJack, "확장 프로그램에 사용"이 명확하지 않습니까?
Demz

명확하지만 IMHO 질문에 대답하지 않습니다. 그것은 "프로그램에 사용하기 위해."그것은 정확하고 분명 함께 "? 클래스의 좋은 사용은 무엇입니까"대답처럼,하지만 요청 당사자는 도대체 좋은의 무엇인지에 정말 현명하지 않다 콘크리트의 사용이다. 귀하의 답변은 더 일반적으로 할 수없는 답변이며, 이미 질문 에있는 것과 동일한 예가 있습니다.
BlackJack

1

여기에 설명 된 모든 용도에도 불구하고, 주석을 강제로 사용하는 것은 아마도 유형 힌트를 위한 것 입니다.

이것은 현재 어떤 방식으로도 시행되지 않지만 PEP 484에서 판단 할 때 이후 버전의 Python에서는 주석의 값으로 유형 만 허용합니다.

인용 기존 주석 사용은 어떻습니까? :

타입 힌트가 결국 주석의 유일한 용도가되기를 희망하지만, 파이썬 3.5에서 타이핑 모듈을 처음 출시 한 후에는 추가 논의와 사용 중단 기간이 필요할 것입니다. 현재 PEP는 Python 3.6이 릴리스 될 때까지 임시 상태 (PEP 411 참조)를 갖습니다. 가장 빠른 방법은 3.6에서 비 유형 힌트 주석을 자동으로 더 이상 사용하지 않고 3.7에서 전체 비추천을 사용하고 Python 3.8에서 주석을 사용할 수있는 유일한 유형으로 유형 힌트를 선언합니다.

3.6에서는 아직 더 이상 사용되지 않는 사용 중단을 보지 못했지만 대신 3.7로 크게 떨어질 수 있습니다.

따라서 다른 유용한 사용 사례가있을 수 있지만이 제한이 적용되는 미래에 모든 것을 변경하지 않으려는 경우 유형 힌트에만 사용하는 것이 가장 좋습니다.


1

약간의 지연 된 답변으로, 내 패키지 중 일부 (marrow.script, WebCore 등)는 유형 캐스팅을 선언하는 데 사용할 수있는 주석을 사용합니다 (즉, 웹에서 들어오는 값을 변환하고 부울 스위치 등의 인수를 감지하는 등). 인수의 추가 마크 업을 수행합니다.

Marrow Script는 임의의 함수 및 클래스에 대한 완전한 명령 줄 인터페이스를 구축하고 주석을 통해 문서, 캐스팅 및 콜백 파생 기본값을 정의 할 수 있으며 데코레이터를 사용하여 이전 런타임을 지원할 수 있습니다. 주석을 사용하는 모든 라이브러리는 다음 형식을 지원합니다.

any_string  # documentation
any_callable  # typecast / callback, not called if defaulting
(any_callable, any_string)  # combination
AnnotationClass()  # package-specific rich annotation object
[AnnotationClass(), AnnotationClass(), …]  # cooperative annotation

docstrings 또는 typecasting 함수에 대한 "Bare"지원은 주석을 인식하는 다른 라이브러리와 쉽게 혼합 할 수있게합니다. (즉, 명령 줄 스크립트로 노출되는 유형 캐스팅을 사용하는 웹 컨트롤러가 있습니다.)

추가 편집 : 나는 또한 유효성 검사를 위해 개발 시간 주장을 사용하여 TypeGuard 패키지를 사용하기 시작했습니다 . 이점 : "최적화"를 활성화 ( -O/ PYTHONOPTIMIZEenv var) 한 상태에서 실행할 때 개발에서 앱을 올바르게 테스트 했으므로 프로덕션에서 검사가 필요하지 않다는 아이디어와 함께 고가 (예 : 재귀 적) 인 검사는 생략됩니다.


-2

코드를 쉽게 모듈화하기 위해 주석을 사용할 수 있습니다. 예를 들어 내가 유지 관리하는 프로그램의 모듈은 다음과 같은 방법을 정의 할 수 있습니다.

def run(param1: int):
    """
    Does things.

    :param param1: Needed for counting.
    """
    pass

"counting을 위해 필요하고" "int"여야하는 "param1"이라는 것을 사용자에게 요청할 수 있습니다. 결국 우리는 사용자가 제공 한 문자열을 원하는 유형으로 변환하여 가장 번거롭지 않은 경험을 얻을 수 있습니다.

이를 지원하고 필요한 값을 자동으로 검색 하고 원하는 유형으로 변환 할 수있는 오픈 소스 클래스에 대한 함수 메타 데이터 개체 를 참조하십시오 (주석은 변환 방법이므로). IDE조차도 자동 완성을 올바르게 표시하고 형식이 주석에 따른다고 가정합니다.


-2

Cython의 이점 목록을 살펴보면 가장 큰 장점은 컴파일러에 Python 객체의 유형을 알려주는 기능입니다.

Cython (또는 일부 Python 코드를 컴파일하는 유사한 도구)이 주석 구문을 사용하여 마법을 사용할 미래를 상상할 수 있습니다.


RPython 내 주석을 적절 파이썬을 느끼는 방법의 일례이다; 응용 프로그램의 그래프를 생성 한 후 모든 변수의 유형을 해결하고 (RPython의 경우) 단일 유형 안전을 시행 할 수 있습니다. OTOH 다이내믹 리치 값을 허용하려면 "복싱"또는 기타 솔루션 / 해결 방법이 필요합니다. multiply함수 'na' * 8 + ' batman!'가 완전히 유효한 경우 정수에 대해서만 작동 하도록 누가 강제 해야합니까? ;)
amcgregor
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.