typing 과 정적 분석

About_work·2022년 12월 31일
0

python clean code

목록 보기
4/11

참고 문서

  • 파이썬 코딩의 기술

언제 써야해?

  • API와 같은 코드베이스의 경계에서 가장 중요하다.
  • API는 아니지만, 코드베이스에서 가장 복잡하고 오류가 발생하기 쉬운 부분에 적용해도 유용할 수 있다.
  • 모든 코드에 100% 적용하는 것은 바람직하지 않을 수 있다.
  • 자동 빌드와 테스트 시스템의 일부분으로 정적 분석을 포함시켜서, 코드베이스에 커밋할 때마다 오류가 없는지 검사해야 한다.
    • 타입 검사에 사용할 설정을 저장소에 유지해서, 모든 사람이 똑같은 규칙을 사용하게 해야한다.

google python guide 에서는?

  • 장점
    • 가독성과 유지보수성을 증가시킨다.
    • type checker가 이를 자동화해준다.
  • 단점
    • 귀찮다.
  • 할지 말지 결정하기
    • 파이썬 type checking은 강하게 권장된다.
    • public API를 더하거나 변경할 때 , type annotation을 추가하라.
    • type checker가 잘못된 버그를 냈으면, author은 TODO 커멘트를 적거나, 커멘트에 link를 적어라.

기본

  • 파이썬에는 특별한 구문과 typing 모듈이 도입돼, "변수/클래스 필드/함수/메서드"에 type annotation 을 덧붙일 수 있다.
  • 이런 type hint 를 이용하면, type이 필요할 때마다 코드베이스를 점진적으로 변경하는 "점진적 타입 지정"이 가능하다.
  • type annotation을 이용하면, 정적 분석 도구로 프로그램 소스 코드를 검사할 수 있다.
  • 참고: 파이썬 interpreter 구현은 여러개가 있다. (예: CPython, PyPy)
  • typing을 분석하는 정적 분석 도구 종류
  • 이 문서에서는 mypy에 --strict 플래그를 사용합니다.
    • --strict 플래그로 여러 가지 경고를 모두 사용할 수 있다.
    • $ python3 -m mypy --strict example.py

함수 예시

def subtract(a: int, b:int) -> int:
	return a-b

subtract(10, '5')

$ python3 -m mypy --strict example.py
.../example.py: error: Argument 2 to "subtract" has incompatible type "str"; expected "int"

class 예시

  • class에 type annotation을 적용할 수도 있다.
  • 정적 분석기는 self를 안 쓴 것, return을 깜빡한 것 까지 체크해준다.
class Counter:
	def __init__(self) -> None:
    	self.value: int = 0 # 필드/변수 annotation
    
    def add(self, offset: int) -> None:
    	value += offset # 헉. 'self.'를 안썼다..
    
    def get(self) -> int:
    	self.value # 헉. 'return'을 안썼다..
 
counter = Counter()
counter.add(5)
counter.add(3)
assert counter.get() == 8

$ python3 -m mypy --strict example.py
.../example.py:6: error: Name 'value' is not defined
.../example.py:8: error: Missing return statement

duck type 예시

  • duck type?
    • 사람이 오리처럼 행동하면 오리로 봐도 무방하다라는게 덕 타이핑(Duck Typing)이다.
    • 타입을 미리 정하는게 아니라 실행이 되었을 때 해당 Method들을 확인하여 타입을 정한다.
  • 동적으로 작동하는 python의 강점은 duck type에 대해 작동하는 generic 기능을 작성하기 쉽다는 점이다.
    • generic 기능: 여러 type에 대해 작동할 수 있는 일반적인 코드를 작성할 수 있게 해주는 기능
  • duck type에 대한 generic 기능을 사용하면, 한 구현으로 다양한 타입을 처리할 수 있으므로 반복적인 수고를 줄일 수 있고 + 테스트도 단순해진다.
from typing import Callable, List, TypeVar

Value = TypeVar('Value') # Can be anything
Func = Callable[[Value, Value], Value]

def combine(func: Func[Value], values: List[Value]) -> Value:
	assert len(values) > 0 
    result = values[0]
    for next_value in values[1:]:
    	result = func(result, next_value)
    return result
    
Real = TypeVar('Real', int, float)

def add(x: Real, y: Real) -> Real:
	return x + y

inputs = [1, 2, 3, 4j]
result = combine(add, inputs)
assert result == 10

$ python3 -m mypy --strict example.py
.../example.py:21: error: Argument 1 to "combine" has incompatible type "Callable[[Real, Real], Real]"; expected "Callable[[complex, complex], complex]"

Typing 사용법

typing.Type 사용법

  • Type은 Python의 typing 모듈에서 제공하는 클래스로, 클래스나 인스턴스의 타입을 나타냅니다.
  • Type을 사용하면 클래스나 인스턴스의 타입을 동적으로 가져오거나 전달할 수 있습니다.
  • 이는 런타임에서 객체의 타입을 검사하거나 객체의 동적인 생성 및 조작 등 다양한 용도로 사용됩니다.

