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

Python 코드 최적화 팁 사용법

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

Python은 코드 베이스가 커짐에 따라 성능 문제의 위험도 커집니다. 비효율적인 Python 코드는 답답할 정도로 느릴 수 있으며 종종 병목 현상을 정확히 찾아내기 어려울 수 있음. Python 코드 성능을 최적화하기 위한 몇 가지 유용한 팁과 요령을 살펴보고, 코드를 디버깅하기 전에 먼저 Python 코드의 속도와 효율성을 개선방법을 알아보기.

 

1. 내장 함수 및 라이브러리
- Python은 복잡한 작업을 효율적으로 수행할 수 있는 다양한 내장 함수와 라이브러리를 제공.  
- map()함수를 사용하여 목록의 모든 요소에 함수 적용
- pandas라이브러리를 사용하여 DataFrame의 데이터 조작

# map() 함수를 사용하여 리스트의 각 요소에 함수를 적용하는 예제
lst = [1, 2, 3, 4, 5]
# lambda 함수를 사용하여 리스트의 각 요소를 2배하고 새로운 리스트로 변환
new_lst = list(map(lambda x: x * 2, lst))

# 각 요소가 2배로 곱해진 새 리스트 출력
print(new_lst)
# 출력: [2, 4, 6, 8, 10]

2. 전역 변수 피하기
- 전역 변수는 프로그램 전체에서 액세스 가능하며 해당 값은 프로그램의 모든 부분에서 변경 가능.
- 이로 인해 예기치 않은 결과가 발생하고 프로그램을 디버그하기 더 어려워질 수 있음.
- 대신 가능하면 함수 내에서 지역 변수를 사용하는 것이 좋음.

# 함수 내에서 지역 변수를 사용하는 예제
def add_numbers(num1, num2):
  result = num1 + num2  # 지역 변수 result에 두 숫자의 합 저장
  return result  # 지역 변수 result 반환

# add_numbers 함수의 반환 값을 sum에 저장
sum_result = add_numbers(10, 20) 
print(sum_result)  
# 출력: 30

3. 제너레이터 사용
- 생성기는 Python에서 반복자를 만드는 메모리 효율적인 방법.
  - 한 번에 하나의 값을 생성하고 이전 상태를 유지.
  - 메모리에 저장하지 않고도 대규모 데이터 세트를 반복할 수 있음.

- `read_file` 코드 예시
  - 파일을 한 줄씩 읽는 함수를 정의.
  - 생성기를 사용하여 읽을 때 각 줄을 생성.
  - `with` 명령문을 사용하여 파일이 완료되었을 때 제대로 닫히는지 확인.
  - 파일 이름을 인수로 사용하여 함수를 호출하면 생성기 객체 반환.
  - `for` 루프를 사용하여 생성기가 반환한 각 줄을 반복하고 콘솔에 인쇄.

# 큰 데이터셋을 반복하여 읽기 위해 제너레이터 사용 예제
def read_file(filename):
  with open(filename) as file:
    for line in file:
      yield line.strip()  # 파일의 각 라인을 읽고 양쪽 공백을 제거한 후 하나씩 반환

# 'data.txt' 파일에서 read_file 함수를 사용하여 각 라인을 출력
for line in read_file("data.txt"):
  print(line)

 

4. Set 과 Dictionary 컨프리핸션 사용
- 집합 및 사전 이해는 목록 이해와 유사하나 목록 대신 집합 및 사전을 생성.
- Python에서 집합과 사전을 만드는 간결하고 효율적인 방법.

# SET Comprehension을 사용하여 새로운 집합 생성하는 예제
lst = [1, 2, 3, 4, 5]
new_set = {x * 2 for x in lst}  # 리스트의 각 요소를 2배하여 새로운 집합 생성

# 리스트의 각 요소에 2를 곱하여 새로운 집합 생성
print(new_set)  
# 출력: {2, 4, 6, 8, 10}

# Dictionary Comprehension을 사용하여 새로운 딕셔너리 생성하는 예제
lst = [("apple", 2), ("banana", 3), ("orange", 4)]
new_dict = {k: v for k, v in lst}  # 튜플의 리스트를 딕셔너리로 변환

# 튜플의 리스트를 딕셔너리로 변환
print(new_dict)  
# 출력: {'apple': 2, 'banana': 3, 'orange': 4}

 

5. 작업에 가장 효율적인 데이터 구조 사용
- 효율적인 데이터 구조는 다양한 작업에 대한 시간 복잡도를 최적화하여 Python 코드 성능을 향상.
- 최적의 데이터 구조 선택은 코드 속도에 큰 영향을 미칠 수 있음.
  - 예: 목록을 사용한 대기열 데이터 구조 구현은 더 큰 목록에서 느릴 수 있음.
  - 대신, collections.deque 개체를 사용하여 대기열을 구현하면 성능 향상 가능.
- 숫자 목록의 최대값과 최소값을 검색할 때 올바른 데이터 구조 선택은 코드 성능을 향상시키는 좋은 예시.

# 리스트를 사용하여 최대 및 최소 값을 찾는 방법
numbers = [5, 10, 2, 8, 1]
max_val = max(numbers) # 리스트에서 최대값 찾기
min_val = min(numbers) # 리스트에서 최소값 찾기

# 힙(heap)을 사용하여 최대 및 최소 값을 찾는 방법
import heapq # heapq 모듈 가져오기
numbers = [5, 10, 2, 8, 1]
max_val = heapq.nlargest(1, numbers)[0] # 힙을 사용하여 리스트에서 최대값 찾기
min_val = heapq.nsmallest(1, numbers)[0] # 힙을 사용하여 리스트에서 최소값 찾기

 

