소개
이 섹션에서는 리스트 (lists), 딕셔너리 (dictionaries), 그리고 세트 (sets) 에 대해 논의합니다.
개요
프로그램은 종종 많은 객체와 함께 작동해야 합니다.
- 주식 포트폴리오
- 주가 테이블
사용할 수 있는 세 가지 주요 선택 사항이 있습니다.
- 리스트 (Lists). 정렬된 데이터.
- 딕셔너리 (Dictionaries). 정렬되지 않은 데이터.
- 세트 (Sets). 고유 항목의 정렬되지 않은 컬렉션.
컨테이너로서의 리스트 (Lists)
데이터의 순서가 중요한 경우 리스트를 사용합니다. 리스트는 어떤 종류의 객체든 담을 수 있다는 것을 기억하세요. 예를 들어, 튜플 (tuple) 의 리스트가 있습니다.
portfolio = [
('GOOG', 100, 490.1),
('IBM', 50, 91.3),
('CAT', 150, 83.44)
]
portfolio[0] ## ('GOOG', 100, 490.1)
portfolio[2] ## ('CAT', 150, 83.44)
리스트 구성
처음부터 리스트를 구성합니다.
records = [] ## Initial empty list
## .append() 를 사용하여 더 많은 항목을 추가합니다.
records.append(('GOOG', 100, 490.10))
records.append(('IBM', 50, 91.3))
...
파일에서 레코드를 읽을 때의 예시입니다.
records = [] ## Initial empty list
with open('portfolio.csv', 'rt') as f:
next(f) ## Skip header
for line in f:
row = line.split(',')
records.append((row[0], int(row[1]), float(row[2])))
컨테이너로서의 딕셔너리 (Dicts)
딕셔너리는 (키 이름으로) 빠른 무작위 조회 (random lookup) 를 원할 때 유용합니다. 예를 들어, 주식 가격의 딕셔너리가 있습니다.
prices = {
'GOOG': 513.25,
'CAT': 87.22,
'IBM': 93.37,
'MSFT': 44.12
}
다음은 몇 가지 간단한 조회 예시입니다.
>>> prices['IBM']
93.37
>>> prices['GOOG']
513.25
>>>
딕셔너리 구성
처음부터 딕셔너리를 구성하는 예시입니다.
prices = {} ## Initial empty dict
## 새로운 항목 삽입
prices['GOOG'] = 513.25
prices['CAT'] = 87.22
prices['IBM'] = 93.37
파일의 내용으로 딕셔너리를 채우는 예시입니다.
prices = {} ## Initial empty dict
with open('prices.csv', 'rt') as f:
for line in f:
row = line.split(',')
prices[row[0]] = float(row[1])
참고: prices.csv 파일에서 이 코드를 실행하면 거의 작동하지만, 마지막에 빈 줄이 있어 충돌이 발생합니다. 이를 해결하기 위해 코드를 수정하는 방법을 찾아야 합니다 (연습문제 2.6 참조).
딕셔너리 조회
키의 존재 여부를 테스트할 수 있습니다.
if key in d:
## YES
else:
## NO
존재하지 않을 수 있는 값을 조회하고, 존재하지 않는 경우 기본값을 제공할 수 있습니다.
name = d.get(key, default)
예시:
>>> prices.get('IBM', 0.0)
93.37
>>> prices.get('SCOX', 0.0)
0.0
>>>
복합 키 (Composite keys)
Python 에서는 거의 모든 유형의 값을 딕셔너리 키로 사용할 수 있습니다. 딕셔너리 키는 불변 (immutable) 타입이어야 합니다. 예를 들어, 튜플 (tuple) 과 같습니다.
holidays = {
(1, 1) : 'New Years',
(3, 14) : 'Pi day',
(9, 13) : "Programmer's day",
}
접근 방법:
>>> holidays[3, 14]
'Pi day'
>>>
리스트 (list), 세트 (set), 또는 다른 딕셔너리는 딕셔너리 키로 사용할 수 없습니다. 리스트와 딕셔너리는 가변 (mutable) 이기 때문입니다.
세트 (Sets)
세트는 정렬되지 않은 고유 항목들의 모음입니다.
tech_stocks = { 'IBM','AAPL','MSFT' }
## Alternative syntax
tech_stocks = set(['IBM', 'AAPL', 'MSFT'])
세트는 멤버십 테스트 (membership tests) 에 유용합니다.
>>> tech_stocks
set(['AAPL', 'IBM', 'MSFT'])
>>> 'IBM' in tech_stocks
True
>>> 'FB' in tech_stocks
False
>>>
세트는 중복 제거에도 유용합니다.
names = ['IBM', 'AAPL', 'GOOG', 'IBM', 'GOOG', 'YHOO']
unique = set(names)
## unique = set(['IBM', 'AAPL','GOOG','YHOO'])
추가적인 세트 연산:
unique.add('CAT') ## Add an item
unique.remove('YHOO') ## Remove an item
s1 = { 'a', 'b', 'c'}
s2 = { 'c', 'd' }
s1 | s2 ## Set union { 'a', 'b', 'c', 'd' }
s1 & s2 ## Set intersection { 'c' }
s1 - s2 ## Set difference { 'a', 'b' }
이 연습에서는 이 과정의 나머지 부분에서 사용될 주요 프로그램 중 하나를 구축하기 시작합니다. report.py 파일에서 작업을 수행하십시오.
연습 문제 2.4: 튜플의 리스트
portfolio.csv 파일에는 포트폴리오에 있는 주식 목록이 포함되어 있습니다. 연습 문제 1.30 에서 이 파일을 읽고 간단한 계산을 수행하는 함수 portfolio_cost(filename)을 작성했습니다.
작성한 코드는 다음과 유사해야 합니다.
## pcost.py
import csv
def portfolio_cost(filename):
'''Computes the total cost (shares*price) of a portfolio file'''
total_cost = 0.0
with open(filename, 'rt') as f:
rows = csv.reader(f)
headers = next(rows)
for row in rows:
nshares = int(row[1])
price = float(row[2])
total_cost += nshares * price
return total_cost
이 코드를 대략적인 가이드로 사용하여 새 파일 report.py를 만듭니다. 해당 파일에서 주어진 포트폴리오 파일을 열어 튜플의 리스트로 읽어 들이는 함수 read_portfolio(filename)을 정의합니다. 이렇게 하려면 위의 코드에 몇 가지 사소한 수정을 해야 합니다.
먼저, total_cost = 0을 정의하는 대신, 처음에는 빈 리스트로 설정된 변수를 만듭니다. 예를 들어:
portfolio = []
다음으로, 비용을 합산하는 대신, 각 행을 바로 이전 연습에서 했던 것처럼 튜플로 변환하고 이 리스트에 추가합니다. 예를 들어:
for row in rows:
holding = (row[0], int(row[1]), float(row[2]))
portfolio.append(holding)
마지막으로, 결과 portfolio 리스트를 반환합니다.
함수를 대화형으로 실험해 보십시오 (이 작업을 수행하려면 먼저 인터프리터에서 report.py 프로그램을 실행해야 함을 다시 한 번 상기시켜 드립니다).
힌트: 터미널에서 파일을 실행할 때 -i를 사용하십시오.
>>> portfolio = read_portfolio('/home/labex/project/portfolio.csv')
>>> portfolio
[('AA', 100, 32.2), ('IBM', 50, 91.1), ('CAT', 150, 83.44), ('MSFT', 200, 51.23),
('GE', 95, 40.37), ('MSFT', 50, 65.1), ('IBM', 100, 70.44)]
>>>
>>> portfolio[0]
('AA', 100, 32.2)
>>> portfolio[1]
('IBM', 50, 91.1)
>>> portfolio[1][1]
50
>>> total = 0.0
>>> for s in portfolio:
total += s[1] * s[2]
>>> print(total)
44671.15
>>>
이렇게 생성한 튜플의 리스트는 2 차원 배열과 매우 유사합니다. 예를 들어, portfolio[row][column]과 같은 조회를 사용하여 특정 열과 행에 접근할 수 있습니다. 여기서 row와 column은 정수입니다.
즉, 다음과 같은 문을 사용하여 마지막 for 루프를 다시 작성할 수도 있습니다.
>>> total = 0.0
>>> for name, shares, price in portfolio:
total += shares*price
>>> print(total)
44671.15
>>>
연습 문제 2.5: 딕셔너리의 리스트
연습 문제 2.4 에서 작성한 함수를 가져와 튜플 대신 딕셔너리로 포트폴리오의 각 주식을 나타내도록 수정합니다. 이 딕셔너리에서 "name", "shares", "price" 필드 이름을 사용하여 입력 파일의 서로 다른 열을 나타냅니다.
연습 문제 2.4 에서와 동일한 방식으로 이 새로운 함수를 실험해 보십시오.
>>> portfolio = read_portfolio('/home/labex/project/portfolio.csv')
>>> portfolio
[{'name': 'AA', 'shares': 100, 'price': 32.2}, {'name': 'IBM', 'shares': 50, 'price': 91.1},
{'name': 'CAT', 'shares': 150, 'price': 83.44}, {'name': 'MSFT', 'shares': 200, 'price': 51.23},
{'name': 'GE', 'shares': 95, 'price': 40.37}, {'name': 'MSFT', 'shares': 50, 'price': 65.1},
{'name': 'IBM', 'shares': 100, 'price': 70.44}]
>>> portfolio[0]
{'name': 'AA', 'shares': 100, 'price': 32.2}
>>> portfolio[1]
{'name': 'IBM', 'shares': 50, 'price': 91.1}
>>> portfolio[1]['shares']
50
>>> total = 0.0
>>> for s in portfolio:
total += s['shares']*s['price']
>>> print(total)
44671.15
>>>
여기에서 각 항목의 서로 다른 필드는 숫자 열 번호 대신 키 이름으로 액세스됨을 알 수 있습니다. 결과 코드가 나중에 읽기 더 쉽기 때문에 이 방법이 선호되는 경우가 많습니다.
큰 딕셔너리와 리스트를 보는 것은 지저분할 수 있습니다. 디버깅을 위해 출력을 정리하려면 pprint 함수를 사용하는 것을 고려하십시오.
>>> from pprint import pprint
>>> pprint(portfolio)
[{'name': 'AA', 'price': 32.2, 'shares': 100},
{'name': 'IBM', 'price': 91.1, 'shares': 50},
{'name': 'CAT', 'price': 83.44, 'shares': 150},
{'name': 'MSFT', 'price': 51.23, 'shares': 200},
{'name': 'GE', 'price': 40.37, 'shares': 95},
{'name': 'MSFT', 'price': 65.1, 'shares': 50},
{'name': 'IBM', 'price': 70.44, 'shares': 100}]
>>>
연습 문제 2.6: 컨테이너로서의 딕셔너리
딕셔너리는 정수가 아닌 다른 인덱스를 사용하여 항목을 조회하려는 경우 항목을 추적하는 데 유용한 방법입니다. Python 셸에서 딕셔너리를 가지고 놀아보십시오.
>>> prices = { }
>>> prices['IBM'] = 92.45
>>> prices['MSFT'] = 45.12
>>> prices
... 결과를 확인하십시오 ...
>>> prices['IBM']
92.45
>>> prices['AAPL']
... 결과를 확인하십시오 ...
>>> 'AAPL' in prices
False
>>>
prices.csv 파일에는 일련의 주가가 포함되어 있습니다. 파일은 다음과 같습니다.
"AA",9.22
"AXP",24.85
"BA",44.85
"BAC",11.27
"C",3.72
...
이와 같은 일련의 가격을 딕셔너리로 읽어 들이는 함수 read_prices(filename)을 작성하십시오. 여기서 딕셔너리의 키는 주식 이름이고 딕셔너리의 값은 주가입니다.
이렇게 하려면 빈 딕셔너리에서 시작하여 위에서 했던 것처럼 값을 삽입하십시오. 그러나 이제 파일에서 값을 읽고 있습니다.
이 데이터 구조를 사용하여 주어진 주식 이름의 가격을 빠르게 조회할 것입니다.
이 부분에 필요한 몇 가지 작은 팁이 있습니다. 먼저, 이전과 마찬가지로 csv 모듈을 사용해야 합니다. 여기서는 바퀴를 다시 발명할 필요가 없습니다.
>>> import csv
>>> f = open('/home/labex/project/prices.csv', 'r')
>>> rows = csv.reader(f)
>>> for row in rows:
print(row)
['AA', '9.22']
['AXP', '24.85']
...
[]
>>>
또 다른 작은 복잡한 점은 prices.csv 파일에 빈 줄이 있을 수 있다는 것입니다. 위의 마지막 데이터 행이 빈 목록인 것을 확인하십시오. 즉, 해당 줄에 데이터가 없었습니다.
이로 인해 프로그램이 예외와 함께 종료될 수 있습니다. try 및 except 문을 사용하여 적절하게 이를 잡으십시오. 생각: if 문을 사용하여 잘못된 데이터를 방지하는 것이 더 나을까요?
read_prices() 함수를 작성했으면 대화형으로 테스트하여 작동하는지 확인하십시오.
>>> prices = read_prices('/home/labex/project/prices.csv')
>>> prices['IBM']
106.28
>>> prices['MSFT']
20.89
>>>
연습 문제 2.7: 은퇴 가능 여부 확인
이 모든 작업을 report.py 프로그램에 이익/손실을 계산하는 몇 가지 추가 문을 추가하여 함께 묶습니다. 이러한 문은 연습 문제 2.5 의 주식 목록과 연습 문제 2.6 의 가격 딕셔너리를 가져와 포트폴리오의 현재 가치와 이익/손실을 계산해야 합니다.
요약
축하합니다! 컨테이너 랩을 완료했습니다. LabEx 에서 더 많은 랩을 연습하여 기술을 향상시킬 수 있습니다.