파이썬 함수는 전달하는 매개 변수 유형을 어떻게 처리합니까?


305

내가 실수하지 않는 한, 파이썬에서 함수를 생성하면 다음과 같이 작동합니다.

def my_func(param1, param2):
    # stuff

그러나 실제로 해당 매개 변수 유형을 제공하지는 않습니다. 또한 내가 기억한다면 파이썬은 강력한 유형의 언어이므로 파이썬이 함수 생성자가 예상 한 것과 다른 유형의 매개 변수를 전달해서는 안되는 것처럼 보입니다. 그러나 파이썬은 함수의 사용자가 올바른 유형으로 전달되고 있음을 어떻게 알 수 있습니까? 함수가 실제로 매개 변수를 사용한다고 가정하면 잘못된 유형의 경우 프로그램이 죽습니까? 유형을 지정해야합니까?


15
이 질문에 허용되는 답변은 Python이 제공 하는 현재 기능 과 더 일치하도록 업데이트되어야한다고 생각 합니다. 이 답변 이 효과가 있다고 생각 합니다 .
code_dredd

답변:


172

파이썬은 모든 객체 타입을 가지고 있기 때문에 강력하게 타이핑됩니다. 모든 객체 타입을 알고 있습니다. 우연히 또는 고의적으로 다른 타입 의 객체 인 것처럼 "유사한"타입의 객체를 사용할 수 없으며 객체의 모든 기본 연산은 다음과 같습니다. 해당 유형으로 위임되었습니다.

이것은 이름 과 관련이 없습니다 . 이름 의 이름이 정의 때, 이름이 참조하는 경우와 : 파이썬에서 "는 유형이"하지 않는 개체개체 유형을 가지고있다 (하지만 사실 힘에의 유형하지 않는 이름을 하십시오 이름은 이름입니다).

파이썬의 이름은 다른 시간에 다른 객체를 완벽하게 참조 할 수 있습니다 (모두는 아니지만 대부분의 프로그래밍 언어에서와 같이) .X 유형의 객체를 한 번 언급 한 경우 이름에 제한이 없습니다. 그런 다음 X 유형의 다른 객체 만 참조하도록 영원히 제한됩니다. 이름 에 대한 제약 조건 은 "강력한 타이핑"개념의 일부가 아니지만 정적 유형에 대한 애호가 (이름 제약을받는 정적 AKA 컴파일- 시간, 패션도) 이런 식으로 용어를 잘못 사용합니다.


71
따라서 강력한 타이핑은 그렇게 강력하지 않은 것처럼 보입니다.이 특별한 경우 정적 타이핑보다 약합니다 .IMHO, 컴파일 타임 타이핑 제약 조건 이름 / 변수 / 참조는 실제로 매우 중요하므로 파이썬이 정적 타이핑만큼 좋지 않다고 대담하게 주장합니다. 이 측면에서. 내가 틀렸다면 정정 해주세요.
liang

19
@liang 그것은 의견이므로, 당신은 옳고 그름 일 수 없습니다. 그것은 또한 내 의견이기도하며 많은 언어를 시도했습니다. IDE를 사용하여 매개 변수의 유형 (및 멤버)을 찾을 수 없다는 사실은 파이썬의 주요 단점입니다. 이 단점이 오리 타이핑의 장점보다 중요한 경우 요청한 사람에 따라 다릅니다.
Maarten Bodewes

6
그러나 이것은 어떤 질문에도 대답하지 않습니다. "그러나 파이썬은 함수의 사용자가 올바른 유형을 전달하고 있다는 것을 어떻게 알 수 있습니까? 함수가 실제로 매개 변수를 사용한다고 가정하면 잘못된 유형이면 프로그램이 죽을까요? 유형을 지정해야합니까? " 또는 ..
qPCR4vir

