본문 바로가기

문돌이 존버/프로그래밍 스터디

List, Set, Dictionary 비전공자 문돌이가 설명하는 파이썬(Python) 기본 문법 (2)

반응형

1. 리스트(List)란? [ ]

리스트는 각 오브젝트를 "그룹화" 한다고 생각하시면 됩니다. 아래를 보시면 리스트는 내부 오브젝트에 인덱스를 부여하기 때문에 인덱스를 통해 출력할 수 있습니다.

company = ['kakao', 'naver', 'aws', 'google', 'apple']
company[0]
company[-1]

리스트 내부 오브젝트는 오버라이딩이 가능합니다. 즉 다른 값으로 대체할 수 있다는 뜻입니다. 

company[0] = 'facebook'
print(company[0])

리스트는 어떤 타입의 데이터든 그룹화할 수 있습니다. 심지어 그룹 내 데이터 형태가 각기 달라도 됩니다. 이는 편해보일 수도 있지만 코드를 작성한 사람이 잘 기억하고 있어야겠죠. 몇 번째가 문자열인지, 몇 번째가 숫자형인지 말이죠. 

company_detail = ['apple', 12, 'iphone'] # apple, iphone -> string / 12 -> integer
company_detail

아래는 리스트 내부에 소수점(float) 타입의 데이터가 들어간다는 것을 명시적으로 표기한 것입니다. 

from typing import List
def average(L: List[float]) -> float:
    ...

아래는 리스트에 사용할 수 있는 기본적인 함수 및 연산 방법입니다. 

A = [1, 2, 3, 4, 5, 6]
print(len(A))
print(max(A))
print(min(A))
print(sum(A))
print(sorted(A))
print(A + [2, 3, 5])
print(A * 2)

del A[1] # 인덱스 1번에 해당하는 값 삭제
print(A)

"apple" in company

슬라이싱(slicing)을 하면 리스트를 출력합니다. 그리고 마지막 숫자에 해당하는 인덱스는 포함되지 않는다는 것에 주의해주세요! 

print(company)
print(company[1: 3])
print(company[1:-1]) # company[-1] = 'apple'
print(company[:3])
print(company[:])

복사(Copy)와 알리아스(Alias)의 차이점은?

복사와 알리아스의 개념은 조금 다릅니다. 복사는 말 그대로 나의 복제본이 생긴 느낌이라 복제본이 바뀌어도, 아예 사라진다 해도 "나"는 그대로 남아 있습니다. 하지만 알리아스는 나의 분신으로 분신에 변화가 생긴다면 나도 영향을 받습니다. 분신의 첫 번째 값이 'kakao'에서 'MS'로 바뀌면 나의 첫 번째 값도 'MS'로 바뀌게 됩니다. 

리스트를 조작할 때 주의할 필요가 있겠죠? 특히 함수를 정의할 때 잘 생각하셔서 복제본을 넘길지, 분신을 넘길지 고민해야 합니다. 

# Copy
company_version1 = company[:]
print(company_version1)
company_version1[0] = 'MS'
print(company) # 원래 company 리스트에는 영향이 없음
print(company_version1)

# Alias
company_version2 = company
company_version2[0] = 'MS'
print(company_version2)
print(company) # 원래 company 리스트도 바뀌게 됨

company_copy = company[:] # copy를 쓰자!
company_copy.append('kakao')
print(company_copy)

company_copy.clear() # 요소(element) 전부 삭제
print(company_copy)

company_copy = company[:] # 다시 살리기
print(company_copy.index('aws'))

company_copy.insert(3, 'openai') # index=3 위치에 삽입
print(company_copy)

company_copy.pop() # 마지막 요소 제거
print(company_copy)

company_copy.remove('aws')
print(company_copy)

company_copy.reverse() # 거꾸로 정렬
print(company_copy)

company_copy.sort() # 알파벳 순서대로 정렬
print(company_copy)

company_copy.sort(reverse=True) # 알파벳 순서대로 역정렬
print(company_copy)

리스트의 리스트

company_list = [['IT', 'facebook', 'naver', 'kakao'], ['Media', 'bloomberg', 'newyorktimes'], 
['Finance', 'jpmorganchase', 'boa']]
print(company_list[0])
print(company_list[1][0])
print(company_list[2][1])

2. Loop이란?

특정 프로세스를 반복적으로 진행하는 과정으로 for loop가 대표적입니다. "Do you like <company_name> ?"를 반복적으로 출력하고 싶은데, 아래처럼 단 2줄의 코드로 완성할 수 있습니다. 리스트에 요소가 많아질수록 훨씬 작업이 편해짐을 느낄 수 있을 것입니다. 

