상세 컨텐츠

본문 제목

파이썬 기초

카테고리 없음

by rakyun 2025. 9. 15. 13:44

본문

인데코레이터

자바의 어노테이션의 본질은 메타데이터이며 그 자체로 실행이 가능하지 않고 컴파일러나 프레임워크가 읽기 위한 주석 같은 용도이다.

 

그러나 파이썬의 데코레이터는 고차 함수로 그 자체로 실행 주체가 되며 함수를 실행할 대상 함수를 직접 수정하거나 확장이 가능하다.

핵심은 함수를 인자로 받아서 새로운 함수를 반환하는 함수이다. 그러므로 데코레이터는 고차 함수의 일종이라고 볼 수 있다.

def my_decorator(func): # 데코레이터 함수 (func를 인자로 받음)
    def wrapper(*args, **kwargs): # 원본 함수를 감싸는 내부 함수
        print("함수 실행 전에 추가할 기능")
        result = func(*args, **kwargs) # 원본 함수 실행
        print("함수 실행 후에 추가할 기능")
        return result
    return wrapper # 새로운 함수(wrapper) 반환

@my_decorator # 이 구문은 아래 코드와 동일하게 작동합니다.
def say_hello():
    print("Hello!")

## 결과
# 함수 실행 전에 추가할 기능"
# Hello!
# 함수 실행 후에 추가할 기능

@asynccontextmanager

비동기 컨텍스트 관리자를 제너레이터 함수 형태로 간결하게 정의해주는 도구
  • 비동기 컨텍스트란?
    • 비동기 환경에서 파일, 네트워크 연결, 데이터베이스 세션과 같이 비동기적인 설정 및 해제 작업이 필요한 리소스를 안전하게 관리하기 위한 구조
    • 여러 비동기 작업(Task)이 동시에 리소스에 접근하는 것을 막으며 교통 정리
@asynccontextmanager
async def lifespan(app: FastAPI):
    """
    FastAPI 라이프사이클 이벤트 핸들러

    앱 시작 시: MongoDB 연결 등 초기화 작업
    앱 종료 시: 리소스 정리
    """

    # MongoDB 연결 초기화
    await mongodb_manager.connect()

    yield

    # 종료 시 정리
    await mongodb_manager.disconnect()
  • 보통 위 코드와 같이 데이터베이스를 연결하고 연결을 해제하고 리소스를 정리해주는 역할을 한다.

__call__()

 

__call__() 함수는 객체를 마치 함수를 사용하듯이 사용 가능하다.

랭그래프의 노드는 함수로 정의하는 경우가 많은데 객체를 써서 랭그래프의 노드를 정의할 경우 __call__() 함수를 사용해서 랭그래프 내부에서 객체를 함수처럼 사용하도록 할 수 있다.


랭그래프에서 그래프에 노드를 추가할때 __call__() 함수 없이 객체를 전달하면 에러가 발생하고, __call__() 함수 없다면 객체의 메서드를 전달해야 에러가 발생하지 않는다. __call__() 함수가 있다면 객체를 그대로 전달해도 에러가 발생하지 않는다.

 

__call__이 없는 객체

# 인스턴스 생성
counter_obj = Counter()

# 메서드를 직접 호출해야 함
result = counter_obj.add(5)


__call__이 있는 객체

counter_obj = Counter()
# 출력: 카운터 객체가 생성되었습니다.

# 인스턴스를 함수처럼 바로 호출!
result = counter_obj(5)

가변 인자

가변 키워드 인자

  • **뒤에 변수 이름이 붙으면 호출 시 키워드 형태로 전달되는 인자들 중 남은 모든 인자들을 모아서 하나의 딕셔너리로 만들어줌
    • 관례적으로 kwargs를 붙임
 async def ainvoke(self, input, config=None, *, stop=None, **kwargs):
      llm_result = await self.agenerate_prompt(
          [self._convert_input(input)],
          stop=stop,
          **kwargs  # ← 모두 그대로 전달
      )
      
      
      
 llm.ainvoke('temperature'=0.7, 'max_tokens'=1000, 'top_p'=0.9, 'stop_sequences'=["END"])

 

  • **뒤에 변수 이름을 붙여서 가변 키워드 인자를 만들면 함수 호출 부에서 보이듯이 함수의 인자로 정의되어 있지 않은 이름의 인자를 넣을 경우 **kwargs가 모두를 묶어서 하나의 딕셔너리로 만든다.
  • 그리고 사용할때에는 미리 정의되어 있는 값만 그 딕셔너리에서 꺼내서 사용하게 된다. 이는 무수히 많은 인자가 들어올 수 있고 그 인자들의 이름으로 구분해야할때 사용하면 좋다.

 

패킹

def print_names(first_name, *middle_names, last_name):
    """
    첫 이름과 마지막 이름을 받고, 그 사이에 전달된 모든 이름을 튜플로 묶어 처리합니다.
    """
    print(f"첫 이름 (First Name): {first_name}")
    
    if middle_names:
        print(f"중간 이름들 (Packed as Tuple): {middle_names}")
        print(f"중간 이름 개수: {len(middle_names)}")
    else:
        print("중간 이름 없음")
        
    print(f"마지막 이름 (Last Name): {last_name}")
    print("-" * 20)