4
@ qPCR4vir, 모든 객체를 인수로 전달할 수 있습니다. 개체가 지원하지 않는 작업을 시도 할 때 오류가 발생하는 경우 (예외, 프로그램이이를 포착하도록 코딩 된 경우 프로그램이 "사라지지 않음, try/ 참조" except)가 발생합니다. Python 3.5에서는 이제 선택적으로 인수의 "유형 지정"을 할 수 있지만, 사양을 위반하면 그 자체로 오류가 발생하지 않습니다. 타이핑 표기법은 분석 등을 수행하는 도구를 분리하는 데 도움이되며 Python 자체의 동작을 변경하지 않습니다.
Alex Martelli

2
@AlexMartelli. 감사! 나에게 이것은 정답이다 : "오류 (예외, 프로그램은 그것을 잡으려고 코딩된다면,"죽어 "지지 않을 것입니다. try / except를보십시오) .."
qPCR4vir

753

다른 답변은 오리 타이핑과 tzot의 간단한 답변 을 설명하는 데 훌륭한 역할을했습니다 .

파이썬에는 변수에 유형과 값이있는 다른 언어와 같이 변수가 없습니다. 유형을 알고있는 객체를 가리키는 이름이 있습니다.

그러나 2010 년 이후 (질문이 처음 제기 될 때) 한 가지 흥미로운 점, 즉 PEP 3107 (Python 3에서 구현 됨)이 변경되었습니다. 이제 실제로 다음과 같이 매개 변수의 유형과 함수의 리턴 유형을 지정할 수 있습니다.

def pick(l: list, index: int) -> int:
    return l[index]

여기서 우리는 pick리스트 l와 정수의 2 개의 매개 변수 를 취하는 것을 볼 수 있습니다 index. 또한 정수를 반환해야합니다.

따라서 여기에는 l많은 노력없이 볼 수있는 정수 목록이 포함되어 있지만 더 복잡한 함수의 경우 목록에 포함 해야하는 내용에 대해 약간 혼란 스러울 수 있습니다. 또한 기본값 인 index0을 원합니다.이를 해결하려면 다음 pick과 같이 작성하십시오 .

def pick(l: "list of ints", index: int = 0) -> int:
    return l[index]

l구문 유형으로 허용되는 유형의 문자열을 문자열에 넣었 지만 프로그래밍 방식으로 구문 분석하는 것은 좋지 않습니다 (나중에 다시 올 것입니다).

파이썬은 인상하지 않을 것이 중요합니다 TypeError당신이에 float를 전달하는 경우 index: 그 이유는 파이썬의 디자인 철학의 주요 포인트 중 하나입니다 "우리는 모두 여기에 성인을 동의하고" 당신이 예상하는 수단 함수에 전달할 수있는 것과 할 수없는 것을 알고 있어야합니다. 실제로 TypeErrors를 발생시키는 코드를 작성하려면이 isinstance함수를 사용 하여 전달 된 인수가 다음과 같이 올바른 유형 또는 서브 클래스인지 확인하십시오.

def pick(l: list, index: int = 0) -> int:
    if not isinstance(l, list):
        raise TypeError
    return l[index]

왜 이런 일을 거의하지 말아야하는지에 대한 자세한 내용은 다음 섹션과 주석에서 설명합니다.

PEP 3107 은 코드 가독성을 향상시킬뿐만 아니라 여기에서 읽을 수있는 몇 가지 적합한 사용 사례도 있습니다 .


타입 주석에 대한 표준 모듈을 소개하는 PEP 484 가 도입 되면서 파이썬 3.5에서 타입 주석이 더 주목을 받았습니다 .

이 유형 힌트는 유형 검사기 mypy ( GitHub )에서 왔으며 현재 PEP 484를 준수합니다.

