본문 바로가기

Python_programming

[Python] " side effect (부수 효과) " 에 대한 고찰

https://dev.to/dev0928/what-is-a-side-effect-of-a-function-in-python-36ei

 

What is a side-effect of a function in Python?

Any meaningful function or procedure needs some sort of data from its calling environment to produce...

dev.to

이번 포스팅은 위 글을 번역 및 실습하면서 이에 대한 보충 설명을 덧붙이는 형식으로 진행하겠습니다. 

(검은색이 아닌 색깔의 글자는 저의 추가적인 설명이나 사견임을 유의하고 읽어주세요) 

 

 

 

Any meaningful function or procedure needs some sort of data from its calling environment to produce meaningful results. What happens if the data sent to the function gets changed within the function? A function is said to have a side-ffect if the supplied values or anything in function's environment like global variable gets updated within the function. This article attempts to explain the concept of mutability or side effect of values passed to a function or procedure in Python.

 

의미 있는 함수 또는 절차는 의미 있는 결과를 생성하기 위해 호출 환경에서 어떤 종류의 데이터를 필요로 합니다. 함수로 전송된 데이터가 함수 내에서 변경되면 어떻게 됩니까? 함수는 제공된 값이나 전역 변수와 같은 함수 환경의 어떤 것이 함수 내에서 업데이트되면 부수 효과(=side effect)이 있다고 한다. 이 글은 파이썬에서 함수 또는 프로시저로 전달된 값의 돌연변이성 또는 부작용의 개념을 설명하려고 한다. 

 

즉, 함수 호출이 일어나고 종료된 뒤에 함수 호출때 들어간 파라미터 값이 변할 경우, 이를 "부수 효과"가 발생했다고 합니다.

 

Let’s start with a few fundamental terms before we attempt to answer Python’s side-effect behavior.

(파이썬의 부수효과에 대한 답을 찾기 전에 몇 가지 기본적인 용어로 시작해보자.)

What are parameters? (매개 변수란 무엇입니까?)

Data that gets passed to a function as input are called parametersnum1 and num2 in the below function are parameters. (입력으로 함수에 전달되는 데이터를 매개 변수라고 합니다. 아래 함수의 number1 및 number2는 파라미터입니다.)

 

 

def sum(num1, num2):
    return num1 + num2

 

 

What are arguments?

Values that are supplied during function invocation are called arguments. So val1 and val2 in the below function call are arguments. Although many use these terms interchangeably.

 

arguments란 무엇인가?
함수 호출 중에 제공되는 값을 인수라고 합니다. 따라서 아래 함수 호출의 val1과 val2는 인수이다. 비록 많은 사람들이 이 용어들을 서로 교환해서 사용하지만.

 

 

val1 = 10
val2 = 20
ans = sum(val1 + val2)

 

함수를 정의할 때는 "매개변수(parameter)라고 사용하고 호출 시에는 "인자 or 인수(argument)" 라고 합니다. 정의할 떄와 호출할 때는 이를 다르게 표현함에 있어서 유의하셔야 합니다. 

 

 

What are passing arguments by value and reference? (값 및 참조에 의한 전달 인수란?)

 

If a new copy of arguments are made during a function call for the supplied parameter, then arguments are said to be passed by value. If a reference of the same variable is passed to a function then arguments are passed by reference. (제공된 매개 변수에 대한 함수 호출 중에 인수의 새 복사본이 만들어지면 인수는 값으로 전달된다고 합니다. 동일한 변수의 참조가 함수에 전달되면 인수는 참조를 통해 전달됩니다.)

 

Are the arguments passed by value or by reference in Python?

(Python에서 인수는 값으로 전달됩니까, 아니면 참조로 전달됩니까?)

 

Python uses the mechanism pass arguments by sharing object reference during function calls. Let’s examine Python’s behavior using below function example: (파이썬은 함수 호출 중에 객체 참조를 공유함으로써 메커니즘 패스 인수를 사용한다. 아래 함수 예제를 사용하여 Python의 동작을 살펴보겠습니다.)

 

def ref_copy_demo(x):
    print(f"x = {x}, id = {id(x)}")
    x += 45
    print(f"x = {x}, id = {id(x)}")


num = 10
print(f"before function call - num = {num}, id = {id(num)}") 
ref_copy_demo(num)
print(f"after function call - num = {num}, id = {id(num)}")

