Post

파이썬의 부작용 | Side Effect in Python


파이썬의 부작용을 알아봅시다.

파이썬의 부작용은 함수가 값을 리턴하는 대신 외부 세계의 어떤 state를 수정하거나 상호작용을 할 때 발생합니다.

이런 변화는 코드를 이해하기 어렵게 만들고 버그의 발생 가능성을 높일 수도 있습니다.

     

Side Effect in Python

  • 리턴 값, 함수의 state, global program state 에 대한 모든 변경사항
    • global/static 변수 및 original object의 수정, console output의 생성, 파일 및 데이터베이스 쓰기
  • 추적 및 수정이 어려운 버그가 발생할 수 있음
  • Side Effect가 있는 함수
1
2
3
4
5
6
7
8
# Python side effect example
def add_element(data, element):
    data.append(element)
    return data
 
my_list = [1, 2, 3]
print(add_element(my_list, 4))  # Output: [1, 2, 3, 4]
print(my_list)  # Output: [1, 2, 3, 4]

     

  • Side Effect가 없는 함수: 동일한 입력에 대해 항상 동일한 출력을 생성
    • Pure Function: 동일한 입력에 대해 항상 동일한 출력을 생성
1
2
3
4
5
6
7
8
9
# Python pure function example
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))  # Output: [1, 2, 3, 4]
print(my_list)  # Output: [1, 2, 3]

     

Decorator로 Side Effect 제어하기

  • 함수/클래스를 래핑하면 decorator는 소스 코드를 변경하지 않고도 래핑된 함수가 실행되기 전/후에 코드를 실행
    • 원본 데이터를 그대로 유지하면서 데이터 복사본을 작업에 사용하도록 보장
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def no_side_effects_decorator(func):
    def wrapper(*args, **kwargs):
        data_copy = args[0].copy()  # create a copy of the data
        return func(data_copy, *args[1:], **kwargs)
    return wrapper

# 함수를 `no_side_effects_decorator` 래핑
@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))  # Output: [1, 2, 3, 4]
print(my_list)  # Output: [1, 2, 3]

     

불변 데이터 구조

  • 불변 객체는 생성된 후에는 변경될 수 없으므로 Side Effect를 피하는데 효과적
    • 불변 데이터 구조를 사용하는 경우 데이터를 수정하는 모든 작업에서는 새 객체가 생성됨
    • tuples, strings, frozensets

     

Example. Tensorflow tf.function Decorator

1
2
3
4
5
6
7
8
9
@tf.function
def get_MSE(y_true, y_pred):
  print("Calculating MSE!")
  sq_diff = tf.pow(y_true - y_pred, 2)
  return tf.reduce_mean(sq_diff)

error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
    # 한 번만 출력됨
    Calculating MSE!

     

  • print 문은 Function이 원래 코드를 실행할 때 실행되며 Tracing Process를 통해 Graph를 생성
  • 추적은 TensorFlow 연산을 Graph로 캡처하고 print는 Graph로 캡처되지 않음
  • 이 Graph는 세 번의 모든 호출시 실행되지만 Python 코드를 다시 실행하지는 않음
1
2
3
# Globally set everything to run eagerly to force eager execution.
tf.config.run_functions_eagerly(True)
(...)
    Calculating MSE!
    Calculating MSE!
    Calculating MSE!

     

  • Eager/Graph Execution 모두에서 값을 인쇄하려면 tf.print 사용

     

References

  1. Tensorflow Guide - Performance
  2. Side Effect in Python
This post is licensed under CC BY 4.0 by the author.