타이핑 모듈에는 다음을 포함하여 매우 포괄적 인 유형 힌트 모음이 제공됩니다.

  • List, Tuple, Set, Map-에 list, tuple, setmap각각.
  • Iterable -발전기에 유용합니다.
  • Any -언제라도 될 수있을 때.
  • Union-와 달리 지정된 유형의 세트 내에있는 것이있을 수있는 경우 Any.
  • Optional- 없음 때 . 의 속기입니다 Union[T, None].
  • TypeVar -제네릭과 함께 사용됩니다.
  • Callable -주로 함수에 사용되지만 다른 콜 러블에 사용될 수 있습니다.

가장 일반적인 유형 힌트입니다. 전체 목록은 입력 모듈 설명서 에서 찾을 수 있습니다 .

다음은 입력 모듈에 도입 된 주석 방법을 사용하는 이전 예입니다.

from typing import List

def pick(l: List[int], index: int) -> int:
    return l[index]

강력한 기능 중 하나 Callable는 함수를 인수로 취하는 어노테이션이있는 메소드를 입력 할 수있는 기능입니다. 예를 들면 다음과 같습니다.

from typing import Callable, Any, Iterable

def imap(f: Callable[[Any], Any], l: Iterable[Any]) -> List[Any]:
    """An immediate version of map, don't pass it any infinite iterables!"""
    return list(map(f, l))

위의 예는 TypeVar대신에 사용법을 사용하면 더 정확해질 수 Any있지만, 이미 유형 힌트로 활성화 된 멋진 새로운 기능에 대한 정보를 너무 많이 채워서 믿기 때문에 독자에게 연습으로 남았습니다.


예를 들어 Sphinx 와 같은 문서화 된 Python 코드에서 다음과 같은 형식의 docstring을 작성하면 위의 기능 중 일부를 얻을 수 있습니다.

def pick(l, index):
    """
    :param l: list of integers
    :type l: list
    :param index: index at which to pick an integer from *l*
    :type index: int
    :returns: integer at *index* in *l*
    :rtype: int
    """
    return l[index]

보시다시피, 여기에는 여러 줄이 추가로 필요합니다 (정확한 숫자는 명시적인 표현 방법과 문서 문자열의 형식에 따라 다릅니다). 그러나 이제 PEP 3107 이 많은 (모두?) 우월한 대안을 어떻게 제공 하는지 분명히 알 수 있습니다 . 이것은 PEP 484 와 결합하여 특히 그렇습니다. 우리가 살펴본 바와 같이, 이러한 유형 힌트 / 주석에 대한 구문을 정의하는 표준 모듈을 제공하여 명확하고 유연하면서도 유연하게 사용할 수 있습니다. 강력한 조합.

내 개인적인 견해로는 이것이 파이썬에서 가장 큰 기능 중 하나입니다. 사람들이 힘을 활용하기 시작할 때까지 기다릴 수 없습니다. 긴 답변에 대해 죄송하지만 이것이 흥분 될 때 일어나는 일입니다.


유형 힌트를 많이 사용하는 Python 코드의 예는 여기 에서 찾을 수 있습니다 .


2
@rickfoosusa : 기능이 추가 된 Python 3을 실행하지 않는 것 같습니다.
erb

26
잠깐만! 매개 변수 및 반환 유형을 정의해도 a가 발생하지 않으면 한 줄로 정의하는 것과 같은 TypeError점은 무엇 pick(l: list, index: int) -> int입니까? 또는 내가 잘못 알았어.
Erdin Eray 2019

24
@Eray Erdin : 일반적인 오해이며 나쁜 질문은 아닙니다. 그것은 문서화 목적으로 사용될 수 있으며, IDE가 자동 완성을 향상시키고 정적 분석을 사용하여 런타임보다 앞서 오류를 찾을 수 있도록 도와줍니다 (응답에서 언급 한 mypy와 동일). 런타임이 정보를 활용하고 실제로 프로그램 속도를 높일 수 있기를 희망하지만 구현하는 데 시간이 오래 걸릴 수 있습니다. 당신은 할 수 있습니다 (정보가 저장됩니다 당신을 위해 TypeErrors을 던져 장식 만들 수 __annotations__함수 객체의 속성).
erb

