Python 시퀀스 기본

Intermediate

This tutorial is from open-source community. Access the source code

소개

Python 시퀀스는 항목의 정렬된 컬렉션입니다. 정수로 인덱싱됩니다.

이것은 가이드 실험입니다. 학습과 실습을 돕기 위한 단계별 지침을 제공합니다.각 단계를 완료하고 실무 경험을 쌓기 위해 지침을 주의 깊게 따르세요. 과거 데이터에 따르면, 이것은 중급 레벨의 실험이며 완료율은 80%입니다.학습자들로부터 100%의 긍정적인 리뷰율을 받았습니다.

시퀀스 데이터 타입 (Sequence Datatypes)

Python 에는 세 가지 시퀀스 데이터 타입이 있습니다.

  • 문자열 (String): 'Hello'. 문자열은 문자들의 시퀀스입니다.
  • 리스트 (List): [1, 4, 5].
  • 튜플 (Tuple): ('GOOG', 100, 490.1).

모든 시퀀스는 정렬되어 있으며, 정수로 인덱싱되고, 길이를 갖습니다.

a = 'Hello'               ## String
b = [1, 4, 5]             ## List
c = ('GOOG', 100, 490.1)  ## Tuple

## Indexed order
a[0]                      ## 'H'
b[-1]                     ## 5
c[1]                      ## 100

## Length of sequence
len(a)                    ## 5
len(b)                    ## 3
len(c)                    ## 3

시퀀스는 반복될 수 있습니다: s * n.

>>> a = 'Hello'
>>> a * 3
'HelloHelloHello'
>>> b = [1, 2, 3]
>>> b * 2
[1, 2, 3, 1, 2, 3]
>>>

같은 타입의 시퀀스는 연결될 수 있습니다: s + t.

>>> a = (1, 2, 3)
>>> b = (4, 5)
>>> a + b
(1, 2, 3, 4, 5)
>>>
>>> c = [1, 5]
>>> a + c
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate tuple (not "list") to tuple

슬라이싱 (Slicing)

슬라이싱은 시퀀스에서 부분 시퀀스를 가져오는 것을 의미합니다. 구문은 s[start:end]입니다. 여기서 startend는 원하는 부분 시퀀스의 인덱스입니다.

a = [0,1,2,3,4,5,6,7,8]

a[2:5]    ## [2,3,4]
a[-5:]    ## [4,5,6,7,8]
a[:3]     ## [0,1,2]
  • 인덱스 startend는 정수여야 합니다.
  • 슬라이스는 끝 값을 포함하지 않습니다. 이는 수학의 반 개구간과 같습니다.
  • 인덱스가 생략되면, 리스트의 시작 또는 끝을 기본값으로 사용합니다.

슬라이스 재할당 (Slice re-assignment)

리스트에서는 슬라이스를 재할당하고 삭제할 수 있습니다.

## Reassignment
a = [0,1,2,3,4,5,6,7,8]
a[2:4] = [10,11,12]       ## [0,1,10,11,12,4,5,6,7,8]

참고: 재할당된 슬라이스는 같은 길이를 가질 필요가 없습니다.

## Deletion
a = [0,1,2,3,4,5,6,7,8]
del a[2:4]                ## [0,1,4,5,6,7,8]

시퀀스 축약 (Sequence Reductions)

시퀀스를 단일 값으로 축약하는 몇 가지 일반적인 함수가 있습니다.

>>> s = [1, 2, 3, 4]
>>> sum(s)
10
>>> min(s)
1
>>> max(s)
4
>>> t = ['Hello', 'World']
>>> max(t)
'World'
>>>

시퀀스 반복 (Iteration over a sequence)

for 루프는 시퀀스의 요소들을 반복합니다.

>>> s = [1, 4, 9, 16]
>>> for i in s:
...     print(i)
...
1
4
9
16
>>>

루프의 각 반복마다, 작업할 새로운 항목을 얻습니다. 이 새로운 값은 반복 변수에 배치됩니다. 이 예제에서 반복 변수는 x입니다:

for x in s:         ## `x` 는 반복 변수입니다.
    ...statements

각 반복마다, 반복 변수의 이전 값은 (있는 경우) 덮어쓰여집니다. 루프가 완료된 후, 변수는 마지막 값을 유지합니다.

break 문 (break statement)

break 문을 사용하여 루프를 조기에 종료할 수 있습니다.

