-
[파이썬] by value, by referencePython/이것저것 파이썬 2022. 8. 24. 14:55반응형
1. by value, by reference
재귀를 이용해 순열을 만들겠습니다.
문자열을 이용하면 잘 작동합니다.
파이썬에서 문자열은 불변(immutable)입니다.
def recursive(result, visited): if len(result) == 3: print(result) return for each in 'abc': if each not in visited: visited.add(each) recursive(result + each, visited) visited.remove(each) # 순열~! recursive('', set()) # abc # acb # bac # bca # cab # cba 출처: https://comdoc.tistory.com/entry/파이썬-순열과-조합 [ComDoc:티스토리]
리스트와 '+' 연산자를 사용해도 잘 작동합니다.
def recursive(result, visited): if len(result) == 3: print(result) return for each in ('a', 'b', 'c'): if each not in visited: visited.add(each) recursive(result + [each], visited) visited.remove(each) # 순열~! recursive([], set()) # ['a', 'b', 'c'] # ['a', 'c', 'b'] # ['b', 'a', 'c'] # ['b', 'c', 'a'] # ['c', 'a', 'b'] # ['c', 'b', 'a'] 출처: https://comdoc.tistory.com/entry/파이썬-순열과-조합 [ComDoc:티스토리]
리스트와 append를 사용하면
원하는 결과가 나오지 않습니다.한 줄 나오고 끝.
def recursive(result, visited): if len(result) == 3: print(result) return for each in ('a', 'b', 'c'): if each not in visited: visited.add(each) result.append(each) recursive(result, visited) visited.remove(each) # 순열~! recursive([], set()) # ['a', 'b', 'c']
왜 이럴까요?
'참조를 전달(by reference)'한 것과
'값을 복사(by value)'한 것의
차이입니다.파이썬에서는
불변(immutable)인 객체를
함수의 인자로 전달하면
객체는 복사됩니다.수정 가능(mutable)한 객체를
함수의 인자로 전달하면,
객체가 복사되는 것이 아니라,
참조가 전달됩니다.원본의
링크 같은 것(포인터)이
함수로 전달된다고
이해할 수 있습니다.링크를 수정하면,
원본이 수정됩니다.-------------------
정상적으로 작동한 +를 보겠습니다.
result + [each]를 실행하면,
원본의 수정 없이,
원래 리스트에 each가 추가된
새 리스트가 리턴됩니다.즉 함수가 실행될 때마다
새로운 리스트가
생성되는 것입니다.뒤에 다시 한번...
언급하겠습니다.따라서 원본은
크기가 유지됩니다.더보기재귀 실행된 함수에서,
리턴으로 돌아오면,
크기가 유지된 이전 원본을 다시 만나고,
이 원본에 each를 추가한
새 복사본을 만들어
재귀함수에 인수로 전달하고 실행하고,
리턴으로 돌아오고.....
의 반복이죠....코드 상에서는
같은(?) 이름으로 보일 수 있지만,
재귀적으로 실행된 함수의 result(새 리스트)와
실행 이전 함수의 result(원본)는
다른 존재입니다.재귀의 함정이죠. ^^
디버거로
이 차이를 확인할 수 있습니다.----------------------------------------
반면에 append를
(잘 못) 쓴 코드를 보면result라는 리스트를
함수의 인자로 직접 넣었습니다.이렇게 하면,
복사가 아니라,
참조가 되기 때문에재귀를 반복할수록,
복사본 생성은 없고,
원본에 원소가 추가됩니다.원본에 계속
원소가 append가 되면서
3회가 반복되면
길이가 3이 되어
프로그램이 바로 종료됩니다.--------------------------------
착각하기 쉬운 부분이라,
copy를 명시적으로 작성하는 것이 좋습니다.def recursive(result, visited): if len(result) == 3: print(result) return for each in ('a', 'b', 'c'): if each not in visited: visited.add(each) new_result = result.copy() new_result.append(each) recursive(new_result, visited) visited.remove(each) # 순열~! recursive([], set()) # ['a', 'b', 'c'] # ['a', 'c', 'b'] # ['b', 'a', 'c'] # ['b', 'c', 'a'] # ['c', 'a', 'b'] # ['c', 'b', 'a'] 출처: https://comdoc.tistory.com/entry/파이썬-순열과-조합 [ComDoc:티스토리]
다음 주제는
by value, by reference와
비슷한 느낌입니다.2. 원본의 수정, 새 객체(복사본) 생성
앞서
리스트의 + 연산을 말하면서
살짝 언급한원본의 수정과
새 리스트(복사본) 생성도
알아봅시다.결론부터 말씀을 드리자면
'+'는 새 리스트(복사본) 생성,
'extend'와 'append'는 원본의 수정입니다.list1 = [1] list1 + [3, 4] print(list1) # [1]
당연한 이야기지만
'list1 + [3, 4]'를 실행해도
list1에는 변화가 없습니다.list1 = [1] list2 = list1 + [3, 4] # list1 + [3, 4] 한 새 리스트가 list2에 바인딩됩니다. print(list1) # [1] print(list2) # [1, 3, 4] list1.extend([2, 9]) # list1에 추가됩니다. list2에 영향을 주지 않습니다. print(list1) # [1, 2, 9] print(list2) # [1, 3, 4] list2.extend([5, 7]) # list2에 추가됩니다. list1에 영향을 주지 않습니다. print(list1) # [1, 2, 9] print(list2) # [1, 3, 4, 5, 7]
extend는 원본이 수정됩니다.
이 둘은 같은 일을 하지만,
처리하는 방식이 다릅니다.당연한 이야기지만
새 리스트(복사본)를 하나 더 만드는 것이
일반적으로 느립니다.조금 더 실용적인 예를
보여드리기 위해
sort와 sorted를
가져오겠습니다.sort와 sorted의 관계도 같습니다.
sort는 '원본 수정', sorted는 '새 리스트(복사본) 생성'입니다.a = [3, 1, 2] a.sort() # 원본을 수정 print(a) # [1, 2, 3] a = [3, 1, 2] sorted(a) # '복사 + 소트'한 새 리스트를 리턴, 원본의 변화 없음. print(a) # [3, 1, 2] a = [3, 1, 2] b = sorted(a) # '복사 + 소트'한 새 리스트를 리턴, b로 리턴을 받음. 원본의 변화 없음. print(a) # [3, 1, 2] print(b) # [3, 1, 2]
3. 왜 새 리스트를 생성할까?
그러면 왜 더 느림에도 불구하고,
새 리스트(복사본) 생성을 사용할까요?원본을 수정하지 않고,
결과값을 만들어서,
바로 다음 함수에 전달할 수 있다.이것이 어떤 장점을 만들까요?
a = [3, 1, 2] a.sort() # 원본을 수정 a.reverse() # 원본을 수정 print(a) # [3, 2, 1] a = [3, 1, 2] b = list(reversed(sorted(a))) # 원본을 유지하고 처리 결과를 리턴, 처리 리턴, 처리 리턴~! print(a) # [3, 1, 2] print(b) # [3, 2, 1] # 물론 reverse=True를 쓰면 간단합니다만, 예를 위해...
ㄷㄷㄷ~!
두 줄을 한 줄로 만들 수 있습니다.
(농담 반 진담 반입니다. ^^;)한 줄로 코딩하기는코테(코드 뽐내기?)의 기본이 아닌가요?
매직 같은 코딩~! 당신도 가능합니다.두 줄로 작성한 로또 번호 추첨기..
from random import sample print(sorted(sample(range(1, 46), 6)))
출처: https://comdoc.tistory.com/entry/확률-추첨기-만들기 [ComDoc:티스토리]
4. 함수형 프로그래밍
함수에 함수를 물리는
프로그래밍 방식을
함수형 프로그래밍이라고 합니다.함수형 프로그래밍은 방대한 주제입니다.
자세한 것은 구글 검색을 이용하시고..위의 단순한 예제에서도
함수형 프로그래밍을
느낄 수 있습니다.함수형 프로그래밍에서의 데이터는
변하지 않는 불변성을 유지해야 한다.
데이터의 변경이 필요한 경우,
원본 데이터 구조를 변경하지 않고
그 데이터의 복사본을 만들어서 그 일부를 변경하고,
변경한 복사본을 사용해 작업을 진행한다.5. 함수형 프로그래밍에 None을 리턴해주는 함수는 No~!
사실 a.sort()도 리턴 값을 가집니다.
다만 그 리턴값이 None입니다.
a = [3, 1, 2] b = a.sort() # 원본을 수정 print(a) # [3, 2, 1] print(b) # None
그래서 다음 구문들은 에러가 발생합니다.
a = [3, 1, 2] a.sort().reverse() # AttributeError: 'NoneType' object has no attribute 'reverse' None.reverse() # AttributeError: 'NoneType' object has no attribute 'reverse' list(reversed(a.sort())) # TypeError: 'NoneType' has no length
원본을 수정하고,
None을 리턴하는 함수들은
함수형 프로그래밍에 사용할 수 없습니다.None을 리턴하지 않는 함수들을 사용해야
'흐름'을 유지할 수 있습니다.자바 8에 '스트림'이 추가되었습니다.
자바의 함수형 프로그래밍이죠.
왜 이런 이름이 붙었는지 아시겠죠?6. 결론
위의 예에서 우린 몇 가지를 건져야 하는데요.
0. 불변과 가변. (이건 미리 알고 계셔야)
1. 불변, 가변 객체가 함수의 인자로 전달될 때 차이.
2. append, extend와 '+' 연산자의 차이.
sort와 sorted의 차이.
4. 함수형 프로그래밍에서 불변성.
5. 함수형 프로그래밍에서 흐름의 유지.글이 너무 길어져서...
여기까지만...찾아보기)
어떤 것들이 함수형 프로그래밍의 흐름을 끊을까요?
어떻게 흐름의 끊어짐을 예방할 수 있나요?ex) 조건문이 흐름을 끊습니다.
조건문 대신 filter 함수를 쓰면 됩니다.https://opentogether.tistory.com/76
참고) 파이썬을 기반으로 한 코코넛이라는 함수형 언어가 있습니다.
어렵지 않습니다.
http://coconut-lang.org/반응형