Set 사용하기
리스트를 사용한 멤버십 테스트
# 리스트 생성: 1부터 5까지의 숫자를 포함
list_example = [1, 2, 3, 4, 5]
# 리스트 내에 5가 있는지 확인
if 5 in list_example:
print("Found in list!") # 5를 찾았을 경우 출력 메시지
설명
list_example
은 1부터 5까지의 숫자를 포함하는 리스트입니다.5 in list_example
구문을 사용하여 리스트 내에 숫자 5가 있는지 확인합니다.- 5가 리스트 내에 있으므로, "Found in list!"라는 문자열이 출력됩니다.
세트를 사용한 멤버십 테스트
# 세트 생성: 1부터 5까지의 숫자를 포함
set_example = {1, 2, 3, 4, 5}
# 세트 내에 5가 있는지 확인
if 5 in set_example:
print("Found in set!") # 5를 찾았을 경우 출력 메시지
설명
set_example
은 1부터 5까지의 숫자를 포함하는 세트입니다.5 in set_example
구문을 사용하여 세트 내에 숫자 5가 있는지 확인합니다.- 세트에서 멤버십 검사는 해시 테이블을 사용하므로 일반적으로 리스트보다 빠르게 수행됩니다.
- 5가 세트 내에 있으므로, "Found in set!"라는 문자열이 출력됩니다.
적절한 파이썬 내부 라이브러리 쓰기
defaultdict 사용
defaultdict
는 키가 없는 경우에 기본 값을 반환하는 딕셔너리입니다. 이를 사용하면 복잡한 딕셔너리를 더 쉽게 만들 수 있습니다.
from collections import defaultdict
# defaultdict 생성, 기본 값 타입으로 리스트 지정
my_dict = defaultdict(list)
# 'key' 키에 'value' 값을 추가, 키가 없는 경우 빈 리스트로 시작
my_dict['key'].append('value')
설명
defaultdict(list)
는 키가 없는 경우 기본 값으로 빈 리스트를 반환합니다.- 따라서
my_dict['key'].append('value')
는 키'key'
에 대한 값을 찾을 수 없을 때 새로운 리스트를 생성하고'value'
를 추가합니다.
islice 사용
islice
는 이터러블 객체(예: 제너레이터)의 일부를 잘라내는 기능을 제공합니다. 이를 사용하면 메모리 효율적으로 큰 이터러블을 슬라이스 할 수 있습니다.
from itertools import islice
# 제너레이터 표현식을 사용하여 0부터 9까지 숫자의 제곱을 생성
my_gen = (x**2 for x in range(10))
# islice를 사용하여 1부터 8까지 인덱스, 2 간격으로 제너레이터 슬라이싱
sliced_gen = islice(my_gen, 1, 9, 2)
설명
my_gen
은 0부터 9까지 숫자의 제곱을 생성하는 제너레이터입니다.islice(my_gen, 1, 9, 2)
는 제너레이터에서 1부터 8까지의 인덱스를 2 간격으로 슬라이스합니다.- 결과적으로
sliced_gen
은 인덱스 1, 3, 5, 7의 값을 포함하게 됩니다.
알고리즘 선택에서 Big-O 표기법과 그 중요성
count_elements_slow 함수
이 함수는 입력 리스트의 각 요소를 세고, 해당 요소와 그 개수를 딕셔너리 형태로 반환합니다. 함수의 작동 방식은 비효율적이므로 느릴 수 있습니다.
def count_elements_slow(input_list):
output = {} # 결과를 저장할 빈 딕셔너리
for item in input_list: # 입력 리스트의 각 요소에 대해
output[item] = input_list.count(item) # 해당 요소의 개수를 세어 딕셔너리에 할당
return output # 결과 딕셔너리 반환
설명
input_list.count(item)
은 전체 리스트를 탐색하여 해당 요소의 개수를 세므로, 이 함수의 시간 복잡도는 (O(n^2))이 됩니다.
count_elements_fast 함수
이 함수도 입력 리스트의 각 요소를 세지만, 훨씬 효율적인 방식을 사용합니다.
def count_elements_fast(input_list):
output = {} # 결과를 저장할 빈 딕셔너리
for item in input_list: # 입력 리스트의 각 요소에 대해
output[item] = output.get(item, 0) + 1 # 딕셔너리에서 해당 요소의 현재 개수를 가져오고 1을 더함
return output # 결과 딕셔너리 반환
설명
output.get(item, 0)
은 딕셔너리에서 해당 요소의 개수를 가져오되, 없으면 0을 반환합니다.- 이 함수의 시간 복잡도는 (O(n))이므로,
count_elements_slow
함수보다 훨씬 빠릅니다.
요약
두 함수 모두 입력 리스트의 요소와 그 개수를 딕셔너리 형태로 반환합니다. count_elements_fast
함수는 count_elements_slow
함수보다 효율적인 방법을 사용하여 같은 작업을 수행하므로 더 빠릅니다.
Python의 기본 제공 cProfile 및 timeit 모듈
코드 설명
이 코드는 두 개의 함수 slow_function
과 fast_function
의 실행 성능을 측정하는 예제입니다. cProfile
모듈을 사용하여 함수의 실행 프로파일을 확인하고, timeit
모듈을 사용하여 두 함수의 실행 속도를 비교합니다.
코드와 주석
import cProfile
import timeit
def slow_function():
pass # 아무 작업도 수행하지 않음
def fast_function():
pass # 아무 작업도 수행하지 않음
# Using cProfile to profile your function's execution
# cProfile을 사용하여 slow_function의 실행 프로파일을 분석
cProfile.run('slow_function()')
# Using timeit to compare the speed of slow_function vs. fast_function
# timeit을 사용하여 slow_function과 fast_function의 실행 속도 비교
# setup_code: 두 함수를 임포트하는 설정 코드
setup_code = "from __main__ import slow_function, fast_function"
# slow_function의 실행 시간 측정, 10000번 반복 실행
slow_time = timeit.timeit("slow_function()", setup=setup_code, number=10000)
# fast_function의 실행 시간 측정, 10000번 반복 실행
fast_time = timeit.timeit("fast_function()", setup=setup_code, number=10000)
# 두 함수의 실행 시간을 출력
print(f"slow_function took {slow_time:.2f}s, fast_function took {fast_time:.2f}s")
설명
cProfile.run('slow_function()')
:slow_function
의 실행 프로파일을 분석하고 출력합니다. 여기서는pass
문만 있으므로 실제 작업은 수행되지 않습니다.timeit.timeit(...)
: 함수의 실행 시간을 측정합니다.setup
인자에는 실행에 필요한 설정 코드를 넣고,number
인자에는 반복 횟수를 지정합니다.- 마지막 줄의
print
문은 두 함수의 실행 시간을 출력합니다.slow_function
과fast_function
은 실제로 아무 작업도 수행하지 않으므로, 실행 시간은 거의 동일할 것입니다.
리스트 컴프리핸션을 주로 쓰기
# Simple loop optimization example
input_list = [1, 2, 3, 4, 5]
# Slow loop
result_slow = []
for item in input_list:
squared = item**2
result_slow.append(squared)
# Optimized loop using a list comprehension
result_fast = [item**2 for item in input_list]
제너레이터 표현식 사용
리스트 컴프리헨션을 사용한 제곱수 리스트 생성
첫 번째 부분은 리스트 컴프리헨션을 사용하여 0부터 9까지의 수를 제곱한 값을 담은 리스트를 생성합니다.
# 리스트 컴프리헨션을 사용하여 0부터 9까지의 수를 제곱한 리스트 생성
squared_list = [x ** 2 for x in range(10)] # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
설명
range(10)
은 0부터 9까지의 숫자를 생성합니다.x ** 2
는x
의 제곱값을 계산합니다.- 리스트 컴프리헨션은 이 값을 리스트로 만들어
squared_list
에 할당합니다.
제너레이터 표현식을 사용한 제곱수 제너레이터 생성
두 번째 부분은 제너레이터 표현식을 사용하여 0부터 9까지의 수를 제곱한 값을 생성하는 제너레이터를 생성합니다.
# 제너레이터 표현식을 사용하여 0부터 9까지의 수를 제곱한 제너레이터 생성
squared_gen = (x ** 2 for x in range(10)) # 제곱 값을 순차적으로 생성하는 제너레이터
설명
range(10)
은 여전히 0부터 9까지의 숫자를 생성합니다.x ** 2
는x
의 제곱값을 계산합니다.- 괄호를 사용한 이 표현식은 제곱값을 즉시 계산하는 것이 아니라 필요할 때마다 값을 생성하는 제너레이터를 만듭니다. 따라서 메모리 효율성이 높아집니다.
요약
- 리스트 컴프리헨션은 모든 값을 즉시 계산하여 리스트에 저장하는 반면,
- 제너레이터 표현식은 값을 필요할 때마다 생성하므로 더 메모리 효율적인 방법을 제공합니다.
파이썬의 functools
모듈에서 제공하는 lru_cache
데코레이터를 사용하여 함수 호출의 결과를 캐시하는 예시 expensive_function
이라는 이름의 함수가 정의, lru_cache
데코레이터 적용
코드 및 주석
from functools import lru_cache
# lru_cache 데코레이터를 사용하여 캐시 기능을 추가한 함수
# maxsize=None으로 설정하여 캐시의 크기 제한을 없애고 모든 호출을 캐시
@lru_cache(maxsize=None)
def expensive_function(x):
pass # 실제 구현은 생략되어 있음
# 함수 호출 결과는 캐시됨, 이후 동일한 인자로의 호출은 성능 향상을 가져올 것
result = expensive_function(42) # 42를 인자로 함수 호출, 결과를 result에 저장
설명
@lru_cache(maxsize=None)
:lru_cache
데코레이터는 최근에 사용된 인수와 결과를 캐시하여 이후 동일한 인수로의 함수 호출이 더 빠르게 수행되도록 합니다.maxsize=None
은 캐시의 크기에 제한이 없음을 의미합니다.expensive_function(x)
: 실제 함수의 구현은 주어진 코드에서 생략되어 있으므로, 실제로 어떤 작업을 수행하는지는 알 수 없습니다. 함수 이름에서 짐작할 수 있듯이, 이 함수는 실행에 시간이 많이 걸리는 비싼 작업을 수행할 것으로 예상됩니다.result = expensive_function(42)
: 이 부분에서 함수가 처음 호출되며, 이 호출의 결과는 캐시에 저장됩니다. 동일한 인수42
로 함수를 다시 호출하면, 캐시된 결과가 반환되어 함수의 실행 시간이 크게 단축됩니다.lru_cache
데코레이터는 계산 비용이 높은 함수의 성능을 향상시키는 간편한 방법을 제공하며, 특히 동일한 인수로 함수가 여러 번 호출되는 경우 유용합니다.
threading
과 multiprocessing
모듈을 사용하여 쓰레드와 프로세스를 생성하고 실행
코드 및 주석
import threading
import multiprocessing
# 쓰레드로 실행할 함수 정의
def thread_function(arg1, arg2):
pass # 실제 작업은 여기에 구현될 것임
# 프로세스로 실행할 함수 정의
def process_function(arg1, arg2):
pass # 실제 작업은 여기에 구현될 것임
# Using threading to create and start a new thread
# threading을 사용하여 새 쓰레드 생성 및 시작
t = threading.Thread(target=thread_function, args=(1, 2)) # 대상 함수와 인수를 지정하여 쓰레드 객체 생성
t.start() # 쓰레드 시작
t.join() # 쓰레드가 완료될 때까지 기다림
# Using multiprocessing to create and start a new process
# multiprocessing을 사용하여 새 프로세스 생성 및 시작
p = multiprocessing.Process(target=process_function, args=(1, 2)) # 대상 함수와 인수를 지정하여 프로세스 객체 생성
p.start() # 프로세스 시작
p.join() # 프로세스가 완료될 때까지 기다림
설명
쓰레드 생성 및 실행:
threading.Thread
클래스를 사용하여 쓰레드를 생성하고 시작합니다.target
인수로 실행할 함수를,args
인수로 함수에 전달할 인수를 지정합니다.start()
메서드로 쓰레드를 시작하고,join()
메서드로 쓰레드가 완료될 때까지 기다립니다.프로세스 생성 및 실행:
multiprocessing.Process
클래스를 사용하여 프로세스를 생성하고 시작합니다. 쓰레드와 유사한 방식으로target
과args
를 사용하여 실행할 함수와 인수를 지정합니다.start()
로 프로세스를 시작하고,join()
으로 프로세스가 완료될 때까지 기다립니다.요약 : 쓰레드와 프로세스 모두 병렬 실행을 가능하게 하지만, 쓰레드는 같은 메모리 공간을 공유하고 프로세스는 독립된 메모리 공간을 갖습니다. 따라서 쓰레드는 메모리 공유로 인한 문제가 발생할 수 있으며, 프로세스는 메모리가 분리되어 있어 이러한 문제가 없지만 생성과 통신이 더 복잡하고 무거울 수 있음.
asyncio
와 aiofiles
모듈을 사용하여 비동기 방식으로 파일을 읽는 예시
코드 및 주석
import asyncio # 비동기 프로그래밍을 위한 라이브러리
import aiofiles # 비동기 파일 작업을 위한 라이브러리
# 비동기 함수로 파일 읽기 함수 정의
async def read_file_async(file_path):
# aiofiles.open을 사용하여 비동기로 파일 열기
async with aiofiles.open(file_path, 'r') as file:
data = await file.read() # 파일의 내용을 비동기로 읽기
return data # 읽은 데이터 반환
# asyncio.run을 사용하여 비동기 함수 실행
# "large_file.txt" 파일의 내용을 읽어 data 변수에 저장
data = asyncio.run(read_file_async("large_file.txt"))
설명
import asyncio
와import aiofiles
: 비동기 작업을 위한 라이브러리를 임포트합니다.async def read_file_async(file_path)
: 비동기로 파일을 읽는 함수를 정의합니다.async
키워드는 이 함수가 비동기 함수임을 나타냅니다.async with aiofiles.open(file_path, 'r') as file:
:aiofiles.open
을 사용하여 지정된 경로의 파일을 읽기 모드('r'
)로 비동기로 엽니다.data = await file.read()
:await
키워드를 사용하여 파일의 내용을 비동기로 읽고, 읽은 데이터를data
변수에 할당합니다.return data
: 읽은 데이터를 반환합니다.data = asyncio.run(read_file_async("large_file.txt"))
:asyncio.run
함수를 사용하여read_file_async
함수를 실행하고, "large_file.txt" 파일의 내용을 읽어data
변수에 저장합니다.이 코드는 대용량 파일을 읽는 등의 I/O 바운드 작업에서 특히 유용하며, 비동기 프로그래밍을 통해 여러 작업을 동시에 수행하면서도 응답성을 유지할 수 있습니다.
댓글