for company_name in company:
    print("Do you like", company_name, "?")

for loop은 리스트, 문자열, 숫자형, 딕셔너리 모두 가능합니다. 또 흥미로운 것은 range() 인데요, ()안에는 숫자가 들어가 설정된 범위에 따라 반복적으로 살펴보겠다는 것입니다.

for num in range(10): # range(10)은 인덱스=0부터 시작
    print("The number is:", num) 
    
# for num in range(1:10) <- 인덱스=0부터 시작하기 싫을 때 

리스트에서 복사와 알리아스를 살펴봤는데, 마찬가지로 루프에서는 인덱싱(indexing)를 주의해야 합니다. 

values = [0, 1, 2, 3, 4]
for num in values:
    num = num * 3
    print(num)
print(values)

# 인덱스를 사용하면 원본이 바뀜
for i in values:
    values[i] = values[i] * 2
print(values)

이번엔 while loop을 알아보겠습니다. while 문은 조건이 만족하는 한 계속 반복을 진행하기 때문에, 끝날 수 있는 조건을 반드시 선언해야 합니다. 조건이 없다면 무한 루프(infitie loop)에 빠지게 되는 문제가 발생합니다. 물론, Ctrl + C를 눌러 강제 종료할 수 있습니다. 

count = 0
while count < 5:
    print('okay')
    count += 1

breakcontinue에 대해서도 알아봅시다. break는 내가 원하는 값을 얻었을 때 반복 프로그램을 멈출 수 있도록 해주는 기능을 합니다.

아래에서 우리는 문자열에서 처음으로 등장하는 대문자의 위치를 알고 싶습니다. 즉 H의 위치를 알고 싶지, 뒤에 등장하는 B의 위치를 알고 싶지 않습니다. 따라서 첫 대문자 위치를 알게 되면 break 선언을 통해 반복 기능을 제거하는 것이죠. 

first_upper_index = -5
random_string = 'How are you doing, Babe?'
for char in range(len(random_string)):
    if random_string[char].isupper():
        first_upper_index = char
        break

first_upper_index # B -> index=19

아래는 continue를 사용한 것입니다. continue는 break와 반대로 어떤 조건에 해당하면 계속 뒤에 남아있는 코드를 진행하라는 것입니다. 여기선 소문자라면 넘어가고 first_uppder_index를 업데이트하는 코드를 진행하는 것이죠. 만약 소문자가 아니라면 first_update_index를 업데이트하는 동시에 break가 되어 코드가 종료됩니다.

그런데 우리는 B의 index=19를 기대했는데, 아래는 3을 출력했네요. 그 이유는 무엇일까요? 답은 댓글에 달아두겠습니다! 

first_upper_index = -5
random_string = 'how are you doing, Babe?'
for char in range(len(random_string)):
    if random_string[char].islower():
        continue
    first_upper_index = char
    break

first_upper_index

3. Set이란? { }

리스트와 달리 순서가 없고 별개의 원소로 구성되어 있습니다. 하지만 리스트와 마찬가지로 수정 가능하고(mutable) 함수의 인자로 사용될 수 있습니다. 또 중요한 점은 int, str, bool, float처럼 클래스입니다!

순서가 없다는 의미는 중복된 값을 허용하지 않는다는 것입니다. 아래 예시를 살펴보면 이해가 가능합니다. 

alphabet = {"a", "b", "c", "a", "b", "d"}
set(alphabet)

아래와 같이 a를 set()으로 선언하는 경우 클래스 자체가 반환됩니다. 

# a = set()
# a
>>> set()

# 리스트를 set으로 변환 가능
company = set(['apple', 'apple', 'samsung', 'google', 'huawei']) 
for i in company:
    print(i)

Set의 메서드를 살펴봅시다. 

digits = set([0, 1, 2, 3, 4, 5])
odds = set([1, 3, 5, 7, 9])

digits.add(6) # 어디에 추가한지는 중요하지 않음, set은 순서가 상관없음
print(digits) 
digits.remove(2)
print(digits)
digits.clear()
print(digits)

digits = set([0, 1, 2, 3, 4, 5]) # 없어진 set 다시 살리기
print(digits.issubset(odds)) # 부분집합인지 표시
print(digits.issuperset(odds)) # 부모집합인지 표시

print(digits.difference(odds)) # digits를 기준으로 한 차집합
print(digits.intersection(odds)) # 교집합
print(digits.symmetric_difference(odds)) # 합집합 - 교집합
print(digits.union(odds)) # 합집합 

