Python에서 여러 반환 유형에 대해 유형 힌트를 사용하는 방법 I

2024. 2. 6. 19:01python/intermediate

Python에서 유형 힌트는 선택 사항이지만 코드를 더 쉽게 읽고, 추론하고, 디버그할 수 있도록 하는 유용한 기능입니다. 유형 힌트를 사용하면 다른 개발자에게 변수, 함수 인수 및 반환 값에 대한 예상 데이터 유형을 알릴 수 있습니다. 더 큰 유연성이 필요한 애플리케이션에 대한 코드를 작성할 때 코드를 더욱 강력하고 다양한 상황에 적응할 수 있도록 여러 반환 유형을 지정해야 할 수도 있습니다 .

Python의 단일 함수 내에서 여러 반환 유형에 주석을 추가하려는 다양한 사용 사례가 있습니다. 즉, 반환되는 데이터의 유형이 다양할 수 있습니다. 이 튜토리얼에서는 이메일 주소의 문자열을 구문 분석하여 도메인 이름을 가져오는 함수에 대해 여러 반환 유형을 지정하는 방법의 예를 살펴보겠습니다.

또한 콜백 함수나 다른 함수를 입력으로 사용하는 함수에 대한 유형 힌트를 지정하는 방법에 대한 예도 볼 수 있습니다. 이러한 예제를 사용하면 함수형 프로그래밍에서 유형 힌트를 표현할 수 있습니다.

참고: 일반적으로 반환 값의 유형에 대해서는 구체적이면서 수용하는 인수 유형에 대해서는 관대한 함수로 작업하기를 원합니다. 예를 들어, 함수는 목록, 튜플, 생성기와 같은 모든 반복 가능 항목을 허용할 수 있지만 항상 목록을 반환합니다.

함수가 여러 가지 다른 유형을 반환할 수 있는 경우 먼저 단일 반환 유형을 갖도록 리팩토링 할 수 있는지 고려해야 합니다. 이 튜토리얼에서는 여러 반환 유형이 필요한 함수를 처리하는 방법을 배웁니다.

이 튜토리얼을 최대한 활용하려면 Python의 유형 힌트가 무엇 인지, 어떻게 사용하는지에 대한 기본 사항을 알아야 합니다.

대체 유형의 데이터 한 조각에 Python의 유형 힌트 사용

이 섹션에서는 다양한 유형일 수 있는 데이터 한 조각을 반환 할 수 있는 함수에 대한 유형 힌트를 작성하는 방법을 알아봅니다 . 여러 반환 유형을 고려하는 시나리오는 다음과 같습니다.

  1. 조건문 : 함수가 다양한 유형의 결과를 반환하는 조건문을 사용하는 경우 유형 힌트를 사용하여 함수에 대한 대체 반환 유형을 지정하여 이를 전달할 수 있습니다.
  2. 선택적 값 : 함수는 때때로 값을 반환하지 않을 수 있습니다. 이 경우 유형 힌트를 사용하여 가끔 반환 값이 없음을 알릴 수 있습니다.
  3. 오류 처리 : 함수에 오류가 발생하면 일반적인 결과의 반환 유형과 다른 특정 오류 개체를 반환할 수 있습니다. 그렇게 하면 다른 개발자가 코드 오류를 처리하는 데 도움이 될 수 있습니다.
  4. 유연성 : 코드를 디자인하고 작성할 때 일반적으로 코드가 다용도, 유연성 및 재사용이 가능하기를 원합니다. 이는 다양한 데이터 유형을 처리할 수 있는 함수를 작성하는 것을 의미할 수 있습니다. 유형 힌트에 이를 지정하면 다른 개발자가 코드의 다양성과 다양한 사례에서의 용도를 이해하는 데 도움이 됩니다.

아래 예에서는 조건문 작업 시 유형 힌트를 사용합니다. 고객 데이터를 처리 중이고 사용자의 이메일 주소를 구문 분석하여 사용자 이름을 추출하는 함수를 작성한다고 가정해 보세요.

