본문으로 건너뛰기

리스트를 함수에 넘기면

파이썬 목차함수레슨 42 / 59

목차

리스트를 함수에 넘기면 매개변수는 그 리스트의 사본이 아니라 같은 리스트를 가리킵니다. 그래서 함수 안에서 리스트를 바꾸면 밖의 이름으로 봐도 바뀌어 있습니다.

리스트와 이름 둘에서 두 이름이 한 리스트를 가리키면 한쪽으로 바꾼 것이 다른 쪽에서도 보였습니다. 부르면 매개변수가 인자 값에 붙으니(규칙 5), 리스트를 넘길 때도 같은 일이 일어납니다. 이것을 모르면 주문 목록을 지키려던 함수가 목록을 바꾸거나, 바꾸려던 함수가 바꾸지 못합니다.

8단계 중 1단계. 리스트를 함수에 넘기고, 함수 안에서 append를 쓰는 코드입니다.

1넘긴 리스트를 매개변수도 가리킨다

그림의 코드입니다.

def add(items):
    items.append('쿠키')


order = ['커피']
add(order)
print(order)
실행 결과
['커피', '쿠키']

6번 줄에서 add를 부르면 새 함수 칸이 생기고, 매개변수 items가 order와 같은 리스트에 붙습니다. 리스트는 하나이고 이름이 둘입니다(규칙 3). 2번 줄의 append는 items가 가리키는 그 리스트의 끝에 '쿠키'를 더합니다. 제자리에서 바꾸므로 새 리스트는 생기지 않습니다. add가 끝나면 함수 칸과 이름 items는 사라지지만, 리스트는 order가 그대로 가리킵니다. 그래서 7번 줄은 '쿠키'까지 찍습니다.

2매개변수를 새 리스트에 붙이면 밖은 그대로

이번 함수는 넘겨받은 주문을 비우려고 매개변수에 빈 리스트를 대입합니다. 무엇이 찍힐까요?

def reset(items):
    items = []


order = ['커피']
reset(order)
print(order)
실행 결과 보기
실행 결과
['커피']

예상과 다르면 그곳이 배울 곳입니다. []를 예상했다면, 매개변수를 다시 붙이면 밖의 리스트도 바뀐다고 본 것입니다. 2번 줄은 새 빈 리스트를 만들어 함수 칸의 이름 items만 그 리스트에 다시 붙입니다(규칙 2·3). order가 가리키는 리스트는 아무도 건드리지 않았습니다. 1절의 append는 리스트 자체를 바꾸고, items = []는 이름 items를 옮길 뿐입니다.

3밖의 리스트를 지키려면 사본을 넘긴다

넘긴 리스트가 바뀌지 않게 하려면 리스트 복사: copy와 이름 더 붙이기의 copy()로 사본을 만들어 넘깁니다.

def add(items):
    items.append('쿠키')


order = ['커피']
add(order.copy())
print(order)
실행 결과
['커피']

6번 줄의 order.copy()는 값이 같은 새 리스트를 만들고, items는 그 사본에 붙습니다. append는 사본의 끝에만 '쿠키'를 더하므로 order의 리스트는 그대로입니다.

예제

바꾸지 않고 새 리스트를 돌려주기

원래 주문은 그대로 두고, '쿠키'를 더한 새 주문을 돌려받는 함수입니다. 함수 안에서 사본을 만들어 바꾼 뒤 돌려줍니다.

def added(items):
    new = items.copy()
    new.append('쿠키')
    return new


order = ['커피']
more = added(order)
print(order)
print(more)
실행 결과
['커피']
['커피', '쿠키']

2번 줄의 new는 items의 사본을 가리키고, 3번 줄은 그 사본만 바꿉니다. 돌려받은 사본에는 8번 줄이 이름 more를 붙입니다. 제자리에서 바꾸는 add는 .sort(), 새 리스트를 돌려주는 added는 sorted()와 같은 꼴입니다(정렬: sort와 sorted).

자주 틀리는 곳

틀린 생각: 함수에 넘긴 리스트는 사본이라, 함수 안에서 바꿔도 밖의 리스트는 그대로다.

함수 칸이 사라지면 그 안에서 한 일도 함께 사라질 것 같습니다. 사라지는 것은 이름이지 리스트가 아닙니다.

def add(items):
    items.append('쿠키')


order = ['커피']
add(order)
add(order)
print(order)
실행 결과
['커피', '쿠키', '쿠키']

부를 때마다 items가 order와 같은 리스트에 붙어, 두 번 더한 '쿠키'가 모두 남았습니다.

맞는 규칙: 리스트를 넘기면 매개변수가 같은 리스트를 가리켜, 함수 안의 append 같은 제자리 바꾸기가 밖에서도 보입니다. 밖의 리스트를 지키려면 copy()로 사본을 넘깁니다(규칙 3).

틀린 생각: 함수 안에서 매개변수를 새 리스트에 붙이면 밖의 리스트도 바뀐다.

매개변수가 넘긴 리스트를 가리키니 그 이름에 대입해도 밖에 닿을 것 같지만, 이름에 대입하면 그 이름만 옮겨 갑니다.

def add(items):
    items = items + ['쿠키']


