티스토리 뷰

3. Strings, lists, and tuples

openbookproject의 Beginning Python Programming for Aspiring Web Developers 3장 번역  [Copyright Notice]

3.1. Sequence data types

이전까지는 파이썬에 들어있는 기본 타입들인 int, float, str 타입을 배웠다. intfloat는 수치 타입이라고 하며 숫자를 표현한다. 사칙연산이나 비교연산, % 연산 같은 수치 연산자를 이용하여 수치 수식을 만들 수 있다. 이런 수식들을 포함한 파이썬 프로그램은 실행에 의해 수식의 결과를 우리에게 보여준다. 프로그램이란 입력과 출력을 가지고 수식과 지정문, 조건문과 반복문으로 구성된 코드 부분을 뜻한다.

여기서는 여러 개의 값을 가지는 자료구조인 시퀀스 타입에 대해 살펴보겠다. 시퀀스 타입이란 여러 객체들이 모여 있고 객체들 간에 순서를 가지는 타입다. 타입이란 데이터를 표현하는 방법을 나타내며 이름이 있다.

시퀀스 타입은 기본 수치 타입과는 차원이 다르다. intfloat 같은 수치 타입은 값을 한 개만 가지지만 시퀀스 타입은 값을 여러 개를 가지는 복합 타입이다. 또 포함되는 요소들 간에 순서가 있다. 예를 들어 시퀀스 타입인 스트링은 글자 여러 개가 모여서 만들어진 복합 타입이다. 여기서 한 글자가 요소(element)라고 할 수 있다. 그리고 스트링에는 아무 글자도 갖지 않는 빈 스트링도 있을 수 있다.

리스트도 시퀀스 타입다. 리스트의 요소는 파이썬의 어떤 타입이든지 가능한다. 스트링이나 리스트 같은 시퀀스 타입도 요소가 될 수 있다. 리스트는 대괄호([])로 둘러싸져 있고 어떤 값이든 콤마로 연결하여 나열할 수 있다.

또 하나의 시퀀스 타입으로 튜플이 있다. 튜플은 괄호 ()로 둘러싸져 있고 역시 어떤 타입의 값이든 요소가 될 수 있다. 빈 튜플은 ()로 표시한다. 리스트와 튜플은 생성하면서 값을 바로 써 줄 수 있다.

[10, 20, 30, 40, 50]
["spam", "bungee", "swallow"]
(15, 22, 4)

첫 번째 리스트는 다섯 개의 숫자를 가지고 그 다음 것은 세 개의 스트링을 가진다. 세 번째는 세 개의 값을 가지는 튜플이다.

[("cheese", "치즈"), ("red", "빨간색"), ("school", "학교")]

위의 리스트는 세 개의 튜플을 원소로 가진다. 마지막 리스트의 원소가 되는 각 튜플은 두 개의 스트링을 포함하고 있다.

시퀀스 타입은 특정 원소를 한 개씩 가져오거나 검사할 수도 있어야 하고 복합 타입 전체를 하나의 객체로 다룰 수도 있어야 한다. 전체를 하나의 객체로 다룬다면 요소의 개수를 확인하거나 어떤 값이 몇 개나 들어있는지 개수를 세는 것 같은 기능이 가능한다. 타입에서 데이터에 대해 취할 수 있는 동작 또는 명령을 연산이라고 한다. 아래에서 이런 연산에 대해 차례로 살펴볼 것다.

3.1.1. 튜플의 표현에서 주의할 점

튜플은 괄호로 둘러싸인 값들을 나타내지만 괄호를 생략하고 요소 값들을 콤마로 연결하는 것도 가능한다. 아thing 예제처럼 콤마로 연결한 두 개 이상의 값들도 파이썬은 튜플로 인식한다.

>>> thing = 2, 4, 6, 8
>>> type(thing)
<class 'tuple'>
>>> thing
(2, 4, 6, 8)