Python 3.10 이상 에서 유형 힌트를 사용하여 여러 유형의 한 데이터 조각을 나타내려면 파이프 연산자(|) 를 사용할 수 있습니다. 다음은 일반적으로 사용자 이름이 포함된 문자열을 반환하지만 해당 이메일 주소가 불완전한 경우 None을 반환할 수 있는 함수에서 유형 힌트를 사용하는 방법입니다.

In [ ]:
def parse_email(email_address: str) -> str | None:
    if "@" in email_address:
        username, domain = email_address.split("@")
        return username
    return None

위의 예에서 함수에는 parse_email()인수로 전달된 이메일 주소에 기호(@)가 포함되어 있는지 확인하는 조건문이 있습니다. 그렇다면 함수는 해당 기호를 분할하여 기호 앞과 뒤의 요소를 추출하고 이를 지역 변수에 저장한 다음 사용자 이름을 반환합니다. 인수에 기호가 포함되어 있지 않으면 반환 값은 None이며 이는 잘못된 이메일 주소를 나타냅니다.

참고: 실제로 이메일 주소에 대한 확인 규칙은 훨씬 더 복잡합니다.

따라서 이 함수의 반환 값은 사용자 이름이 포함된 문자열이거나 이메일 주소가 불완전한 경우, None입니다. 반환 값에 대한 유형 힌트는 파이프 연산자(|)를 사용하여 함수가 반환하는 단일 값의 대체 유형을 나타냅니다. 3.10 이전 Python 버전에서 동일한 함수를 정의하려면 대체 구문을 사용할 수 있습니다.

In [ ]:
from typing import Union

def parse_email(email_address: str) -> Union[str, None]:
    if "@" in email_address:
        username, domain = email_address.split("@")
        return username
    return None

이 함수는 입력 값에 따라 문자열 또는 None을 반환하는 parse_email()을 가르키는 typing 모듈에서 Union의 유형을 사용합니다. 이전 구문을 사용하든 새 구문을 사용하든 통합 유형 힌트는 두 개 이상의 데이터 유형을 결합할 수 있습니다.

최신 Python 릴리스를 사용하는 경우에도 코드를 이전 Python 버전에서 실행해야 하는 경우 파이프 연산자보다 Union 유형을 선호할 수 있습니다.

참고: 다양한 유형을 반환할 수 있는 함수의 한 가지 과제는 함수를 호출할 때 반환 유형을 확인해야 한다는 것입니다. 위의 예에서는 이메일 주소를 구문 분석할 때 None을 얻었는지 테스트해야 합니다.

반환 유형이 인수 유형에서 추론될 수 있는 경우 대신 @overload을 사용하여 다른 유형 서명을 지정할 수 있습니다.

이제 잠재적으로 다른 유형의 단일 값을 반환하는 함수를 정의하는 방법을 알았으므로 유형 힌트를 사용하여 함수가 두 개 이상의 데이터를 반환할 수 있음을 선언하는 쪽으로 주의를 돌릴 수 있습니다.

다양한 유형의 여러 데이터 조각에 Python의 유형 힌트 사용

때로는 함수가 두 개 이상의 값을 반환하며, 유형 힌트를 사용하여 Python에서 이를 전달할 수 있습니다. 튜플을 사용하여 함수가 한 번에 반환하는 개별 데이터 조각의 유형을 나타낼 수 있습니다. Python 3.9 이상에서는 내장 tuple 데이터 구조를 사용할 수 있습니다 . 이전 버전에서는 typing.Tuple주석에 사용해야 합니다 .

이제 이전 예제를 기반으로 구축하려는 시나리오를 고려해보세요. 다양한 유형의 여러 데이터 조각을 통합하는 반환 값을 갖는 함수를 선언하려고 합니다. 이메일 주소에서 얻은 사용자 이름을 반환하는 것 외에도 도메인 도 반환하도록 기능을 업데이트하려고 합니다 .

