소개
일반적인 작업은 목록의 항목을 처리하는 것입니다. 이 섹션에서는 바로 그 작업을 수행하기 위한 강력한 도구인 리스트 컴프리헨션 (list comprehensions) 을 소개합니다.
새로운 리스트 생성
리스트 컴프리헨션은 시퀀스의 각 요소에 연산을 적용하여 새로운 리스트를 생성합니다.
>>> a = [1, 2, 3, 4, 5]
>>> b = [2*x for x in a ]
>>> b
[2, 4, 6, 8, 10]
>>>
또 다른 예시:
>>> names = ['Elwood', 'Jake']
>>> a = [name.lower() for name in names]
>>> a
['elwood', 'jake']
>>>
일반적인 구문은 다음과 같습니다: [ <expression> for <variable_name> in <sequence> ].
필터링
리스트 컴프리헨션 동안 필터링도 할 수 있습니다.
>>> a = [1, -5, 4, 2, -2, 10]
>>> b = [2*x for x in a if x > 0 ]
>>> b
[2, 8, 4, 20]
>>>
사용 사례
리스트 컴프리헨션은 매우 유용합니다. 예를 들어, 특정 딕셔너리 필드의 값을 수집할 수 있습니다.
stocknames = [s['name'] for s in stocks]
시퀀스에 대해 데이터베이스와 유사한 쿼리를 수행할 수 있습니다.
a = [s for s in stocks if s['price'] > 100 and s['shares'] > 50 ]
또한 리스트 컴프리헨션과 시퀀스 축약을 결합할 수 있습니다.
cost = sum([s['shares']*s['price'] for s in stocks])
일반적인 구문 (General Syntax)
[ <expression> for <variable_name> in <sequence> if <condition>]
의미:
result = []
for variable_name in sequence:
if condition:
result.append(expression)
역사적 변천 (Historical Digression)
리스트 컴프리헨션은 수학에서 유래되었습니다 (집합 - 구성 표기법).
a = [ x * x for x in s if x > 0 ] ## Python
a = { x^2 | x ∈ s, x > 0 } ## Math
또한 다른 여러 언어에서도 구현되었습니다. 대부분의 코더는 아마도 수학 수업을 생각하지 않을 것입니다. 따라서, 이것을 멋진 리스트 단축키로 보는 것도 괜찮습니다.
연습 문제 2.19: 리스트 컴프리헨션 (List comprehensions)
구문에 익숙해지기 위해 몇 가지 간단한 리스트 컴프리헨션을 시도해 보세요.
>>> nums = [1,2,3,4]
>>> squares = [ x * x for x in nums ]
>>> squares
[1, 4, 9, 16]
>>> twice = [ 2 * x for x in nums if x > 2 ]
>>> twice
[6, 8]
>>>
리스트 컴프리헨션이 데이터를 적절하게 변환하거나 필터링하여 새로운 리스트를 생성하는 방식을 확인하세요.
연습 문제 2.20: 시퀀스 축약 (Sequence Reductions)
단일 Python 문을 사용하여 포트폴리오의 총 비용을 계산합니다.
>>> portfolio = read_portfolio('portfolio.csv')
>>> cost = sum([ s['shares'] * s['price'] for s in portfolio ])
>>> cost
44671.15
>>>
그 후, 단일 문을 사용하여 포트폴리오의 현재 가치를 계산하는 방법을 보여주세요.
>>> value = sum([ s['shares'] * prices[s['name']] for s in portfolio ])
>>> value
28686.1
>>>
위의 두 연산 모두 맵 - 리덕션 (map-reduction) 의 예입니다. 리스트 컴프리헨션은 리스트 전체에 연산을 매핑합니다.
>>> [ s['shares'] * s['price'] for s in portfolio ]
[3220.0000000000005, 4555.0, 12516.0, 10246.0, 3835.1499999999996, 3254.9999999999995, 7044.0]
>>>
그런 다음 sum() 함수는 결과에 대한 축약 (reduction) 을 수행합니다.
>>> sum(_)
44671.15
>>>
이 지식을 바탕으로 이제 빅데이터 스타트업 회사를 시작할 준비가 되었습니다.
연습 문제 2.21: 데이터 쿼리 (Data Queries)
다양한 데이터 쿼리의 다음 예제를 시도해 보세요.
먼저, 100 주 이상 보유한 모든 포트폴리오 보유 목록입니다.
>>> more100 = [ s for s in portfolio if s['shares'] > 100 ]
>>> more100
[{'price': 83.44, 'name': 'CAT', 'shares': 150}, {'price': 51.23, 'name': 'MSFT', 'shares': 200}]
>>>
MSFT 및 IBM 주식에 대한 모든 포트폴리오 보유 목록입니다.
>>> msftibm = [ s for s in portfolio if s['name'] in {'MSFT','IBM'} ]
>>> msftibm
[{'price': 91.1, 'name': 'IBM', 'shares': 50}, {'price': 51.23, 'name': 'MSFT', 'shares': 200},
{'price': 65.1, 'name': 'MSFT', 'shares': 50}, {'price': 70.44, 'name': 'IBM', 'shares': 100}]
>>>
10,000 달러 이상 비용이 드는 모든 포트폴리오 보유 목록입니다.
>>> cost10k = [ s for s in portfolio if s['shares'] * s['price'] > 10000 ]
>>> cost10k
[{'price': 83.44, 'name': 'CAT', 'shares': 150}, {'price': 51.23, 'name': 'MSFT', 'shares': 200}]
>>>
연습 문제 2.22: 데이터 추출 (Data Extraction)
name과 shares가 portfolio에서 가져온 튜플 (name, shares)의 목록을 어떻게 만들 수 있는지 보여주세요.
>>> name_shares =[ (s['name'], s['shares']) for s in portfolio ]
>>> name_shares
[('AA', 100), ('IBM', 50), ('CAT', 150), ('MSFT', 200), ('GE', 95), ('MSFT', 50), ('IBM', 100)]
>>>
대괄호 ([,]) 를 중괄호 ({, }) 로 변경하면 집합 컴프리헨션 (set comprehension) 이라고 하는 것을 얻게 됩니다. 이는 고유하거나 구별되는 값을 제공합니다.
예를 들어, 이것은 portfolio에 나타나는 고유한 주식 이름의 집합을 결정합니다.
>>> names = { s['name'] for s in portfolio }
>>> names
{ 'AA', 'GE', 'IBM', 'MSFT', 'CAT' }
>>>
key:value 쌍을 지정하면 딕셔너리를 만들 수 있습니다. 예를 들어, 주식 이름을 보유한 총 주식 수에 매핑하는 딕셔너리를 만듭니다.
>>> holdings = { name: 0 for name in names }
>>> holdings
{'AA': 0, 'GE': 0, 'IBM': 0, 'MSFT': 0, 'CAT': 0}
>>>
이 후자의 기능은 **딕셔너리 컴프리헨션 (dictionary comprehension)**이라고 합니다. 표로 정리해 보겠습니다.
>>> for s in portfolio:
holdings[s['name']] += s['shares']
>>> holdings
{ 'AA': 100, 'GE': 95, 'IBM': 150, 'MSFT':250, 'CAT': 150 }
>>>
prices 딕셔너리를 포트폴리오에 나타나는 이름만으로 필터링하는 이 예제를 시도해 보세요.
>>> portfolio_prices = { name: prices[name] for name in names }
>>> portfolio_prices
{'AA': 9.22, 'GE': 13.48, 'IBM': 106.28, 'MSFT': 20.89, 'CAT': 35.46}
>>>
연습 문제 2.23: CSV 파일에서 데이터 추출
리스트, 집합 및 딕셔너리 컴프리헨션 (list, set, and dictionary comprehensions) 의 다양한 조합을 사용하는 방법을 아는 것은 다양한 형태의 데이터 처리에 유용할 수 있습니다. 다음은 CSV 파일에서 선택한 열을 추출하는 방법을 보여주는 예제입니다.
먼저, CSV 파일에서 헤더 정보 행을 읽습니다.
>>> import csv
>>> f = open('portfoliodate.csv')
>>> rows = csv.reader(f)
>>> headers = next(rows)
>>> headers
['name', 'date', 'time', 'shares', 'price']
>>>
다음으로, 실제로 관심 있는 열을 나열하는 변수를 정의합니다.
>>> select = ['name', 'shares', 'price']
>>>
이제 소스 CSV 파일에서 위의 열의 인덱스를 찾습니다.
>>> indices = [ headers.index(colname) for colname in select ]
>>> indices
[0, 3, 4]
>>>
마지막으로, 데이터 행을 읽고 딕셔너리 컴프리헨션을 사용하여 딕셔너리로 변환합니다.
>>> row = next(rows)
>>> record = { colname: row[index] for colname, index in zip(select, indices) } ## dict-comprehension
>>> record
{'price': '32.20', 'name': 'AA', 'shares': '100'}
>>>
방금 일어난 일에 익숙하다면, 파일의 나머지 부분을 읽으세요.
>>> portfolio = [ { colname: row[index] for colname, index in zip(select, indices) } for row in rows ]
>>> portfolio
[{'price': '91.10', 'name': 'IBM', 'shares': '50'}, {'price': '83.44', 'name': 'CAT', 'shares': '150'},
{'price': '51.23', 'name': 'MSFT', 'shares': '200'}, {'price': '40.37', 'name': 'GE', 'shares': '95'},
{'price': '65.10', 'name': 'MSFT', 'shares': '50'}, {'price': '70.44', 'name': 'IBM', 'shares': '100'}]
>>>
맙소사, read_portfolio() 함수의 많은 부분을 단일 문장으로 줄였습니다.
해설 (Commentary)
리스트 컴프리헨션 (list comprehensions) 은 데이터를 변환, 필터링 또는 수집하는 효율적인 수단으로 파이썬에서 일반적으로 사용됩니다. 구문 때문에 과도하게 사용하지 않도록 주의하고, 각 리스트 컴프리헨션을 가능한 한 간단하게 유지하려고 노력해야 합니다. 여러 단계로 나누는 것도 괜찮습니다. 예를 들어, 마지막 예제를 예상치 못한 동료들에게 갑자기 보여주는 것은 명확하지 않을 수 있습니다.
그렇긴 하지만, 데이터를 빠르게 조작하는 방법을 아는 것은 매우 유용한 기술입니다. 데이터 가져오기, 내보내기, 추출 등과 관련된 일회성 문제를 해결해야 하는 상황이 많이 있습니다. 리스트 컴프리헨션의 전문가가 되면 솔루션을 고안하는 데 소요되는 시간을 상당히 줄일 수 있습니다. 또한, collections 모듈도 잊지 마세요.
요약 (Summary)
축하합니다! 리스트 컴프리헨션 랩을 완료했습니다. LabEx 에서 더 많은 랩을 연습하여 실력을 향상시킬 수 있습니다.