하나의 원소만 가지는 튜플을 표시할 때는 괄호를 하더라도 값 뒤에 콤마를 해주어야 한다. (2) + 3이라고 쓴 수식에서 괄호한 숫자는 그냥 숫자로 인식될 수 있다. 그러므로 콤마가 없으면 그냥 수식의 괄호로 인식될 수 있기 때문이다.

>>> singleton = (2,) # 콤마를 넣으면 한 개 숫자도 튜플로 취급
>>> type(singleton)
<class 'tuple'>
>>> not_tuple = (2) # 콤마 없이 한 개 숫자에 괄호하면 int로 취급
>>> type(not_tuple)
<class 'int'>

빈 괄호는 빈 튜플로 취급한다. 콤마가 없어도 튜플로 취급되는 경우는 빈 괄호 뿐이다.

>>> empty_tuple = () # 빈 괄호는 빈 튜플로 취급
>>> type(empty_tuple)
<class 'tuple'>

빈 튜플이 아닌 경우에는 괄호가 없더라도 값이나 이름 뒤에 콤마가 있으면 파이썬에서 튜플로 인식하게 된다.

3.2. 시퀀스의 요소를 다루는 방법

시퀀스 타입은 공통되는 연산들을 가지고 있다.

3.2.1. 인덱싱

인덱싱 연산([ ])은 시퀀스에서 하나의 요소를 고르는 데 사용된다. 그 때 대괄호 안에 있는 수식은 인덱스라고 하며 정수 값이어야 한다. 인덱스는 시퀀스에서 어떤 요소를 고를지를 나타냅니다. 인덱스로 시퀀스의 요소를 표시하는 방법을 인덱싱이라고 한다.

인덱싱은 대괄호에 인덱스를 붙여서 해당하는 요소를 표현하게 된다.

>>> fruit = "banana"
>>> fruit[1]
'a'

수식 fruit[1]이라고 쓰면 fruit에서 인덱스가 1인 글자(두 번째 글자)를 고르게 된다. 그리고 그 결과를 가진 새로운 스트링을 만들어서 돌려줍니다. 즉 결과는 'a'가 된다.

컴퓨터에서 인덱스는 항상 0부터 시작한다. 그러므로 [1]은 두번째 요소를 나타내게 된다.

>>> fruits = ['apples', 'cherries', 'pears']
>>> fruits[0]
'apples'

리스트에서도 인덱싱으로 요소를 나타낼 수 있다. 위의 예에서는 [0]으로 첫 번째 요소를 나타내고 있다.

>>> prices = (3.99, 6.00, 10.00, 5.25)
>>> prices[3]
5.25

튜플에서도 동일한 방식으로 인덱싱을 사용할 수 있다. prices[3]은 앞에서부터 네 번째 요소를 나타나게 된다.

>>> pairs = [("cheese", "치즈"), ("red", "빨간색"), ("school", "학교")]
>>> pairs[2]
('school', 'escuela')

이 예에서 pairs는 리스트인데 각 요소가 값을 두 개씩 가지는 튜플이다.

3.2.2. 길이

len() 함수는 스트링이나 리스트의 길이를 돌려준다..

>>> len('banana')
6

리스트와 마찬가지로 튜플도 len() 함수에 의해 시퀀스에 포함되는 요소의 개수를 돌려준다.

>>> len(['a', 'b', 'c', 'd'])
4
>>> len((2, 4, 6, 8, 10, 12))
6
>>> pairs = [('cheese', 'queso'), ('red', 'rojo'), ('school', 'escuela')]
>>> len(pairs)
3

[연습문제] 다음을 실행한 결과는 무엇일지 예상해 보자.

>>> len(pairs[1])

 

3.2.3. 시퀀스의 제일 끝에 있는 요소를 접근하기

컴퓨터 프로그램에서는 시퀀스의 마지막 요소를 접근해야 할 일이 종종 있다. 앞에서 본 len() 함수를 이용해서 다음과 같이 접근한다면 어떤 일이 일어날까?

>>> seq = [1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0]
>>> last = seq[len(seq)] # ERROR!