order = ['커피']
add(order)
print(order)
실행 결과
['커피']

items + ['쿠키']는 두 리스트를 이은 새 리스트를 만들고, 2번 줄은 이름 items만 그 새 리스트에 다시 붙입니다. order의 리스트는 그대로입니다.

맞는 규칙: 매개변수에 대입하면 함수 칸의 이름만 다른 값으로 옮겨 가고 밖의 리스트는 그대로입니다. 밖의 리스트를 바꾸려면 리스트를 바꾸는 메서드를 쓰고, 새 리스트가 필요하면 돌려줘서 밖에서 이름을 붙입니다(규칙 3).

틀린 생각: 기본값 []는 부를 때마다 새 빈 리스트가 된다.

부를 때마다 칸이 새로 생기니 기본값 []도 새 리스트일 것 같습니다. 기본값은 함수를 만드는 1번 줄에서 한 번만 만들어집니다.

def add(x, items=[]):
    items.append(x)
    return items


print(add(1))
print(add(1))
실행 결과
[1]
[1, 1]

items를 넘기지 않은 두 부르기 모두 items가 그 한 리스트에 붙어, 둘째 부르기에서 1이 쌓였습니다.

맞는 규칙: 기본값은 함수를 만들 때 한 번 만들어져 부를 때마다 같은 값이 쓰입니다. 그래서 리스트를 기본값으로 두지 않고, 부를 때 리스트를 넘깁니다.

확인 문제

1. 2절 코드의 2번 줄을 items = ['녹차']로 바꿨습니다. 무엇이 찍힐까요?

def reset(items):
    items = ['녹차']


order = ['커피']
reset(order)
print(order)
힌트

2번 줄이 리스트를 바꾸는지, 이름 items를 옮기는지 봅니다.

정답
실행 결과
['커피']

2번 줄은 새 리스트 ['녹차']를 만들어 함수 칸의 이름 items만 그 리스트에 다시 붙입니다. order가 가리키는 ['커피']는 그대로입니다. 흔한 오답은 ['녹차']입니다. 매개변수를 다시 붙이면 밖의 이름도 따라간다고 본 것입니다(따라 옮겨 간다는 생각). 이름에 대입하면 그 이름만 옮겨 갑니다(규칙 3).

2. 빈칸 한 줄을 채워 add를 부른 뒤 밖의 리스트 끝에 '쿠키'가 더해지게 하세요. 다 채운 코드는 ['라테', '쿠키']를 찍고, 5번 줄을 order = ['라테', '녹차']로 바꾸면 ['라테', '녹차', '쿠키']를 찍어야 합니다.

def add(items):
    ____


order = ['라테']
add(order)
print(order)
힌트

2번 줄은 items가 가리키는 리스트 자체를 바꿔야 합니다. 리스트를 제자리에서 바꾸기의 메서드를 떠올려 봅니다.

정답

items.append('쿠키')입니다.

def add(items):
    items.append('쿠키')


order = ['라테']
add(order)
print(order)
실행 결과
['라테', '쿠키']

items가 order와 같은 리스트를 가리키므로, 그 리스트의 끝에 더한 '쿠키'가 밖에서도 보입니다.

다르게 써도 되는 곳: items.extend(['쿠키'])와 items += ['쿠키']도 같은 리스트를 늘려 두 시험 값 모두 맞습니다. 리스트의 +=는 새 리스트를 만들지 않고 그 리스트를 늘립니다. items.insert(1, '쿠키')⁠는 첫 시험 값에서만 맞고 둘째에서는 ['라테', '쿠키', '녹차']가 됩니다.

흔한 오답은 items = items + ['쿠키']⁠입니다. 새 리스트에 이름 items만 옮겨 붙어 ['라테']가 그대로 찍힙니다. items를 새 리스트에 붙이면 order도 그 리스트로 따라간다고 본 것입니다(따라 옮겨 간다는 생각).

3. 시험 점수 리스트에 보너스 점수를 더하는 함수입니다. 무엇이 찍힐까요?

def curve(scores):
    scores.append(100)
    return len(scores)


mine = [70, 80]
n = curve(mine)
print(n, mine)
힌트

7번 줄에서 scores가 어느 리스트를 가리키는지, 2번 줄이 그 리스트를 바꾸는지 봅니다.

정답
실행 결과
3 [70, 80, 100]

scores는 mine과 같은 리스트를 가리키고, 2번 줄의 append가 그 리스트를 바꿉니다. 함수가 돌려준 칸 수 3에 이름 n이 붙고, mine으로 봐도 100이 더해져 있습니다. 흔한 오답은 3 [70, 80]입니다. 함수가 넘긴 리스트의 사본을 받는다고 본 것입니다(복사된다는 생각). 리스트를 넘기면 매개변수가 같은 리스트를 가리킵니다(규칙 3).

1절 코드의 2번 줄을 items[0] = '녹차'로 바꾸면 7번 줄이 무엇을 찍는지 봅니다.

처음 펼 때 약 6MB를 받습니다.컴퓨터에서 해 보기

끝까지 내려오면 저절로 표시됩니다. 이 기기에만 저장합니다.

파이썬 목차로