-
11. 컬렉션 관리Python 2021. 1. 22. 13:49
11.1 컬렉션 관리 함수
- enumerate(for문을 돌리는데 인덱스 정보도 같이 필요할 때)
enumerate(시퀀스[, start]) # 시퀀스의 인덱스와 요소를 튜플로 묶어서 순회 # start는 인덱스(순서) 시작 값, 없으면 0부터 디폴트 값
학생들의 성적을 for문으로 하나씩 꺼내어 순회할 때, 요소를 읽기는 편하지만 순서값은 알 수 없다.
성적뿐만 아니라 학생 출석 번호도 같이 출력하고 싶다면 변수를 하나 더 선언한다.
그러나 변수를 매번 초기화하고 루프에서 1씩 증가시키는 일은 번거롭고 코드도 길다.
score = [89, 48, 77, 82, 100] for no in range(len(score)): print(str(no + 1) + "번 학생의 성적 :", score[no])
1번 학생의 성적 : 89 2번 학생의 성적 : 48 3번 학생의 성적 : 77 4번 학생의 성적 : 82 5번 학생의 성적 : 100
이 예제처럼 순서값과 요소값 둘을 한꺼번에 구해주는 내장 함수가 enumerate이다.
리스트의 순서값과 요소값을 튜플로 묶은 컬렉션으로 리턴한다.
두 값의 조합을 구성해야 하니 튜플로 묶은 컬렉션이 적합하다.
score = [89, 48, 77, 82, 100] for no, s in enumerate(score, 1): # no : 인덱스, s : 값 print(str(no) + "번 학생의 성적 :", s)
1번 학생의 성적 : 89 2번 학생의 성적 : 48 3번 학생의 성적 : 77 4번 학생의 성적 : 82 5번 학생의 성적 : 100
(1, 89), (2, 48), (3, 77), (4, 82), (5, 100) 이렇게 묶여있는 튜플을 no과 s로 받는다.
- zip(아카이브를 한데 묶는다.)
zip 함수는 여러 개의 컬렉션을 합쳐 하나로 만든다. 두 리스트의 대응되는 요소끼리 짝을 지어 튜플의 리스트를 생성한다.
두 개의 리스트를 병렬로 순회할 때 편리하다.
zip(시퀀스1, 시퀀스2) # 시퀀스1 = [값11, 값12, 값13, ..] # 시퀀스2 = [값21, 값22, 값23, ..] # zip(시퀀스1, 시퀀스2) => [(값11, 값21), (값12, 값22), ...]
ex)
yoil = ["월", "화", "수", "목", "금", "토", "일"] food = ["갈비탕", "순대국", "칼국수", "삼겹살"] menu = zip(yoil, food) for y, f in menu: # menu는 튜플의 리스트 print(f"{y}요일 메뉴 : {f}")
월요일 메뉴 : 갈비탕 화요일 메뉴 : 순대국 수요일 메뉴 : 칼국수 목요일 메뉴 : 삼겹살
zip은 길이가 작은 시퀀스에 맞춘다. 길이가 더 긴 시퀀스의 내용은 버린다.
생성되는 튜플의 순서는 원본 리스트의 순서와 같다.
zip(food, yoil) 순으로 함수를 사용하면 요리가 먼저 나오고, 요일이 나중에 온다.
세 개 이상의 리스트를 인수로 주어 병합할 수도 있다. 이 경우는 결과 튜플의 요소도 세 개이다.
zip 함수가 생성하는 튜플을 dict 함수로 넘겨 변환해 앞 요소를 키로, 뒤 요소를 값으로 하는 사전이 만들어진다.
menu_dic = dict(zip(yoil, food)) print(menu_dic)
{'월': '갈비탕', '화': '순대국', '수': '칼국수', '목': '삼겹살'}
any 함수는 리스트를 순회하며 참인 요소가 하나라도 있는지 조사한다.
all 함수는 리스트를 순회하며 모든 요소가 참인지 조사한다.
any(시퀀스) # 시퀀스 요소 중 하나라도 참이면 참 리턴 all(시퀀스) # 시퀀스 요소 중 모든 요소가 참이면 참 리턴
ex)
adult = [True, False, True, False] print(any(adult)) print(all(adult))
True False
11.2 람다 함수
- filter(참인지 거짓인지 판정해 거르는 함수)
filter 함수는 리스트의 요소 중 조건에 맞는 것만 골라낸다.
첫 번째 인수는 조건을 지정하는 함수이고, 두 번째 인수는 대상 리스트이다.
filter(판정함수, 시퀀스) # 시퀀스 리턴 # 판정함수는 참, 거짓을 리턴해주는 함수를 지정해야 한다. # 시퀀스의 요소를 루프 돌리며 판정함수에게 판정받아 참인 값만 새로운 시퀀스에 담는다.
ex)
def flunk(s): return s < 60 score = [54, 72, 21, 89, 66] for s in filter(flunk, score): print(s) print(list(filter(flunk, score)))
54 21 [54, 21]
filter 자체를 print()하면 객체를 리턴해 객체의 주소를 알려준다. list 함수를 사용해 변형하면 조작이 가능하다.
모든 요소를 함수를 호출해 제어권을 넘겨주므로 정밀한 점검을 할 수 있어서 자유도가 높다.
- map(모든 요소를 함수를 통해 변경시켜야 할 때 쓰이는 함수)
map 함수는 모든 요소의 변환 함수를 호출해 새 요소값으로 구성된 리스트를 생성한다.
map(변환함수, 시퀀스[, 시퀀스, ...]) # 변환함수는 참 거짓뿐만 아니라 어떤 값이라도 리턴하면 된다. # 시퀀스의 각 요소를 변환하고, 그 함수의 리턴값으로 구성된 새로운 시퀀스를 리턴한다.
ex)
def half(s): return s / 2 score = [45, 76, 88, 12, 65, 83] print(list(map(half, score))) for s in map(half, score): print(s, end = ", ")
[22.5, 38.0, 44.0, 6.0, 32.5, 41.5] 22.5, 38.0, 44.0, 6.0, 32.5, 41.5,
map 또한 변경하고 싶다면 list 함수를 통해 변형한다.
def half(s): return s > 60 score = [45, 76, 88, 12, 65, 83] print(list(map(half, score))) for s in map(half, score): print(s, end = ", ")
[False, True, True, False, True, True] False, True, True, False, True, True,
map 함수를 참 거짓을 리턴하는 함수를 사용하면 위의 출력 결과처럼 요소의 값을 그대로 리턴하는 것이 아니라 True 혹은 False인지 리턴한다.
ex)
def total(s, b): return s + b score = [76, 35, 87, 98, 100] bonus = [2, 3, 0, 0, 5] for s in map(total, score, bonus): print(s, end = ", ")
78, 38, 87, 98, 105,
두 개 이상의 리스트를 받아 각 리스트의 요소를 조합할 수도 있다.
함수의 인자로 두 리스트를 전달하면 순서대로 total 함수의 매개변수로 전달된다.
- 람다 함수(한 줄만 표기, 함수 이름 X, 변수에 대입해 사용, 코드블럭 X)
filter 함수나 map 함수는 필터링과 변환을 위해 다른 함수를 인수로 받는다.
이 함수를 호출하기 전엔 인수로 전달할 함수부터 정의해야 하기 때문에 귀찮은 면이 있다.
이럴 때 간편하게 람다식을 사용하면 된다.
lambda 인수:식
인수는 콤마로 구분해 여러 개를 가질 수 있다.
return 문이 없지만 인수로부터 계산한 식을 리턴한다.
ex)
lambda x:x+1
: 왼쪽 편의 x가 인숙 목록이고, : 오른쪽이 함수의 코드블럭이다. 이 식의 값을 리턴한다.
리턴의 키워드도 생략한다.
def increase(x): return x + 1 # 함수 명, () 생략 -> x:return x+1 # return 생략 -> x:x+1
ex)
score = [54, 77, 27, 87, 54] for s in filter(lambda x: x < 60, score): print(s)
54 27 54
score = [56, 87, 23, 87, 22, 84] for s in map(lambda x: x/2, score): print(s, end = ", ")
28.0, 43.5, 11.5, 43.5, 11.0, 42.0,
만일 다음에 변수가 쓰인다면 람다식을 변수에 지정해놓고 쓰면 용이하다.
score = [56, 87, 23, 87, 22, 84] l = lambda x:x/2 for s in map(l, score): print(s, end = ", ")
28.0, 43.5, 11.5, 43.5, 11.0, 42.0,
11.3 컬렉션의 사본
- 리스트의 사본
기본형 변수는 서로 독립적이다.
대입하면 일시적으로 값이 같아질 뿐 이후 둘 중 하나를 바꾸어도 다른 변수에는 전혀 영향을 주지 않는다.
a = 3 b = a print(f"a = {a}, b = {b}") a = 5 print(f"a = {a}, b = {b}")
a = 3, b = 3 a = 5, b = 3
하지만 컬렉션의 경우는 다르다.
li1 = [1, 2, 3] li2 = li1 li2[1] = 100 # li2를 바꿨음에도 li1의 값이 바뀐다. print(li1) print(li2)
[1, 100, 3] [1, 100, 3]
li2 = li1은 li1의 값을 li2에 대입한 것이 아니라 li1의 주소값을 li2에 넣었다고 생각하면 된다.
즉, li2와 li1은 같은 곳을 가리키게 된다.
li2가 별명이 될 뿐 독립적인 메모리까지 확보되지 않는다.
두 리스트를 완전히 독립적인 사본으로 만들려면 copy 메서드로 복사본을 생성하거나, 대입할 때 li[:]을 사용한다.
list.copy() # 원본 리스트와 똑같은 리스트를 새롭게 생성해 리턴한다
ex)
li1 = [1, 2, 3] li2 = li1.copy() # li2 = li1[:]와 동일 li2[1] = 100 print(li1) print(li2)
[1, 2, 3] [1, 100, 3]
메모리가 완전히 분리되어 별도의 저장소를 가지르모 한쪽을 바꾸어도 영향을 받지 않는다.
list[:]를 통해 사본을 만들어도 효과는 간다.
[:]는 전체 범위를 의미하고, list의 처음부터 끝까지 범위를 추출해 새로운 리스트를 만들어 대입하므로 두 리스트는 독립적이다.
보통의 함수들은 의도하지 않은 원본 변경을 방지하기 위해 카피본으로 작업 후 새로운 카피본을 리턴하는 경우가 대다수이다.
list0 = ["a", "b"] list1 = [list0, 1, 2] list2 = list1.copy() # 얕은 복사 list2[0][1] = "c" print(list1) print(list2)
[['a', 'c'], 1, 2] [['a', 'c'], 1, 2]
list1 안에 list0이 포함되어 있는 상황에서 copy 메서드를 사용해 list2를 복사하면 list0을 공유하게 된다. 이 상태에서 list2에 포함된 list0을 변경하면 list1도 영향을 받는다.
완전한 사본을 만들려면 깊은 복사를 수행해야 하는데 이때는 copy 모듈의 deepcopy 함수를 사용한다.
import copy list0 = ["a", "b"] list1 = [list0, 1, 2] list2 = copy.deepcopy(list1) list2[0][1] = "c" print(list1) print(list2)
[['a', 'b'], 1, 2] [['a', 'c'], 1, 2]
deepcopy 함수는 중첩된 리스트까지 모두 복사하여 사본을 만드는 깊은 복사를 수행한다.
이제 list2를 수정해도 list이 영향받지 않는다.
즉, 리스트 안에서 요소가 가리키는 리스트 또한 새로운 메모리에 복사해 새로운 곳을 가리키게 한다.
각가의 참조를 따라간다고 생각하면 된다.
얕은 복사 -> 빠르다.
깊은 복사 -> 보안 면에서 중요하다.
call by reference -> 원본에 영향을 미친다.
- is 연산자
두 변수가 같은 객체를 가리키고 있는지 조사할 때 is 구문을 사용한다.
객체1 is 객체2 # 같은 객체를 가리키고 있다면 True 리턴, 아니면 False 리턴
ex)
list1 = [1, 2, 3] list2 = list1 # 같은 객체 참조 list3 = list1.copy() # 같은 객체 참조 X print("1 == 2", list1 is list2) print("1 == 3", list1 is list3) print("2 == 3", list2 is list3)
1 == 2 True 1 == 3 False 2 == 3 False
list3은 copy 메서드를 통해 독립적 사본을 가리키고 있으므로 같은 곳을 가리키는 list1과 list2가 가리키는 곳이 다르다.
a = 1 b = a print("a =", a, "b =", b, ":", a is b) b = 2 print("a =", a, "b =", b, ":", a is b)
a = 1 b = 1 : True a = 1 b = 2 : False
b = a일 경우 같은 곳을 가리키지만 b에 새로운 변수를 대입하는 순간 b가 가리키는 객체가 바뀐다.
정수는 대입에 의해서 일시적으로 같은 객체를 가리킬 수 있지만, 다른 값을 대입하면 참조가 변경되어 즉시 분리된다. 따라서 컬렉션과 다르게 서로 독립적이다.
primitive 값(숫자, bool)는 대입 연산 시 값 복사가 진행된다.
primitive 이외는 대입 연산 시 참조가 넘어간다.
참조인데 값을 대입, 넘기고 싶다면 카피해서 넘겨야 한다.
보통 리스트 -> .copy()
이중 리스트 -> .deepcopy()
참조는 stack -> heap, heap -> heap 형식으로 참조한다.
heap은 생성과 삭제 시점을 조절(제어) 가능하다.
만일 스택이 끝나도 사라지지 않은 상태에서 접근할 수 있는 방법이(참조 변수) 없다면 이 값은 garbage 값이 된다.
가비지 값 : 참조 변수가 없는 데이터
파이썬은 틈틈이 이런 가비지값을 없애는 가비지 컬렉터가 존재한다.
'Python' 카테고리의 다른 글
Python의 효율적인 반복에 유용한 itertools 함수 (0) 2021.01.28 Python에서 between 대체하기 (0) 2021.01.27 리스트 중복 요소 제거 (0) 2021.01.21 리스트 안 빈 문자 제거하기 (0) 2021.01.21 변수의 값 바꾸기 (swap) (0) 2021.01.21