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

[Python]파이썬 마법사로 레벨 업! 데코레이터, 제너레이터, 이터레이터 완전 정복 🧙‍♂️

by 다다면체 2025. 3. 31.
728x90
반응형

안녕하세요, 파이썬을 사랑하는 개발자 여러분! 지난 시간에는 예외 처리와 파일 입출력을 통해 우리의 코드를 더욱 안전하고 융통성 있게 만드는 방법을 배웠습니다. 오늘은 한 단계 더 나아가 파이썬의 강력한 마법 같은 기능들, 데코레이터, 제너레이터, 그리고 이터레이터와 이터러블에 대해 함께 탐험해보려고 합니다. 이 세 가지 개념은 여러분의 코드를 더욱 간결하고 효율적으로 만들어 줄 뿐만 아니라, 파이썬의 깊이를 이해하는 데에도 큰 도움을 줄 것입니다. 자, 그럼 지금부터 파이썬 마법의 세계로 함께 떠나볼까요? 🚀

🪄 데코레이터 (Decorator): 함수에 마법을 불어넣다

**데코레이터(Decorator)**는 파이썬의 강력한 기능 중 하나로, 기존 함수나 클래스의 기능을 수정하거나 확장할 때 유용하게 사용됩니다. 마치 우리가 옷 위에 액세서리를 더하거나 기능을 추가하는 것과 비슷하다고 생각할 수 있습니다. 데코레이터를 사용하면 코드를 더욱 깔끔하고 재사용 가능하게 만들 수 있습니다.

✨ 개념 및 활용

데코레이터는 함수를 인자로 받아서 다른 함수를 반환하는 함수입니다. 이 반환된 함수는 원래 함수의 기능을 수행하면서 추가적인 기능을 갖게 됩니다. 데코레이터는 주로 다음과 같은 상황에서 활용됩니다.

  • 로깅: 함수 호출 정보나 실행 시간을 기록하고 싶을 때
  • 접근 제어: 특정 조건 하에서만 함수를 실행하도록 제한하고 싶을 때
  • 성능 측정: 함수의 실행 시간을 측정하고 싶을 때
  • 데이터 유효성 검사: 함수의 입력값이나 반환값을 검증하고 싶을 때

⚙️ 함수 데코레이터

함수 데코레이터는 함수 위에 @데코레이터_이름과 같은 형태로 사용하여 적용합니다. 이는 원래 함수를 데코레이터 함수에 인자로 넘겨주고, 데코레이터 함수가 반환하는 새로운 함수로 원래 함수를 덮어쓰는 것과 같습니다.

예시 코드:

import time

def 시간_측정(func):
    def wrapper(*args, **kwargs):
        시작_시간 = time.time()
        결과 = func(*args, **kwargs)
        종료_시간 = time.time()
        실행_시간 = 종료_시간 - 시작_시간
        print(f"⏱️ {func.__name__} 함수 실행 시간: {실행_시간:.4f} 초")
        return 결과
    return wrapper

@시간_측정
def 오래걸리는_작업():
    time.sleep(1)
    return "작업 완료!"

결과 = 오래걸리는_작업()
print(결과)

위 코드에서 @시간_측정은 오래걸리는_작업 함수에 시간_측정 데코레이터를 적용한 것입니다. 이제 오래걸리는_작업 함수를 호출하면 먼저 시간_측정 데코레이터의 wrapper 함수가 실행되어 실행 시간을 측정하고, 원래 함수의 결과를 반환합니다.

🏢 클래스 데코레이터

클래스 데코레이터는 클래스 전체에 적용되어 클래스의 동작 방식을 변경하거나 기능을 추가할 수 있습니다. 클래스 데코레이터는 클래스를 인자로 받아서 수정된 클래스를 반환하는 함수입니다.

예시 코드:

def 로깅_데코레이터(cls):
    class Wrapper:
        def __init__(self, *args, **kwargs):
            print(f"📝 {cls.__name__} 클래스의 인스턴스가 생성됩니다.")
            self.instance = cls(*args, **kwargs)

        def __getattr__(self, name):
            print(f"🔍 {self.instance.__class__.__name__} 인스턴스의 '{name}' 속성에 접근합니다.")
            return getattr(self.instance, name)

        def __setattr__(self, name, value):
            if name != 'instance':
                print(f"✍️ {self.instance.__class__.__name__} 인스턴스의 '{name}' 속성을 '{value}'로 설정합니다.")
            super().__setattr__(name, value)

    return Wrapper

@로깅_데코레이터
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def say_hello(self):
        print(f"👋 안녕하세요, 제 이름은 {self.name}이고, {self.age}살입니다.")

person = Person("Alice", 30)
person.say_hello()
person.age = 31
print(person.name)

위 예시에서 @로깅_데코레이터는 Person 클래스에 적용되어 인스턴스 생성, 속성 접근 및 설정 시 로그 메시지를 출력하도록 기능을 추가합니다.

⚙️ 제너레이터 (Generator): 메모리 효율적인 데이터 시퀀스 만들기

**제너레이터(Generator)**는 이터레이터를 생성하는 특별한 형태의 함수입니다. 일반적인 함수는 값을 반환하고 종료되지만, 제너레이터 함수는 yield 키워드를 사용하여 값을 하나씩 순차적으로 반환할 수 있습니다. 제너레이터는 모든 값을 미리 생성하여 메모리에 저장하는 대신, 필요할 때마다 값을 생성하므로 메모리 사용 효율이 매우 높습니다.