이 코드에 대해 파이썬은 아마도 다음과 같은 오류 메시지를 보여줄 것다.

IndexError: list index out of range.

에러가 발생하는 이유는 len(seq) 값은 리스트에 있는 요소의 개수를 돌려주니까 16일 것이고 seq에는 16번째 요소는 없기 때문이다. 인덱스는 항상 0부터 시작하므로 seq16개 요소는 0번부터 15번까지로 구성된다. 그러므로 마지막 요소를 가져오고 싶다면 인덱스는 길이에서 1을 뺀 값을 주어야 한다.

>>> last = seq[len(seq) - 1]

이런 일이 워낙 많이 생기므로 파이썬은 음수를 이용한 인덱싱을 허용한다. 즉 시퀀스의 뒤에서부터 세는 인덱싱 방법이다. 수식 seq[-1]은 마지막 원소, seq[-2]는 뒤에서 두 번째 원소를 나타낸다.

>>> prime_nums = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31]
>>> prime_nums[-2]
29
>>> classmates = ("Alejandro", "Ed", "Kathryn", "Presila", "Sean", "Peter")
>>> classmates[-5]
'Ed'
이러한 인덱싱은 스트링과 튜플에 대해서도 똑같이 적용될 수 있다.
>>> word = "Alphabet"
>>> word[-3]
'b'

3.2.4. 루프를 이용해 차례로 처리

시퀀스를 다루는 계산에서 많은 부분이 요소를 한 개씩 처리하게 된다. 가장 많이 쓰이는 패턴은 처음 것부터 차례로 한 요소씩 가져와서 뭔가를 하고 그런 일을 마지막 요소까지 반복하는 것이다. 이것을 시퀀스를 차례로 조회(traverse)한다 라고 한다.

파이썬의 for 루프는 이러한 차례로 조회를 쉽게 해주는 가장 대표적인 방법이다.

prime_nums = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31]
for num in prime_nums:
	print(num ** 2)

for 루프는 튜플과 리스트에 대해서도 똑같이 적용된다.

3.3. 인덱스를 사용해 시퀀스를 다양한 방법으로 접근하기

시퀀스에 있는 요소는 인덱스로 표시할 수 있다. 앞에서 살펴본 인덱싱 방법 이외에도 인덱스는 시퀀스의 부분을 표시하기도 하고 그 안에 있는 요소의 위치를 나타내는 역할도 한다. 인덱스에 관련된 기능과 인덱스를 사용하는 기법을 살펴본다.

3.3.1. enumerate (번호 매기기)

표준 for 루프는 시퀀스를 차례로 조회하면서 루프 변수에 각 원소를 차례로 지정한다. 가끔은 원소의 값과 함께 인덱스도 필요로 하는 경우가 있는데 이것을 제공하는 것이 enumerate 함수다.

fruits = ['apples', 'bananas', 'blueberries', 'oranges', 'mangos']
for index, fruit in enumerate(fruits):
	print("The fruit, " + fruit + ", is in position " + str(index) + ".")

3.3.2. 슬라이스

시퀀스의 일부분을 슬라이스라고 하는데, 일부분을 추출하는 방법을 슬라이싱이라고 한다. 인덱싱과 마찬가지로 슬라이싱에도 대괄호 [ ]를 쓰는데, [ ] 안에는 하나의 숫자만 나오는 것이 아니고 콜론(:)으로 구분되는 두 개의 정수가 나온다.

>>> singers = "Peter, Paul, and Mary"
>>> singers[0:5]
'Peter'
>>> singers[7:11]
'Paul'
>>> singers[17:21]
'Mary'
>>> classmates = ("Alejandro", "Ed", "Kathryn", "Presila", "Sean", "Peter")
>>> classmates[2:4]
('Kathryn', 'Presila')