2
@ErdinEray TypeErrors를 던지는 것은 나쁜 생각이라고 덧붙여 야합니다 (예외가 얼마나 잘 발생했는지에 관계없이 디버깅은 결코 재미가 없습니다). 그러나 내 대답에 설명 된 새로운 기능의 이점은 더 나은 방법을 가능하게합니다. 런타임에서 검사에 의존하지 말고, mypy로 런타임보다 앞서 모든 것을 수행하거나 PyCharm과 같은 정적 분석을 수행하는 편집기를 사용하십시오 .
erb

2
@Tony : 둘 이상의 객체를 반환 할 때는 실제로 튜플을 반환하므로 Tuple 형식 주석을 사용해야합니다.def f(a) -> Tuple[int, int]:
erb

14

유형을 지정하지 않았습니다. 메소드는 전달 된 매개 변수에 정의되지 않은 속성에 액세스하려는 경우에만 런타임에 실패합니다.

따라서이 간단한 기능 :

def no_op(param1, param2):
    pass

두 개의 인수가 전달 되더라도 실패하지 않습니다.

그러나이 기능은 다음과 같습니다.

def call_quack(param1, param2):
    param1.quack()
    param2.quack()

... 경우 런타임에 실패 param1하고 param2모두 이름이 호출 속성이 없습니다 quack.


+1 : 속성과 메소드가 정적으로 결정되지 않습니다. 이 "적절한 유형"또는 "잘못된 유형"의 유형에 대한 개념은 유형이 함수에서 올바르게 작동하는지 여부에 따라 결정됩니다.
S.Lott

11

많은 언어에는 특정 유형의 변수가 있으며 값이 있습니다. 파이썬에는 변수가 없습니다. 여기에는 객체가 있으며 이름을 사용하여 이러한 객체를 나타냅니다.

다른 언어로 말할 때 :

a = 1

그런 다음 (일반적으로 정수) 변수는 내용을 값 1로 변경합니다.

파이썬에서

a = 1

“이름 1 을 사용하여 객체 1 을 참조하십시오 ”를 의미합니다. 대화식 Python 세션에서 다음을 수행 할 수 있습니다.

>>> type(1)
<type 'int'>

함수 type는 객체와 함께 호출됩니다 1. 모든 객체는 해당 유형을 알고 있기 때문에 해당 유형 type을 찾아서 반환하는 것이 쉽습니다 .

마찬가지로 함수를 정의 할 때마다

def funcname(param1, param2):

함수는 두 개의 객체를 수신하고 이름을 param1하고 param2자신의 유형에 관계없이. 수신 된 객체가 특정 유형인지 확인하려면 필요한 유형 인 것처럼 함수를 코딩하고 그렇지 않은 경우 발생하는 예외를 포착하십시오. 예외는 일반적으로 TypeError유효하지 않은 작업 AttributeError을 사용했으며 존재하지 않는 멤버에 액세스하려고했습니다 (메소드도 멤버 임).


8

정적 또는 컴파일 타임 유형 검사의 의미에서 파이썬은 강력하게 유형화되지 않습니다.

대부분의 파이썬 코드는 소위 "덕 타이핑 (Duck Typing)"에 속합니다. 예를 들어, read객체에 대한 메소드를 찾고 있습니다. 객체가 디스크 나 소켓에있는 파일인지 상관하지 않습니다. N 만 읽으려고합니다. 그것의 바이트.


21
파이썬 강력하게 타이핑됩니다. 또한 동적으로 입력됩니다.
Daniel Newby