✨ 개념 및 특징

  • Lazy Evaluation (느긋한 연산): 제너레이터는 값이 필요할 때까지 연산을 미룹니다. 이는 대규모 데이터 처리 시 메모리 부담을 줄여줍니다.
  • Memory Efficiency (메모리 효율성): 한 번에 하나의 값만 생성하고 저장하므로, 리스트와 같은 자료 구조에 비해 훨씬 적은 메모리를 사용합니다.
  • Iteration Protocol 지원: 제너레이터는 자동으로 이터레이터 프로토콜을 따르므로 for 루프 등에서 편리하게 사용할 수 있습니다.

🔩 제너레이터 함수 및 표현식

제너레이터를 만드는 방법은 두 가지가 있습니다.

  • 제너레이터 함수: 함수 내부에 yield 키워드를 사용하여 값을 반환하는 함수입니다. 함수가 호출될 때마다 처음부터 실행되는 것이 아니라, 마지막으로 yield된 지점부터 다시 실행됩니다.
  • 제너레이터 표현식: 리스트 컴프리헨션과 유사한 문법을 사용하지만, 대괄호 [] 대신 괄호 ()를 사용하여 정의합니다. 제너레이터 표현식은 간단한 제너레이터를 만들 때 유용합니다.

예시 코드:

# 제너레이터 함수
def 제곱_제너레이터(n):
    for i in range(n):
        yield i ** 2

for 제곱 in 제곱_제너레이터(5):
    print(제곱)

# 제너레이터 표현식
홀수_제곱 = (x ** 2 for x in range(10) if x % 2 != 0)
for 제곱 in 홀수_제곱:
    print(제곱)

🔑 yield 키워드

yield 키워드는 제너레이터 함수에서 값을 반환하고 함수의 실행을 일시 중단하는 역할을 합니다. 함수가 다시 호출되면 중단된 지점부터 실행을 재개합니다. 마치 책갈피를 꽂아두고 나중에 다시 그 페이지부터 읽는 것과 같습니다.

🚶‍♀️🚶이터레이터 (Iterator)와 이터러블 (Iterable): 순회 가능한 객체들

**이터레이터(Iterator)**와 **이터러블(Iterable)**은 파이썬에서 여러 개의 항목을 순차적으로 접근할 수 있도록 하는 핵심적인 개념입니다.

✨ 개념

  • 이터러블(Iterable): 반복 가능한 객체를 의미합니다. 리스트, 튜플, 문자열, 딕셔너리, 집합 등이 모두 이터러블입니다. 이터러블 객체는 __iter__ 메서드를 호출하여 이터레이터를 얻을 수 있습니다. 마치 책의 목록과 같습니다.
  • 이터레이터(Iterator): 이터러블 객체의 요소를 순차적으로 접근할 수 있는 객체입니다. 이터레이터는 __next__ 메서드를 호출하여 다음 요소를 반환하며, 더 이상 요소가 없으면 StopIteration 예외를 발생시킵니다. 마치 책을 읽는 사람과 같습니다.

📜 이터레이터 프로토콜

파이썬에서 객체가 이터레이터가 되기 위해서는 다음 두 가지 메서드를 구현해야 합니다.

  • __iter__(): 이터레이터 객체 자신을 반환합니다.
  • __next__(): 다음 요소를 반환합니다. 더 이상 요소가 없으면 StopIteration 예외를 발생시킵니다.

이터러블 객체는 __iter__() 메서드를 구현하여 이터레이터를 반환할 수 있도록 해야 합니다.

🛠️ 이터러블 객체 만들기

사용자 정의 클래스를 이터러블하게 만들려면 __iter__ 메서드를 구현하고, 이 메서드에서 이터레이터 객체를 반환하도록 하면 됩니다. 이터레이터 객체는 일반적으로 클래스 내부에 정의하거나 별도의 클래스로 만들 수 있습니다.

예시 코드:

class 카운터:
    def __init__(self, 시작, 끝):
        self.현재값 = 시작
        self.끝값 = 끝

    def __iter__(self):
        return self

    def __next__(self):
        if self.현재값 > self.끝값:
            raise StopIteration
        else:
            결과 = self.현재값
            self.현재값 += 1
            return 결과

for 숫자 in 카운터(1, 5):
    print(숫자)

위 예시에서 카운터 클래스는 __iter__ 메서드를 통해 자기 자신을 이터레이터로 반환하고, __next__ 메서드를 통해 시작 값부터 끝 값까지 순차적으로 숫자를 반환합니다. for 루프는 StopIteration 예외가 발생할 때까지 __next__ 메서드를 계속 호출하며 값을 가져옵니다.

🎉 마무리하며

오늘은 파이썬의 강력한 기능인 데코레이터, 제너레이터, 이터레이터와 이터러블에 대해 깊이 있게 알아보았습니다. 이 세 가지 개념을 잘 이해하고 활용하면 여러분의 파이썬 코드는 더욱 우아하고 효율적으로 변모할 것입니다. 처음에는 다소 어렵게 느껴질 수 있지만, 꾸준히 코드를 작성하고 다양한 예제를 통해 익숙해지면 어느새 여러분도 파이썬 마법사가 되어 있을 거예요! ✨

728x90
반응형