시퀀스에 [n:m]를 붙이면 n번째 요소부터 m번째 요소까지 가진 시퀀스의 부분을 돌려준다. 여기서도 처음 요소는 포함이고 마지막 요소를 미만이다. 이것은 직관적이지 않은데, 슬라이싱의 번호는 요소와 요소의 사이를 가리킨다고 보면 좀더 이해하기 쉬울 것이다. 다음 그림에서 인덱스는 글자의 시작 위치를 가리킨다.

콜론 앞에 처음 숫자를 생략하면 슬라이스는 스트링의 시작부터를 나타내고 콜론 뒤의 숫자를 생략하면 슬라이스는 스트링의 끝까지를 나타내게 된다.

>>> fruit = "banana"
>>> fruit[:3]
'ban'
>>> fruit[3:]
'ana'

[연습문제] 위의 리스트 예제에서 사용된 변수에 대해 다음 슬라이스를 구해보자.

(1) singers[:]는 뭘까?

(2) classmates[4:]는 어떤 리스트를 나타낼까?

슬라이스에서 0보다 작은 인덱스를 사용하는 것도 가능하다.

>>> fruit[-2:]
'na'
>>> classmates[:-2]
('Alejandro', 'Ed', 'Kathryn', 'Presila')

[Tip] 슬라이싱에 대해 완벽하게 이해하는 것이 매우 중요하다. 시퀀스와 슬라이스를 계속 연습하면서 실행하기 전에 결과를 예측해 보면서 연습하는 것이 중요하다.

[생각해 봅시다]

슬라이스의 결과는 부분시퀀스인데 이것은 항상 원래 시퀀스와 같은 타입이다. 즉 리스트의 슬라이스는 리스트, 스트링의 슬라이스는 스트링이다.

그러나 인덱싱은 요소를 돌려주므로 리스트의 인덱싱는 리스트가 아닌 경우가 많다. 물론 스트링의 인덱싱은 항상 스트링이다.

>>> strange_list = [(1, 2), [1, 2], '12', 12, 12.0] # 리스트의
>>> print(strange_list[0], type(strange_list[0])) # 인덱싱 결과는 튜플이다.
(1, 2) <class 'tuple'>
>>> print(strange_list[0:1], type(strange_list[0:1])) # 리스트의 슬라이스는 리스트다.
[(1, 2)] <class 'list'>
>>> print(strange_list[2], type(strange_list[2])) # 리스트의 인덱싱이 스트링이다.
12 <class 'str'>

리스트의 요소는 여러 가지 타입이 될 수 있지만 리스트의 슬라이스는 항상 리스트다.

>>> print(strange_list[2:3], type(strange_list[2:3]))
[12] <class 'list'>

슬라이스는 콜론 앞의 숫자가 뒤의 숫자보다 크면 빈 슬라이스를 나타내게 된다.

>>> seq[3:2]
[]

3.3.3. in 연산

시퀀스에 대해 우리는 어떤 요소가 그 안에 포함되어 있는지 확인해야 할 일이 자주 생긴다. in 연산은 어떤 요소가 리스트나 튜플에 포함되는지 여부를 검사해 준다.

>>> stuff = ['this', 'that', 'these', 'those']
>>> 'this' in stuff
True
>>> 'everything' in stuff
False
>>> 4 in (2, 4, 6, 8)
True
>>> 5 in (2, 4, 6, 8)
False

그런데 in이 스트링에 대해서는 좀 다르게 동작한다. 한 스트링이 다른 스트링의 부분이면 참을 리턴한다. 즉 요소가 아니라 부분스트링을 검사하는 역할을 한다.

>>> 'p' in 'apple'
True
>>> 'i' in 'apple'
False
>>> 'ap' in 'apple'
True
>>> 'pa' in 'apple'
False

스트링은 in의 포함관계가 리스트나 튜플과는 약간 다르게 적용된다. 모든 스트링이 자신의 부분스트링이 된다는 점이 특이하다. 또한 빈 스트링은 모든 스트링의 부분스트링이다. 프로그래머들은 항상 이런 edge cases(경계값 요인)에 특히 주의를 기울여야 한다.