for name in namelist:
    if name == 'Jake':
        break
    ...
    ...
statements

break 문이 실행되면, 루프를 종료하고 다음 statements로 이동합니다. break 문은 가장 안쪽의 루프에만 적용됩니다. 이 루프가 다른 루프 안에 있는 경우, 바깥쪽 루프는 종료되지 않습니다.

continue 문 (continue statement)

하나의 요소를 건너뛰고 다음 요소로 이동하려면 continue 문을 사용하십시오.

for line in lines:
    if line == '\n':    ## Skip blank lines
        continue
    ## More statements
    ...

이는 현재 항목이 관심 대상이 아니거나 처리 과정에서 무시해야 할 때 유용합니다.

정수 반복 (Looping over integers)

카운트가 필요한 경우, range()를 사용하십시오.

for i in range(100):
    ## i = 0,1,...,99

구문은 range([start,] end [,step])입니다.

for i in range(100):
    ## i = 0,1,...,99
for j in range(10,20):
    ## j = 10,11,..., 19
for k in range(10,50,2):
    ## k = 10,12,...,48
    ## Notice how it counts in steps of 2, not 1.
  • 종료 값은 절대 포함되지 않습니다. 슬라이스의 동작을 미러링합니다.
  • start는 선택 사항입니다. 기본값은 0입니다.
  • step은 선택 사항입니다. 기본값은 1입니다.
  • range()는 필요에 따라 값을 계산합니다. 실제로 많은 수의 숫자를 저장하지 않습니다.

enumerate() 함수

enumerate 함수는 반복에 추가적인 카운터 값을 추가합니다.

names = ['Elwood', 'Jake', 'Curtis']
for i, name in enumerate(names):
    ## Loops with i = 0, name = 'Elwood'
    ## i = 1, name = 'Jake'
    ## i = 2, name = 'Curtis'

일반적인 형태는 enumerate(sequence [, start = 0])입니다. start는 선택 사항입니다. enumerate()를 사용하는 좋은 예는 파일을 읽는 동안 줄 번호를 추적하는 것입니다.

with open(filename) as f:
    for lineno, line in enumerate(f, start=1):
        ...

결론적으로, enumerate는 다음의 간편한 단축키입니다.

i = 0
for x in s:
    statements
    i += 1

enumerate를 사용하면 타이핑이 줄어들고 약간 더 빠르게 실행됩니다.

For 와 튜플 (For and tuples)

여러 개의 반복 변수로 반복할 수 있습니다.

points = [
  (1, 4),(10, 40),(23, 14),(5, 6),(7, 8)
]
for x, y in points:
    ## Loops with x = 1, y = 4
    ##            x = 10, y = 40
    ##            x = 23, y = 14
    ##            ...

여러 변수를 사용할 때, 각 튜플은 일련의 반복 변수로 *언팩 (unpacked)*됩니다. 변수의 수는 각 튜플의 항목 수와 일치해야 합니다.

zip() 함수

zip 함수는 여러 시퀀스를 받아 결합하는 이터레이터 (iterator) 를 만듭니다.

columns = ['name', 'shares', 'price']
values = ['GOOG', 100, 490.1 ]
pairs = zip(columns, values)
## ('name','GOOG'), ('shares',100), ('price',490.1)

결과를 얻으려면 반복해야 합니다. 앞서 보여드린 것처럼 여러 변수를 사용하여 튜플을 언팩 (unpack) 할 수 있습니다.

for column, value in pairs:
    ...

zip의 일반적인 사용법은 딕셔너리를 구성하기 위한 키/값 쌍을 만드는 것입니다.

d = dict(zip(columns, values))

연습 문제 2.13: 카운팅 (Counting)

몇 가지 기본적인 카운팅 예제를 시도해 보세요:

>>> for n in range(10):            ## Count 0 ... 9
        print(n, end=' ')

0 1 2 3 4 5 6 7 8 9
>>> for n in range(10,0,-1):       ## Count 10 ... 1
        print(n, end=' ')

10 9 8 7 6 5 4 3 2 1
>>> for n in range(0,10,2):        ## Count 0, 2, ... 8
        print(n, end=' ')

0 2 4 6 8
>>>

연습 문제 2.14: 더 많은 시퀀스 연산 (More sequence operations)

몇 가지 시퀀스 축소 연산 (sequence reduction operations) 을 대화형으로 실험해 보세요.