# Output 
before function call - num = 10, id = 140704100632512
x = 10, id = 140704100632512
x = 55, id = 140704100633952
after function call - num = 10, id = 140704100632512

참고: https://dev.to/dev0928/what-is-a-side-effect-of-a-function-in-python-36ei

 

 

Let’s analyze the function call and its output:

  • We have used the id function in the above call to get the identity value of the object. Id function returns an integer which is unique and constant for this object during its lifetime. In the above context, it helps us in tracking whether the same object is passed by reference to the function. (우리는 개체의 ID 값을 얻기 위해 위의 호출에서 id 함수를 사용했습니다. ID 함수는 수명 동안 이 개체에 대해 고유하고 상수인 정수를 반환합니다. 위의 맥락에서, 동일한 객체가 함수에 대한 참조를 통해 전달되는지 여부를 추적하는 데 도움이 됩니다.)
  • Please note that the id function’s value has changed for the parameter passed before and after variable value change. (변수 값 변경 전후에 전달된 파라미터에 대한 id 함수의 값이 변경되었음을 유념하시기 바랍니다.)
  • So this means the parameter inside the function remains the same as the argument until there is no change in parameter’s value. Python keeps the reference of the argument passed. But as soon the parameter gets updated, a local copy of the parameter is made leaving the argument value unchanged. (따라서 이는 함수 내부의 매개 변수가 매개 변수 값에 변화가 없을 때까지 인수와 동일하게 유지된다는 것을 의미합니다. Python은 인수의 참조를 전달하도록 유지합니다. 그러나 매개 변수가 업데이트되는 즉시 매개 변수의 로컬 복사본이 만들어지며 인수 값은 변경되지 않습니다.)

ref_copy_demo 함수 내에서 기존 매개변수에 값을  변경하도록 += 45 를 해줬습니다. 헌데 다른 id 가 만들어졌음을 볼 수 있는데요.  as soon the parameter gets updated, a local copy of the parameter is made leaving the argument value unchanged. 이 문장이 그 부분을 설명해주고 있습니다. 파라미터가 업데이트(=변하자마자) 다른 복사본이 만들어졌고(=새로운 id를 부여해서 다른 레퍼런스를 만듬) 이는 기존 인수값이 변경되지 않았음을 의미하는 것입니다. 일러스트레이션을 참고하시면 무슨 말인지 이해에 도움이 될 것입니다.

 

What is a side effect?

 

Function is said to have a side effect if it changes anything outside of its function definition like changing arguments passed to the function or changing a global variable. For example:

(함수는 함수에 전달된 인수를 변경하거나 전역 변수를 변경하는 것과 같이 함수 정의 이외의 것을 변경하면 부수효과가 있다고 한다. 예를 들어:)

 

 

def fn_side_effects(fruits):
    print(f"Fruits before change - {fruits} id - {id(fruits)}")
    fruits += ["pear", "banana"]
    print(f"Fruits after change - {fruits} id - {id(fruits)}")

fruit_list = ["apple", "orange"]
print(f"Fruits List before function call - {fruit_list} id - {id(fruit_list)}")
fn_side_effects(fruit_list)
print(f"Fruits List after function call - {fruit_list} id - {id(fruit_list)}")

# Output 
Fruits List before function call - ['apple', 'orange'] id - 1904767477056
Fruits before change - ['apple', 'orange'] id - 1904767477056
Fruits after change - ['apple', 'orange', 'pear', 'banana'] id - 1904767477056
Fruits List after function call - ['apple', 'orange', 'pear', 'banana'] id - 1904767477056

 

 

So this function clearly has side effect due to below reasons:

(따라서 이 기능은 아래 이유로 인해 분명히 부수효과가 있습니다.)

  • Id value argument and parameter are exactly the same.(ID 값 인수와 매개 변수가 동일합니다.)
  • Argument has additional values added after the function call.(함수 호출 후 인수에 추가 값이 추가되었습니다.)

함수 호출 뒤에, 기존 매개변수가 변경되었습니다. 새로운 id 는 만들어지지 않았고, 계속 기존 id 에 연산의 결과가 반영이 되었습니다.  함수 호출 뒤에 함수 안에 들어갔던 매개변수가 어떤 로직에 의해 변형되었습니다. 이를 "부수 효과"라고 합니다.

 

 

 

