기본적으로 함수는 완전히 독립되어야 다루기 편리하다. 함수가 외부 변수에 종속되어버리면 함수를 고칠 때 외부 변수도 함께 고려해야 하고 그렇게 되면 하나의 함수를 수정하는 것이 코드 전체를 아우르는 작업이 될 수 있다. 가독성이 매우 떨어지며 디버깅에도 굉장한 혼란이 올 수 있다. 하지만 파이썬을 처음 다뤄보는 사람으로써, 아직 프로그래밍 언어에 대해 충분히 익숙해지지 않아 함수의 독립성을 완전히 이룰 수 없는 나로써는 함수가 외부의 변수에 접근할 수 있도록 해야하는 경우가 있다.
예를 들어 깊이 우선 탐색을 실행하는 아래 코드
#1520: 내리막길
M, N = map(int, input().split())
board = []
check = [[False for j in range(N)] for i in range(M)]
d = [[-1,0],[0,1],[1,0],[0,-1]]
for i in range(M):
board.append(list(map(int, input().split())))
count = 0
def dfs(x, y):
check[x][y] = True
if x == M - 1 and y == N - 1:
global count
count += 1
return
for i in range(4):
dx = x + d[i][0]
dy = y + d[i][1]
if dx < 0 or dy < 0 or dx >= M or dy >= N:
continue
if check[dx][dy]:
continue
if board[x][y] > board[dx][dy]:
dfs(dx, dy)
check[dx][dy] = False
dfs(0,0)
print(count)
이 코드는 백준에서 시간초과를 받은 코드이지만 살펴볼 부분이 있다. 이 코드의 깊이우선탐색에서 해를 찾은 경우 count 변수를 올려주어야 한다. 가장 먼저 떠오른 생각은 해를 찾은 경우 함수 안에서 바로 변수에 접근해 값을 1씩 올려주는 것이다.
함수에서 외부 변수에 접근하는 경우를 몇 가지 살펴보자
1. 외부 변수에 접근하는 경우
a = 10
def Print():
print(a)
Print()
print(a)
결과
10
10
외부 정수형 변수는 읽어올 수 있다. Print 함수 안에서 print(a)는 정상적으로 외부 변수 a를 불러와 출력했다.
a = 10
def Write(k):
a = k
Write(5)
print(a)
결과
10
외부 정수형 변수의 값을 수정할 수 없다. Write 함수 안에서 a = k로 바뀌었으나 함수 내부 a의 주소와 함수 외부 a의 주소는 다르다. 파이썬은 이렇게 함수 내외에 같은 이름의 변수를 선언하고 각각 다루는 것이 가능하다.
a = 10
def function(k):
print(a)
a = k
function(5)
print(a)
결과
UnboundLocalError: local variable 'a' referenced before assignment
이번에는 함수에 읽고 쓰는 기능을 모두 넣었다. 여기서는 a = k 때문에 함수 내에서 a를 선언하기 전 print(a)를 실행할 수 없었다.
a = 10
def function(k):
a = k
print(a)
function(5)
print(a)
결과
5
10
이렇게 함수 내에서 외부 변수와 같은 이름의 변수를 사용하는 경우, 함수 내에서 새로운 주소를 지정해 따로 처리하게 된다.
이 결과는 float, boolean, str 등에도 똑같이 적용된다.
2. 외부 리스트에 접근하는 경우
a = [1, 2, 3, 4, 5]
def function(k):
a[k] = 0
function(3)
print(a)
결과
[1, 2, 3, 0, 5]
하지만 리스트에서는 문제 없이 작동한다.
a = [1, 2, 3, 4, 5]
def function(k):
a = [5, 4, 3, 2, 1]
a[k] = 0
print(a)
function(3)
print(a)
결과
[5, 4, 3, 0, 1]
[1, 2, 3, 4, 5]
그런데 리스트 안에서 같은 이름을 갖는 새로운 리스트를 만들어버리면 역시 리스트도 새로운 주소를 갖게 된다.
그렇다면 외부 변수에 접근해 값을 바꿀 수 있는 방법은 무엇이 있을까? 간단하게 실행할 수 있는 방법은 세 가지인 것 같다.
1. return으로 데이터 반환하기
a = 10
def function(k):
return k
print(a)
a = function(3)
print(a)
결과
10
3
정확히 따지면 함수 내에서 데이터에 접근해 수정한 것은 아니지만 이렇게 함수 내부의 값을 외부로 전달할 수 있다.
2. global 키워드로 전역 변수 사용하기
상술한 이유로 많은 개발자들이 비추하는 방법이다. 일단 대충 생각해봐도 이 방법으로 개발을 한다면 1000줄 정도만 넘어가도 스파게티 코드가 될 것 같다. 하지만 간단한 문제 해결에 필요한 수십줄 안팎의 코딩에서는 전역 변수 몇 개 쯤 사용해도 큰 문제는 없다(고 생각하고 싶음).
a = 10
def function(k):
global a
a = k
print(a)
function(50)
print(a)
결과
10
50
global 키워드를 사용해 변수를 수식해주면 그 변수는 전역 변수로 처리된다. 하지만 해당 함수에서만 전역변수로 취급되기 때문에 외부에 있는 변수를 여러 함수에서 다루고 싶다면 각 함수에서 전역 변수로 수식해주어야 한다.
a = 10
def function(k):
global a
a = k
def function2(k):
a = k
print(a)
function(50)
print(a)
function2(100)
print(a)
결과
10
50
50
function2에서는 a가 전역 변수로 처리되지 않으므로 function2(100)이 외부 변수 a의 값을 변경하지 않았다.
3. 리스트 사용하기
a = [10, 20, 30]
def function(index, k):
a[index] = k
print(a)
function(1, 50)
print(a)
결과
[10, 20, 30]
[10, 50, 30]
함수 외부에 있는 리스트는 별도의 처리 없이 바로 접근해 읽고 쓰기가 모두 가능하다. 함수 내에서 다뤄야 할 변수가 있다면 그 변수를 리스트로 처리하는 것이 나을 수도 있다. 리스트 외에 tuple, dict 등의 자료형에서도 적용되는지는 확인해보지 않았다.
결론
파이썬에서 각 데이터 타입이 갖는 특성이 무엇인지 아직 자세히는 모르지만 실험해본 결과는 이렇다.
- 함수 내에서 외부 변수를 읽어올 수 있다.
- 함수 내에서 외부 변수와 같은 이름의 변수를 선언하면 별개의 변수로 처리된다.
- 리스트는 함수 내에서도 읽고 쓰기가 가능하다.
- 하지만 리스트도 함수 내에서 새롭게 선언하면 별개의 리스트로 처리된다.
따라서 함수에서 외부의 변수에 접근하려면
- return으로 값을 반환해 외부에 전달한다.
- global 키워드로 변수를 전역 변수로 처리한다.
- 리스트를 활용한다.
중에서 적절한 방법을 활용하면 되겠다.