Python의 제어 흐름, 함수, 이터레이터, 제너레이터, 리스트 컴프리헨션 및 람다 표현식에 대한 종합적인 가이드입니다.
제어 흐름
조건문
조건문은 코드 실행 경로를 제어하는 데 사용됩니다.
# if-elif-else 구조
number = 10
divisor = 2
if number == divisor:
print("동일합니다.")
elif number < divisor:
print("더 작습니다.")
else:
print("더 큽니다.")
# match-case 문 (Python 3.10+)
grade = 89
match grade:
case 100:
print("등급 A+")
case x if 90 < x < 100:
print("등급 A")
case 85 | 87 | 88 | 89 | 90:
print("등급 A-")
case x if 60 < x < 85:
print("등급 B")
case _: # 다른 모든 경우
print("등급 C")
반복문
반복문은 코드 블록을 여러 번 실행하는 데 사용됩니다.
# while 루프
counter = 2
limit = 10
while counter < limit:
counter += 1
if counter % 2 == 0:
continue # 현재 반복을 건너뛰고 다음 반복으로 진행
print(counter)
else:
print("while 루프 완료.")
# for 루프
for i in range(5):
if i > 3:
break # 루프 종료
print(i)
else:
pass # 루프가 break 없이 완료되면 실행 (이 경우엔 실행되지 않음)
# 리스트 반복
my_list = ["apple", "banana"]
for item in my_list:
print(item)
# 튜플 반복
my_tuple = ("apple", "banana")
for item in my_tuple:
print(item)
# 딕셔너리 반복
my_dict = {"name": "Alice", "age": 19}
for key, value in my_dict.items():
print(f"{key}: {value}")
for key in my_dict.keys():
print(key)
for value in my_dict.values():
print(value)
for key in my_dict:
print(f"{key}: {my_dict[key]}")
함수
매개변수 전달 및 반환
함수는 값을 복사하여 전달(값 전달)하거나 객체에 대한 참조를 전달(참조 전달)할 수 있습니다. 함수는 여러 값을 튜플로 반환할 수 있습니다.
def process_data(name_param, age_param, data_list):
"""이 함수는 데이터를 처리합니다."""
print(f"함수 내부 (초기): 이름={name_param}, 나이={age_param}, 데이터={data_list}")
name_param = "Bob" # 불변 객체(문자열)는 새로운 객체 할당
age_param = 2 # 불변 객체(정수)는 새로운 객체 할당
data_list.append("ccc") # 가변 객체(리스트)는 변경됨
print(f"함수 내부 (변경 후): 이름={name_param}, 나이={age_param}, 데이터={data_list}")
return name_param, age_param
initial_name = "Alice"
initial_age = 1
initial_data = ["aaa", "bbb"]
print(process_data.__doc__) # 함수 설명 출력
result_name, result_age = process_data(initial_name, initial_age, initial_data)
print(f"반환 값: 이름={result_name}, 나이={result_age}")
print(f"함수 외부: 이름={initial_name}, 나이={initial_age}, 데이터={initial_data}")
출력
이 함수는 데이터를 처리합니다.
함수 내부 (초기): 이름=Alice, 나이=1, 데이터=['aaa', 'bbb']
함수 내부 (변경 후): 이름=Bob, 나이=2, 데이터=['aaa', 'bbb', 'ccc']
반환 값: 이름=Bob, 나이=2
함수 외부: 이름=Alice, 나이=1, 데이터=['aaa', 'bbb', 'ccc']
내부 함수 및 스코프
함수 내부에 정의된 변수는 지역 스코프를 가지며, 함수 외부에 정의된 변수는 전역 스코프를 가집니다. 내부 함수는 외부 함수의 변수에 접근할 수 있습니다.
# 전역 변수 (위에서 정의된 initial_name, initial_age, initial_data 사용)
def outer_function():
print(f"외부 함수: 이름={initial_name}, 나이={initial_age}, 데이터={initial_data}")
outer_scope_var = "outer"
def inner_function():
inner_scope_var = "inner"
print(f"내부 함수: 이름={initial_name}, 나이={initial_age}, 데이터={initial_data}")
print(f"외부 스코프 변수: {outer_scope_var}")
print(f"내부 스코프 변수: {inner_scope_var}")
inner_function()
outer_function()
출력
외부 함수: 이름=Alice, 나이=1, 데이터=['aaa', 'bbb', 'ccc']
내부 함수: 이름=Alice, 나이=1, 데이터=['aaa', 'bbb', 'ccc']
외부 스코프 변수: outer
내부 스코프 변수: inner
함수 매개변수
Python은 위치 매개변수, 기본값 매개변수, 가변 길이 매개변수(*args, **kwargs)를 지원합니다.
# 위치 및 기본값 매개변수
def greet(name, greeting="Hello"):
print(f"{greeting}, {name}!")
greet("Alice", "Hi") # Hi, Alice!
greet("Bob") # Hello, Bob!
# 가변 길이 위치 매개변수 (*args)
def sum_all(*numbers):
total = 0
for num in numbers:
total += num
print(f"합계: {total}")
sum_all(1, 2, 3) # 합계: 6
sum_all(10, 20, 30, 40) # 합계: 100
# 가변 길이 키워드 매개변수 (**kwargs)
def display_info(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
display_info(name="Charlie", age=25, city="New York")
# name: Charlie
# age: 25
# city: New York
# 위치 및 키워드 매개변수 결합
def combined_args(pos_arg, *args, **kwargs):
print(f"위치 인수: {pos_arg}")
print(f"추가 위치 인수: {args}")
print(f"키워드 인수: {kwargs}")
combined_args("A", 1, 2, 3, key1="val1", key2="val2")
# 위치 인수: A
# 추가 위치 인수: (1, 2, 3)
# 키워드 인수: {'key1': 'val1', 'key2': 'val2'}
# 키워드 전용 인수 ( * 이후의 매개변수)
def keyword_only_args(arg1, *, kwarg1, kwarg2):
print(f"arg1: {arg1}, kwarg1: {kwarg1}, kwarg2: {kwarg2}")
keyword_only_args("value1", kwarg1="value_k1", kwarg2="value_k2")
# arg1: value1, kwarg1: value_k1, kwarg2: value_k2
고차 함수
Python은 `map`, `filter`, `reduce`와 같은 고차 함수를 지원합니다. `lambda` 표현식과 함께 자주 사용됩니다.
from functools import reduce
# 람다 표현식
add_lambda = lambda a, b, c: a + b + c
print(f"람다 덧셈: {add_lambda(1, 2, 3)}") # 람다 덧셈: 6
# sorted() 함수
numbers = [1, 3, 5, 7, 0, -1, -9, -4, -5, 8]
print(f"정렬된 리스트: {sorted(numbers)}") # 정렬된 리스트: [-9, -5, -4, -1, 0, 1, 3, 5, 7, 8]
print(f"역순 정렬: {sorted(numbers, reverse=True)}") # 역순 정렬: [8, 7, 5, 3, 1, 0, -1, -4, -5, -9]
colors_dict = {"red": 2, "yellow": 4, "green": 1, "black": 3}
# 값 기준 정렬
sorted_by_value = sorted(colors_dict.items(), key=lambda item: item[1])
print(f"값 기준 정렬 딕셔너리: {dict(sorted_by_value)}") # 값 기준 정렬 딕셔너리: {'green': 1, 'red': 2, 'black': 3, 'yellow': 4}
# 키 기준 정렬
sorted_by_key = sorted(colors_dict.items(), key=lambda item: item[0])
print(f"키 기준 정렬 딕셔너리: {dict(sorted_by_key)}") # 키 기준 정렬 딕셔너리: {'black': 3, 'green': 1, 'red': 2, 'yellow': 4}
# map() 함수
colors = ["red", "yellow", "green", "black"]
upper_colors = list(map(lambda word: word.upper(), colors))
print(f"대문자 색상: {upper_colors}") # 대문자 색상: ['RED', 'YELLOW', 'GREEN', 'BLACK']
list_a = [1, 3, 5]
list_b = [2, 4, 6]
sum_lists = list(map(lambda x, y: x + y, list_a, list_b))
print(f"리스트 요소 합계: {sum_lists}") # 리스트 요소 합계: [3, 7, 11]
# reduce() 함수
numbers_range = list(range(1, 101))
sum_of_numbers = reduce(lambda x, y: x + y, numbers_range)
print(f"1부터 100까지의 합: {sum_of_numbers}") # 1부터 100까지의 합: 5050
# filter() 함수
positive_numbers = list(filter(lambda x: x > 0, numbers))
print(f"양수 필터링: {positive_numbers}") # 양수 필터링: [1, 3, 5, 7, 8]
filtered_dict = dict(filter(lambda item: item[1] > 3, colors_dict.items()))
print(f"값 3 초과 필터링: {filtered_dict}") # 값 3 초과 필터링: {'yellow': 4}
# enumerate() 함수
print("enumerate 결과:")
for index, color in enumerate(colors):
print(f"{index}: {color}")
# enumerate 결과:
# 0: red
# 1: yellow
# 2: green
# 3: black
# zip() 함수
fruits = ["apple", "pineapple", "grapes", "cherry"]
zipped_data = list(zip(colors, fruits))
print(f"zip 결과 리스트: {zipped_data}") # zip 결과 리스트: [('red', 'apple'), ('yellow', 'pineapple'), ('green', 'grapes'), ('black', 'cherry')]
zipped_dict = dict(zip(colors, fruits))
print(f"zip 결과 딕셔너리: {zipped_dict}") # zip 결과 딕셔너리: {'red': 'apple', 'yellow': 'pineapple', 'green': 'grapes', 'black': 'cherry'}
클로저
클로저는 내부 함수가 외부 함수의 상태(스코프)를 기억하는 함수입니다.
def outer_func(x):
def inner_func(y):
return x + y
return inner_func
closure_instance = outer_func(10)
print(f"클로저 호출 결과: {closure_instance(5)}") # 클로저 호출 결과: 15
람다 표현식
람다 표현식은 간단한 익명 함수를 생성하는 데 사용됩니다.
square = lambda x: x * x
print(f"람다 제곱: {square(4)}") # 람다 제곱: 16
데코레이터
데코레이터는 기존 함수의 기능을 수정하거나 확장하는 데 사용됩니다.
# 매개변수 없는 데코레이터
def add_logging(func):
def wrapper(param):
print(f"함수 '{func.__name__}' 호출 전 로깅")
result = func(param)
print(f"함수 '{func.__name__}' 호출 후 로깅")
return result
return wrapper
@add_logging
def process_item(item):
print(f"항목 처리 중: {item}")
return f"Processed {item}"
process_item("Data1")
# 함수 'process_item' 호출 전 로깅
# 항목 처리 중: Data1
# 함수 'process_item' 호출 후 로깅
# 매개변수 있는 데코레이터
def apply_log_level(level):
def logger_decorator(func):
def wrapper(param):
print(f"[{level.upper()}] 함수 '{func.__name__}' 호출 전")
result = func(param)
print(f"[{level.upper()}] 함수 '{func.__name__}' 호출 후")
return result
return wrapper
return logger_decorator
@apply_log_level("info")
def update_record(record_id):
print(f"레코드 업데이트: {record_id}")
return f"Record {record_id} updated."
update_record(101)
# [INFO] 함수 'update_record' 호출 전
# 레코드 업데이트: 101
# [INFO] 함수 'update_record' 호출 후
모듈
Python 파일은 모듈 역할을 합니다. `import` 문을 사용하여 다른 모듈의 함수나 클래스를 가져올 수 있습니다. 별칭(alias)을 사용하여 이름 충돌을 피하거나 코드의 가독성을 높일 수 있습니다.
# module1.py
# def greet():
# return "Hello from module1"
# module2.py
# def greet():
# return "Hi from module2"
# main_script.py
# import module1 as m1
# from module2 import greet as greet_m2
# print(m1.greet()) # Hello from module1
# print(greet_m2()) # Hi from module2
이터레이터
이터레이터는 `__iter__()` 및 `__next__()` 메서드를 구현하는 객체로, 컬렉션의 요소를 순회하는 데 사용됩니다.
from collections.abc import Iterable, Iterator
# Iterable은 __iter__() 메서드를 가지고 있습니다.
print(f"문자열은 Iterable인가? {isinstance('abc', Iterable)}") # True
print(f"리스트는 Iterable인가? {isinstance([], Iterable)}") # True
# Iterator는 __iter__()와 __next__() 메서드를 가지고 있습니다.
print(f"리스트는 Iterator인가? {isinstance([], Iterator)}") # False
# 파일 객체는 Iterator입니다.
try:
with open("sample.txt", "w") as f: # 임시 파일 생성
f.write("test line 1\ntest line 2")
file_iterator = open("sample.txt", "r")
print(f"파일 객체는 Iterator인가? {isinstance(file_iterator, Iterator)}") # True
file_iterator.close()
except Exception as e:
print(f"파일 관련 오류: {e}")
# 이터레이터 사용 예시
file_path = "sample.txt"
try:
with open(file_path, "r") as f:
for line in f:
print(f"파일 라인: {line.strip()}")
except FileNotFoundError:
print(f"오류: 파일 '{file_path}'를 찾을 수 없습니다.")
제너레이터
제너레이터는 `yield` 키워드를 사용하여 이터레이터를 만드는 함수입니다. 함수 실행을 일시 중지하고 재개할 수 있어 메모리 효율적입니다.
# 간단한 제너레이터
def count_up_to(n):
print("제너레이터 시작...")
i = 0
while i < n:
yield i
i += 1
print("제너레이터 종료...")
generator_obj = count_up_to(3)
print(f"첫 번째 next(): {next(generator_obj)}")
# 제너레이터 시작...
# 첫 번째 next(): 0
print(f"리스트로 변환: {list(generator_obj)}")
# 1
# 2
# 제너레이터 종료...
# 리스트로 변환: [1, 2]
# 대용량 파일 읽기 시뮬레이션
def read_large_file_blocks(filepath, block_size=1024):
try:
with open(filepath, "r") as f:
while True:
block = f.read(block_size)
if not block:
break
yield block
except FileNotFoundError:
print(f"오류: 파일 '{filepath}'를 찾을 수 없습니다.")
# 'sample.txt' 파일이 있다고 가정
file_block_generator = read_large_file_blocks("sample.txt", block_size=10)
print("\n대용량 파일 블록 읽기:")
for block in file_block_generator:
print(f"블록: {block}")
# send() 메서드를 사용한 제너레이터 (값 전달)
def interactive_generator():
print("제너레이터 시작")
received_value = yield "초기 값" # 첫 번째 yield는 값을 반환하고 여기서 멈춤
print(f"send()로 받은 값: {received_value}")
yield received_value * 2 # 두 번째 yield는 계산된 값을 반환
gen = interactive_generator()
print(f"제너레이터 초기화: {next(gen)}") # gen.send(None)과 동일
# 제너레이터 시작
# 제너레이터 초기화: 초기 값
print(f"첫 번째 send() 호출: {gen.send(5)}") # 5를 제너레이터 내부의 received_value로 전달
# send()로 받은 값: 5
# 첫 번째 send() 호출: 10
# 평균 계산 제너레이터
def average_calculator():
total, count = 0, 0
average = None
while True:
current_number = yield average # 현재 평균 반환
total += current_number
count += 1
average = total / count
print("\n평균 계산기:")
avg_gen = average_calculator()
avg_gen.send(None) # 제너레이터 활성화
numbers_to_average = [10, 20, 30, 40, 50]
print(f"입력 숫자: {numbers_to_average}")
for num in numbers_to_average:
returned_average = avg_gen.send(num)
print(f"숫자 {num} 입력 후 평균: {returned_average}")
# 제너레이터 표현식 (리스트 컴프리헨션과 유사하지만 이터레이터 반환)
even_numbers_gen = (x for x in range(10) if x % 2 == 0)
print(f"\n제너레이터 표현식 결과: {list(even_numbers_gen)}") # 제너레이터 표현식 결과: [0, 2, 4, 6, 8]
리스트 컴프리헨션
리스트 컴프리헨션은 간결한 구문으로 리스트를 생성하는 강력한 방법입니다.
original_data = [5, 2, -3, 14, 9, 10]
# 조건부 필터링
greater_than_5 = [x for x in original_data if x > 5]
print(f"5보다 큰 숫자: {greater_than_5}") # 5보다 큰 숫자: [14, 9, 10]
# 조건부 변환
squared_if_positive = [x**2 if x > 0 else x for x in original_data]
print(f"양수 제곱, 음수는 그대로: {squared_if_positive}") # 양수 제곱, 음수는 그대로: [25, 4, -3, 196, 81, 100]
# 조건부 리스트 생성
conditional_list = [x for x in range(5)] if 5 > 3 else [x for x in range(10, 15)]
print(f"조건부 리스트: {conditional_list}") # 조건부 리스트: [0, 1, 2, 3, 4]
# 딕셔너리 컴프리헨션
squared_dict = {x: x*x for x in range(5)}
print(f"제곱 딕셔너리: {squared_dict}") # 제곱 딕셔너리: {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
# 세트 컴프리헨션
unique_squares = {x*x for x in range(5)}
print(f"제곱 세트: {unique_squares}") # 제곱 세트: {0, 1, 4, 9, 16}
# 문자열 처리
string_data = "abc"
char_list = [char.upper() for char in string_data]
print(f"대문자 문자 리스트: {char_list}") # 대문자 문자 리스트: ['A', 'B', 'C']
# 중첩 리스트 평탄화
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
flattened_matrix = [element for row in matrix for element in row]
print(f"평탄화된 행렬: {flattened_matrix}") # 평탄화된 행렬: [1, 2, 3, 4, 5, 6, 7, 8, 9]
# 복잡한 데이터 구조 처리
complex_data = [{"name": "Alice", "value": 10}, {"name": "Bob", "value": 5}]
values = [item["value"] for item in complex_data if item["value"] > 7]
print(f"값 7 초과 항목: {values}") # 값 7 초과 항목: [10]
람다 표현식
람다 표현식은 짧고 익명인 함수를 정의하는 데 사용되며, 종종 `filter`, `map`, `reduce`와 함께 사용됩니다.
from functools import reduce
data = [5, 2, -3, 14, 9, 10]
# filter와 함께 사용
filtered_data = list(filter(lambda x: x > 5, data))
print(f"람다 필터링 (5 초과): {filtered_data}") # 람다 필터링 (5 초과): [14, 9, 10]
# map과 함께 사용
doubled_data = list(map(lambda x: x * 2, data))
print(f"람다 맵핑 (2배): {doubled_data}") # 람다 맵핑 (2배): [10, 4, -6, 28, 18, 20]
# reduce와 함께 사용
sum_with_lambda = reduce(lambda x, y: x + y, data)
print(f"람다 reduce (합계): {sum_with_lambda}") # 람다 reduce (합계): 37
# None 필터링 (False, 0, 빈 시퀀스 등 제거)
truthy_values = list(filter(None, [1, 0, False, True, "", "hello"]))
print(f"Truthy 값 필터링: {truthy_values}") # Truthy 값 필터링: [1, True, 'hello']