08-2 파이썬 강좌 – 정규 표현식 시작하기

정규 표현식이란?

정규 표현식은 문자열을 특정한 패턴과 일치시키기 위한 강력한 도구입니다. 주로 데이터 유효성 검사, 검색, 그리고 텍스트 처리 작업에 사용됩니다. 프로그래밍 언어들, 특히 파이썬에서 정규 표현식을 활용하면 복잡한 패턴 매칭을 쉽게 처리할 수 있습니다.

파이썬에서 정규 표현식 사용하기

파이썬의 re 모듈에서는 다양한 정규 표현식 관련 기능을 제공합니다. 주로 사용하는 함수로는 matchsearchfindallfinditer 등을 소개할 수 있습니다.


# re 모듈 임포트
import re

# 패턴 매칭 예제
pattern = re.compile(r'\d+')

# 문자열에서 숫자를 검색
match = pattern.search("비용은 1200원입니다.")
if match:
    print("숫자 발견:", match.group())
    

정규 표현식의 기본 패턴

정규 표현식에서 자주 사용되는 메타 문자들을 통해 보다 복잡한 패턴 매칭을 수행할 수 있습니다. 예를 들어:

  • . : 임의의 한 문자
  • ^ : 문자열의 시작
  • $ : 문자열의 끝
  • * : 0개 이상의 반복
  • + : 1개 이상의 반복
  • ? : 0개 또는 1개의 반복

고급 패턴 매칭

정규 표현식을 보다 깊이 있게 사용하려면, 그룹화와 캡쳐, 전방 탐색 및 후방 탐색 같은 고급 기능을 이해해야 합니다.


# 그룹화 예제
pattern = re.compile(r'(\d{3})-(\d{3,4})-(\d{4})')
match = pattern.search("전화번호는 010-1234-5678입니다.")
if match:
    print("지역 번호:", match.group(1))
    print("중간 번호:", match.group(2))
    print("마지막 번호:", match.group(3))
    

정규 표현식의 유용한 예제

정규 표현식은 다양한 문자열 패턴을 식별하고 처리하는 데 사용할 수 있습니다. 예를 들어, 이메일 주소의 유효성을 검사하거나, 텍스트에서 URL을 추출하는 등의 작업을 할 수 있습니다.

실전 예제

실무에서의 다양한 사례를 통해 정규 표현식의 응용을 살펴보겠습니다. 이 부분에서는 구체적인 코드를 통해 정규 표현식이 문제 해결에 어떻게 기여할 수 있는지를 보여줍니다.

정규 표현식을 사용할 때의 주의점

정규 표현식은 강력한 도구이지만, 때때로 성능 문제가 발생할 수 있습니다. 특히 매우 복잡한 패턴이나 대용량 데이터에 적용할 때는 주의를 기울여야 합니다. 또한, 가독성과 유지 보수성을 고려하여 사용해야 합니다.

결론

정규 표현식은 파이썬과 같은 프로그래밍 언어에서 매우 유용한 기능입니다. 충분한 연습과 이해를 통해, 코드를 보다 효율적이고 간결하게 작성할 수 있습니다.

08-1 파이썬 강좌 – 정규 표현식 살펴보기

정규 표현식이란?

정규 표현식(Regular Expressions, 줄여서 regex 또는 regexp)은 특정한 규칙에 맞는 문자열을 검색, 치환, 추출할 때 사용되는 문자열입니다. 주로 텍스트 프로세싱에서 여러 패턴을 검색하거나 데이터 유효성 검사를 위해 사용됩니다.

정규 표현식의 기본 개념

정규 표현식의 기본 개념을 이해하기 위해 일반적으로 사용되는 몇 가지 특별한 문자를 이해해야 합니다.

기본 패턴

  • 점(.): 임의의 한 문자를 나타냅니다.
  • 대괄호([]): 대괄호 안의 문자 중 하나를 나타냅니다. 예: [abc]
  • 캐럿(^): 문자열의 시작을 나타냅니다. 예: ^Hello
  • 달러 기호($): 문자열의 끝을 나타냅니다. 예: world$
  • 별표(*): 이전 문자가 0번 이상 반복되는 것을 나타냅니다. 예: a*
  • 플러스(+): 이전 문자가 1번 이상 반복되는 것을 나타냅니다. 예: a+
  • 물음표(?): 이전 문자가 0번 또는 1번 나타나는 것을 나타냅니다. 예: a?

