웹사이트 검색

Python 타이핑 모듈 - 유형 검사기를 효과적으로 사용


Python 3.5부터 도입된 Python의 타이핑 모듈은 정적 유형 검사기 및 린터가 오류를 정확하게 예측하는 데 도움이 되는 힌트 유형 방법을 제공하려고 시도합니다.

Python은 런타임 동안 객체 유형을 결정해야 하기 때문에 개발자가 코드에서 정확히 무슨 일이 일어나고 있는지 알아내는 것이 때때로 매우 어려워집니다.

PyCharm IDE와 같은 외부 유형 검사기도 최상의 결과를 생성하지 않습니다. StackOverflow의 이 답변에 따르면 평균적으로 약 50%의 오류만 정확하게 예측합니다.

Python은 외부 유형 검사기가 오류를 식별하는 데 도움이 되도록 유형 힌트(유형 주석)라고 알려진 것을 도입하여 이 문제를 완화하려고 시도합니다. 이는 프로그래머가 컴파일 시간 동안 사용 중인 개체의 유형을 암시하고 유형 검사기가 올바르게 작동하는지 확인하는 좋은 방법입니다.

이것은 파이썬 코드를 다른 독자들에게도 훨씬 더 읽기 쉽고 강력하게 만듭니다!

참고: 이것은 컴파일 시간에 실제 유형 검사를 수행하지 않습니다. 반환된 실제 객체가 암시된 것과 동일한 유형이 아닌 경우 컴파일 오류가 발생하지 않습니다. 이것이 우리가 유형 오류를 식별하기 위해 mypy와 같은 외부 유형 검사기를 사용하는 이유입니다.

권장 전제 조건

typing 모듈을 효과적으로 사용하려면 외부 유형 검사기/린터를 사용하여 정적 유형 일치를 확인하는 것이 좋습니다. 파이썬에서 가장 널리 사용되는 타입 검사기 중 하나가 mypy이므로 나머지 기사를 읽기 전에 설치하는 것이 좋습니다.

우리는 이미 파이썬에서 타입 검사의 기초를 다뤘습니다. 이 기사를 먼저 살펴볼 수 있습니다.

이 기사에서는 mypy를 정적 유형 검사기로 사용할 것이며 다음을 통해 설치할 수 있습니다.

pip3 install mypy

모든 Python 파일에 대해 mypy를 실행하여 유형이 일치하는지 확인할 수 있습니다. 이것은 마치 파이썬 코드를 '컴파일'하는 것과 같습니다.

mypy program.py

오류를 디버깅한 후 다음을 사용하여 프로그램을 정상적으로 실행할 수 있습니다.

python program.py

이제 전제 조건을 다뤘으니 모듈의 일부 기능을 사용해 봅시다.

유형 힌트/유형 주석

기능에

함수에 주석을 달아 반환 유형과 매개변수 유형을 지정할 수 있습니다.

def print_list(a: list) -> None:
    print(a)

이것은 유형 검사기(제 경우에는 mypy)에 print_list() 함수가 있음을 알립니다. 이 함수는 list를 인수로 사용하고 없음을 반환합니다.

def print_list(a: list) -> None:
    print(a)

print_list([1, 2, 3])
print_list(1)

먼저 타입 검사기 mypy에서 이것을 실행해 봅시다:

vijay@JournalDev:~ $ mypy printlist.py 
printlist.py:5: error: Argument 1 to "print_list" has incompatible type "int"; expected "List[Any]"
Found 1 error in 1 file (checked 1 source file)

예상대로 오류가 발생합니다. 5번째 줄에 list가 아닌 int 인수가 있기 때문입니다.

변수에

Python 3.6부터는 유형을 언급하면서 변수 유형에 주석을 달 수도 있습니다. 그러나 함수가 반환되기 전에 변수의 유형을 변경하려는 경우 필수 사항은 아닙니다.

# Annotates 'radius' to be a float
radius: float = 1.5

# We can annotate a variable without assigning a value!
sample: int

# Annotates 'area' to return a float
def area(r: float) -> float:
    return 3.1415 * r * r


print(area(radius))

# Print all annotations of the function using
# the '__annotations__' dictionary
print('Dictionary of Annotations for area():', area.__annotations__)

mypy 출력:

vijay@JournalDev: ~ $ mypy find_area.py && python find_area.py
Success: no issues found in 1 source file
7.068375
Dictionary of Annotations for area(): {'r': <class 'float'>, 'return': <class 'float'>}

이것은 유형 검사기를 사용하기 전에 먼저 유형 주석을 제공하는 mypy를 사용하는 권장 방법입니다.

유형 별칭

typing 모듈은 별칭에 유형을 할당하여 정의되는 유형 별칭을 제공합니다.

from typing import List

# Vector is a list of float values
Vector = List[float]

def scale(scalar: float, vector: Vector) -> Vector:
    return [scalar * num for num in vector]