>>> data = [4, 9, 1, 25, 16, 100, 49]
>>> min(data)
1
>>> max(data)
100
>>> sum(data)
204
>>>

데이터를 반복 (looping) 해 보세요.

>>> for x in data:
        print(x)

4
9
...
>>> for n, x in enumerate(data):
        print(n, x)

0 4
1 9
2 1
...
>>>

때때로 for 문, len(), 그리고 range()는 초보자에 의해 녹슨 C 프로그램의 심연에서 튀어나온 듯한 끔찍한 코드 조각으로 사용됩니다.

>>> for n in range(len(data)):
        print(data[n])

4
9
1
...
>>>

그렇게 하지 마세요! 그렇게 읽는 것은 모두의 눈을 아프게 할 뿐만 아니라, 메모리 측면에서 비효율적이며 훨씬 느리게 실행됩니다. 데이터를 반복하고 싶다면 일반적인 for 루프를 사용하세요. 어떤 이유로 인덱스 (index) 가 필요한 경우 enumerate()를 사용하세요.

연습 문제 2.15: 실용적인 enumerate() 예제

missing.csv 파일에는 주식 포트폴리오 (stock portfolio) 데이터가 포함되어 있지만, 일부 행에 데이터가 누락되어 있다는 것을 기억하세요. enumerate()를 사용하여, 잘못된 입력을 발견할 때 경고 메시지와 함께 행 번호를 출력하도록 pcost.py 프로그램을 수정하세요.

>>> cost = portfolio_cost('/home/labex/project/missing.csv')
Row 4: Couldn't convert: ['MSFT', '', '51.23']
Row 7: Couldn't convert: ['IBM', '', '70.44']
>>>

이렇게 하려면 코드의 몇 부분을 변경해야 합니다.

...
for rowno, row in enumerate(rows, start=1):
    try:
        ...
    except ValueError:
        print(f'Row {rowno}: Bad row: {row}')

연습 문제 2.16: zip() 함수 사용하기

portfolio.csv 파일의 첫 번째 줄에는 열 머리글 (column headers) 이 포함되어 있습니다. 이전의 모든 코드에서 우리는 그것들을 버렸습니다.

>>> f = open('/home/labex/project/portfolio.csv')
>>> rows = csv.reader(f)
>>> headers = next(rows)
>>> headers
['name', 'shares', 'price']
>>>

하지만, 머리글을 유용하게 사용할 수 있다면 어떨까요? 이것이 zip() 함수가 등장하는 부분입니다. 먼저 파일 머리글을 데이터 행과 쌍으로 묶어보세요.

>>> row = next(rows)
>>> row
['AA', '100', '32.20']
>>> list(zip(headers, row))
[ ('name', 'AA'), ('shares', '100'), ('price', '32.20') ]
>>>

zip()이 열 머리글을 열 값과 어떻게 쌍으로 묶었는지 확인하세요. 여기서는 결과를 볼 수 있도록 list()를 사용하여 리스트로 변환했습니다. 일반적으로 zip()은 for 루프에 의해 소비되어야 하는 이터레이터 (iterator) 를 생성합니다.

이 쌍으로 묶는 것은 딕셔너리 (dictionary) 를 구축하기 위한 중간 단계입니다. 이제 다음을 시도해 보세요.

>>> record = dict(zip(headers, row))
>>> record
{'price': '32.20', 'name': 'AA', 'shares': '100'}
>>>

이 변환은 많은 데이터 파일을 처리할 때 알아두면 가장 유용한 트릭 중 하나입니다. 예를 들어, pcost.py 프로그램이 다양한 입력 파일에서 작동하도록 하되, 이름, 주식 수, 가격이 나타나는 실제 열 번호는 고려하지 않으려는 경우를 생각해 보세요.

pcost.pyportfolio_cost() 함수를 다음과 같이 수정하세요.

## pcost.py

def portfolio_cost(filename):
    ...
        for rowno, row in enumerate(rows, start=1):
            record = dict(zip(headers, row))
            try:
                nshares = int(record['shares'])
                price = float(record['price'])
                total_cost += nshares * price
            ## This catches errors in int() and float() conversions above
            except ValueError:
                print(f'Row {rowno}: Bad row: {row}')
        ...

이제 완전히 다른 데이터 파일 portfoliodate.csv에서 함수를 시도해 보세요. 이 파일은 다음과 같습니다.

