< 컬렉션 관리 함수 >
> enumerate (v. 열거하다)
컬렉션은 여러 개의 값을 저장한다는 면에서 기능적으로 비슷해 관리하는 방법도 유사하다.
때로는 상호 대체 가능하고 필요할 때 서로 변환하기도 한다.
여러 개의 요소가 모여 있는 컬렉션의 가장 기본적인 동작은 요소를 모두 순회하며 읽어내는 것이다.
리스트의 요소를 순서대로 읽을 때는 for 반복문이 제일 편리하다.
# sequence
score = [88, 95, 70, 100, 99]
for s in score:
print("성적:", s)
"""
성적: 88
성적: 95
성적: 70
성적: 100
성적: 99
"""
* s가 100점일 때 이 성적이 몇 번째 학생의 성적인지 루프 내부에서 알 수가 없다.
내부적인 계산만 할 때는 상관없지만 출력할 때는 성적만 쭉 나열하면 누가 몇 점인지 바로 알아보기 어렵다.
# sequence_2
score = [88, 95, 70, 100, 99]
no = 1
for s in score:
print(str(no) + "번 학생의 성적 :", s)
no += 1
"""
1번 학생의 성적 : 88
2번 학생의 성적 : 95
3번 학생의 성적 : 70
4번 학생의 성적 : 100
5번 학생의 성적 : 99
"""
* no 변수를 1로 초기화하고 매 루프에서 1 씩 증가시키면 출석 번호로 쓸 수 있다.
그러나 별도의 변수가 더 필요해 번거롭고 코드도 길다.
더 간단한 방법은 반복 대상을 요소가 아닌 순서 값으로 하여 리스트의 길이만큼 순회하는 것이다.
요소로 순서값을 알 수는 없지만 순서값을 알면 요소는 쉽게 구할 수 있다.
# sequence_3
score = [88, 95, 70, 100, 99]
for no in range(len(score)):
print(str(no + 1) + "번 학생의 성적 :", score[no])
"""
1번 학생의 성적 : 88
2번 학생의 성적 : 95
3번 학생의 성적 : 70
4번 학생의 성적 : 100
5번 학생의 성적 : 99
"""
* len(score)로 길이를 조사하여 0 ~ 5 범위를 돌면 no 가 계속 증가하는 출석 번호가 된다.
위 예제처럼 순서값과 요소값 둘을 한꺼번에 구해 주는 내장 함수가 enumerate 이다.
리스트의 순서값과 요소값을 튜플로 묶은 컬렉션을 리턴한다.
두 값의 조합을 구성해야 하니 튜플로 묶은 컬렉션이 적합하다.
race = ['저그', '테란', '프로토스']
print(list(enumerate(race)))
# [(0, '저그'), (1, '테란'), (2, '프로토스')]
* 리스트의 각 종족이 0 부터 시작하는 순서값과 함께 출력된다.
순서값은 0 부터 시작하는 것이 보통이지만 enumerate 의 두 번째 인수로 시작값을 지정하면 이 값에서 시작한다.
# enumerate
score = [88, 95, 70, 100, 99]
for no, s in enumerate(score, 1):
print(str(no) + "번 학생의 성적 :", s)
"""
1번 학생의 성적 : 88
2번 학생의 성적 : 95
3번 학생의 성적 : 70
4번 학생의 성적 : 100
5번 학생의 성적 : 99
"""
* enumerate(score, 1) 은 1부터 시작하는 순서값과 요소값을 튜플로 생성하여 차례대로 리턴한다.
(1, 88) | (2, 95) | (3, 70) | (4, 100) | (5, 99) |
> zip
zip 함수는 여러 개의 컬렉션을 합쳐 하나로 만든다.
두 리스트의 대응되는 요소끼리 짝을 지어 튜플의 리스트를 생성한다.
두 개의 리스트를 병렬로 순회할 때 편리하다.
# zip
yoil = ['월', '화', '수', '목', '금', '토', '일']
food = ['갈비탕', '순대국', '칼국수', '삼겹살']
menu = zip(yoil, food)
for y, f in menu:
print("%s요일 메뉴 : %s" % (y, f))
"""
월요일 메뉴 : 갈비탕
화요일 메뉴 : 순대국
수요일 메뉴 : 칼국수
목요일 메뉴 : 삼겹살
"""
* yoil 리스트에는 요일 이름이 있고 food 리스트에는 요리 이름이 있다.
이 두 리스트를 zip 함수로 병합하면 대응되는 요소끼리 튜플로 합쳐 menu 라는 새로운 리스트가 생성된다.
합쳐지는 두 리스트는 길이가 달라도 상관없다.
짧은 쪽의 길이에 맞추어 지며 긴 쪽의 남는 요소는 사용하지 않는다.
생성되는 튜플의 순서는 원본 리스트의 순서와 같다.
# dict
yoil = ['월', '화', '수', '목', '금', '토', '일']
food = ['갈비탕', '순대국', '칼국수', '삼겹살']
menu = dict(zip(yoil, food))
print(menu)
"""
{'월': '갈비탕', '화': '순대국', '수': '칼국수', '목': '삼겹살'}
"""
* zip 함수가 생성하는 튜플을 dict 함수로 넘겨 변환하면 앞 요소를 키로 하고 요소를 값으로 하는 사전이 만들어 진다.
이 방법을 사용하면 요일별 식단을 손쉽게 만들 수 있고, 식단을 빠른 속도로 검색할 수도 있다.
# any / all
adult = [True, False, True, False]
print(any(adult)) # True
print(all(adult)) # False
* any 함수는 리스트를 순회하며 참인 요소가 하나라도 있는지 조사한다. (or)
반면 all 함수는 리스트의 모든 요소가 참인지 조사한다. (and)
리스트에 저장된 모든 요소의 진리값 조합을 구하는 것이다.
빈 리스트에 대해서는 any 함수는 참이 하나도 없다고 판단하여 거짓으로 평가하는데 비해
all 함수는 거짓이 하나도 없다고 판단하여 참으로 평가한다.
< 람다 함수 >
> filter
filter 함수는 리스트의 요소 중 조건에 맞는 것만 골라낸다.
첫 번째 인수는 조건을 지정하는 함수(flunk) 이고 두 번째 인수는 대상 리스트(score) 이다.
# filter
def flunk(s):
return s < 60
score = [45, 89, 72, 53, 94]
for s in filter(flunk, score):
print(s)
"""
45
53
"""
* flunk 함수는 점수 s를 인수로 받아 60 미만의 낙제점인지 조사한다.
filter 함수는 점수 리스트의 각 값에 대해 flunk 함수를 호출하여 60보다 작은 요소를 가려낸다.
filter 함수가 리스트를 리턴하므로 for 루프로 filter 호출식을 순회하면 골라낸 점수를 모두 구할 수 있다.
필터링 함수를 먼저 정의해야 한다는 면에서 호출 방법이 복잡하고 어려워 보이낟.
조건을 점검하는 것은 단순한 값이나 수식이 아닌 동작이어서 함수가 필요하다.
> map
map 함수는 모든 요소에 대해 변환 함수를 호출하여 새 요소값으로 구성된 리스트를 생성한다.
인수 구조는 filter 함수와 동일하며 요소값을 어떻게 변경할 것인가는
첫 번째 인수로 전달된 변환 함수의 동작에 따라 달라진다.
# map
def half(s):
return s / 2
score = [45, 89, 72, 53, 94]
for s in map(half, score):
print(s, end=', ')
# 22.5, 44.5, 36.0, 26.5, 47.0,
* half 함수는 인수로 전달받은 s 를 절반으로 나누어 리턴한다.
map 함수는 score 리스트의 모든 값에 대해 half 를 호출하여 절반 값을 구하고 이 값으로 구성된 새 리스트를 생성한다.
원본인 score 리스트는 읽기만 할 뿐 변경하지 않는다.
# map_2
def total(s, b):
return s + b
score = [45, 89, 72, 53, 94]
bonus = [2, 3, 0, 0, 5]
for s in map(total, score, bonus):
print(s, end=", ")
# 47, 92, 72, 53, 99,
* 두 개 이상의 리스트를 받아 각 리스트의 요소를 조합할 수도 있는데 이때 변환 함수는 두 개의 요소값을 전달받아야 하므로 대상 리스트 인수가 두개다.
> 람다 함수
filter 함수나 map 함수는 필터링과 변환을 위해 다른 함수를 인수로 받는다.
이 함수를 호출하기 전에 인수로 전달할 함수부터 정의해야 하리 때문에 귀찮은 면이 있다.
이럴 때 간편하게 쓸 수 있는 것이 람다식이다.
람다는 이름이 없고 입력과 출력만으로 함수를 정의하는 축약된 방법이다.
lambda 인수:식
* 키워드 lambda 로 시작하고 인수와 리턴할 값을 밝힌다.
인수는 콤마로 구분하여 여러 개 가질 수 있다.
return 문이 없지만 인수로부터 계산한 식을 리턴한다.
가장 간단한 람다식의 예는 다음과 같다.
lambda x:x + 1
* 인수 x 를 받아 x + 1 을 리턴하는 람다식이다.
def increase(x):
return x += 1
* 위 함수와 기능적으로 같지만 함수 이름과 return 키워드가 생략되어 더 짧고 간단하다.
축약된 형태이다 보니 filter, map 처럼 함수를 요구하는 호출식의 인수 목록에 람다식을 바로 사용할 수 있다.
# lambda
score = [45, 89, 72, 53, 94]
for s in filter(lambda x: x < 60, score):
print(s)
"""
45
53
"""
# lambda_2
score = [45, 89, 72, 53, 94]
for s in map(lambda x: x / 2, score):
print(s, end=", ")
# 22.5, 44.5, 36.0, 26.5, 47.0,
< 컬렉션의 사본 >
> 리스트의 사본
기본형 변수는 서로 독립적이다.
# var copy
a = 3
b = a
print("a = %d, b = %d" % (a, b))
a = 5
print("a = %d, b = %d" % (a, b))
"""
a = 3, b = 3
a = 5, b = 3
"""
* 대입하면 일시적으로 값이 같아질 뿐 이후 둘 중 하나를 바꾸어도 다른 변수에는 전혀 영향을 주지 않는다.
# list copy
list1 = [1, 2, 3]
list2 = list1
list2[1] = 100
print(list1)
print(list2)
"""
[1, 100, 3]
[1, 100, 3]
"""
* 그러나 이 당연해 보이는 현상도 컬렉션의 경우는 다르다.
이렇게 되는 이유는 두 리스트가 독립된 사본이 아니라 같은 메모리를 가리키고 있기 때문이다.
같은 리스트를 두 변수가 가리키고 있는 상황이라 어느 쪽을 바꿔도 상대편이 영향을 받는다.
# list copy_2
list1 = [1, 2, 3]
list2 = list1.copy()
list2[1] = 100
print(list1)
print(list2)
"""
[1, 2, 3]
[1, 100, 3]
"""
* 두 리스트를 완전히 독립적인 사본으로 만들려면 copy 메서드로 복사본을 생성한다.
copy 메서드는 원본 리스트와 똑같은 리스트를 새로 생성하여 리턴한다.
메모리가 완전히 분리되어 별도의 저장소를 가지므로 한쪽을 바꿔도 영향을 받지 않는다.
copy 메서드를 사용하는 대신 list[:] 으로 전체 범위에 대한 사본을 만들어도 효과는 같다.
list2 = list1[:]
* [:]은 전체 범위를 의미한다.
list1 의 처음부터 끝까지 범위 추출하여 새로운 리스트를 만들어 list2에 대입하므로 두 리스트는 독립적이다.
copy 메서드는 리스트의 요소들을 전부 복사하지만 얕은 복사를 수행하기 때문에 중첩된 리스트까지 사본을 뜨지는 않는다.
# deep copy
list0 = ['a', 'b']
list1 = [list0, 1, 2]
list2 = list1.copy()
list2[0][1] = 'c'
print(list1) # [['a', 'c'], 1, 2]
print(list2) # [['a', 'c'], 1, 2]
* list1 안에 list0 가 포함되어 있는 상황인데 copy 메서드로 list2를 복사하면 두 리스트가 내부의 list0 을 공유하게 된다.
이 상태에서 list2 에 포함된 list0 을 변경하면 list1 도 영향을 받는다
완전한 사본을 만들려면 깊은 복사를 수행해야 하는데 이때는 copy 모듈의 deepcopy 함수를 사용한다.
# deep copy_2
import copy
list0 = ['a', 'b']
list1 = [list0, 1, 2]
list2 = copy.deepcopy(list1)
list2[0][1] = "c"
print(list1) # [['a', 'b'], 1, 2]
print(list2) # [['a', 'c'], 1, 2]
* deepcopy 함수는 중첩된 리스트까지 모두 복사하여 완전한 사본을 만드는 깊은 복사를 수행한다.
> is 연산자
두 변수가 같은 객체를 가리키고 있는지 조사할 때 is 구문을 사용한다.
좌우의 변수가 똑같은 객체를 가리키고 있으면 True를 리턴하고 그렇지 않다면 False를 리턴한다.
# is
list1 = [1, 2, 3]
list2 = list1
list3 = list1.copy()
print("1 == 2", list1 is list2) # 1 == 2 True
print("1 == 3", list1 is list3) # 1 == 3 False
print("2 == 3", list2 is list3) # 2 == 3 False
* list2 는 list1을 단순 대입받았으므로 같은 객체를 가리키고 있다.
두 리스트가 가리키는 대상이 같아 list1 is list2 는 True를 리턴한다.
이 상태에서 둘 중 어떤 리스트를 변경하면 상대편이 영향을 받는다.
반면 list3 은 copy 메서드로 list1의 사본을 뜬 것이어서 메모리가 완전히 분리된 다른 객체이다.
list2와 list3도 당연히 다른 변수이다.
컬렉션에 대한 대입은 단순히 별명을 하나 더 만드는 것과 같으며 완전히 분리된 사본을 만드려면 copy 메서드로 메모리를 복사해야 한다.
그렇다면 정수형 변수도 대입에 의해 같은 객체를 가리키는지 테스트해자.
# var is
a = 1
b = a
print("a =", a, "b =", b, ":", a is b) # a = 1 b = 1 : True
b = 2
print("a =", a, "b =", b, ":", a is b) # a = 1 b = 2 : False
* 정수는 대입에 의해 일시적으로 같은 객체를 가리킬 수 있지만 다른 값을 대입하면 참조가 변경되어 즉시 분리된다.
따라서 컬렉션과는 달리 서로 독립적이다.
'Language > Python' 카테고리의 다른 글
Basic Python_ Class exam : FourCal_Sourcecode (0) | 2022.08.26 |
---|---|
Jump to Python_Source code (0) | 2022.08.26 |
Python_10. Dictionary and Set (0) | 2022.08.01 |
Python_09. List and Tuple (0) | 2022.08.01 |
Python_08. String management (0) | 2022.07.31 |