Skip to content

Python에서의 Side_effect에 대한 간단한 설명

Updated on

Python 프로그래밍 세계에서 side effect를 이해하고 적절하게 관리하는 것은 매우 중요합니다. 이러한 부분들은 종종 간과되지만 중요한 요소로서, 잘 작동하는 프로그램과 예측할 수 있고 유지보수하기 쉬운 프로그램 사이의 차이를 만들어낼 수 있습니다.

Python에서 side effect는 함수가 단순히 값을 반환하는 것이 아닌 상태를 변경하거나 외부 세계와 상호 작용하는 경우 발생합니다. 이러한 side effect는 항상 부정적인 것은 아니지만, 추적하고 수정하기 어려운 버그를 도입할 수 있습니다. 따라서 이를 제어하거나 피하는 방법을 배우면 코드의 안정성과 신뢰성을 크게 향상시킬 수 있습니다.

Python Pandas 데이터프레임에서 No code로 데이터 시각화를 빠르게 생성하고 싶으신가요?

PyGWalker는 시각적 탐색을 위한 Python 라이브러리입니다. PyGWalker (opens in a new tab)를 사용하면 판다스 데이터프레임과 polars 데이터프레임을 tableau-alternative의 사용자 인터페이스로 변환하여 Jupyter Notebook 데이터 분석 및 데이터 시각화 워크플로우를 단순화할 수 있습니다.

PyGWalker를 사용한 데이터 시각화 (opens in a new tab)

Python에서 Side Effect란 무엇인가요?

Python에서 side effect는 함수가 반환 값 이외의 상태를 수정하거나 전역 프로그램 상태와 상호 작용하는 경우 발생하는 모든 변경 사항입니다. side effect에는 전역 또는 정적 변수를 수정하는 것, 원본 객체를 변경하는 것, 콘솔 출력을 생성하는 것, 파일이나 데이터베이스에 기록하는 것 등이 포함될 수 있습니다. 이는 I/O 작업, 전역 상태 업데이트, 심지어 객체 상태를 변경하는 클래스 메서드 내에서 일반적으로 볼 수 있습니다.

# Python side effect 예제
def add_element(data, element):
    data.append(element)
    return data
 
my_list = [1, 2, 3]
print(add_element(my_list, 4))  # 출력: [1, 2, 3, 4]
print(my_list)  # 출력: [1, 2, 3, 4]

위의 add_element 함수에서 side effect가 발생합니다. 이 함수는 업데이트된 리스트를 반환할 뿐만 아니라 원래 my_list도 수정합니다. 이것은 전형적인 Python의 side effect 예제이며, Python 코드를 작성할 때 피해야 할 일반적인 side effect 실수 중 하나입니다.

Python에서의 순수 함수

반면에 순수 함수는 동일한 입력에 대해 항상 동일한 출력을 생성하며 side effect가 없는 함수입니다. 순수 함수의 순수성은 함수의 독립성을 의미하므로 예측 가능하고 테스트하기 쉬운 함수입니다.

# Python 순수 함수 예제
def add_element_pure(data, element):
    new_list = data.copy()
    new_list.append(element)
    return new_list
 
my_list = [1, 2, 3]
print(add_element_pure(my_list, 4))  # 출력: [1, 2, 3, 4]
print(my_list)  # 출력: [1, 2, 3]

위의 예제에서 add_element_pure는 순수 함수입니다. 입력을 기반으로 새로운 리스트를 생성하고 이 리스트에 새로운 요소를 추가합니다. 원래의 my_list는 변경되지 않습니다. Python에서 순수 함수의 특성을 이해하는 것은 견고하고 디버깅하기 쉬운 코드를 작성하는 데 중요합니다.

데코레이터를 사용하여 Side Effect 제어하기

Python의 데코레이터는 함수나 클래스의 동작을 변경하는 강력한 방법을 제공합니다. 함수나 클래스를 래핑함으로써 데코레이터는 원래의 소스 코드를 변경하지 않고도 래핑된 함수가 실행되기 전후에 코드를 실행할 수 있습니다.

# Python 데코레이터 예제
def no_side_effects_decorator(func):
    def wrapper(*args, **kwargs):
        data_copy = args[0].copy()  # 데이터의 사본 생성
        return func(data_copy, *args[1:], **kwargs)
    return wrapper
 
@no_side_effects_decorator
def add_element(data, element):
    data.append(element)
    return data
 
my_list = [1, 2, 3]
print(add_element(my_list, 4))  # 출력: [1, 2, 3, 4]
print(my_list)  # 출력: [1, 2, 3]

위의 코드 스니펫에서 데코레이터 no_side_effects_decoratoradd_element 함수를 래핑합니다. 이 데코레이터는 작업에 대해 데이터의 사본을 사용하도록 보장하여 원래의 데이터가 수정되지 않도록 합니다. 이렇게 Python에서 데코레이터를 사용하여 side effect를 제어하는 방법입니다.

Mock 객체를 사용하여 Side Effect 테스트하기

Python의 unittest.mock 라이브러리는 테스트 작성에 강력한 도구입니다. 테스트 대상 시스템의 일부를 대체하고 사용된 방식에 대한 단언을 할 수 있는 기능을 제공합니다. unittest.mock 라이브러리는 또한 mock 객체를 생성하는 데 사용할 수 있는 Mock 클래스를 제공합니다.

Python 단위 테스트에서 mock 객체를 사용하는 일반적인 사례 중 하나는 제어된 조건에서 코드의 동작을 테스트하는 것입니다. 예를 들어, 함수가 side effect를 가진 다른 함수를 호출하는 경우, mock 객체를 사용하여 호출된 함수의 시뮬레이션을 수행하고 side effect를 방지할 수 있습니다.