다음은 유형 힌트를 사용하여 함수가 사용자 이름에 대한 문자열과 도메인에 대한 다른 문자열이 포함된 튜플을 반환함을 나타내는 방법입니다.

In [ ]:
def parse_email(email_address: str) -> tuple[str, str] | None:
    if "@" in email_address:
        username, domain = email_address.split("@")
        return username, domain
    return None

위 함수의 반환 유형은 이메일 주소의 사용자 이름과 도메인에 해당하는 두 문자열의 쌍입니다. 또는 입력 값이 유효한 이메일 주소를 구성하지 않는 경우 함수는 를 반환합니다 None.

반환 값에 대한 유형 힌트에는 대괄호 안에 쉼표로 구분된 두 개의 str 요소가 있는 튜플이 포함되어 있습니다. 이는 튜플에 정확히 두 개의 요소가 있고 둘 다 문자열임을 알려줍니다. 그런 다음 파이프 연산자(|) 뒤에 오는 None은 반환 값이 입력 값에 따라 두 문자열 튜플 또는 None 가 될 수 있음을 나타냅니다.

3.10 이전 Python에서 동일한 기능을 구현하려면 typing 모듈 에서 Tuple및 Union 유형을 사용하십시오.

In [ ]:
from typing import Tuple, Union

def parse_email(email_address: str) -> Union[Tuple[str, str], None]:
    if "@" in email_address:
        username, domain = email_address.split("@")
        return username, domain
    return None

이 표기법은 약간 더 장황하며 typing 모듈에서 두 가지 추가 유형을 가져와야 합니다. 반면에 이전 Python 버전에서는 사용할 수 있습니다.

참고: 공용체의 대체 유형과 마찬가지로 튜플에 임의 개수의 요소와 유형을 포함하여 유형 힌트에 여러 데이터 조각을 결합할 수 있습니다. 예는 다음과 같습니다.

def get_user_info(user: User) -> tuple[str, int, bool]: ...

이 경우 함수는 세 개의 값을 반환합니다. 하나는 문자열이고, 다음은 정수, 세 번째는 부울 값입니다.

자, 이제 Python의 고급 유형 힌트로 넘어갈 시간입니다!

콜백을 받는 함수 선언

Python을 포함한 일부 프로그래밍 언어에서는 함수가 다른 함수를 반환하거나 인수로 사용할 수 있습니다. 일반적으로 고차 함수 라고 알려진 이러한 함수는 함수형 프로그래밍 의 강력한 도구입니다 . 유형 힌트로 호출 가능 객체에 주석을 달려면 collections.abc 모듈에서 Callable의 유형을 사용할 수 있습니다.

참고: Python 3.9부터 더 이상 사용되지 않는 collections.abc.Callable와 typing.Callable를 혼동 하지 마십시오.

일반적인 유형의 고차 함수는 콜백을 인수로 사용하는 함수입니다. sorted(), map()및 filter()를 포함한 Python의 많은 내장 함수는 콜백 함수를 받아들이고 이를 일련의 요소에 반복적으로 적용합니다. 이러한 고차 함수는 명시적 루프를 작성할 필요가 없으므로 함수형 프로그래밍 스타일에 부합합니다.

다음은 콜러블을 인수로 사용하는 사용자 정의 함수로, 유형 힌트로 주석을 추가하는 방법을 보여줍니다.

In [ ]:
from collections.abc import Callable

def apply_func(
    func: Callable[[str], tuple[str, str]], value: str
) -> tuple[str, str]:
    return func(value)

def parse_email(email_address: str) -> tuple[str, str]:
    if "@" in email_address:
        username, domain = email_address.split("@")
        return username, domain
    return "", ""

apply_func(parse_email, "claudia@realpython.com")

위의 첫 번째 함수인 apply_func()는 호출 가능 객체를 첫 번째 인수로, 문자열 값을 두 번째 인수로 사용합니다. 호출 가능 개체는 일반 함수, 람다식 또는 특수 .call() 메서드가 있는 사용자 정의 클래스 일 수 있습니다. 그 외에 이 함수는 문자열 쌍을 반환합니다.