메타 문자

메타 문자들은 정규식에서 특별한 의미로 사용되며, 종종 필요에 따라 이스케이프하여 문자 그대로 사용해야 합니다.

  • 백슬래시(\\): 이스케이프 문자로, 특수 문자를 일반 문자로 사용하기 위해 씁니다.
  • 파이프(|): OR 연산자로 여러 패턴 중 하나라도 매칭되면 참으로 간주합니다. 예: a|b
  • 괄호(()): 그룹화를 나타내며, 서브패턴을 만들 때 사용합니다. 예: (ab)

파이썬에서의 정규 표현식 사용법

파이썬에서는 `re` 모듈을 사용하여 정규 표현식을 다룹니다. 이 모듈은 다양한 함수를 제공하여 정규 표현식을 쉽게 사용할 수 있습니다.

re 모듈 함수

  • re.match(): 문자열의 시작 부분이 지정한 패턴과 매칭되는지 확인합니다.
  • re.search(): 문자열 전체를 검색하여 첫 번째 매칭되는 패턴을 찾습니다.
  • re.findall(): 패턴과 매칭되는 모든 부분 문자열을 리스트로 반환합니다.
  • re.finditer(): 패턴과 매칭되는 모든 부분 문자열을 순환 가능한 객체로 반환합니다.
  • re.sub(): 패턴과 매칭되는 부분 문자열을 다른 문자열로 치환합니다.

정규 표현식 사용 예제

기본 사용 예시


import re

# 문자열의 시작이 'Hello'인지 확인
result = re.match(r'^Hello', 'Hello, world!')
print(result)  # 매칭에 성공하면 매치 객체를 반환하고, 실패하면 None을 반환합니다.
    

문자열 내 패턴 찾기


import re

search_result = re.search(r'world', 'Hello, world!')
print(search_result)  # 매칭된 부분에 대한 매치 객체를 반환합니다.
    

모든 매칭 패턴 추출


# 문자열에서 모든 'a' 문자 찾기
all_matches = re.findall(r'a', 'banana')
print(all_matches)  # 찾은 모든 매칭의 리스트가 반환됩니다.
    

패턴을 기반으로 문자열 변환하기

re.sub() 함수를 사용하여 문자열 내 패턴을 다른 문자열로 변환할 수 있습니다.


# 모든 공백을 언더스코어로 교체
transformed_string = re.sub(r'\s', '_', 'Hello world!')
print(transformed_string)  # 'Hello_world!' 출력
    

고급 정규 표현식 기능

그룹과 캡처링

그룹화는 정규식의 서브패턴을 캡처하고 재사용하거나 특정 작업을 수행하는 데 매우 유용합니다.


pattern = r'(\d+)-(\d+)-(\d+)'
string = '전화번호: 123-456-7890'
match = re.search(pattern, string)

if match:
    print(match.group(0))  # 전체 매칭된 문자열
    print(match.group(1))  # 첫 번째 그룹: 123
    print(match.group(2))  # 두 번째 그룹: 456
    print(match.group(3))  # 세 번째 그룹: 789
    

Lookahead 및 Lookbehind

Lookahead와 Lookbehind는 특정 패턴 앞 또는 뒤에 있는 조건을 검사할 때 사용합니다. 이러한 기능은 흔히 사용되는 기술이지만 다소 복잡할 수 있습니다.

Lookahead 사용


# 'abc' 뒤에 'def'가 오는 패턴 찾기
lookahead_pattern = r'abc(?=def)'
lookahead_string = 'abcdefghi'
lookahead_match = re.search(lookahead_pattern, lookahead_string)
print(lookahead_match)
    

Lookbehind 사용


# '123' 앞에 오는 패턴
lookbehind_pattern = r'(?<=123)abc'
lookbehind_string = '123abc'
lookbehind_match = re.search(lookbehind_pattern, lookbehind_string)
print(lookbehind_match)
    

종합 예제: 이메일 주소 추출