set 자체는 추가, 삭제 등 바꿀 수 있으나 요소는 값을 바꿀 수 없습니다. 반면, 리스트는 요소 역시 수정 가능한 mutable object이기 때문에 아래처럼 코드를 작성하면 오류가 발생합니다. 요소 수정이 가능한 리스트를 set 내부에 추가하지 못하겠다는 의미죠. 이는 set이 요소를 찾을 때 hashing 기법을 사용하는 것과 관련되어 있고, 따라서 리스트보다 요소를 찾는 속도가 훨씬 빠릅니다. hashing 기법에 대해선 다음에 설명하도록 하겠습니다. 

S = set()
L = [1, 2, 3] # mutable object
S.add(L)

4. 튜플(Tuple)이란? ( )

튜플은 리스트와 마찬가지로 순서를 가지고 있습니다. 하지만 set과 똑같이 요소를 수정할 수는 없습니다. 

nums = ()
print(type(nums))
nums = (8, ) # 원소가 1개일 때, 튜플을 위해선 쉼표 필수
nums = (5+3, ) 
print(nums)

nums = (1, 2, 3, 4)
for num in nums:
    print(num)

튜플은 원소를 수정할 수 없지만, 순서가 존재하기 때문에 인덱스로 값을 불러올 수 있습니다. 원소 추가는 튜플 간 연산으로 가능합니다. 

company = ('LG', 'HD', 'Samsung', 'SK')
company[0] = 'KT'

company = ('LG', 'HD', 'Samsung', 'SK')
company += ('KT', ) # 쉼표 주의
company[-1]

재밌는 점은 튜플 원소 내의 원소가 mutable 가능할 경우, 해당 원소를 바꿀 수 있습니다. 아래 예시를 들어 설명하면, 튜플 company 내 리스트 ['LG', 'korea'] 내 원소 'korea'는 값을 변경할 수 있다는 것입니다. k를 대문자로 다시 선언했고, 그 결과 대문자로 바뀌어 있습니다. 

company = (['LG', 'korea'], ['HD', 'Korea'], ['Samsung', 'Korea'], ['apple', 'USA'])
company[0][1] = 'Korea'
company

5. 딕셔너리(Dictionary)란? { }

딕셔너리는 set과 비슷하게 순서가 없는, list와 비슷하게 원소 수정이 가능합니다. 특이한 점은 원소가 키(key)와 밸류(value)로 구성되어 있다는 것인데요. 키는 리스트의 인덱스와 비슷한 역할을 해서 키로 밸류값을 찾기도 합니다. 

dict_demo = {} # 선언하기
dict_company = {'SK': 'KOREA', 'GOOGLE': 'USA'}
dict_company['SK'] 

딕셔너리 키는 수정 불가하며 리스트의 인덱스처럼 별도로(distinct) 존재하기 때문에 set과 마찬가지로 hashing을 사용하기 때문에 찾는 속도가 빠릅니다. 

dict_company = {'SK': 'koreaaa', 'GOOGLE': 'USA'}
dict_company['SK'] = 'KOREA'
print(dict_company)

print('GOOGLE' in dict_company)
del dict_company['GOOGLE'] # key-value 쌍이 동시에 지워짐
dict_company

dict_company = {'SK': 'KOREA', 'GOOGLE': 'USA'}
for company in dict_company: # company는 key를 가리킴
    print(company, "was founded in ", dict_company[company])

다음은 딕셔너리의 여러 메서드들입니다. 

dict_company.clear()
dict_company.keys() # 모든 키 반환
dict_company.items() # key-value 쌍 반환
dict_company.values() # 모든 밸류 반환
dict_company.get('SK') # 키에 따른 밸류 반환
dict_company.get('SK', default) # SK가 딕셔너리에 있다면 해당 밸류 반환 or 없다면 우리가 임의로 정한 default 값 반환
dict_company.pop('SK') # 키를 입력하면 해당 key-value 쌍 삭제, 키에 해당하는 밸류값 출력
dict_company.pop('SK', default)

# 결과는 아래 예시 코드 참고
dict_company.setdefault('SK') # 키에 따른 밸류 반환, 키가 없다면 밸류로 None이 추가 
dict_company.setdefault('SK', default)
dict_company.update(dict2)
dict_company.setdefault('LG')
print(dict_company)

dict_company = {'SK': 'KOREA', 'GOOGLE': 'USA'}
dict_company.setdefault('LG', 'KOREA') # 디폴트 밸류 값 = KOREA
print(dict_company)