How to create a similar function without side effect?

 

def fn_no_side_effects(fruits):
    print(f"Fruits before change - {fruits} id - {id(fruits)}")
    fruits = fruits + ["pear", "banana"]
    print(f"Fruits after change - {fruits} id - {id(fruits)}")

fruit_list = ["apple", "orange"]
print(f"Fruits List before function call - {fruit_list} id - {id(fruit_list)}")
fn_no_side_effects(fruit_list)
print(f"Fruits List after function call - {fruit_list} id - {id(fruit_list)}")

# output
Fruits List before function call - ['apple', 'orange'] id - 2611623765504
Fruits before change - ['apple', 'orange'] id - 2611623765504
Fruits after change - ['apple', 'orange', 'pear', 'banana'] id - 2611625160320
Fruits List after function call - ['apple', 'orange'] id - 2611623765504

With explicit call to the assignment during fruits list update, list value changed only within the function as it supposed to be. So this function has no side effect. (과일 목록 업데이트 중에 할당에 대한 명시적 호출을 통해 목록 값은 함수 내에서만 원래대로 변경되었습니다. 그래서 이 기능은 부수효과가 없습니다.)

 

Please note we could have also avoided side-effect in the first function call if we would have explicitly made a copy of the list - fn_side_effects(fruit_list[:]) 

(목록의 복사본을 명시적으로 만들었으면 첫 번째 함수 호출에서도 부작용을 피할 수 있었을 것입니다. - fn_side_ effects(fruit_list[:]))

 

 

새로운 id 가 함수 내에서 만들어졌고 그 레퍼런스에 변형된 값이 반영되었습니다. 최종적으로 함수 호출 뒤, 함수 호출할 때 넣었던 매개변수는 그대로입니다. 함수 호출 당시에 넣었던 매개변수가 어떤 변형없이 그대로 유지되었으니 이는 "부수 효과가 발생하지 않았다"라고 할 수 있습니다.

 

 

Why is it important to write functions without side-effects?

 

  • Functions with side effects especially when it is unintended could lead to a lot of potential bugs which are harder to debug. (특히 의도하지 않은 경우 부작용이 있는 기능은 디버깅하기 어려운 잠재적인 버그를 많이 발생시킬 수 있습니다.)
  • It is easier to write tests for functions with no side-effects. (부수효과가 없는 함수에 대한 테스트 함수를 작성하는 것이 더 쉽다.)
  • If the function is supposed to change anything in the environment, it must be clearly documented to avoid confusion. (만약 함수가 환경의 어떤 것을 변경하도록 되어 있다면, 혼란을 피하기 위해 명확하게 문서화되어야 한다.)
  • Care must be taken in writing function definitions containing mutable data types (like Lists, Sets, Dictionary etc…) as their function parameter. (변형 가능한 데이터 유형(Lists, Sets, Dictionary 등)을 기능 매개변수로 포함하는 함수 정의를 작성할 때 주의해야 한다.)

함수 정의할 때, mutable 데이터 타입이 들어가면 주의해야한다고 합니다. 이 이유는 파이썬이 "Call by Object Reference" 의 함수 인자 전달 방식을 사용해서 그런 거라고 생각합니다. 이 부분으로 인해 함수에 immutable 데이터 타입과 mutable 데이터 타입이 들어갈 때 부수효과가 발생하고 안하고의 차이가 생깁니다. 자세한 건은 아래 블로그를 참고하시면 좋습니다.

 

이번 시간에 다룬 "부수 효과"를 짧게 정리하면

 

함수 호출 때 들어간 매개변수가 "함수 호출이 종료된 뒤에" 매개변수가 어떤 로직에 의해 "변형"이 되었다면 "부수 효과가 발생했다" 라고 이해하면 될 것 같습니다.

 

 

-추가적으로 보면 좋은 블로그-

 

https://yes90.tistory.com/47

 

[Python]파이썬의 Call by Object Reference

파이썬의 함수 객체 참조에 의한 호출 Call by Object Reference 일반적으로 프로그래밍 언어에서 함수 인수 전달 방식은 크게 두가지로 나뉜다. 1.Call by Value: 값 복사 ==> C언어 - 값 형태 1)pass by value:..

yes90.tistory.com