본문 바로가기
카테고리 없음

python 개발에 알아야될 필수 팁

by 퍼포먼스마케팅코더 2023. 8. 11.
반응형

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_functionfast_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_functionfast_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 ** 2x의 제곱값을 계산합니다.
  • 리스트 컴프리헨션은 이 값을 리스트로 만들어 squared_list에 할당합니다.

제너레이터 표현식을 사용한 제곱수 제너레이터 생성

두 번째 부분은 제너레이터 표현식을 사용하여 0부터 9까지의 수를 제곱한 값을 생성하는 제너레이터를 생성합니다.

# 제너레이터 표현식을 사용하여 0부터 9까지의 수를 제곱한 제너레이터 생성
squared_gen = (x ** 2 for x in range(10))  # 제곱 값을 순차적으로 생성하는 제너레이터

설명

  • range(10)은 여전히 0부터 9까지의 숫자를 생성합니다.
  • x ** 2x의 제곱값을 계산합니다.
  • 괄호를 사용한 이 표현식은 제곱값을 즉시 계산하는 것이 아니라 필요할 때마다 값을 생성하는 제너레이터를 만듭니다. 따라서 메모리 효율성이 높아집니다.

요약

  • 리스트 컴프리헨션은 모든 값을 즉시 계산하여 리스트에 저장하는 반면,
  • 제너레이터 표현식은 값을 필요할 때마다 생성하므로 더 메모리 효율적인 방법을 제공합니다.

파이썬의 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 데코레이터는 계산 비용이 높은 함수의 성능을 향상시키는 간편한 방법을 제공하며, 특히 동일한 인수로 함수가 여러 번 호출되는 경우 유용합니다.

threadingmultiprocessing 모듈을 사용하여 쓰레드와 프로세스를 생성하고 실행

코드 및 주석

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()   # 프로세스가 완료될 때까지 기다림

설명

  1. 쓰레드 생성 및 실행: threading.Thread 클래스를 사용하여 쓰레드를 생성하고 시작합니다. target 인수로 실행할 함수를, args 인수로 함수에 전달할 인수를 지정합니다. start() 메서드로 쓰레드를 시작하고, join() 메서드로 쓰레드가 완료될 때까지 기다립니다.

  2. 프로세스 생성 및 실행: multiprocessing.Process 클래스를 사용하여 프로세스를 생성하고 시작합니다. 쓰레드와 유사한 방식으로 targetargs를 사용하여 실행할 함수와 인수를 지정합니다. start()로 프로세스를 시작하고, join()으로 프로세스가 완료될 때까지 기다립니다.

  3. 요약 : 쓰레드와 프로세스 모두 병렬 실행을 가능하게 하지만, 쓰레드는 같은 메모리 공간을 공유하고 프로세스는 독립된 메모리 공간을 갖습니다. 따라서 쓰레드는 메모리 공유로 인한 문제가 발생할 수 있으며, 프로세스는 메모리가 분리되어 있어 이러한 문제가 없지만 생성과 통신이 더 복잡하고 무거울 수 있음.

asyncioaiofiles 모듈을 사용하여 비동기 방식으로 파일을 읽는 예시

코드 및 주석

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 asyncioimport 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 바운드 작업에서 특히 유용하며, 비동기 프로그래밍을 통해 여러 작업을 동시에 수행하면서도 응답성을 유지할 수 있습니다.

반응형

댓글