>>> 'a' in 'a'
True
>>> 'apple' in 'apple'
True
>>> '' in 'a'
True
>>> '' in 'apple'
True

3.4. Objects and methods

스트링, 리스트, 튜플은 객체인데, 객체라 함은 값들을 포함하고 있을 뿐 아니라 그 값들에 대해 동작하는 연산을 가지고 있다는 뜻이다. 이렇게 객체에 적용되는 함수처럼 생긴 연산을 메소드라 한다.

 스트링의 메소드들이 어떻게 동작하는지 예를 살펴보자. 메소드 호출은 객체 뒤에 점을 쓰고 그 다음에 함수 호출과 같은 형태를 가진다. 이 메소드는 점 앞의 객체에 대해 뭔가 동작을 실행한다. 다음의 예제를 보자.

>>> 'apple'.upper()
'APPLE'
>>> 'COSATU'.lower()
'cosatu'
>>> 'rojina'.capitalize()
'Rojina'

위의 세 개 메소드는 매개변수 리스트가 비어있다. 점 앞의 객체 외에 따로 매개변수가 필요없는 메소드들이다. 스트링에 적용되어서 스트링을 대문자로, 소문자로, 또는 첫글자만 대문자로 바꾸어주는 메소드들이다. 이들 메소드는 연산을 적용하여 변경된 스트링을 반환한다.

첫 예제에서 스트링 'apple'은 점 뒤에 upper() 메소드가 따라오는데, 이것은 매개변수는 없다. 이것을 스트링 'apple'upper() 메소드가 불렸다"라고 한다. 메소드를 호출하는 것은 점 앞의 객체에 어떤 동작이 일어나게 한다. 그리고 그 동작은 어떤 결과를 돌려주는데, 이 경우에는 결과는 'APPLE'이라는 스트링 값이다. 이것을 말로 표현하면 'apple' 스트링에 upper() 메소드가 호출되어서 결과로 'APPLE' 스트링을 돌려주었다(리턴했다)라고 할 수 있다.

>>> '42'.isdigit()
True
>>> 'four'.isdigit()
False

위의 예제에서는 isdigit() 메소드가 스트링 '42'에 대해 불려졌다. 이 스트링 안에 있는 모든 글자가 숫자이므로 isdigit()True 값을 리턴한다. 'four'에 대해 isdigit()를 호출한 결과는 False.

>>> ' remove_the_spaces '.strip()
'remove_the_spaces'
>>> 'Mississippi'.startswith('Miss')
True
>>> 'Aardvark'.startswith('Ant')
False

strip() 메소드는 스트링 제일 앞과 제일 뒤에 있는 공백을 제거한다. 공백은 빈칸, , 줄바꿈을 포함한다. 예를 들어 다음과 같이 앞에 줄바꿈을 포함한 스트링에 대해 strip()을 적용한 결과를 보자. 삼중따옴표의 스트링은 줄바꿈 문자도 포함할수 있다.

>>> '''


test string  '''.strip()
‘test string’

3.5. dir() 함수 사용법

앞절에서 스트링 객체의 메소드들을 몇 개 살펴보았다. 그런데 실은 훨씬 많은 스트링 메소드들이 파이썬에서는 이미 만들어져 있다. 스트링에 대해 쓸 수 있는 메소드가 어떤 것들이 있는지 확인하는 방법이 dir() 함수다.

>>> dir(str) 
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', 
		'__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', 
        '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', 
        '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', 
        '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', 
        '__str__', '__subclasshook__', 'capitalize', 'center', 'count', 'encode', 
        'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 
        'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 
        'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 
        'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 
        'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 
        'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill'] 
>>>

__로 시작하는 것들은 나중에 살펴보기로 하고 여기서는 일반 메소드들을 살펴보자. 각 메소드에 대해 그것이 무슨 일을 하는지 출력해 볼 수 있다. 메소드마다 그 함수가 하는 일을 나타내는 간단한 설명이 들어있다. 예를 들어 replace 메소드가 무슨 일을 하는지 보려면 help 함수를 이용할 수 있다.