name,date,time,shares,price
"AA","6/11/2007","9:50am",100,32.20
"IBM","5/13/2007","4:20pm",50,91.10
"CAT","9/23/2006","1:30pm",150,83.44
"MSFT","5/17/2007","10:30am",200,51.23
"GE","2/1/2006","10:45am",95,40.37
"MSFT","10/31/2006","12:05pm",50,65.10
"IBM","7/9/2006","3:15pm",100,70.44
>>> portfolio_cost('/home/labex/project/portfoliodate.csv')
44671.15
>>>

제대로 했다면, 데이터 파일이 이전과 완전히 다른 열 형식을 가지고 있음에도 불구하고 프로그램이 여전히 작동하는 것을 알 수 있을 것입니다. 멋지죠!

여기서 이루어진 변경은 미묘하지만 중요합니다. portfolio_cost()가 단일 고정 파일 형식을 읽도록 하드코딩 (hardcoded) 하는 대신, 새 버전은 모든 CSV 파일을 읽고 관심 있는 값을 선택합니다. 파일에 필요한 열이 있는 한 코드는 작동합니다.

섹션 2.3 에서 작성한 report.py 프로그램을 수정하여 동일한 기술을 사용하여 열 머리글을 선택하도록 하세요.

portfoliodate.csv 파일에서 report.py 프로그램을 실행하여 이전과 동일한 답을 생성하는지 확인하세요.

연습 문제 2.17: 딕셔너리 뒤집기

딕셔너리는 키 (key) 를 값 (value) 에 매핑합니다. 예를 들어, 주식 가격의 딕셔너리가 있습니다.

>>> prices = {
        'GOOG' : 490.1,
        'AA' : 23.45,
        'IBM' : 91.1,
        'MSFT' : 34.23
    }
>>>

items() 메서드를 사용하면 (key,value) 쌍을 얻을 수 있습니다.

>>> prices.items()
dict_items([('GOOG', 490.1), ('AA', 23.45), ('IBM', 91.1), ('MSFT', 34.23)])
>>>

하지만, 대신 (value, key) 쌍의 목록을 얻고 싶다면 어떻게 해야 할까요? 힌트: zip()을 사용하세요.

>>> pricelist = list(zip(prices.values(),prices.keys()))
>>> pricelist
[(490.1, 'GOOG'), (23.45, 'AA'), (91.1, 'IBM'), (34.23, 'MSFT')]
>>>

왜 이렇게 할까요? 한 가지 이유는 딕셔너리 데이터에 특정 종류의 데이터 처리를 수행할 수 있기 때문입니다.

>>> min(pricelist)
(23.45, 'AA')
>>> max(pricelist)
(490.1, 'GOOG')
>>> sorted(pricelist)
[(23.45, 'AA'), (34.23, 'MSFT'), (91.1, 'IBM'), (490.1, 'GOOG')]
>>>

이것은 또한 튜플 (tuple) 의 중요한 기능을 보여줍니다. 비교에 사용될 때, 튜플은 첫 번째 항목부터 시작하여 요소별로 비교됩니다. 문자열이 문자별로 비교되는 방식과 유사합니다.

zip()은 이처럼 서로 다른 위치의 데이터를 쌍으로 묶어야 하는 상황에서 자주 사용됩니다. 예를 들어, 열 이름을 열 값과 쌍으로 묶어 이름이 지정된 값의 딕셔너리를 만드는 경우입니다.

zip()은 쌍으로 제한되지 않습니다. 예를 들어, 원하는 수의 입력 목록과 함께 사용할 수 있습니다.

>>> a = [1, 2, 3, 4]
>>> b = ['w', 'x', 'y', 'z']
>>> c = [0.2, 0.4, 0.6, 0.8]
>>> list(zip(a, b, c))
[(1, 'w', 0.2), (2, 'x', 0.4), (3, 'y', 0.6), (4, 'z', 0.8))]
>>>

또한 zip()은 가장 짧은 입력 시퀀스가 소진되면 중지됩니다.

>>> a = [1, 2, 3, 4, 5, 6]
>>> b = ['x', 'y', 'z']
>>> list(zip(a,b))
[(1, 'x'), (2, 'y'), (3, 'z')]
>>>

요약

축하합니다! 시퀀스 (Sequences) 랩을 완료했습니다. LabEx 에서 더 많은 랩을 연습하여 실력을 향상시킬 수 있습니다.