일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- Home Assistant
- 매터
- cluster
- matter
- 힐스테이트 광교산
- 해외주식
- homebridge
- 국내주식
- 배당
- Bestin
- 파이썬
- Apple
- 빅데이터분석기사
- Python
- 주식
- raspberry pi
- 미국주식
- MQTT
- esp32
- 나스닥
- RS-485
- 현대통신
- Espressif
- 공모주
- SK텔레콤
- ConnectedHomeIP
- 애플
- 홈네트워크
- 월패드
- 라즈베리파이
- Today
- Total
YOGYUI
Python::typing::Literal - 변수값 범위 명시 본문
typing - Literal
파이썬, 자바스크립트, 루비 등 최근 유행하는 프로그래밍 언어의 가장 큰 특징 중 하나는 변수의 형(type)을 지정하지 않아도 원활하게 동작하는 코드를 작성할 수 있다는 점이다 (dynamic typing)
v = '123456789'
>> type(v)
<class 'str'>
v = 123456789
>> type(v)
<class 'int'>
하지만 대형 팀 프로젝트 작업 시에 함수 인자 및 반환의 형을 명시해두지 않으면 각종 예외 발생으로 고통받게 된다
def my_func(x):
return x + 1
>> my_func(1)
2
>> my_func('123')
Traceback (most recent call last):
File "C:\Python38\lib\code.py", line 90, in runcode
exec(code, self.locals)
File "<input>", line 3, in <module>
File "<input>", line 2, in my_func
TypeError: can only concatenate str (not "int") to str
이러한 문제를 해결하기 위해 python 3.5 버전부터 typing 패키지가 추가되었다
위 함수는 다음과 같이 변수 타입과 반환 타입을 명시해줄 수 있다
def my_func(a: int) -> int:
return a + 1
PyCharm과 같은 통합 개발 환경(IDE)에서 잘못된 타입의 변수를 대입할 때 warning 메시지를 확인할 수 있으므로 런타임 오류 가능성을 크게 줄일 수 있다
typing 패키지 내에 선언되어 있는 특수형과 관련된 클래스를 import하여 변수형을 더욱 명시적으로 만들 수도 있다
from typing import Tuple
def my_func2(a: tuple) -> int:
return a[0] + a[1]
def my_func3(a: Tuple[int, int]) -> int:
return a[0] + a[1]
>> my_func2((1,2))
3
>> my_func3((1,2))
3
my_func2, my_func3 모두 길이 2 (이상의)요소들로 구성된 튜플을 대상으로 하여 동일한 결과를 도출하는데, my_func3의 경우 튜플이 int형 2개만 갖고 있도록 명시하고 있어 코딩 오류를 억제할 수 있다
중·대형 프로젝트에서는 왠만하면 자료형을 명시해서 사소한 실수로 인한 런타임 오류를 억제하도록 하는게 좋다
Alias 클래스로 제공되는 Tuple, List, Dict, Any, Union 등은 여러모로 유용하게 활용할 수 있으니 활용법은 python 공식 문서를 참고하도록 하자
https://docs.python.org/3/library/typing.html
Python 3.8에 처음 도입된 Literal 클래스를 활용하면 변수가 가질 수 있는 값의 범위를 명시적으로 제한할 수 있다
from typing import Literal
class Student:
grade: Literal[1, 2, 3, 4]
def __init__(self):
pass
def setGrade(self, g: Literal[1, 2, 3, 4]):
self.grade = g
학부생의 학년(grade)을 1~4로만 제한하고자 할 때, Literal을 활용하면 if문 사용하지 않고 사용자 오류를 억제할 수 있다
Literal로 활용할 수 있는 자료형은 int, str, bytes, bool, enum.Enum, None 그리고 Literal이 있다
물론 함수 혹은 메서드의 반환형에도 사용할 수 있다
class Student:
grade: Literal[1, 2, 3, 4]
gender: Literal['male', 'female', 'unspecified']
scholarship: Literal['all', 'half', None]
def getGender(self) -> Literal['male', 'female', 'unspecified']:
return self.gender
def setGender(self, g: Literal['male', 'female', 'unspecified']):
self.gender = g
>> s1.setGender('female')
>> s1.getGender()
'female'
위와 같이 구현하면 grade, gender, scholarship 멤버변수가 가질 수 있는 값의 범위를 명시적으로 제한할 수 있어 IDE를 통한 사전 오류 체크가 가능하며, 다른 사람이 코드를 해석할 때 큰 도움을 줄 수 있다
물론 python 언어 자체가 가지는 특성 때문에 명시되지 않은 값을 대입할 수 있기는 하다 (IDE상 경고메시지만 출력할 뿐 실제 실행 시 런타임 오류는 발생하지 않는다)
>> s1.setGender('unknown')
>> s1.getGender()
'unknown'
Literal로 명시적으로 값의 범위를 명시해두고 함수 내에서 조건문을 통해 예외를 발생하게 하는게 일반적인 구현 방법
class Student:
def setGender(self, g: Literal['male', 'female', 'unspecified']):
if g in ['male', 'female', 'unspecified']:
self.gender = g
else:
raise ValueError('invalid gender')
>> s1.setGender('unknown')
Traceback (most recent call last):
File "C:\Python38\lib\code.py", line 90, in runcode
exec(code, self.locals)
File "<input>", line 1, in <module>
File "<input>", line 13, in setGender
ValueError: invalid gender
나도 주로 3.7로 작업하다보니 Literal을 접할 기회가 별로 없었는데, 빌드 서버 이슈로 3.8을 도입해보니 기존에 구현해둔 int.to_bytes 구문에 전부 warning이 잡혀있길래 빌트인 소스코드(builtins.py)를 열어봤더니
class int:
@overload
def __new__(cls: Type[_T], x: str | bytes | SupportsInt | SupportsIndex | _SupportsTrunc = ...) -> _T: ...
@overload
def __new__(cls: Type[_T], x: str | bytes | bytearray, base: SupportsIndex) -> _T: ...
if sys.version_info >= (3, 8):
def as_integer_ratio(self) -> tuple[int, Literal[1]]: ...
@property
def real(self) -> int: ...
@property
def imag(self) -> int: ...
@property
def numerator(self) -> int: ...
@property
def denominator(self) -> int: ...
def conjugate(self) -> int: ...
def bit_length(self) -> int: ...
if sys.version_info >= (3, 10):
def bit_count(self) -> int: ...
def to_bytes(self, length: SupportsIndex, byteorder: Literal["little", "big"], *, signed: bool = ...) -> bytes: ...
@classmethod
def from_bytes(
cls, bytes: Iterable[SupportsIndex] | SupportsBytes, byteorder: Literal["little", "big"], *, signed: bool = ...
) -> int: ... # TODO buffer object argument
to_bytes와 from_bytes의 byteorder가 Literal로 잡혀있어서 "little", "big" 두 종류의 endian 관련 문자열만 입력받을 수 있다는 것을 명시하고 있는 것을 알 수 있다
dynamic typing이 파이썬의 장점이라고는 하지만, 중·대형 팀 프로젝트에서는 변수를 중구난방으로 사용할 수 있다는 치명적인 약점이 되어버리기에 버전이 올라감에 따라 사용자들의 요구에 의해 typing 그리고 literal까지 도입되고 있는 것을 알 수 있다
python 3.8 이상 버전으로 작업하는 개발자라면, typing을 도입해서 코드를 해석하기 좋게 만들어서 협업하기 좋은 환경을 만들어보자 (아무리 최신식 협업툴을 갖춰도 소스코드 자체가 읽기 불편하면 말짱 도루묵이다)
> static typing은 clean code를 위한 첫걸음이기도 하다
끝~!
[참고]
[1] https://docs.python.org/3/library/typing.html
[2] https://adamj.eu/tech/2021/07/09/python-type-hints-how-to-use-typing-literal/
'Software > Python' 카테고리의 다른 글
Python - 대출 이자 계산기 앱 개발 (1) | 2022.04.21 |
---|---|
PyQt5 - QSpinBox 0x7FFFFFFFF 이상 최대값으로 설정하기 (0) | 2022.02.08 |
Pyppeteer - Chromium 다운로드 시 SSLCertVerificationError 발생할 경우 (0) | 2022.01.04 |
2022년 공휴일을 알아보자 (feat. 공공데이터포털) (0) | 2021.12.22 |
pandas - 중복된 요소 넘버링하기 (0) | 2021.12.15 |