고급 Python 기능들 | GeekNews
파이썬에서 흔히 알려지지 않은 고급 기능 14가지를 실제 예제와 함께 소개함typing, generics, protocols, context managers 등 정적 타이핑과 구조적 설계에 대한 심도 있는 설명 제공파이썬 3.10 이상부터
news.hada.io
14 Advanced Python Features [원본 글]
14 Advanced Python Features
Python is one of the most widely adopted programming languages in the world. Yet, because of it’s ease and simplicity to just “get something working”, it’s also one of the most underappreciated. If you search for Top 10 Advanced Python Tricks on Go
blog.edward-li.com
들어가며....
최근 자바 언어보다 많이 사용하는 언어가 파이썬이다.
친숙하고 생산성이 좋은 언어라서 항상 곁에 두는 언어였는데, 이제는 엔터프라이즈 수준에서 언어를 다루고 있다.
여름에 출시할 서비스를 fast api 기반으로 구현하고 있다.
그전에는 가볍고 통통 튀는 언어였는데, 이제는 꽤나 진지한 언어로 받아들여졌고 같이 협업하는 클라우드(GCP) 쪽에서는
파이썬을 메인언어로 두고 AI 서비스를 개발하라고 권장하고 있어서 파이썬과 관련된 소식들이 들리면 정리하는 편이다.
이번에 추천할 글도 그런 맥락에서 가져와봤다.
파이썬의 고급 기능들 14가지를 정리한 글인데, 실제 사용하거나 현재 구현 중인 코드에서 사용되는 몇 가지 부분만 추려보려고 한다.
키워드 전용, 위치 전용 인자
*를 사용하면 키워드 전용 인자로 설정할 수 있다. [위치 인자는 사용이 불가능하며, 키워드를 명시하는 방식으로 인자를 넘겨야 한다]
/를 사용하면 위치 전용 인자로 설정 가능하다. [이 경우 키워드 인자를 사용할 수 없다]
내부 API 설계 시 유용한데, 키워드 전용 인자를 강제하면 호출자로 하여금 넘겨야 할 값을 명시적으로 받게 할 수 있기 때문이다.
아래는 GPT가 만들어준 예시이다.
def upload_file(file_path, /, overwrite=False, *, retries=3, timeout=30):
print(f"file_path: {file_path}")
print(f"overwrite: {overwrite}")
print(f"retries: {retries}")
print(f"timeout: {timeout}")
# ✅ 올바른 사용법
upload_file("/path/to/file") # 기본 값 사용
upload_file("/path/to/file", True) # overwrite 위치 인자로 전달
upload_file("/path/to/file", overwrite=True, retries=5, timeout=60)
# ❌ 잘못된 사용법
upload_file(file_path="/path/to/file") # TypeError: 'file_path' must be positional
upload_file("/path/to/file", retries=5) # TypeError: 'retries' must be a keyword argument
# 인자에 대한 설명은 아래와 같다.
file_path # 위치 전용 인자
/ # 여기까지는 위치 전용
overwrite=False # 위치 또는 키워드 모두 가능 (기본값 있음)
*, # 이후는 키워드 전용 인자
retries=3 # 키워드 전용 (기본값 3)
timeout=30 # 키워드 전용 (기본값 30)
이러한 유연성은 꽤나 매력적인 부분이다.
java의 경우 위치 기반 인자만 지원한다. [코틀린은 파이썬과 같이 기본값과 키워드 기반 인자를 지원한다.]
키워드 기반 인자 혹은 키워드에 디폴트값을 지정하는 유연함은 파이썬이 가진 장점 중 하나이다.
컨텍스트 매니저(Context Manager)
__enter__, __exit__ 메서드를 가진 객체로 with 블록에서 사용된다.
yield를 기준으로 전후에 대한 설정 및 정리 작업을 수행한다.
나의 경우 주로 트랜잭션을 관리할 때 사용하는 편이다. [리소스를 다룰 때 사용하기 좋은 패턴이다]
class MyContextManager:
def __enter__(self):
print("[ENTER] 리소스 획득")
return "리소스 객체"
def __exit__(self, exc_type, exc_val, exc_tb):
print("[EXIT] 리소스 해제")
if exc_type:
print(f"[ERROR] 예외 발생: {exc_val}")
return True # 예외를 처리하고 무시함 (False면 예외 전파)
# 사용 예시
with MyContextManager() as resource:
print(f"작업 중... resource = {resource}")
raise ValueError("테스트 예외 발생")
print("정상 종료")
위와 같이 지정된(enter, exit) 메서드를 가진 객체를 커스터마이징 하게 구현하고 with 구문과 함께 사용하면
리소스 획득 시 enter 메서드가 자동 호출되고 리소스 해제 시(yield 호출 이후) exit 메서드가 자동 호출된다.
exit에서 True를 리턴하면 예외를 무시하고 정상 종료하며, False 리턴시 예외가 호출자 쪽으로 전파된다.
자바의 try-with-resource 구문과 비슷한 개념으로, 리소스를 안전하게 관리해 주며 강제로 호출하던 작업을 구문에서 알아서 처리해 주는 방식으로 반복된 코드나 실수를 줄여주는 중요한 역할을 한다.
나의 경우 @contextmanager 데코레이터로 리소스를 다루는 메서드를 만들고 사용하는 편이다.
- yield 이전 코드가 셋업 코드
- yield 이후 코드가 클린업 코드(자원 해제)
가 된다.
구조적 패턴 매칭
파이썬 3.10에 도입된 기능으로, 파이썬 개발자로 하여금 조건식에 대한 처리를 깔끔하게 만들어준다.
조건들에 대해서 패턴들을 사용해 묶어서 관리할 수 있게 해주는 문법이다.
해당 글에서는 구조 분해(destructuring)와 같이 사용하는 게 매력적이라고 소개해준다.
# Destructuring and matching tuples
match point:
case (0, 0):
return "Origin"
case (0, y):
return f"Y-axis at {y}"
case (x, 0):
return f"X-axis at {x}"
case (x, y):
return f"Point at ({x}, {y})"
조건절도 우아하게 사용해 볼 수 있다
# Using OR pattern (|) to match multiple patterns
match day:
case ("Monday"
| "Tuesday"
| "Wednesday"
| "Thursday"
| "Friday"):
return "Weekday"
case "Saturday" | "Sunday":
return "Weekend"
if문도 넣을 수 있다
# Guard clauses with inline 'if' statements
match temperature:
case temp if temp < 0:
return "Freezing"
case temp if temp < 20:
return "Cold"
case temp if temp < 30:
return "Warm"
case _:
return "Hot"
배열 매칭도 가능
# Capture entire collections using asterisk (*)
match numbers:
case [f]:
return f"First: {f}"
case [f, l]:
return f"First: {f}, Last: {l}"
case [f, *m, l]:
return f"First: {f}, Middle: {m}, Last: {l}"
case []:
return "Empty list"
복잡한 케이스도 사용 가능하다
# Check if a packet is valid or not
packet: list[int] = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]
match packet:
case [c1, c2, *data, footer] if ( # Deconstruct packet into header, data, and footer
(checksum := c1 + c2) == sum(data) and # Check that the checksum is correct
len(data) == footer # Check that the data length is correct
):
print(f"Packet received: {data} (Checksum: {checksum})")
case [c1, c2, *data]: # Failure case where structure is correct but checksum is wrong
print(f"Packet received: {data} (Checksum Failed)")
case [_, *__]: # Failure case where packet is too short
print("Invalid packet length")
case []: # Failure case where packet is empty
print("Empty packet")
case _: # Failure case where packet is invalid
print("Invalid packet")
패턴을 우아하게 다룬다면, 데이터에 대한 조건절을 복잡하게 처리하기보단, 조건들을 분리해서 위와 같이 우하하게 match 구문으로 정리해 볼 수 있을 것 같다.
코드 스타일 팁들
for-else 구문
found_server = False # Keep track of whether we found a server
for server in servers:
if server.check_availability():
primary_server = server
found_server = True # Set the flag to True
break
if not found_server:
# Use the backup server if no server was found
primary_server = backup_server
# Continue execution with whatever server we found
deploy_application(primary_server)
#위의 코드를 아래와 같이 변경할 수 있다
# ===== Write this instead =====
for server in servers:
if server.check_availability():
primary_server = server
break
else:
# Use the backup server if no server was found
primary_server = backup_server
# Continue execution with whatever server we found
deploy_application(primary_server)
워루스 연산자 : 변수 선언과 검사를 동시에 수행하는 방식
# ===== Don't write this =====
response = get_user_input()
if response:
print('You pressed:', response)
else:
print('You pressed nothing')
# ===== Write this instead =====
if response := get_user_input():
print('You pressed:', response)
else:
print('You pressed nothing')
스트림릿에서 사용자의 질의 정보를 워루스 연산자로 처리하고 있다. 주로 None 유무를 처리할 때 매우 유용한 기법이다.
(None이 아닌 경우를 바로 처리하고 싶은 경우)
or 단축 평가 : 참인 값을 먼저 반환
# ===== Don't write this =====
username, full_name, first_name = get_user_info()
if username is not None:
display_name = username
elif full_name is not None:
display_name = full_name
elif first_name is not None:
display_name = first_name
else:
display_name = "Anonymous"
===== Write this instead =====
username, full_name, first_name = get_user_info()
display_name = username or full_name or first_name or "Anonymous"
참이 되는 값을 가져와야 하는 경우 간단하게 처리할 수 있다.
비교 연산자 체이닝 : 0 < x < 10 코드 간결 화가능
if 0 < x < 10: # Instead of if 0 < x and x < 10
print("x is between 0 and 10")
f-string
print(f"Pi: {pi:.2f}") # Pi: 3.14
print(f"Avogadro: {avogadro:.2e}") # Avogadro: 6.02e+23
print(f"Big Number: {big_num:,}") # Big Number: 1,000,000
print(f"Hex: {num:#0x}") # Hex: 0x1a4
print(f"Number: {num:09}") # Number: 000000420
마무리...
14가지 중에서 실제 실무에 도입하면 괜찮다고 판단되는 고급 기능 5개 정도만 뽑아서 정리해봤다.
함수의 시그니처에 충분한 정보를 제공하는 키워드 기반 인자 처리는 자바로 돌아갈 때마다 아쉬움을 느끼는 부분이기도 하다.
[f-string은 더더욱...]
몇몇 연산자는 처음 보는 상황에서는 당혹스러울 수 있지만, 손에 익힌다면 꽤나 괜찮은 스타일처럼 보인다.
파이썬 개발자 분들은 이런 파이썬스러운 코드에 열정적이어서 스타일 팁이나, 파이썬의 문법을 극대화하는 사용 사례들을
보여주곤 하는데, 파이썬에 대한 사랑과 애정이 느껴져서 좋았던 글이다. :)
'IT 고찰 > 좋은 글' 카테고리의 다른 글
Tracing the thoughts of a large language model -Claude 기술 블로그- (0) | 2025.04.01 |
---|---|
AI 데이터 엔지니어의 새로운 역할 (0) | 2025.01.22 |
ADR : 아키텍처 결정 기록물. -엔지니어링 결정에 대한 논쟁을 줄여줄 도구- (1) | 2024.11.19 |
EU 인공지능 법안 요약본 [GPAI: General Purpose AI] (7) | 2024.09.13 |
[DB] 마이크로서비스에서 공유형 DB를 사용한다는 것 (3) | 2024.09.03 |