정규 표현식은 특히 입력된 텍스트에서 이메일 주소를 추출하는 데 유용합니다.


email_pattern = r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'
text = "연락처 이메일: example@example.com 또는 support@domain.com"
emails = re.findall(email_pattern, text)

print(emails)  # ['example@example.com', 'support@domain.com']
    

정리

정규 표현식은 문자열 처리에서 매우 강력한 도구이며, 파이썬의 `re` 모듈은 이를 다루기에 충분한 기능을 제공합니다. 정규 표현식의 기본 문법을 이해하고 실습하면 복잡한 텍스트 패턴을 손쉽게 다룰 수 있습니다. 이러한 기술을 꾸준히 연습하고 활용하면 더욱 복잡한 문자열 처리 문제를 효과적으로 해결할 수 있을 것입니다.

07장: 파이썬 날아오르기

이 강좌에서는 파이썬의 고급 기능을 활용하여 복잡한 문제를 해결하고 효율적인 코드를 작성하는 방법에 대해 알아보겠습니다. 우리가 다루게 될 주요 주제들은 다양한 프로그래밍 패러다임, 고급 데이터 구조, 그리고 파이썬이 제공하는 강력한 내장 모듈 기능들을 포함합니다.

1. 고급 프로그래밍 패러다임

파이썬은 다중 패러다임 프로그래밍 언어입니다. 프로시저형, 객체 지향, 함수형 프로그래밍을 지원하며, 필요에 따라 각각의 장점을 취할 수 있습니다. 이번 장에서는 주로 객체 지향 프로그래밍(OOP)과 함수형 프로그래밍의 고급 기법에 대해 다루겠습니다.

1.1 객체 지향 프로그래밍 심화

OOP의 기본적인 개념은 클래스와 객체의 이해에서 출발합니다. 그러나 보다 복잡한 프로그램을 설계하기 위해서는 다른 개념도 알아야 합니다.

1.1.1 상속과 폴리모르피즘

상속은 새로운 클래스가 기존의 클래스의 속성과 메서드를 물려받는 기능입니다. 상속을 통해 코드의 재사용성을 높일 수 있습니다. 폴리모르피즘은 다른 클래스의 객체들에 대해 동일한 인터페이스를 사용할 수 있게 해줍니다.

class Animal:
    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        return "Woof!"

class Cat(Animal):
    def speak(self):
        return "Meow!"

def animal_sound(animal):
    print(animal.speak())

dog = Dog()
cat = Cat()

animal_sound(dog)  # Woof!
animal_sound(cat)  # Meow!

위 예제는 폴리모르피즘의 예시입니다. 각기 다른 클래스의 객체들이 speak() 메서드를 가짐으로써, animal_sound 함수에서 동일한 방식으로 호출이 가능합니다.

1.1.2 추상화와 인터페이스

추상 클래스는 기본 동작을 정의하는 클래스이며, 하나 이상의 추상 메서드를 가집니다. 인터페이스는 이러한 추상 메서드의 집합으로 생각할 수 있습니다. 파이썬에서는 abc 모듈의 ABC 클래스를 통해 추상화를 구현합니다.

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.1415 * self.radius * self.radius

circle = Circle(5)
print(circle.area())  # 78.5375

위 예제에서 Shape 클래스는 추상 클래스로써, area라는 추상 메서드를 정의합니다. Circle 클래스는 Shape를 상속받아 area 메서드를 구체화합니다.

1.2 함수형 프로그래밍

함수형 프로그래밍은 순수 함수를 사용하여 부작용을 줄이고, 함수 합성을 통해 복잡한 동작을 구현합니다. 파이썬은 이러한 스타일을 장려하기 위해 강력한 함수형 도구를 제공합니다.

1.2.1 람다 함수

람다 함수는 익명의 함수로, 보통 한 줄의 표현식으로 정의됩니다. 짧고 간결한 함수를 작성할 때 유용합니다.

add = lambda x, y: x + y
print(add(5, 3))  # 8

위의 예제에서 lambda는 두 매개변수를 더하는 익명 함수를 정의합니다.

1.2.2 고차 함수

고차 함수는 함수 자체를 인자로 받거나 반환하는 함수입니다. 파이썬의 mapfilterreduce는 이런 함수형 프로그래밍 기법을 활용한 예입니다.