6. 데코레이터를 사용하여 코드 단순화
- 데코레이터는 Python에서 함수나 클래스의 동작을 수정하는 강력한 도구.
- 다른 기능을 감싸고 동작을 수정하는 본질적인 기능.
- 코드를 단순화하고 성능을 향상시키는 데 사용 가능.

예시코드) 잠재적 오류 처리를 위한 래퍼 함수와 데코레이터 사용
   - 잠재적 오류를 처리하는 래퍼 함수 포함
   - 데코레이터로서 래퍼 함수 활용
   - "potentially_error_prone_function" 함수가 동일한 이름으로 두 번 정의
   - 데코레이터 적용으로 두 번째 함수 정의가 첫 번째 함수를 덮어쓰기
   - 특정 조건 충족 시 오류 발생하는 함수에 대한 오류 처리 제공

def potentially_error_prone_function(): # 잠재적으로 오류가 발생할 수 있는 함수 정의
    if some_condition: # 특정 조건을 검사
        raise ValueError("Oops!") # 조건이 참이면 ValueError 예외를 발생
    return "Success" # 조건이 거짓이면 "Success" 문자열 반환

def error_handling_wrapper(func): # 오류 처리 래퍼 함수 정의
    def wrapper(): # 래퍼 함수 내부
        try:
            result = func() # 인수로 전달된 func를 실행하고 결과 저장
        except Exception as e: # 예외 발생 시
            print("An error occurred:", e) # 오류 메시지 출력
            return None # None 반환
        return result # 오류가 없으면 결과 반환
    return wrapper # 래퍼 함수 반환

@error_handling_wrapper # 데코레이터 구문 사용
def potentially_error_prone_function(): # 위에서 정의된 함수와 이름이 같음
    if some_condition:
        raise ValueError("Oops!") # ValueError 예외 발생 조건 동일
    return "Success" # "Success" 문자열 반환

 

7. 문자열 조인을 위해 조인 방법 사용
- 루프에서 '+' 연산자를 사용하여 문자열을 연결하는 것보다 `join` 메서드를 사용하여 문자열을 결합.
- 이 방법은 연산이 더 빠르고 메모리 효율적임.

# Slow way
words = ['Hello', 'welcome', 'to', 'the', 'blog'] # 단어 리스트 정의

result = '' 
for word in words: # 단어 리스트를 반복
    result += word + ' ' # 각 단어를 결과 문자열에 추가하고 뒤에 공백을 붙임

# Fast way
wayresult = ' '.join(words) # ' '를 사용하여 단어 리스트를 결합하여 문자열로 만듦. 훨씬 효율적인 방법

print(wayresult) # 'Hello welcome to the blog'을 출력

## 'Hello welcome to the blog' - 출력 결과

 

8. 가능하면 try-except 블록을 사용하지 말것.
- Try-except 블록은 코드 속도를 저하시킬 수 있으므로 피하는 것이 좋음.
- 잘못된 코드 예: 분모가 0인 경우를 처리하기 위해 try-except 블록 사용. 특히 자주 호출되는 함수에 있을 경우 비효율적일 수 있음.
- 좋은 코드 예: if-else 문을 사용하여 나누기 수행 전에 분모가 0인지 확인. 분모가 0인 경우를 처리하는 더 빠르고 효율적인 방법임.

 

# Bad Code
def divide(a, b):
    try:
        result = a / b  # 분자를 분모로 나누려고 시도합니다.
    except ZeroDivisionError:  # 분모가 0인 경우 ZeroDivisionError 예외가 발생합니다.
        result = None  # 예외가 발생하면 결과를 None으로 설정합니다.
    return result  # 계산된 결과를 반환합니다. 분모가 0이었다면 None을 반환합니다.

# Good Code
def divide(a, b):
    if b == 0:  # 분모가 0인지 확인합니다.
        return None  # 분모가 0이면 None을 즉시 반환합니다.
    else:
        return a / b  # 그렇지 않은 경우, 분자를 분모로 나눈 결과를 반환합니다.

 

9. Python 디버거(pdb)를 사용하여 코드 최적화
- Python 디버거를 사용하면 코드를 단계별로 실행하고 성능 문제를 식별할 수 있음. 병목 현상을 찾고 코드를 최적화하는데 사용.

- 예제 코드 설명:
  - `pdb.set_trace()` 문은 각 반복에서 디버거를 중단하기 위해 루프 내부에 배치.
  - 각 반복에서 변수와 코드 흐름을 검사하고 코드의 성능 병목 현상이나 버그를 식별하는데 사용.

import pdb  # 파이썬 디버거 모듈을 임포트합니다.

def calculate_sum(n):
    total = 0  # 총합을 저장할 변수를 초기화합니다.
    for i in range(n):  # 0부터 n-1까지의 범위를 순회합니다.
        pdb.set_trace()  # 디버거의 중단점을 설정합니다. 코드 실행이 이 지점에 도달하면, 디버거가 실행을 중지하고 사용자 입력을 기다립니다.
        total += i  # 현재의 i 값을 total에 더합니다.
    return total  # 총합을 반환합니다.

calculate_sum(5)  # 함수를 5라는 인자와 함께 호출합니다. 결과로 0 + 1 + 2 + 3 + 4의 합인 10을 반환
반응형

댓글