목차
1.1 주석은 왜 필요할까?
주석은 프로그램에서 가장 중요한 기능 중에 하나이다.
내가 작성한 코드를 남들이 해석하려 할 때, 코드가 긴 경우나 복잡하게 설계 된 경우,
보는 사람으로 하여금, 이해하기 쉽게 하기 위함이다.
주석은 컴퓨터의 컴파일러에 의해 무시되며, 프로그램의 실행에 직접적인 영향을 미치지 않는다.
코딩을 하다보면 오래된 소스인 경우, 기억이 잘 나지 않는 경우가 더려 있기 때문에, 설정하는 편이다.
소스에서 주석을 다는 습관을 들이면 유지보수를 함에 있어서, 좋은 습관이다.
주석은 다른 개발자들과의 협업이나 나중에 코드를 다시 컴토할 때 까먹는 현상을 방지할 수 있게 도와준다.
하지만 너무 복잡하게 쓰면 오히려 안좋을 수 있다.
1.2 한줄 주석
한줄 주석에선 파이쎤에선 "#", 대부분의 언어에선 "//"을 사용한다.
주석의 위치는 따로 문법적으로 정해진 바는 없다.
1.3 범위 주석
def subtract(var_a, var_b):
"""
Input: var_a (int): Variable A
var_b (int): Variable B
Output:
subtraction (int): Subtraction of B from A
"""
subtraction = var_a - var_b
return subtraction
다른 여러 개발 언어 (대표적으로 자바, C, C#, C++)에선 /* */를 채택하고 있다.
하지만 Python에는 여러줄의 주석을 지원하는 내장 기능은 사실 존재하지 않는다.
대신 파이썬에서는 코드의 문서화와 편리한 문서 참조를 위해 기본 기능으로 독스트링(Docstring) 이라는 기능을 제공해주고 있다.
독스트링은 큰따옴표 """ 또는 작은따옴표 ''' 3개를 연달아서 사용하는 문자열이다.
또한 파이썬의 독스트링은 소스와 동일하게 들여쓰기를 사용하여야 한다.
사용하지 않으면 "IndentationError : unexpected indent" 에러와 함께 에러가 발생된다.
1.4 파이썬의 중요한 사실
파이썬은 기본적으로 동작 타입 언어( dynamically typed language)이다.
어떤 변수에 값을 대입할 때, 타입을 선언해줄 필요 없이 파이썬 인터프리터가 프로그램 실행 중 변수의 타입을 판단한다.
이런 특성은 C++이나 Java 등 변수를 선언할 때 부터 타입을 strict 하게 지정해 줘야 하는 정적 타입 언어들과 비교된다.
컴파일 이후에 유연하게 타입이 결정된다는 파이썬의 특징은 빠른 개발을 가능하게 하며, 언어 자체의 진입 장벽이 낮다는 장점을 갖고 있다.
실제로 처음 개발을 시작할 때, 파이썬을 먼저 배우는 이유도 그 때문이다.
다만, 타입의 안전성이 컴파일 단계에서 보장되지 않아, 실행 중 예상치 못한 Type Error와 마주할 수 있다는 단점을 갖고 있는 언어이다.
또한 마침표인 ";"를 사용하지 않아도, 코드가 정상 실행된다.
그래서일까, 대부분의 언어는 (정적 타입 언어)의 성격을 띄기에 C언어부터 배우라는 견해도 존재한다.
1.5 Type hint
이런 문제에 대응하기 위해 Python 3.5 버전 이후부터 기본 모듈로 typing 모듈이 추가되어, 타입 힌트 (type hint) 기능을 이용할 수 있게 되었다. 한번 다음 예제를 보자.
def subtract(var_a, var_d) :
return var_a - var_b;
subtract(3, '5')
>>
TypeError: unsupported operand type (s) for -: 'int' and 'str'
위 코드는 파이썬 코드의 실행 단계에선 아무런 문제가 없어, 컴파일이 된다.
하지만, 코드 실행 중에 에러가 발생한다.
짧은 코드에서는 함수에 어떤 타입의 인자가 들어가야 잘 작동하고 어떤 타입이 결과로 출력되는지 바로바로 파악이 가능하지만, 코드가 복잡해질수록 이 과정은 어려워진다.
물론 변수명을 잘 지어놓거나 아래와 같이 독스트링, 주석을 사용해 설명하는 방법도 있지만 독스트링이나 주석 자체에는 타입 설명에 관한 뾰족한 공통 컨벤션이 없기 때문에, 쓰는 입장에서나 읽는 입장에서나 어느 정도 작성 규칙에 관해 노력을 들일 필요가 있다.
def subtract(var_a, var_b):
"""
Input: var_a (int): Variable A
var_b (int): Variable B
Output:
subtraction (int): Subtraction of B from A
"""
subtraction = var_a - var_b
return subtraction
본문의 예제처럼 독스트링이나 주석(#)을 이용해 타입을 병기해놓을 수도 있지만, 명확한 포맷이 없을 경우 쓰기도 읽기도 버겁다.
그래서일까. 파이썬에서도 타입 힌트라는 기능으로 타입을 병기하는 걸 허용하고 있다.
Type hinting w/ basic types
def subtract(var_a: int, var_b: int) -> int:
return var_a - var_b
위의 예제는 함수의 인자와 함수의 반환 값을 명시해주고 있다.
주석을 사용해 타입을 명시하는 방법보다 보기 쉬우며, 정형화 되어 있다. 이 방식은 클래스의 멤버 함수에도 똑같이 적용할 수 있다.
var_a: int = 5
var_b: str = 'ABCD'
var_c: list = [1,2,3]
디폴트 값이 존재하는 함수 인자도 위와 똑같은 방식으로 작성한다.
함수의 인자와 반환값 뿐만 아니라, 위와 같이 단순히 변수를 initialize 할 때도 구체적인 타입을 명시해 줄 수 있다.
여기서 더 나아가, 더욱 복잡한 형태의 타입 힌트를 적용하고 싶다면 파이썬 내장 모듈인 typing 모듈을 이용할 수 있다. 몇 가지 예시를 통해 간단한 기능들을 알아보자.
TypeVar
from typing import TypeVar, Tuple, List
Integer = TypeVar('Integer', int)
def swap_and_collate(var_a: Integer, var_b: List[str]) -> Tuple[List[str], Integer]:
return var_b, var_a
위 예제에서는 TypeVar를 이용해 사용자 지정 타입 지정자를 새로 정의하고 있다.
이렇게 만들어진 타입 지정자는 str, int와 같은 디폴트 타입 지정자와 같은 방식으로 사용할 수 있다.
만약 여러 타입을 허용하는 타입 지정자를 표현하고 싶다면, 아래와 같이 TypeVar 이니셜라이즈 시 여러 타입을 동시에 인자로 제공해 주면 된다.
Real = TypeVar('Real',int,float)
List, Tuple
var_list: List[str]
var_tuple: Tuple[str,int,int]
또한, 리스트 형 변수의 경우에도 대괄호를 활용해 변수 타입을 고정하여 타입의 리스트를 표현할 수 있다.
Tuple도 마찬가지다.
Union
from typing import TypeVar, Tuple, List, Union, Callable, Optional
def swap_and_collate(
var_a: Union[int, float],
var_b: List[str],
) -> Tuple[List[str], Union[int, float]]:
return var_b, var_a
인자 타입으로 여러 가지의 타입을 허용하고 싶을 때에는 TypeVar를 이용할 수도 있지만, 위와 같이 Union을 이용할 수도 있다. Union 뒤의 브라켓 안에 허용하고 싶은 타입을 연속으로 나열하면 된다.
파이썬 3.10부터는 단순히 '|' 오퍼레이터만을 이용해 표현할 수도 있다.
var_real : int | float
해당 문법의 의미는 int형과 float의 정보만 해당 변수에 집어 넣을 수 있다는 의미이다.
Generic, Callable
from typing import TypeVar, Callable, Optional
Value = TypeVar('Value')
Func = Callable[[Value], Value]
def repetitive_eval(
func: Func[Value],
val: Value,
repeat: Optional[int] = None,
) -> Value:
if not repeat:
return val
for iteration in range(repeat):
val = func(val)
return val
def add_one(a: int) -> int:
return a+4
repetitive_eval(add_one, 2, 5)
>> 7
repetitive_eval(add_one, 4, None)
>> 4
또한 TypeVar를 이용해 제네릭 타입을 구현하거나 Callable를 이용해 특정 타입을 인자로 받아서 특정 타입의 값을 반환하는 콜백 함수를 표현할 수도 있다.
Optional
def repetitive_eval (
func: Func[Value],
val: Value,
repeat: Optional[int] = None,
) -> Value:
...
Optional은 인자에 비어있는 값, None 값이 전달되는 것을 허용하는 걸 표현해두고 싶을 때 사용한다.
이 경우와 같이 None 디폴트 값을 가지는 가변 인자에 적용하고, 함수 내부에는 None을 처리하는 구문을 따로 두는 것이 일반적인 적용 방식이다.
이 정도면 대부분의 타입 힌트를 구현하는 데에 문제는 없을 것이다. 더 다양한 기능이 필요하다면, 파이썬 공식 도큐멘테이션을 참고하는 것이 좋다. 대부분 다 나와있으니까ㅎㅎㅎㅎㅎ
#. 과연 파이썬은 타입 힌트를 설정할 시, 컴파일 이전에 에러를 탐지할 수 있을까?
def subtract ( var_a: int, var_b: int ) -> int :
return var_a - var_b
subtract(3, '5')
>> TypeError: unsupported operand type(s) for -: 'int' and 'str'
똑같이 컴파일 단계에서 에러를 발생시킨다.
타입 힌트는 개발자로 하여금, 바람직한 변수에 대한 힌트를 제공해주는 요소 일 뿐이지, 코드 단계에서 해당 변수에 들어갈 수 있는 정보의 자료형을 제한한다는 의미는 절대로 아니다.
다만, 정형화된 타입 힌트를 적어둔 덕분에 타입힌트에 속한 변수가 아닐 경우엔 정적 분석기( static analyzer)가 경고 메시지를 띄운다.
컴파일이 불가능한 것은 아니지만.......
Static analyzer
일반적인 코드 작성 프로그램에선 언어 별 정적 분석기가 기본적으로 설치되어 있기에 타입 힌트가 잘 작성되어 있을 경우, 이를 이용해 발생 가능한 컴파일 시 문제점을 경고 창으로 띄워준다.
작성한 타입 힌트와 실제로 대입되는 값이 다를 경우, 경고를 띄워주는 것이다. 변수의 값만 제대로 알아도, 함수의 내용을 샅샅이 보고, 어떤 타입이 들어갔을 때, 출력되는지 파악할 때 할 일이 줄어든다.
작성된 타입 힌트와 실제로 대입되는 값이 다를 경우 경고를 띄워 준다! 이 정도의 기능만 있어도 함수 definition을 따라가 내부 코드를 샅샅이 보고 어떤 타입이 들어가 어떤 타입이 출력 되는지 파악해야 할 일이 줄어든다.
만약 정말 빡세게, 코드 실행 이전에 타입으로 인한 예상 문제를 확실하게 잡아내고 싶다면, mypy와 같은 정적 분석기 툴을 --strict 옵션과 함께 이용하면 된다. 아래의 예시를 한번 보자.
$ python -m mypy --strict main.py
>>
main.py:5: error: Argument 2 to "subtract" has incompatible type "str"; expected "int"
Found 1 error in 1 file (checked 1 source file)
꽤 괜찮게 작동한다.
기본적으로 동적 타입 언어로 만들어진 파이썬에서, 타입 힌트와 정적 분석을 모든 곳에 강제함으로써 동적 타이핑 주는 장점을 포기하는 건 좋지 않는 코딩 방법이다. 파이썬을 쓸 필요가 없다고나 할까.....
따라서 개발 초기에는 타입 힌트를 사용하지 않으면서 빠르게 코드를 개발하고, 후반부엔 코드 테스트를 통해 다른 변수를 집어 넣었을 때 문제가 발생되는 부분에만 타입 힌트를 집어넣는 편이 바람직하다.
1.6 느낀 점.
지구 상에는 다양한 개발 언어가 존재하고, 대한민국이 자바 공화국이라고 할 만큼, 자바가 많이 쓰이지만, 무조건 완벽한 언어는 아니라는 점이다.
언어 별로 사용하는 방법은 조금씩 다르기 때문에 무조건 외워서 코딩하는 것도 좋은 학습 방법은 아니다.
실제로 똑같은 언어일 지 언정, 매년마다 조금씩 로직이 경량화되거나, 또다른 표현법을 허용하기도 하기 때문이다.
제일 중요한 것은 언어의 특성을 이해하는 것이라고 생각한다.
1년 10개월, 짧지 않은 시간 동안, 개발자가 아닌 다른 부서(개발팀에 있었으니 다른 파트라고 해야 하나....?)에서 일을 하느라, 코딩 공부를 놓았었기 때문에 시간 나는대로, 포스팅 해볼 생각이다.
참고 문서
[Python] 타이핑 / 타입 힌트 (typing / type hint) — 잡다한 AI 관련 글들 (tistory.com)
'개발 > phython' 카테고리의 다른 글
파이썬에서의 반복문 (0) | 2024.07.02 |
---|---|
파이썬에서의 연산자 (0) | 2024.07.02 |
파이썬의 주요 특징 - 문자열의 차이 (0) | 2024.06.08 |