-
09. 리스트와 튜플Python 2021. 1. 20. 16:39
9.1 리스트
- reference copy
nums = list(range(10)) nums2 = nums print(nums2) # 값이 nums2에 복사된 것 같지만 nums[0] = 100 print(nums) print(nums2)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [100, 1, 2, 3, 4, 5, 6, 7, 8, 9] [100, 1, 2, 3, 4, 5, 6, 7, 8, 9]
nums2의 공간을 새로 만들고 거기에 nums의 값을 복사해서 넣고 그 저장공간을 가리키는 것이 아니라 그냥 nums2에 nums의 저장공간을 복사해 넣어 똑같은 곳을 가리킨다.
그래서 nums를 수정하고 nums2를 출력하면 nums와 같은 결과를 보인다.
- value copy
nums = list(range(10)) nums2 = nums[:] print(nums) print(nums2) print("수정 후") nums[0] = 100 print(nums) print(nums2)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 수정 후 [100, 1, 2, 3, 4, 5, 6, 7, 8, 9] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
nums2 = nums를 한 것이 아니라 nums[:]를 통해 nums의 값을 새로운 공간에 복사해 넣고 그 주소를 nums2가 가리킨다. 즉, 같은 곳을 가리키는 것이 아닌 각기 다른 곳을 가리키기 때문에 nums를 수정하고 nums2를 출력해도 다른 결과가 나온다.
- 자료의 집합
리스트는 여러 개의 값을 집합적으로 저장한다. 다른 언어의 배열에 해당하며 실제로 배열과 비슷한 방식으로 사용한다. [] 괄호 안에 요소를 콤마로 구분하여 나열함으로써 초기화한다.
score = [88, 65, 23, 78] name = ["최상미", 99, "김기남"] # 데이터 타입을 섞어 사용해도 무방하다.
리스트는 개수의 제한이 없어 얼마든지 많은 변수를 모아서 저장할 수 있다.
리스트에 소속된 각각의 값을 요소(Element) 또는 원소라고 한다.
score = [88, 65, 100, 12, 34] sum = 0 for s in score: sum += s print("총점 :", sum) print("평균 :", sum / len(score))
리스트는 하나의 변수에 여러 개의 값을 모아 놓은 것이기 때문에 루프를 돌리면 개별 요소를 쉽게 꺼낼 수 있다.
또한 가변적인 규모에도 쉽게 대응할 수 있다. 저장할 수가 늘어나도 리스트의 요소만 늘리면 될 뿐 변수가 더 필요한 것은 아니다.
빈 리스트는 [] 또는 list() 함수로 만든다. 요소를 전혀 가지지 않는 빈 리스트를 만들어놓고 실행 중에 요소를 추가할 수 있다. list() 함수는 튜플이나 문자열 등 다른 타입을 리스트로 변환할 때도 사용한다.
print(list("korea"))
출력 내용 ['k', 'o', 'r', 'e', 'a']
- 리스트의 요소
리스트와 문자열은 비슷하다. 문자열과 비슷한 방법으로 리스트를 읽을 수 있다.
score = [88, 95, 70, 100, 99] print(score[0]) print(score[3]) print(score[-1])
출력 내용 88 100 99
0 1 2 3 4 88 95 70 100 99 -5 -4 -3 -2 -1 즉, 리스트[인덱스]의 형태로 접근 가능하다.
슬라이싱과 비슷하게
리스트[begin:end:step]
을 사용해 일부 요소를 분리할 수 있다.
end는 범위에 포함되지 않는다. end - 1까지가 범위다.
nums = [0,1,2,3,4,5,6,7,8,9] print(nums[2:5]) print(nums[:4]) print(nums[6:]) print(nums[::-1])
[2, 3, 4] [0, 1, 2, 3] [6, 7, 8, 9] [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
score = [11, 22, 33, 44] print(score[2]) score[2] = 55 # 불변 객체인 문자열과 달리 리스트 수정 가능 print(score[2])
33 55
불변 객체인 문자열과 달리 리스트는 수정이 가능하다.
슬라이싱 기법을 사용해 리스트의 내용을 바꿀 수도 있다.
nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] nums[2:5] = [20, 30, 40] print(nums) nums[6:8] = [90, 91, 92, 93, 94, 95] print(nums)
[0, 1, 20, 30, 40, 5, 6, 7, 8, 9] [0, 1, 20, 30, 40, 5, 90, 91, 92, 93, 94, 95, 8, 9]
추가하려는 요소 개수가 같으면, 교체한다. 추가하려는 요소 수가 더 많으면, 기존 내용을 삭제하고 새로운 내용을 추가하는 내용으로 덮어쓴다.
만일 list[6:6]처럼 차이를 주지 않으면 삭제하지 않고 삽입한다.
nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] nums[0:0] = ["삭제노", "삽입"] print(nums)
['삭제노', '삽입', 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
일정 범위에 대해 빈 리스트를 대입하면 해당 범위의 요소를 모두 삭제한다.
nums = [0,1,2,3,4,5,6] nums[0:2] = [] # 0, 1 삭제 del nums[3] # [2, 3, 4, 5, 6]에서 [5] 삭제 print(nums)
[2, 3, 4, 6]
부분을 삭제하고 싶다면 슬라이싱 기법을 잉요해 []을 대입하고, 한 요소만 삭제하고 싶다면 del 명령을 사용한다.
+연산자와 *연산자의 동작도 문자열과 같다.
리스트끼리 +연산자로 더하면 두 리스트를 연결한다. 리스트에 정수를 곱하면 요소를 정수 번 반복한다.
list1 = [1, 2, 3, 4, 5] list2 = [10, 11] listadd = list1 + list2 print(listadd) listmulti = list2 * 3 print(listmulti)
[1, 2, 3, 4, 5, 10, 11] [10, 11, 10, 11, 10, 11]
리스트는 여러 개의 변수에 값을 한 번에 대입하는 기능을 제공한다. 튜플도 마찬가지
tu = ["이순신", "김유신", "강감찬"] lee, kim, kang = tu print(lee, kim, kang)
이순신 김유신 강감찬
- 이중 리스트
리스트의 요소에는 제약이 없어서 숫자, 문자열 이외의 리스트 자체도 요소가 될 수 있다.
[] 괄호 안에 또 다른 [] 괄호를 넣으면 된다.
lol = [[1,2,3], [4,5], [6,7,8,9]] print(lol[0]) print(lol[2][1]) for sub in lol: for item in sub: print(item, end = " ") print()
[1, 2, 3] 7 1 2 3 4 5 6 7 8 9
list[0] -> [1, 2, 3]
list[1] -> [4, 5]
list[2] -> [6, 7, 8, 9]
부분 리스트의 길이나 타입은 일치하지 않아도 된다.
부분 리스트 안의 정수값 하나를 읽으려면 [] 기호를 두 번 사용해 접근하면 된다.
score = [ [88, 22, 33, 77], [43, 97, 55, 67], [11, 100, 25, 89] ] # 이중 리스트 여러줄 입력 가능 total = 0 totalsub = 0 for student in score: sum = 0 for subject in student: sum += subject subjects = len(student) print("총점 %d, 평균 %.2f" %(sum, sum/subjects)) total += sum totalsub += subjects print("전체 평균 %.2f" %(total/totalsub))
총점 220, 평균 55.00 총점 262, 평균 65.50 총점 225, 평균 56.25 전체 평균 58.92
수직으로 루프를 돌면서 합계를 구하면 과목별 총점과 평균을 구할 수도 있고 이렇게 구한 총점을 정렬하면 석차도 산출할 수 있다.
- 리스트 컴프리헨션
리스트 안의 요소가 일정한 규칙을 가지는 수열이라면 일일이 나열할 필요 없이 다음 문법으로 요소의 집합을 정의한다.
[수식 for 변수 in 리스트 if 조건]
[] 괄호 안에 요소를 생성하는 for문과 if문이 포함되어 있다. 내부의 리스트를 순회하며 각 요소에 대해 수식을 적용하여 최종 요소를 생성해낸다.
if 조건은 그중 일부 요소만 추려내는데 필요없을 시 생략 가능하다.
예를 들어 1~10 원소를 가지는 리스트라면 다음 식으로 선언한다.
[n for n in range(1, 11)] #[1,2,3,4,5,6,7,8,9,10] # 맨 왼쪽의 n이 n을 요소로 생성한다는 뜻. # for 뒤의 n은 n을 추출하여
for문으로 1에서 10까지 순회하며 n에 순서대로 대입하여 n을 요소로 정의한다.
만일 1~10에 그 배가 되는 값을 가지는 리스트를 얻고 싶다면 다음과 같이 한다.
nums = [n * 2 for n in range(1, 11)] for i in nums: print(i, end = ", ")
2, 4, 6, 8, 10, 12, 14, 16, 18, 20,
nums = [n * 2 for n in range(1, 11)]
은 다음 코드와 같다
nums = [] for n in range(1, 11): nums.append(n * 2)
일정한 규칙을 갖고 있는 수열이라면 컴프리헨션을 사용하면 간단명료하게 표현이 가능하다.
일정한 조건을 지정하려면 뒤에 if 문을 붙이며 조건이 참인 요소만 리스트에 삽입한다.
1~10의 값 중에 3의 배수만 취하려면 if 문에 n이 3의 배수라는 조건을 작성한다.
[n for n in range(1, 11) if n % 3 == 0] # [3, 6, 9] [n * n for n in range(1, 11) if n % 3 == 0] # [9, 36, 81]
9.2 리스트 관리
- 삽입
리스트는 문자열과 달리 변경 가능하다. [] 괄호와 관리 메서드를 활용하여 요소를 마음대로 편집할 수 있다.
nums = [1, 2, 3, 4] nums.append(5) nums.insert(2, 99) print(nums)
[1, 2, 99, 3, 4, 5]
리스트.append(추가할요소) # 추가할 요소를 리스트의 끝에 덧붙인다. 리스트.insert(삽입할인덱스, 삽입할요소) # 삽입할 요소를 삽입할 인덱스에 넣는다.
append는 인수로 전달한 요소를 리스트의 끝에 덧붙여 추가한다.
insert는 삽입할 위치와 요소값을 전달받아 리스트의 중간에 삽입한다.
nums = [1, 2, 3, 4] nums[2:2] = [90, 91, 92] print(nums) nums = [1, 2, 3, 4] nums[2] = [90, 91, 92] # nums.insert(2, [90, 91, 92])와 같은 효과 print(nums)
[1, 2, 90, 91, 92, 3, 4] [1, 2, [90, 91, 92], 4]
[2:2] 범위에 3개의 요소를 삽입했다.
반면 [2]번 위치의 요소에 리스트를 대입하는 것은 아예 원소를 2번 자리의 원소를 새롭게 대체한다는 뜻이다.
리스트에 다른 리스트를 추가하여 병합할 때는 extend 메서드를 사용한다.
list1 = [1, 2, 3, 4, 5] list2 = [10, 11] list1.extend(list2) print(list1)
[1, 2, 3, 4, 5, 10, 11]
list3 = list1 + list2와 효과가 같다.
다만 +연산자를 활용할 때는 원본은 유지되지만, extend를 사용하면 list1 자체가 바뀐다.
- 삭제
리스트의 요소를 삭제할 때는 대상을 선택하는 방법에 따라 메서드를 골라 사용한다.
리스트.remove(삭제할요소의값) del(리스트[삭제할인덱스]) # del 리스트[삭제할인덱스] 리스트[begin:end] = [] # del 리스트[begin:end]
score = [55, 77, 27, 100, 84, 25, 84, 50, 72] score.remove(100) # 100 값을 가진 요소 삭제 # score.remove(1) # 값이 없다면 ValueError: list.remove(x): x not in list 에러 print(score) del(score[2]) # 2인덱스의 값 삭제 print(score) score[1:4] = [] # 인덱스 1~3까지 삭제 print(score) score.clear() print(score)
[55, 77, 27, 84, 25, 84, 50, 72] [55, 77, 84, 25, 84, 50, 72] [55, 84, 50, 72] []
remove는 인수로 전달받은 요소값을 찾아 삭제한다. 해당 값이 없으면 예외를 발생시키고 값이 2개 이상이면 처음 발견한 요소 하나만 삭제한다.
del 명령은 인덱스값을 지정해 삭제한다.
del score[2] 혹은 del(score[2])로 호출할 수 있다. 메서드는 아니므로 score.del(2)은 불가능하다.
일정한 범위의 요소 여러 개를 지울 때는 슬라이싱을 사용해 빈 리스트를 대입한다. 아니면 del score[1:4] 형식으로 범위를 직접 삭제해도 무방하다.
score[:] = [] del score[:] score.clear()
리스트의 모든 요소를 지울 때는 위의 세 개 중 하나를 선택해서 사용하면 된다.
remove와 del은 요소를 지우기만 하는데 비해 pop은 삭제한 요소를 꺼내 리턴한다.
첨자를 지정하여 지울 대상을 지정하되 인수가 없으면 마지막 요소를 빼낸다.
인수를 생략하면 pop(-1)과 같다.
리스트.pop([인덱스]) # 생략시 pop(-1) 디폴트, 삭제한 값 리턴
score = [88, 56, 92, 100, 37] print(score.pop()) # 37 삭제 + 리턴 print(score.pop()) # 100 삭제 + 리턴 print(score.pop(1)) # 56 삭제 + 리턴 print(score)
37 100 56 [88, 92]
pop은 요소를 삭제하며 그 값을 다시 리턴하므로 리스트의 값을 순서대로 제거하며 읽을 때 유용하다. 리스트의 끝에서 append로 추가하고 pop으로 제거하면 선입 선출 방식으로 동작하는 스택이 된다.
pop(0)으로 호출해 리스트의 선두 요소를 빼내면 큐로도 사용할 수 있지만 앞쪽 요소를 지우는 동작은 뒤쪽의 모든 요소를 이동시켜야 하기 때문에 느리고 비효율적이다.
파이썬은 자료구조를 제공하는 별도의 Queue 객체를 지원하므로 리스트를 큐로 쓰지는 않는다.
- 검색
리스트의 검색 메서드는 문자열의 경우와 같다.
리스트.index(요소값) # 요소값을 가지고 있는 인덱스 리턴, 요소값이 없으면 예외 리스트.count(요소값) # 요소값을 가진 요소 개수 리턴
score = [86, 23, 96, 23, 77, 85, 100, 100, 20] perfect = score.index(100) print("만점을 받은 학생은 " + str(perfect) + "번입니다.") pernum = score.count(100) print("만점자 수는 " + str(pernum) + "명입니다.")
만점을 받은 학생은 6번입니다. 만점자 수는 2명입니다.
index()는 중복된 값이 있을 경우 앞의 인덱스만 리턴한다.
len(리스트) # 리스트의 길이 리턴 max(리스트) # 리스트에서 최대값 리턴 min(리스트) # 리스트에서 최소값 리턴
score = [86, 23, 96, 23, 77, 85, 100, 100, 20] print("학생 수는 %d명입니다." % len(score)) print("최고 점수는 %d입니다." % max(score)) print("최저 점수는 %d입니다." % min(score))
학생 수는 9명입니다. 최고 점수는 100입니다. 최저 점수는 20입니다.
요소가 있는지 없는지 검사할 때는 in, not in 연산자를 사용한다.
리스트와 이 연산자를 사용해서 여러 개의 값 중 하나인지 간편하게 조사할 수 있다.
ans = input("결제하시겠습니까?") if ans in ["yes", "y", "Y", "ok", "예", "당근"]: print("구입해주셔서 감사합니다") else: print("안녕히가세요")
- 정렬
정열은 요소를 크기순으로 재배열하는 것이다.
sort 메서드는 리스트를 정렬하며 이 과정에서 요소의 순서가 조정되어 리스트 자체가 바뀐다.
reverse 메서드는 요소의 순서를 반대로 뒤집는다.
sorted 내장 함수는 바뀐 값을 리턴한다. 즉, 원본을 유지한다.
리스트.sort([reverse = True][, key = 키에 적용할 함수]) # 리스트 정렬, 원본 바꿈, 디폴트 오름차순 리스트.reverse() # 리스트 순서를 역으로 바꿈 sorted(시퀀스) # 지정한 시퀀스를 정렬하여 새로운 리스트 리턴
score = [86, 23, 96, 23, 77, 85, 100, 100, 20] score.sort() print(score) score.reverse() print(score)
[20, 23, 23, 77, 85, 86, 96, 100, 100] [100, 100, 96, 86, 85, 77, 23, 23, 20]
sort 메서드의 reverse 인수를 True로 지정하면 내림차순으로 정렬한다.
굳이 sort와 reverse를 연달아 쓸 필요가 없다.
key 인수는 정렬 시 요소를 비교할 키를 추출하는 함수이다. 이 함수로 키를 변형하여 비교 기준으로 사용한다.
country = ["Korea", "japan", "CHINA", "america"] country.sort() print(country) country.sort(key = str.lower) # 대소문자 무시하고 비교 print(country)
['CHINA', 'Korea', 'america', 'japan'] ['america', 'CHINA', 'japan', 'Korea']
나라 이름을 그냥 비교하면 대문자가 더 작은 것으로 평가된다.
그러나 key의 인수에 str.lower을 주어 소문자로 바꾼 후 비교하면 대소문자를 무시하고 비교할 수 있다.
score = [88, 95, 70, 100, 99] score2 = sorted(score) print(score) # 원본 유지 print(score2) # 바뀐 내용
[88, 95, 70, 100, 99] [70, 88, 95, 99, 100]
원본을 유지할 것인가 아닌가에 따라 sort 메서드와 sorted 내장함수를 골라 사용하면 된다.
9.3 튜플
- 불변 자료 집합
튜플은 값의 집합이라는 면에서 리스트와 유사하지만 초기화한 후 편집할 수 없다는 점이 다르다. 그래서 튜플을 상수 리스트라고도 부른다. 튜플을 정의할 때는 () 괄호를 사용한다.
score = (88, 95, 70, 100, 99) sum = 0 for s in score: sum += s print("총점 :", sum) print("평균 :", sum / len(score)) print(score)
총점 : 452 평균 : 90.4 (88, 95, 70, 100, 99)
print 함수는 튜플을 출력할 때 () 괄호를 같이 출력하여 리스트가 아닌 튜플임을 나타낸다.
그런데 정의할 때는 꼭 () 괄호를 감쌀 필요 없이 값만 나열해도 상관없다. 콤마로 구분된 값이 나열되어 있으면 튜플이라고 파악한다.
score = 88, 95, 70, 100, 99
는 score = (88, 95, 70, 100, 99)와 같다.
단, 요소가 하나밖에 없는 튜플은 일반 변수와 구분되지 않아 값 다음에 여분의 콤마를 찍어 튜플임을 표시한다.
tu = 2, value = 2 print(tu) print(value)
(2,) 2
요소를 하나 가진 튜플은 마지막에 ,를 찍어 일반 변수와 구분을 지어야 한다.
- 튜플로 가능한 일
튜플의 요소 읽기는 언제든지 가능하다. 범위를 추출하여 일부를 잘라낼 수도 있고 + 연산자로 튜플끼리 연결하거나 * 연산자로 튜플의 요소를 반복할 수도 있다.
그러나 튜플의 요소를 변경하거나 삭제하는 것은 불가능하다.
tu = 1, 2, 3, 4, 5 print(tu[3]) print(tu[1:4]) print(tu + (6, 7)) print(tu * 2) tu[1] = 100 # 불가능 del tu[1] # 불가능
4 (2, 3, 4) (1, 2, 3, 4, 5, 6, 7) (1, 2, 3, 4, 5, 1, 2, 3, 4, 5) Traceback (most recent call last): File "c:\workspace\JAVA_Python_C++\Python\chapter09 리스트와 튜플\tupleop.py", line 6, in <module> tu[1] = 100 # 불가능 TypeError: 'tuple' object does not support item assignment
연결이나 반복은 원본 튜플로부터 새로운 튜플을 만드는 것이지 튜플 자체를 바꾸지는 않는다.
요소를 변경하거나 삭제하는 것은 튜플의 요소를 바꾸는 것이기 때문에 리스트가 제공하는 append, insert 같은 메서드가 없고, 삭제도 불가능하다.
제공하는 메서드는 index와 count 두 개밖에 없어 요소를 찾거나 개수를 세는 것만 가능하다.
튜플은 여러 개의 변수에 값을 한꺼번에 대입하는 기능이 있다.
tu = "이순신", "김유신", "강감찬" lee, kim, kang = tu print(lee, kim, kang)
이순신 김유신 강감찬
이때 좌변의 변수 개수와 튜플의 요소 개수는 같아야 한다. 튜플에 저장된 요소를 풀어헤쳐(Tuple Unpacking) 각 변수에 나누어 대입하는 셈이다. 이 기능을 사용하면 두 변수의 값을 한번에 초기화하고 두 값을 쉽게 교환할 수 있다.
a, b, c = 12, 34, "string" print(a, b, c) a, b, c = c, b, a print(a, b, c)
12 34 string string 34 12
튜플을 사용하면 리턴값을 여러 개 변환할 수 있다. 인수는 얼마든지 많이 넘길 수 있지만 리턴값은 딱 하나밖에 없는 것이 원칙이다. 그러나 내부에 요소를 포함하는 튜플을 사용하면 두 개 이상의 값을 반환할 수 있다.
import time def gettime(): now = time.localtime() return now.tm_hour, now.tm_min # 두 개의 값 리턴, 튜플로 묶어 반환 result = gettime() # hour, min = gettime() print("지금은 %d시 %d분입니다" % (result[0], result[1]))
지금은 16시 31분입니다
d, m = divmod(7, 3) print("몫", d) print("나머지", m)
몫 2 나머지 1
- 튜플을 사용하는 이유
- 비용의 차이가 있다. 리스트는 변경 가능성을 항상 대비해야 하므로 더 많은 메모리를 사용하고 속도도 느리다. 이에 비해 튜플은 값의 집합만 표현할 뿐 바뀔 일이 없으므로 내부 구조가 훨씬 단순하고 읽는 속도도 빠르다.
- 편집할 수 없어 안정적이다. 리스트는 실수나 불의의 사고로 언제든지 값이 바뀔 수 있지만, 튜플은 한번 정해지면 절대로 바꿀 수 없어 실수할 위험이 없다. 데이터베이스에서 읽었거나 네트워크로 받은 정보는 단순히 참조하면 될 뿐 편집할 일이 없으므로 리스트보다 튜플로 받는 것이 더 안정적이다.
리스트와 튜플은 값 변경 가능성 여부만 다를 뿐 내부 구조가 비슷해서 상호 변환 가능하다. 리스트를 튜플로 바꿀 때는 tuple 함수를 사용하며 튜플로 바뀌면 편집할 수 없다. 반대로 튜플을 리스트로 변환할 때는 list 함수를 사용하며 리스트로 바꾼 후 편집 가능하다.
score = [88, 96, 100, 70, 65] tu = tuple(score) # tu[0] = 100 print(tu) li = list(tu) li[0] = 100 print(li)
(88, 96, 100, 70, 65) [100, 96, 100, 70, 65]
'Python' 카테고리의 다른 글
[error] UnboundLocalError: local variable 'count' referenced before assignment 해결책 (0) 2021.01.20 10. 사전과 집합 (0) 2021.01.20 08. 문자열 관리 (0) 2021.01.20 07. 함수 (0) 2021.01.20 06. 반복문 (0) 2021.01.20