numbers = [1, 2, 3, 4, 5]
squared = map(lambda x: x**2, numbers)
print(list(squared))  # [1, 4, 9, 16, 25]

위의 예제에서 map 함수는 각 리스트 요소에 람다 함수를 적용하여 새로운 이터레이터를 만듭니다.

2. 고급 데이터 구조

고급 데이터 구조 활용은 복잡한 데이터 작업을 보다 효율적으로 수행할 수 있게 합니다. 여기서는 리스트, 딕셔너리 같은 기본 자료형을 넘어, 더 복잡한 데이터 구조를 다룹니다.

2.1 컬렉션 모듈

파이썬의 collections 모듈은 특별한 목적을 가진 여러 데이터 구조를 제공합니다. 그 중 몇 가지를 살펴보겠습니다.

2.1.1 defaultdict

defaultdict는 존재하지 않는 키를 참조할 때 자동으로 기본값을 생성하는 딕셔너리입니다.

from collections import defaultdict

fruit_counter = defaultdict(int)
fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']

for fruit in fruits:
    fruit_counter[fruit] += 1

print(fruit_counter)  # defaultdict(, {'apple': 3, 'banana': 2, 'orange': 1})

이 예제에서는 defaultdict를 사용하여 각 과일의 개수를 쉽게 셀 수 있습니다.

2.1.2 namedtuple

namedtuple은 튜플과 같이 불변하지만, 필드를 이름으로 액세스할 수 있어 코드의 가독성을 높여줍니다.

from collections import namedtuple

Point = namedtuple('Point', ['x', 'y'])
p = Point(10, 20)

print(p.x, p.y)  # 10 20

이처럼 namedtuple을 사용해 필드를 이름으로 접근할 수 있어 명확한 코드를 작성할 수 있습니다.

2.2 힙 큐 모듈

heapq 모듈은 힙 큐 알고리즘을 구현하여, 리스트를 우선순위 큐로 사용할 수 있도록 합니다.

import heapq

numbers = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0]
heapq.heapify(numbers)  # 리스트를 우선순위 큐로 변환

smallest = heapq.heappop(numbers)
print(smallest)  # 0

우선순위 큐를 사용하여 데이터의 최솟값을 빠르게 추출할 수 있습니다.

3. 고급 내장 모듈 활용

파이썬의 풍부한 내장 모듈은 다양한 기능을 제공합니다. 여기서는 고급 작업을 위한 몇 가지 모듈을 소개하겠습니다.

3.1 itertools 모듈

itertools 모듈은 반복자를 다루기 위한 유용한 함수를 제공합니다. 반복적인 데이터 처리에 강력한 도구입니다.

3.1.1 combinations와 permutations

조합과 순열은 데이터 집합에서 요소를 선택하는 다양한 방법을 제공합니다.

from itertools import combinations, permutations

data = ['A', 'B', 'C']

# 조합
print(list(combinations(data, 2)))  # [('A', 'B'), ('A', 'C'), ('B', 'C')]

# 순열
print(list(permutations(data, 2)))  # [('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')]

이러한 기능을 통해 다양한 목록 조합을 빠르게 생성할 수 있습니다.

3.1.2 반복기 집합 다루기

무한 반복, 카운트 증가, 주기 반복 등 다양한 반복 동안 유용하게 사용할 수 있는 도구를 제공합니다.

from itertools import count, cycle

# 무한 카운트
for i in count(10):
    if i > 15:
        break
    print(i, end=' ')  # 10 11 12 13 14 15

print()  # 새로운 줄

# 주기적 반복
for i, char in zip(range(10), cycle('ABC')):
    print(char, end=' ')  # A B C A B C A B C A

위 예제는 무한루프와 주기반복의 활용방법을 보여줍니다.

3.2 functools 모듈

functools 모듈은 함수형 프로그래밍 도구를 제공합니다. 특히 함수를 다루는 데 유용한 다양한 도구들을 제공합니다.

3.2.1 lru_cache 데코레이터

@lru_cache 데코레이터는 메모이제이션을 위해 사용되며, 계산된 결과를 저장하여 같은 인풋에 대한 재계산을 피합니다.

