본문 바로가기
프로그래밍 이야기/Python

[Python] TypeVar 그리고 Generic 이해하기

by meticulousdev 2022. 4. 17.
반응형

1. 데이터 타입의 일반화

    파이썬 코드를 보다 보면 T = TypeVar(‘T’)Generic이라는 표현을 종종 보게 됩니다. T = TypeVar(‘T’)를 먼저 살펴보면 T는 형 변수(type variable)이고 ‘T’는 형 변수의 이름(variable name)입니다. TypeVar로 정의된 T는 어느 자료형이든 될 수 있는 변수가 됩니다. 기본 자료형인 int, string 부터 사용자가 만든 class까지 무엇이든 될 수 있습니다. 그래서 데이터 타입의 일반화입니다.

 

2. TypeVar을 알기 위한 type hints

    파이썬에서는 typing (support for type hints) 이라는 패키지가 있습니다. 파이썬 개발자가 프로그래밍할 때 전달되는 인자 및 반환 값이 어떤 자료형이었으면 좋겠는지에 대한 주석이라고 볼 수 있습니다. 주석이라고 표현한 이유는 설령 명시한 것과 다른 자료형이 들어가더라도 작동에 문제가 없기 때문입니다. 덧셈 함수에 대한 코드를 예로 들면 다음과 같습니다. a, b의 int, float 등 어떤 값이 입력되더라도 두 값의 덧셈 결과를 반환합니다.

 

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

 

같은 코드를 type hints를 사용하면 int 자료형만 받을 것이며, 반환되는 결과도 int 자료형으로 하고 싶을 경우 아래와 같이 코드를 짤 수 있습니다. 물론 이를 무시하고 값을 입력해도 결과는 정상적으로 출력됩니다.

 

def add(a: int, b: int) -> int: 
    return a + b

 

TypeVar()을 사용하여 T를 정의하면 type hintsT를 사용할 수도 있습니다. 이렇게 선언할 경우 a, b로 정수형, 실수형, 문자열 등 덧셈 연산을 할 수 있는 모든 자료형을 받겠다는 것을 의미합니다.

 

from typing import TypeVar 


T = TypeVar('T') 


def add(a: T, b: T) -> T: 
    return a + b

 

T의 후보군을 정할 수도 있습니다. 형 변수 이름 뒤에 " , "로 구분하고 int, float을 써주면 int, float로 자료형을 제한하게 됩니다. 파이썬 버전에 따라서는 여기서 오류를 발생시키기도 합니다.

 

from typing import TypeVar 


T = TypeVar('T', int, float) 


def add(a: T, b: T) -> T: 
    return a + b

 

https://stackoverflow.com/questions/48417071/purpose-of-name-in-typevar-newtype

 

Purpose of __name__ in TypeVar, NewType

In the typing module, both TypeVar and NewType require as a first positional argument, a string to be used as the created object's __name__ attribute. What is the purpose of __name__ here? Conside...

stackoverflow.com

 

3. 제너릭 프로그래밍

    Generic제너릭 프로그래밍(generic programming)과 관련이 깊습니다. 제너릭 프로그래밍은 자료형에 상관없이 동작할 수 있는 프로그램을 만들어서 개발된 프로그램의 재사용성을 높이는 프로그래밍 방식입니다. 아래의 예제는 사람의 정보를 저장하는 예제로 이름과 전화번호를 저장합니다.

 

main.py 

from typing import TypeVar, Generic 


T = TypeVar('T', str, int) 


class PersonInfo(Generic[T]): # 사람의 정보를 저장하는 클래스 
    def __init__(self, name: str, phone: T): 
        self.name = name 
        self.phone = phone 
    
    def get_info(self): # 사람의 정보 출력 
        print(f"name : {self.name}") 
        print(f"phone : {self.phone}") 
        print() 


if __name__ == "__main__": 
    student1 = PersonInfo("John", 1234567) 
    student2 = PersonInfo("Smith", "234-5678") 
    student3 = PersonInfo("Jack", [1, 1, 1, 2, 2, 2, 2]) 
    
    student1.get_info() 
    student2.get_info() 
    student3.get_info()

 

PersonInfo 클래스에서는 Generic[T] 클래스를 상속받습니다. 단순하게 얘기하면 Generic[T]를 사용하여서 PersonInfo가 T를 사용할 수 있도록 하겠다는 것입니다. 누군가는 정수형으로 전화번호를 적을 수도 있고 누군가는 “-”를 포함하여 문자형으로 전화번호를 입력할 수도 있습니다. 그렇기 때문에 int와 str 두 종류의 자료형으로 받겠다고 한정한 것입니다. 물론 이를 무시하고 리스트로 입력해도 문제없이 실행됩니다.

 

name : John 
phone : 1234567 

name : Smith 
phone : 234-5678 

name : Jack 
phone : [1, 1, 1, 2, 2, 2, 2]

 

여기까지가 TypeVar과 Generic에 대한 설명입니다. TypeVar은 형 변수를 정의하는 데 사용되고, Generic은 정의된 TypeVar을 클래스 내부에서 사용하기 위해서 사용됩니다. 이들은 정말 하나의 힌트입니다. 코드의 가독성을 높일 수 있는 매우 좋은 힌트입니다.

 

4. 형 변수의 이름은 왜 필요한가요?

    파이썬 버전에 따라서는 제한된 형 변수의 자료형 이외의 자료형이 입력될 경우 형 변수의 이름이 포함된 오류를 발생시킵니다. 그렇기 때문에 무엇이 문제인지 찾기 위해서 반드시 형 변수와 형 변수의 이름은 동일하게 통일해야 합니다.

 

from typing import TypeVar 


T = TypeVar('N', int, float) 


if __name__ == "__main__": 
    print(f"T: {T.__name__}")
T: N

 

만약 둘을 다르게 할 경우 Visual Studio Code에서는 아래와 같이 경고를 보냅니다.

 

TypeVar must be assigned to a variable named "N"

 

5. 감사 글

    이 글은 항상 게으르다고 하시지만 너무나도 부지런하신 분의 질문 덕분에 탄생한 글입니다.
https://keizikang.tistory.com/m

긴 글 읽어주셔서 감사합니다.
글과 관련된 의견은 언제든지 환영입니다.
반응형

댓글