1
그러나 이것은 어떤 질문에도 대답하지 않습니다. "그러나 파이썬은 함수의 사용자가 올바른 유형을 전달하고 있다는 것을 어떻게 알 수 있습니까? 함수가 실제로 매개 변수를 사용한다고 가정하면 잘못된 유형이면 프로그램이 죽을까요? 유형을 지정해야합니까? " 또는 ..
qPCR4vir

6

Alex Martelli는 다음 과 같이 설명합니다 .

일반적이고 파이썬적인 선호 솔루션은 거의 항상 "덕핑 타이핑 (duck typing)"입니다. 인자를 마치 원하는 특정 유형 인 것처럼 사용해보십시오. 실제로 인수가 아닌 경우 발생할 수있는 모든 예외를 포착하는 try / except 문에서 시도하십시오. 해당 유형 (또는 다른 유형으로 멋지게 오리를 흉내 내기 ;-)하고 except 절에서 다른 것을 시도하십시오 ( "as as"인수를 사용하여 다른 유형 임).

유용한 정보는 그의 게시물의 나머지 부분을 읽으십시오.


5

파이썬은 함수에 전달하는 것을 신경 쓰지 않습니다. 을 호출 my_func(a,b)하면 param1 및 param2 변수가 a 및 b의 값을 보유합니다. 파이썬은 당신이 적절한 타입으로 함수를 호출한다는 것을 알지 못하며 프로그래머가 그것을 처리 할 것으로 기대합니다. 함수가 다른 유형의 매개 변수로 호출되는 경우 try / except 블록으로 액세스하는 코드를 래핑하고 원하는 방식으로 매개 변수를 평가할 수 있습니다.


11
파이썬에는 변수에 유형과 값이있는 다른 언어와 같이 변수가 없습니다. 유형을 알고있는 objects를 가리키는 이름있습니다 .
tzot

2

유형을 지정하지 마십시오. 파이썬은 오리 타이핑 개념을 가지고 있습니다 . 기본적으로 매개 변수를 처리하는 코드는 매개 변수가 구현 될 것으로 예상되는 특정 메소드를 호출하여 매개 변수에 대한 특정 가정을합니다. 매개 변수의 유형이 잘못된 경우 예외가 발생합니다.

일반적으로 올바른 유형의 객체를 전달하는 것은 코드에 달려 있습니다.이를 미리 시행 할 컴파일러는 없습니다.


2

이 페이지에서 언급 할 가치가있는 오리 타이핑에서 한 가지 악명 높은 예외가 있습니다.

str함수가 __str__클래스 메소드를 호출 하면 유형을 미묘하게 나타냅니다.