from functools import lru_cache

@lru_cache(maxsize=None)
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print([fibonacci(n) for n in range(10)])  # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

위의 코드에서, 피보나치 수열에 대한 계산 결과가 캐시에 저장되어, 동일한 인풋에 대해 수행시간을 절약할 수 있습니다.

마무리

이 글에서는 파이썬의 고급 주제에 대해 알아보았습니다. 이러한 기능들을 잘 활용하면 복잡한 문제를 효율적으로 해결하고 높은 수준의 코드를 작성할 수 있습니다. 다음 강좌에서 더욱 많은 주제를 다루며 파이썬 전문가로 나아가 보겠습니다.

파이썬 타입 어노테이션

파이썬은 동적 타이핑(dynamic typing) 언어로 잘 알려져 있습니다. 이는 변수의 타입을 명시할 필요 없이 모든 값이 실행 시간에 체크되는 것을 의미합니다. 그러나 대규모 프로젝트가 복잡해지고 여러 개발자가 협력하게 되면서, 코드의 이해 및 유지보수가 어려워질 수 있습니다. 이를 해결하기 위해 파이썬 3.5부터는 타입 어노테이션(Type Annotation)이 도입되었습니다. 타입 어노테이션을 사용하면 코드의 가독성을 높이고 버그를 예방하며 자동 완성 기능을 향상시키는 데 도움이 됩니다.

1. 타입 어노테이션의 기초

타입 어노테이션은 변수나 함수의 타입을 명시적으로 기술할 수 있게 해주는 문법입니다. 다음은 변수와 함수의 타입을 어노테이션하는 기본적인 방법입니다:

변수 어노테이션:
x: int = 10
y: float = 10.5
name: str = "Alice"

함수 어노테이션:
def greeting(name: str) -> str:
    return "Hello " + name

1.1 변수의 타입 어노테이션

변수의 타입을 지정하여 코드 작성자는 특정한 타입이 기대된다는 의도를 명확히 할 수 있습니다. 이는 툴링과 IDE에서 정적 분석을 통해 오류를 사전에 감지할 수 있게 해줍니다.

1.2 함수의 파라미터와 반환 값 어노테이션

함수 어노테이션을 통해 함수의 입력과 출력 타입을 명시할 수 있으며, 함수가 어떠한 타입의 데이터를 받을지 예상할 수 있도록 도와줍니다. 이는 코드 리뷰 시 큰 도움이 됩니다.

2. 내장 데이터 타입

파이썬은 다양한 내장 데이터 타입을 지원하며, 이 타입들을 어노테이션에 사용할 수 있습니다.

  • Int, float, str, bool, None 등 기본 타입
  • List, Dict, Set, Tuple 등 컨테이너 타입은 typing 모듈을 통해 더 세분화할 수 있습니다.
from typing import List, Dict, Tuple

names: List[str] = ["Alice", "Bob", "Charlie"]
scores: Dict[str, int] = {"Alice": 95, "Bob": 85}
position: Tuple[int, int] = (10, 20)

3. 유니언과 옵셔널

여러 타입을 허용해야 하는 경우 Union을 사용하고, None을 허용하는 경우 Optional을 사용하는 것이 일반적입니다.

from typing import Union, Optional

value: Union[int, float] = 5.5

def get_user(id: int) -> Optional[Dict[str, str]]:
    if id == 1:
        return {"name": "Alice", "role": "admin"}
    return None

4. 사용자 정의 타입

복잡한 타입을 정의해야 하는 경우, Type 또는 NewType 을 사용하여 더욱 명확한 코드를 작성할 수 있습니다.

from typing import NewType

UserID = NewType('UserID', int)
admin_user_id: UserID = UserID(524313)

4.1 타입 앨리어스

타입 앨리어스를 사용하여 복잡한 타입 구조를 간결한 이름으로 표현할 수 있습니다.

Vector = List[float]

def normalize(vec: Vector) -> Vector:
    magnitude = sum(x**2 for x in vec) ** 0.5
    return [x / magnitude for x in vec]

5. 제네릭(Generic) 타입