위의 Callable 유형 힌트에는 대괄호 안에 정의된 두 개의 매개변수가 있습니다. 첫 번째 매개변수는 입력 함수가 사용하는 인수 목록입니다. 이 경우 func()는 문자열 유형의 인수는 하나만 필요합니다. Callable 유형 힌트의 두 번째 매개변수는 반환 유형이며, 이 경우 두 문자열의 튜플입니다.

위 코드 조각의 다음 함수인 parse_email()는 항상 문자열 튜플을 반환하는 이전에 본 함수를 개조한 버전입니다.

그런 다음 첫 번째 인수로 parse_email()을 참조하는 apply_func()과 두 번째 인수로 문자열 "claudia@realpython.com을 사용하여 호출합니다. 그러면 apply_func()는 제공된 인수를 사용하여 제공된 함수를 호출하고 반환 값을 다시 사용자에게 전달합니다.

이제 apply_func()가 여러 입력 유형을 인수로 사용하고 여러 반환 유형을 갖는 다양한 함수를 사용하려면 어떻게 해야 할까요? 이 경우 Callable 유형 힌트 내부의 매개변수를 수정하여 더 일반적으로 만들 수 있습니다.

입력 함수의 개별 인수 유형을 나열하는 대신 줄임표 리터럴( ...) 을 사용하여 콜러블이 임의의 인수 목록을 사용할 수 있음을 나타낼 수 있습니다. 또한 typing 모듈의 Any의 유형을 사용하여 콜러블에 허용되는 모든 반환 유형을 지정할 수도 있습니다.

더 좋은 점은 유형 변수를 사용하여 콜러블의 반환 유형과 apply_func()사이의 연결을 지정할 수 있다는 것입니다.

어느 옵션이든 문제의 함수에 대해서만 반환 유형에 유형 힌트를 적용합니다. 아래에서는 다음과 같은 방식으로 이전 예제를 업데이트합니다.

In [ ]:
from collections.abc import Callable
from typing import Any, TypeVar

T = TypeVar("T")

def apply_func(func: Callable[..., T], *args: Any, **kwargs: Any) -> T:
    return func(*args, **kwargs)

이제 대괄호 안의 첫 번째 요소로 줄임표 리터럴을 사용한 위의 Callable을 추가했습니다. 따라서 입력 함수는 임의 유형의 인수를 원하는 만큼 취할 수 있습니다.

Callable 힌트 유형의 두 번째 매개변수는 이제 T입니다. 이는 모든 유형을 대신할 수 있는 유형 변수입니다. apply_func()의 반환 유형으로 T 도 사용하므로, apply_func()는 func와 동일한 유형을 반환한다고 선언합니다.

apply_func()에 제공된 콜러블은 임의 숫자의 인수를 취하거나 전혀 인수를 취하지 않을 수 있으므로 이를 표시하기 위해 *args 및 **kwargs 를 사용할 수 있습니다.

참고:작성된 대로 Callable 내부의 줄임표 와 *args 및 **kwargs의 Any주석 사이에는 명시적인 관계가 없습니다. 매개변수 사양 변수를 사용하여 이러한 유형 힌트를 더욱 향상할 수 있습니다.

In [ ]:
from collections.abc import Callable
from typing import ParamSpec, TypeVar

P = ParamSpec("P")
T = TypeVar("T")

def apply_func(func: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T:
    return func(*args, **kwargs)

이제 P은 func의 모든 매개변수를 나타내며, 그리고 apply_func()은 *args 및 **kwargs과 동일한 유형을 상속합니다.

Python 3.10 이전 Python 버전을 사용하는 경우 대신 typing_extensions에서 ParamSpec을 가져와야 합니다.

호출 가능한 인수에 주석을 추가하는 것 외에도 유형 힌트를 사용하여 함수의 호출 가능한 반환 유형을 공식적으로 지정할 수도 있습니다. 이제 자세히 살펴보겠습니다.