typing.TypeVar 사용법

  • TypeVar는 제네릭 타입을 만들 때 사용하는 클래스입니다.

  • 제네릭 타입?

    • 제네릭 타입은 클래스나 함수에서 사용할 데이터 타입을 나중에 지정할 수 있는 타입입니다.

    • 제네릭 타입을 사용하면 데이터 타입에 대한 추상화 수준을 높일 수 있으며, 코드 재사용성과 유연성을 높일 수 있습니다.

    • 제네릭 타입은 다음과 같은 장점을 가집니다.

      • 타입 추론
        • 제네릭 타입을 사용하면 실제 데이터 타입이 지정되기 전에 코드를 작성할 수 있으므로, 코드를 더 추상적으로 작성할 수 있습니다.
        • 또한 제네릭 타입을 사용하면 컴파일러가 함수나 클래스를 호출할 때 실제 데이터 타입을 추론할 수 있으므로, 코드를 더 간결하고 유지보수하기 쉽게 만들 수 있습니다.
      • 코드 재사용
        • 제네릭 타입을 사용하면 데이터 타입에 대한 추상화 수준을 높일 수 있으므로, 함수나 클래스를 다른 데이터 타입에서도 재사용할 수 있습니다.
      • 타입 안정성
        • 제네릭 타입을 사용하면 컴파일러가 타입을 검사할 수 있으므로, 런타임에서 발생할 수 있는 타입 오류를 미리 방지할 수 있습니다.
    • 제네릭 타입은 다양한 프로그래밍 언어에서 지원됩니다.

    • Python에서도 제네릭 타입을 지원하며, typing 모듈을 사용하여 제네릭 함수나 클래스를 정의할 수 있습니다.

    • 예를 들어, List, Tuple, Dict 등은 제네릭 타입으로 정의되어 있으며, 다양한 데이터 타입의 요소를 저장할 수 있습니다.

  • TypeVar를 사용하면 타입 어노테이션에서

    • 추상적인 타입 변수를 정의할 수 있으며,
    • 이 변수를 실제 타입으로 대체하는 과정은 런타임에서 수행됩니다.

TypeVar 클래스를 사용하는 방법은 다음과 같습니다.

from typing import TypeVar

T = TypeVar('T')  # Can be anything
S = TypeVar('S', bound=str)  # Can be any subtype of str
A = TypeVar('A', str, bytes)  # Must be exactly str or bytes

U = TypeVar('U', bound=str|bytes)  # Can be any subtype of the union str|bytes
# SupportsAbs -> abstract types (ABCs or protocols)
V = TypeVar('V', bound=SupportsAbs)  # Can be anything with an __abs__ method 

typing.Callable 사용법

  • Callable[[int], str] is a function of (int) -> str.
  • argument list: list of types 거나, ...
  • retrun type: 반드시 single type 이어야 한다.
  • optional 혹은 keyword argument를 나타내는 문법은 없다.
  • Callable[..., ReturnType]은 범용적으로 쓰일 수 있다.
  • collections.abc.Callable 와의 관계
    • Callable = Callable[..., Any] = collections.abc.Callable

typing.Optional 사용법

  • null 검사를 제대로 수행한 경우에만 값을 다룰 수 있게 강제한다.
  • typing.Optinal[X] == typing.Union[X, None]
from typing import Optional


def get_or_default(value: Optional[int], default: int) -> int:
	if value is not None:
    	return value
    return default

typing.Union 사용법

  • X나 Y중 오면 된다~
  • typing.Union[X, Y] == X|Y(얜 파이썬 3.10부터 사용가능 ㅠ)

collections.abc 사용법

  • 대체로, 비슷한 유형의 여러 type들이 가능하다는 것을 표현하기 위해 쓰임.

collections.abc.Mapping

  • A container object that supports arbitrary key lookups and implements the methods specified in the collections.abc.Mapping or collections.abc.MutableMapping abstract base classes.
  • Examples include dict, collections.defaultdict, collections.OrderedDict and collections.Counter.
from collections.abc import Mapping, Sequence
from typing import Any

def keys(mapping: Mapping[str, Any]) -> Sequence[str]:
  return tuple(mapping)

collections.abc.Sequence

  • An iterable which supports efficient element access using integer indices via the __getitem__() special method and defines a __len__() method that returns the length of the sequence.

  • Some built-in sequence types are list, str, tuple, and bytes.

  • Note that dict also supports __getitem__() and __len__(), but is considered a mapping rather than a sequence because the lookups use arbitrary immutable keys rather than integers.

  • The collections.abc.Sequence abstract base class defines a much richer interface that goes beyond just __getitem__() and __len__(), adding count(), index(), __contains__(), and __reversed__().

  • Types that implement this expanded interface can be registered explicitly using register().

collections.abc.Callable

  • ABC for classes that provide the __call__() method.
from collections.abc import Callable

def instantiate(factory: Callable[[], int]) -> int:
  return factory()

전방 참조 문제 해결법

  • 아직 정의되지 않은 클래스를 type annotation으로 사용하면, 코드상에 에러가 발생한다.
    • NameError: name `SecondClass` is not defined
  • 아래의 2가지 방법으로 해결하라.
class FirstClass:
	def __init__(self, value: 'SecondClass') -> None:
    	self.value = value
from __future__ import annotations
# 프로그램 실행 시, type annotation 에 지정된 값을 완전히 무시하라고 지시한다.

class FirstClass:
	def __init__(self, value: SecondClass) -> None:
    	self.value = value

profile
새로운 것이 들어오면 이미 있는 것과 충돌을 시도하라.

0개의 댓글