제네릭 타입을 사용하면 하나의 함수나 클래스를 여러 타입으로 사용할 수 있습니다. typing.Generic 클래스를 사용하여 제네릭 타입을 정의할 수 있습니다.

from typing import TypeVar, Generic

T = TypeVar('T')

class Box(Generic[T]):
    def __init__(self, content: T) -> None:
        self.content = content

int_box = Box(123)
str_box = Box("hello")

6. 고급 예제

다음은 타입 어노테이션을 활용하여 작성된 조금 더 복잡한 예제입니다.

from typing import List, Dict, Union, Callable

def process_data(data: List[Union[int, float, str]]) -> Dict[str, Union[int, float]]:
    result: Dict[str, Union[int, float]] = {'total': 0, 'numeric_count': 0}

    def is_number(val: Union[int, float, str]) -> bool:
        return isinstance(val, (int, float))

    for item in data:
        if is_number(item):
            result['total'] += item  # 타입 경고는 막아준다.
            result['numeric_count'] += 1

    return result

mixed_data: List[Union[int, float, str]] = [10, '20', 30.5, 'forty', '60', 70.2]
output = process_data(mixed_data)
print(output)
# {'total': 110.7, 'numeric_count': 3}

7. 정적 타입 체크 툴

타입 어노테이션은 정적 타입 체크 툴과 함께 사용될 때 가장 유용합니다. 파이썬에서는 mypyPyrightPylance 같은 툴들이 많이 사용됩니다.

예를 들어, mypy는 다음과 같이 사용됩니다:

mypy script.py

이러한 툴은 코드의 타입 일관성을 검사하고 예상치 못한 타입 오류를 예방하는 데 매우 효과적입니다.

8. 결론

타입 어노테이션은 파이썬의 강력한 기능으로, 코드의 가독성을 높이고 유지보수를 쉽게 하며 오류를 사전에 예방하는 데 큰 도움을 줍니다. 또한, 정적 분석 도구와 결합하여 대규모 프로젝트에 더욱 안정성을 부여합니다. 이 강좌를 통해 여러분이 타입 어노테이션을 잘 활용할 수 있게 되어, 보다 견고한 파이썬 코드를 작성하길 기대합니다.

파이썬 이터레이터와 제너레이터

프로그래밍에서 순회 가능한 객체와 그 활용은 대규모 데이터 처리 시 필수적입니다. 파이썬은 이러한 작업을 수행하기 위해 이터레이터와 제너레이터라는 두 가지 강력한 도구를 제공합니다. 이번 글에서는 이터레이터와 제너레이터의 개념, 이들의 차이점, 그리고 사용법을 깊이 있게 다루어 보겠습니다.

이터레이터(Iterator)

이터레이터는 반복 가능한 객체를 나타내는 프로토콜로, 객체의 요소를 순회할 수 있는 인터페이스를 제공합니다. 파이썬에서 이터레이터는 __iter__() 메소드와 __next__() 메소드를 구현하여 만들어집니다. 이들은 반복문에서 순회를 수행할 때 자동으로 호출되며, 일반적으로 많은 양의 데이터를 처리할 때 유용합니다.

이터레이터의 작동 방식

이터레이터의 작동을 이해하기 위해서는 두 메소드에 대해 더 깊이 알아야 합니다.

  • __iter__()이터러블 객체를 반환합니다. 즉, 원래 객체 자신을 반환합니다. 이 메소드는 반복이 시작될 때 호출됩니다. 이터러블 객체는 시작 지점에서 이터레이터를 얻기 위해 사용됩니다.
  • __next__()이터레이션을 통해 데이터의 다음 값을 반환합니다. 만약 더 이상의 데이터가 없다면 StopIteration 예외를 발생시켜야 합니다. 이 메소드는 반복할 요소가 그룹화된 이터러블의 다음 항목을 가져오기 위해 호출됩니다.

간단한 이터레이터 예제

다음은 간단한 카운터 이터레이터의 예제 코드입니다:


class Counter:
    def __init__(self, low, high):
        self.current = low
        self.high = high

    def __iter__(self):
        return self

    def __next__(self):
        if self.current >= self.high:
            raise StopIteration
        else:
            self.current += 1
            return self.current - 1

counter = Counter(1, 5)
for number in counter:
    print(number)
    