>>> class A(object):
...     def __str__(self):
...         return 'a','b'
...
>>> a = A()
>>> print a.__str__()
('a', 'b')
>>> print str(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __str__ returned non-string (type tuple)

Guido가 예상치 못한 유형을 발견하면 프로그램이 어떤 예외를 제기해야 하는지를 알려주는 것처럼


1

파이썬에서는 모든 유형이 있습니다. Python 함수는 인수 유형이 지원하는 경우 요청 된 모든 작업을 수행합니다.

예 : 유형에 대해 크게 걱정하지 않고 ed foo될 수있는 모든 것을 추가 __add__합니다. 따라서 실패를 피하려면 추가를 지원하는 항목 만 제공해야합니다.

def foo(a,b):
    return a + b

class Bar(object):
    pass

class Zoo(object):
    def __add__(self, other):
        return 'zoom'

if __name__=='__main__':
    print foo(1, 2)
    print foo('james', 'bond')
    print foo(Zoo(), Zoo())
    print foo(Bar(), Bar()) # Should fail

1

나는 다른 답변에서 이것을 언급하지 않았으므로 냄비에 이것을 추가 할 것입니다.

다른 사람들이 말했듯이, 파이썬은 함수 또는 메소드 매개 변수에 유형을 적용하지 않습니다. 현재하고있는 일을 알고 있고 전달 된 내용의 유형을 알아야하는 경우이를 확인하고 스스로해야 할 일을 결정할 것입니다.

이를 수행하기위한 주요 도구 중 하나는 isinstance () 함수입니다.

예를 들어, 일반적인 utf-8 인코딩 문자열 대신 원시 이진 텍스트 데이터를 얻을 것으로 예상되는 메소드를 작성하는 경우 매개 변수 유형을 확인하고 찾은 내용에 맞게 조정하거나 거부하는 예외.

def process(data):
    if not isinstance(data, bytes) and not isinstance(data, bytearray):
        raise TypeError('Invalid type: data must be a byte string or bytearray, not %r' % type(data))
    # Do more stuff

파이썬은 또한 객체를 파는 모든 종류의 도구를 제공합니다. 용감한 사람이라면 importlib을 사용하여 임의의 클래스의 객체를 즉석에서 만들 수도 있습니다. JSON 데이터에서 객체를 다시 만들기 위해이 작업을 수행했습니다. 이러한 것은 C ++과 같은 정적 언어에서 악몽이 될 것입니다.


1

타이핑 모듈 (Python 3.5의 새로운 기능)을 효과적으로 사용하려면 모두 ( *)를 포함하십시오 .

from typing import *

그리고 당신은 사용할 준비가 될 것입니다 :

List, Tuple, Set, Map - for list, tuple, set and map respectively.
Iterable - useful for generators.
Any - when it could be anything.
Union - when it could be anything within a specified set of types, as opposed to Any.
Optional - when it might be None. Shorthand for Union[T, None].
TypeVar - used with generics.
Callable - used primarily for functions, but could be used for other callables.

그러나, 여전히 같은 유형 이름을 사용할 수 있습니다 int, list, dict, ...


1

누구나 변수 유형을 지정하려면 래퍼를 구현했습니다.

import functools

def type_check(func):

    @functools.wraps(func)
    def check(*args, **kwargs):
        for i in range(len(args)):
            v = args[i]
            v_name = list(func.__annotations__.keys())[i]
            v_type = list(func.__annotations__.values())[i]
            error_msg = 'Variable `' + str(v_name) + '` should be type ('
            error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ')'
            if not isinstance(v, v_type):
                raise TypeError(error_msg)

        result = func(*args, **kwargs)
        v = result
        v_name = 'return'
        v_type = func.__annotations__['return']
        error_msg = 'Variable `' + str(v_name) + '` should be type ('
        error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ')'
        if not isinstance(v, v_type):
                raise TypeError(error_msg)
        return result

    return check

다음과 같이 사용하십시오.

@type_check
def test(name : str) -> float:
    return 3.0

@type_check
def test2(name : str) -> str:
    return 3.0

>> test('asd')
>> 3.0

>> test(42)
>> TypeError: Variable `name` should be type (<class 'str'>) but instead is type (<class 'int'>)

>> test2('asd')
>> TypeError: Variable `return` should be type (<class 'str'>) but instead is type (<class 'float'>)

편집하다

인수 (또는 리턴) 유형이 선언되지 않은 경우 위 코드는 작동하지 않습니다. 반면에 다음 편집은 kwargs에서만 작동하며 인수를 확인하지 않습니다.

def type_check(func):

    @functools.wraps(func)
    def check(*args, **kwargs):
        for name, value in kwargs.items():
            v = value
            v_name = name
            if name not in func.__annotations__:
                continue

            v_type = func.__annotations__[name]

            error_msg = 'Variable `' + str(v_name) + '` should be type ('
            error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ') '
            if not isinstance(v, v_type):
                raise TypeError(error_msg)

        result = func(*args, **kwargs)
        if 'return' in func.__annotations__:
            v = result
            v_name = 'return'
            v_type = func.__annotations__['return']
            error_msg = 'Variable `' + str(v_name) + '` should be type ('
            error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ')'
            if not isinstance(v, v_type):
                    raise TypeError(error_msg)
        return result

    return check
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.