from unittest.mock import Mock
 
# 원래 대체할 함수
def add_element(data, element):
    data.append(element)
    return data
 
# Mock 객체
add_element = Mock(side_effect=lambda data, element: data + [element])
 
# Mock 객체로 테스트
my_list = [1, 2, 3]
print(add_element(my_list, 4))  # 출력: [1, 2, 3, 4]
print(my_list)  # 출력: [1, 2, 3]

이 예제에서 add_element는 리스트에 요소를 추가하여 side effect를 발생시키는 함수입니다. 그러나 add_element를 mock 객체로 대체하면 side effect 없이 함수를 시뮬레이션할 수 있습니다. 이는 Python에서 mock 객체를 사용하여 side effect를 제어하는 예입니다.

unittest.mock 라이브러리는 또한 테스트 중에 코드의 실제 객체를 Mock 인스턴스로 대체하는 데 사용할 수 있는 patch 함수를 제공합니다. 이렇게 하면 테스트가 완료된 후 자동으로 'patch'가 취소됩니다.

기사의 다음 부분에서는 Python의 patch 라이브러리에 대해 더 자세히 알아보고 Python mock 객체 예제를 더 살펴볼 것입니다. 또한 Python에서 side effect를 다룰 때 일반적인 실수를 피하는 방법과 순수 함수의 특징에 대해 더 자세히 살펴볼 것입니다. Python에서 patch를 사용하는 방법과 Python에서 side effect를 다룰 때 일반적인 실수를 피하는 방법을 배우기 위해 계속 읽어보세요.

Python에서의 Patch 라이브러리 사용하기

Python의 Patch 라이브러리, 특히 unittest.mock.patch는 모의 객체가 대상 객체의 범위를 제어하고, 원래 객체가 대체되고 복원되는 시점을 결정할 수 있게 해줍니다. 이는 테스트 중에 객체의 동작을 모킹하고, 테스트가 완료된 후 모킹이 자동으로 제거되도록 하는 단위 테스트에 특히 유용합니다.

from unittest.mock import patch
 
def add_element(data, element):
    data.append(element)
    return data
 
def test_add_element():
    with patch('__main__.add_element', side_effect=lambda data, element: data + [element]):
        my_list = [1, 2, 3]
        print(add_element(my_list, 4))  # 출력: [1, 2, 3, 4]
        print(my_list)  # 출력: [1, 2, 3]
 
test_add_element()

이 코드 스니펫에서 add_element 함수는 with 문의 범위 내에서 일시적으로 대체됩니다. 테스트 함수를 실행한 후에 원래의 add_element로 복원됩니다.

불변 데이터 구조와 Side Effect

불변 데이터 구조는 Python에서 side effect를 제어하는 데 도움이 되는 또 다른 도구입니다. 불변 객체는 생성된 후에 변경할 수 없습니다. Python에서는 튜플, 문자열, frozenset 등이 불변 데이터 구조의 예입니다.

불변 데이터 구조를 사용하면 데이터를 수정하는 모든 작업은 새로운 객체를 생성합니다. 따라서 원본 데이터는 변경되지 않으므로 side effect를 방지할 수 있습니다.

# Python에서 튜플은 불변합니다.
my_tuple = (1, 2, 3)
new_tuple = my_tuple + (4,)  # 새로운 튜플을 생성합니다.
print(new_tuple)  # 출력: (1, 2, 3, 4)
print(my_tuple)  # 출력: (1, 2, 3)

이 예제에서 my_tuple은 Python에서 불변 데이터 구조인 튜플입니다. 요소를 추가하려고 시도하면 새로운 튜플 new_tuple이 생성되고 my_tuple은 변경되지 않습니다.

결론

Python에서 side effect를 관리하는 것은 고품질의 유지 가능한 코드 작성에 중요한 측면입니다. 순수 함수, 데코레이터, 불변 데이터 구조의 개념을 이해하고 활용하며 mock 객체와 patch 라이브러리의 사용을 숙달하면 Python 코드를 불필요한 side effect로부터 자유롭게 유지할 수 있습니다. 이는 코드의 신뢰성을 높이고 테스트와 디버깅을 쉽게 할 수 있도록 하여 더 고품질의 Python 애플리케이션을 만들어냅니다.

FAQ

자주 묻는 질문 몇 가지를 살펴봅시다:

  1. Python에서의 side effect는 무엇인가요?
    Python에서 side effect는 함수가 반환 값 이외의 프로그램 상태나 함수 자체의 상태에 대한 변경을 의미합니다. side effect에는 전역 또는 정적 변수를 변경하는 것, 원본 객체를 수정하는 것, 콘솔 출력을 생성하는 것, 파일이나 데이터베이스에 기록하는 것 등이 포함될 수 있습니다.

  2. Python에서는 왜 side effect를 피해야 하나요?
    Python에서 side effect는 항상 해로운 것은 아니지만, 추적하고 수정하기 어려운 버그를 도입할 수 있습니다. 코드를 예측할 수 없고 이해하기 어렵게 만들 수 있습니다. side effect를 피함으로써 코드를 더 안정적이고 예측 가능하며 테스트하기 쉽게 만들 수 있습니다.

  3. Python에서 순수 함수는 무엇인가요?
    Python에서 순수 함수는 동일한 입력에 대해 항상 동일한 출력을 생성하고 side effect가 없는 함수입니다. 순수 함수는 독립적이기 때문에 예측 가능하고 테스트하기 쉽습니다.