a = scale(scalar=2.0, vector=[1.0, 2.0, 3.0])
print(a)

산출

vijay@JournalDev: ~ $ mypy vector_scale.py && python vector_scale.py
Success: no issues found in 1 source file
[2.0, 4.0, 6.0]

위 스니펫에서 Vector는 부동 소수점 값 목록을 나타내는 별칭입니다. 우리는 위의 프로그램이 하는 일인 별칭에 힌트를 입력할 수 있습니다.

허용되는 별칭의 전체 목록은 여기에 제공됩니다.

사전의 모든 키:값 쌍을 확인하고 이름:이메일 형식과 일치하는지 확인하는 예를 하나 더 살펴보겠습니다.

from typing import Dict
import re

# Create an alias called 'ContactDict'
ContactDict = Dict[str, str]

def check_if_valid(contacts: ContactDict) -> bool:
    for name, email in contacts.items():
        # Check if name and email are strings
        if (not isinstance(name, str)) or (not isinstance(email, str)):
            return False
        # Check for email xxx@yyy.zzz
        if not re.match(r"[a-zA-Z0-9\._\+-]+@[a-zA-Z0-9\._-]+\.[a-zA-Z]+$", email):
            return False
    return True


print(check_if_valid({'vijay': 'vijay@sample.com'}))
print(check_if_valid({'vijay': 'vijay@sample.com', 123: 'wrong@name.com'}))

mypy의 출력

vijay@JournalDev:~ $ mypy validcontacts.py 
validcontacts.py:19: error: Dict entry 1 has incompatible type "int": "str"; expected "str": "str"
Found 1 error in 1 file (checked 1 source file)

여기에서 두 번째 사전의 name 매개변수가 정수(123)이기 때문에 mypy에서 정적 컴파일 시간 오류가 발생합니다. 따라서 별칭은 mypy에서 정확한 유형 검사를 시행하는 또 다른 방법입니다.

NewType()을 사용하여 사용자 정의 데이터 유형 생성

NewType() 함수를 사용하여 새 사용자 정의 유형을 만들 수 있습니다.

from typing import NewType

# Create a new user type called 'StudentID' that consists of
# an integer
StudentID = NewType('StudentID', int)
sample_id = StudentID(100)

정적 유형 검사기는 새 유형을 원래 유형의 하위 클래스인 것처럼 처리합니다. 이는 논리적 오류를 파악하는 데 유용합니다.

from typing import NewType

# Create a new user type called 'StudentID'
StudentID = NewType('StudentID', int)

def get_student_name(stud_id: StudentID) -> str:
    return str(input(f'Enter username for ID #{stud_id}:\n'))

stud_a = get_student_name(StudentID(100))
print(stud_a)

# This is incorrect!!
stud_b = get_student_name(-1)
print(stud_b)

mypy의 출력

vijay@JournalDev:~ $ mypy studentnames.py  
studentnames.py:13: error: Argument 1 to "get_student_name" has incompatible type "int"; expected "StudentID"
Found 1 error in 1 file (checked 1 source file)

모든 유형

이것은 모든 유형이 이 키워드와 호환됨을 정적 유형 검사기(필자의 경우 mypy)에 알리는 특수 유형입니다.

이제 모든 유형의 인수를 허용하는 이전 print_list() 함수를 고려하십시오.

from typing import Any

def print_list(a: Any) -> None:
    print(a)

print_list([1, 2, 3])
print_list(1)

이제 mypy를 실행할 때 오류가 발생하지 않습니다.

vijay@JournalDev:~ $ mypy printlist.py && python printlist.py
Success: no issues found in 1 source file
[1, 2, 3]
1

반환 유형이나 매개변수 유형이 없는 모든 함수는 묵시적으로 Any를 사용하도록 기본 설정됩니다.

def foo(bar):
    return bar

# A static type checker will treat the above
# as having the same signature as:
def foo(bar: Any) -> Any:
    return bar

따라서 Any를 사용하여 정적으로 유형이 지정된 코드와 동적으로 유형이 지정된 코드를 혼합할 수 있습니다.

결론

이 기사에서는 mypy와 같은 외부 유형 검사기가 모든 오류를 정확하게 보고할 수 있도록 하는 유형 검사 컨텍스트에서 매우 유용한 Python 타이핑 모듈에 대해 배웠습니다.

이것은 설계상 동적으로 유형이 지정된 언어인 Python에서 정적으로 유형이 지정된 코드를 작성하는 방법을 제공합니다!

참조

  • 타이핑 모듈에 대한 Python 문서(이 모듈의 더 많은 메서드에 대한 자세한 내용이 포함되어 있으며 보조 참조로 권장합니다.)
  • 유형 힌트에 대한 StackOverflow 질문(주제에 대한 매우 좋은 토론을 제공합니다. 이 주제도 읽어 보시기 바랍니다!)