위의 예제에서 Counter 클래스는 __iter__()와 __next__() 메소드를 구현하여 이터레이터 프로토콜을 따르고 있습니다. 이 클래스의 객체는 반복문(for loop)에서 사용할 수 있습니다.

제너레이터(Generator)

제너레이터는 이터레이터를 보다 간단히 생성할 수 있도록 도와주는 특별한 함수로, yield 키워드를 사용하여 값을 하나씩 반환합니다. 제너레이터는 호출되면 제너레이터 객체를 반환하며, 이는 제너레이터 함수가 값의 순회에 사용될 때 실행되고, 중지되었다가 다시 호출되면 중단된 지점에서 재개됩니다.

제너레이터의 작동 방식

제너레이터는 내부적으로 __iter__()와 __next__() 메소드를 자동으로 구현하여 사용자에게 이러한 구현을 숨깁니다. 따라서 제너레이터 함수를 호출하면 제너레이터 객체가 반환되며, 이 객체는 이터레이터처럼 사용할 수 있습니다.

제너레이터 예제

다음은 간단한 제너레이터 함수의 예제 코드입니다:


def simple_generator():
    yield 1
    yield 2
    yield 3

for value in simple_generator():
    print(value)
    

위의 예제에서, simple_generator() 함수는 호출될 때마다 yield 키워드를 사용하여 값을 하나씩 반환합니다. 이 제너레이터는 다른 이터레이터처럼 for 반복문에서 사용할 수 있습니다.

이터레이터와 제너레이터의 차이점

이터레이터와 제너레이터는 많은 유사점이 있지만, 몇 가지 중요한 차이점이 있습니다:

  • 구현의 간단함: 제너레이터는 yield 키워드를 사용하여 더 직관적이고 간단하게 구현할 수 있습니다. 이터레이터를 직접 작성할 때의 복잡함을 없앨 수 있습니다.
  • 상태 유지: 제너레이터는 상태를 자동으로 유지합니다. 제너레이터가 실행을 중단하고 있을 때 모든 현 상태를 기억하므로, yield를 계속해서 호출하면 그 상태를 지속적으로 유지합니다.
  • 메모리 사용: 제너레이터는 즉시 결과를 생성하지 않고, 필요할 때마다 하나씩 값을 생성하므로 메모리 효율적입니다. 이터레이터와 비교했을 때 대규모 데이터 처리에 더 유용합니다.

고급 사용 예제

제너레이터는 복잡한 로직과 결합되어 아주 효율적인 코드를 작성할 수 있습니다. 아래는 피보나치 수열을 제너레이터로 생성하는 예제입니다:


def fibonacci_generator():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

fib_gen = fibonacci_generator()
for _ in range(10):
    print(next(fib_gen))
    

이 예제에서 fibonacci_generator는 무한 피보나치 수열을 생성하며, for 반복문이나 next() 함수를 통해 필요한 만큼의 값을 출력할 수 있습니다.

실전에서의 활용

이터레이터와 제너레이터는 특히 대규모 데이터 스트림을 처리하거나, 결과의 전체 리스트를 메모리에 저장할 필요 없이 한 번에 하나씩의 값을 생성하여 메모리 사용을 최적화할 필요가 있는 상황에서 자주 사용됩니다.

파일 읽기: 파일의 각 줄을 제너레이터로 읽어들여 더 큰 파일을 메모리 효율적으로 처리할 수 있습니다.


def read_large_file(file_path):
    with open(file_path, 'r') as file:
        for line in file:
            yield line.strip()

for line in read_large_file("large_file.txt"):
    print(line)
    

결론

이터레이터와 제너레이터는 파이썬의 매우 강력한 기능이며, 이들을 사용하면 복잡하고 많은 양의 데이터 처리를 메모리 효율적이면서도 가독성 좋게 수행할 수 있습니다. 이 두 개념을 잘 이해하고 적절히 활용하면, 보다 효율적이고 확장 가능한 코드 작성을 할 수 있을 것입니다.

이 강좌가 파이썬 이터레이터와 제너레이터의 이해를 깊게 도와주었기를 바랍니다. 앞으로의 파이썬 프로그래밍 여정에서 이 내용을 활용해 보세요.