print_names("James", "Alfred", "B", "C", last_name="Smith")

# 출력:
# 첫 이름 (First Name): James
# 중간 이름들 (Packed as Tuple): ('Alfred', 'B', 'C')
# 중간 이름 개수: 3
# 마지막 이름 (Last Name): Smith
# --------------------
  • 함수 정의 시 *args처럼 사용하여 여러 인자를 튜플로 묶는 것
튜플이란 여러 개의 값을 순서대로 담을 수 있는 시퀀스 자료형 리스트와 비슷하나 불변성을 지니고 있음 또한 정수, 문자열, 리스트, 심지어 다른 튜플 등 모든 종류의 객체를 요소로 포함 가능

언패킹

# Unpacking 예시 (함수 호출 시)
def total(a, b, c):
    print(a + b + c)

numbers = [10, 20, 30]
total(*numbers) # 3개의 개별 인자 total(10, 20, 30)로 풀림
  • 함수 호출 시 튜플이나 리스트 앞에 *를 붙여 요소들을 개별 인자로 풀어 헤치는 것

특별 인자

def configure_settings(log_level, *, timeout=30, debug=False):
    """
    log_level은 위치(또는 키워드)로 전달 가능하지만,
    timeout과 debug는 반드시 키워드(이름)로만 전달해야 합니다.
    """
    print(f"Log Level: {log_level}")
    print(f"Timeout: {timeout}")
    print(f"Debug Mode: {debug}")
    print("-" * 20)

# 1. 정상 호출: 모든 키워드 전용 인자를 이름으로 명시
configure_settings("INFO", timeout=60, debug=True)
# 출력:
# Log Level: INFO
# Timeout: 60
# Debug Mode: True
# --------------------
  • 위 함수에서 볼 수 있듯이 *를 단독으로 썼는데 이런 경우 *뒤에 오는 인자들이 반드시 키워드 형태로 전달되록 강제한다.
  • 위치에 상관없이 무조건 키워드를 명시하고 그 뒤에 값을 넣어줘야 한다.

Pydantic

  • 파이썬 프로그램이 외부의 프로그램과 통신이 성공한 직후, 그 내용물(payload, 예: JSON)을 파이썬 객체로 변환하는 과정에서 생기는 '데이터 정합성' 또는 '타입 불일치' 에러를 런타임 에러로 잡아주는 라이브러리
    • 타입 힌트에 정의된 타입과 다르면 런타임 에러를 일으킨다.
    • age: int인데 "20" (숫자형 문자열)이 들어오면 자동으로 형변환도 할 수 있다.
    • 파이썬 객체를 JSON 데이터로 직렬화도 가능

@dataclass, BaseModel

🧩 개요 비교

출처 Python 표준 라이브러리 (dataclasses) 외부 라이브러리 (pydantic)
주요 목적 단순 데이터 저장 (POJO) 데이터 검증 + 직렬화/역직렬화
타입 힌트 활용 단순 문법적 힌트 (검증 없음) 실제 타입 강제 + 자동 변환
검증(Validation) ❌ 없음 ✅ 있음 (타입·값 자동 검사)
JSON 직렬화 수동 구현 필요 .dict(), .json() 제공
성능 더 빠름 (순수 Python 객체) 약간 느림 (검증 로직 추가됨)
사용 분야 내부 로직, 임시 데이터 저장 API, DTO, 설정, 입력 검증
  • 랭그래프에서 프롬프트에 들어갈 변수들을 정의하는 객체에서는 @dataclass 사용
    • 검증이 필요하지 않은 크게 중요하지 않은 데이터
    • 직렬화 / 역직렬화 필요 없음
  • 웹 서버와 통신하거나 외부와 통신하는 API의 DTO로는 BaseModel 사용
    • 검증이 필요한 중요한 데이터
    • 직렬화 / 역직렬화 필요

클래스 변수, 인스턴스 변수

인스턴스 변수는 각 인스턴스가 개별로 가지는 변수를 의미하고, 클래스 변수는 각 인스턴스가 모두 공유하는 변수를 의미한다.

from typing import ClassVar

@dataclass
class Dog:
    species: ClassVar[str] = "Canine"
    name: str
  • 즉 위 코드에서 species는 클래스 변수, name은 인스턴스 변수인데, 클래스를 정의할때 미리 값을 할당하게 되면 그건 클래스 변수가 되고 그렇지 않으면 인스턴스 변수가 된다.

    여기서 타입체커는 두 개의 변수 모두 인스턴스 변수라고 착각하게 된다. 왜냐하면 타입 체커는 실제로 코드를 실행하는 것이 아닌 코드의 구조만을 보고 판단하기에 변수에 타입 힌트가 있으면 인스턴스 변수라고 판단하게 되는 것이다.

    그래서 ClassVar를 사용해서 이건 클래스 변수라는 것을 명시할 수 있다.