>>>help(str.replace)
Help on method_descriptor:

replace(self, old, new, count=-1)
    Return a copy with all occurrences of substring old replaced by new.
    
      count
        Maximum number of occurrences to replace.
        -1 (the default value) means replace all occurrences.
    
    If the optional argument count is given, only the first count occurrences are
    replaced.

설명이 영어로 되어 있어 이해하기 어렵긴 하지만 여기 써있는 내용은 다음과 같다.

replace(self, old, new, count=-1, /)

여기서 self는 점 앞에 있던 객체를 뜻한다. old를 모두 new로 바꾼 self의 사본을 리턴한다.
count
바꾸는 최대 회수를 나타낸다. -1(디폴트 값)은 모두 바꾸라는 의미가 된다.
만약 호출할 때 새로운 count 값이 주어지면 앞에서 count개 만큼만 바꾼다.

이 정보를 이용하면 우리는 replace 메소드를 이용해서 스트링이 어떻게 바뀌는지 확인해 볼 수 있다.

>>> 'Mississippi'.replace('i', 'X')
'MXssXssXppX'
>>> 'Mississippi'.replace('p', 'MO')
'MississiMOMOi'
>>> 'Mississippi'.replace('i', '', 2)
'Mssssippi'

첫 번째 예제는 스트링에 있는 모든 'i''X'로 바꾼다. 두번째 예제는 'p''MO'로 바꾼다. 세 번째 예제에서는 'i'를 앞의 두 개만 빈스트링으로 바꾼다. 즉 없애버린다.

3.6. countindex 메소드

countindex 두 개 메소드는 모든 시퀀스 타입에 대해 공통되는 메소드다. 스트링, 리스트, 튜플에 모두 적용될 수 잇다.

스트링에서 count는 다음과 같은 형태로 쓰인다.

S.count(sub[, start[, end]]) -> int

매개변수로 sub만 주어지면 스트링 S에서 sub(겹치지 않게) 몇번이나 나오는지 회수를 돌려준다. 사용법을 설명할 때 대괄호 []는 생략가능한 부분을 나타냅니다. [, start[, end]]도 생략 가능한 부분을 나타내는데, 여기서는 start만 올 수도 있고 startend 두 개 다 올 수도 있다. 그 경우 S[start:end]처럼 startend는 슬라이스 방식으로 해석해서 그 부분에 sub이 몇 번 나타나는지를 돌려준다.

리스트라면 L.count(value) 형태로 쓰여서 리스트 L에서 value가 요소로 몇번 등장하는지를 돌려준다. 튜플도 마찬가지다. 여기서 value는 임의의 값이나 변수나 수식이 될 수 있다.

스트링 index 메소드는 다음과 같이 사용된다.

S.index(sub[, start[, end]]) -> int

스트링 Ssub이 등장하는 위치(인덱스)를 반환한다. count와 마찬가지로 startend는 슬라이스 방식으로 해석해서 그 부분에 sub이 등장하는 위치를 돌려준다. 스트링에는 이와 비슷한 역할을 하는 find 라는 메소드가 있다. , 차이는 find는 없을 때 -1을 돌려주는데 index 메소드는 sub이 나타나지 않을 때 ValueError를 일으킨다는 점이다. find는 리스트나 튜플에는 적용되지 않는다.

index 메소드는 스트링의 해당 부분에 sub이 등장하면 첫 번째 등장하는 위치를 인덱스로 돌려주고 없으면 ValueError를 일으킨다.

리스트의 경우는 다음과 같은 형태가 된다. 즉 스트링의 sub 대신 val이 주어지게 된다.

L.index(val[, start[, end]]) -> int

index 메소드는 리스트 L에서 val이 나타나는 곳의 위치(인덱스)를 반환한다. 스트링의 경우와 마찬가지로 해당 값이 리스트에 나타나지 않으면 ValueError를 일으킨다.

튜플의 경우도 리스트와 동일하게 동작한다.   

다음글에서 이어집니다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함