dict_company2 = {'Huawei': 'CHINA', 'SONY': 'JAPAN'}
dict_company.update(dict_company2) # 새로운 딕셔너리 추가
dict_company2

딕셔너리의 in 연산은 키에만 해당합니다. 즉 키가 딕셔너리에 속해 있는지 알려주는 것이지, 밸류가 딕셔너리에 속한지는 알지 못합니다. 

dict_company = {'SK': 'KOREA', 'GOOGLE': 'USA'}

print('KOREA' in dict_company)
print('SK' in dict_company)

 

수정 가능함(Mutability)이란?

1. List(mutable container with mutable elements)

리스트는 컨테이너도 mutable, 원소도 mutable합니다. 아래 바깥 리스트를 부모 리스트(혹은 컨테이너=container)로, 내부 리스트를 자식 리스트(혹은 원소=element)라 칭하겠습니다. 

list_parent = [['SK', 'KOREA'], ['SAMSUNG', 'KOREA'], ['GOOGLE', 'USA']]
list_parent

리스트는 부모 리스트와 자식 리스트 모두 수정 가능한 경우입니다. 보통 부모 리스트가 수정 가능하다는 것은 1) 자식 리스트가 추가되거나 제거될 수 있고, 2) 자식 리스트가 가리키는 오브젝트(메모리 공간)가 달라질 수 있다는 것을 의미합니다. 예시를 살펴보겠습니다. 

list_parent.append(['NAVER', 'KOREA'])
print(list_parent) # 추가

list_parent[0] = ['KAKAO', 'KOREA']
print(list_parent) # list_append[0]이 가리키는 오브젝트가 ['SK', 'KOREA']에서 ['KAKAO', 'KOREA']로 바뀜

2. Tuple(immutable container with mutable elements)

튜플은 리스트와 다릅니다. 자식 리스트를 추가는 할 수 있지만, 일반적으로 자식 리스트 수정 및 삭제가 불가합니다. 튜플 내 자식 리스트가 가리키는 오브젝트는 변하지 않습니다. 

list_parent = (['SK', 'KOREA'], ['SAMSUNG', 'KOREA'], ['GOOGLE', 'USA'])
list_parent += (['NAVER', 'KOREA'],)
print(list_parent)

list_parent[0] = ['KAKAO', 'KOREA'] # 자식 리스트 수정 불가(메모리 공간 상 오브젝트 불변)
print(list_parent)

하지만 위에서도 언급했듯이, 1) 자식 리스트를 다시 부모 리스트로 보고, 2) 각 문자열을 자식 원소로 본다면 리스트는 원소 수정이 가능하기 때문에 값을 변경할 수 있습니다. 예시를 통해 쉽게 이해합시다.

# list_parent[0] -> ['SK', 'KOREA']
list_parent[0][0] = 'KT' # 원래 값은 'SK'
list_parent

3. Set(mutable container with immutable elements)

set은 추가, 삭제는 자유롭게 가능하기 때문에 자식 원소가 가리키는 오브젝트가 바뀔 수 있습니다. 하지만 자식 원소를 1) 부모로 생각했을 때, 2) 그 자식 원소는 수정 불가합니다. 아래 예시를 봅시다. 

list_parent = set(['SK', 'KT', 'KAKAO', 'NAVER'])

list_parent.remove('SK') # 'SK'가 가리키는 오브젝트는 메모리 공간 상에서 사라짐
list_parent

아래 ['MS', 'APPLE']은 set 입장에서 자식 원소에 해당합니다. 하지만 ['MS', 'APPLE'] 자체를 부모 원소로 생각한다면, 자식 원소인 'MS', 'APPLE'은 수정 가능합니다. 하지만 set은 이 경우를 지원하지 않는다는 것이죠. 쉽게 말해, 자식은 바뀌어도 자식의 자식이 바뀌는 경우는 있을 수 없다는 것입니다. 

list_parent.add(['MS', 'APPLE'])

이어서 다음에 설명하겠습니다. 비전공자 문돌이가 설명하는 파이썬(Python) 기본 문법

 

비전공자 문돌이가 설명하는 파이썬(Python) 기본 문법 (3)

1. File I/O란? File I/O = 파일을 입력하고 출력한다는 뜻으로 I/O는 Input/Output의 약자입니다. 간단히 말해, File I/O는 이미 존재하는 데이터 파일을 파이썬에서 읽고, 쓰는 것을 말합니다. file = open('exa..

moondol-ai.tistory.com

 

728x90
반응형