파이썬 내장 자료형

전창우·2023년 6월 14일
0

python for data analysis

목록 보기
2/23

해당 챕터에서는 파이썬 언어에 내장되어 있는 기능을 살펴본다.

자료구조와 순차 자료형

파이썬의 자료구조는 단순하지만, 강력하다. 자료구조의 사용법의 숙지하는 것이 해당 언어의 고수가 되는 지름길이라고 생각한다.

언어에서 '강력하다'는 표현은 해당 언어가 다양한 작업을 수행하고, 다양한 문제를 해결하는데 유용하고 효과적이라는 것을 의미한다. 그만큼 편리하고 효율적인 기능과 도구를 제공한다.

튜플

튜플은 1차원의 고정된 크기를 가지는 변경 불가능한(이뮤터블) 순차 자료형이다.
아래 3가지는 튜플로 자료형을 설정하는 방법이다.

tup1 = 4, 5, 6
tup2 =(4,5,6)
tup3=tuple([4,5,6]) 
# tuple 함수를 호출하여 모든 순차자료형을 튜플으로 캐스팅할 수 있다.

튜플은 한 번 생성되면 각 슬롯에 저장된 객체를 변경하는 것은 불가능하다.

tup = tuple(['foo', [1, 2], True])
tup[2] = False
#TypeError

추가적으로 튜플은 +,* 연산자를 활용하여 튜플 자료형으로 이어붙일 수 있다.

('foo', 'bar') * 4
#('foo', 'bar', 'foo', 'bar', 'foo', 'bar', 'foo', 'bar')
(4, None, 'foo') + (6, 0) + ('bar',)
#(4, None, 'foo', 6, 0, 'bar')

튜플에서 값 분리하기

만일 튜플과 같은 표현의 변수에 튜플을 '대입'하면 파이썬은 등호 오른쪽에 있는 변수에서 값을 분리한다.

tup=(4,5,6)
a,b,c=tup
print(b)
#5

중첩된 튜플을 포함하는 순차 자료형에서도 값을 같은 방법으로 분리 가능하다

tup = 4, 5, (6, 7)
a, b, (c, d) = tup
print(d)
#7

'*'기호를 활용하여 길이를 알 수 없는 인자를 받아들일 수 있다.
rest라는 이름 자체에 특별한 의미가 있는 것은 아니다. 다른 이름을 가진 변수로도 똑같이 활용 가능하다.

values = 1, 2, 3, 4, 5
a, b, *rest = values
a, b
rest

이는 함수에서 가변인자를 받는 방식과 유사하다.

튜플 메서드

튜플은 크기와 내용이 변경 불가능하다는 특성이 있어 다른 자료형에 비해 인스턴스 메서드가 적은 편이다.
count 메서드는 그중 유용하게 사용하는 편이다.

a=(1,2,2,2,1,3,4)
a.count(2)
#3

리스트

튜플과 대조적으로 리스트는 크기와 내용의 변경이 가능(뮤터블)하다. 리스트는 대괄호[ ]나 list 함수를 사용해서 생성할 수 있다.

a_list = [2, 3, 7, None] #대괄호를 사용하여 리스트 자료형 생성
tup = ('foo', 'bar', 'baz') 
b_list = list(tup) #list 함수를 이용해서 튜플 자료형을 리스트로 캐스팅
b_list[1] = 'peekaboo' #리스트는 튜플과 달리 크기와 내용의 변경이 가능
b_list
#['foo', 'peekaboo', 'baz']

리스트 원소 추가하고 삭제하기

append 메소드를 이용해서 리스트의 '끝'에 값을 추가할 수 있다.

b_list.append('dwarf') #이전에 정의된 b_list
b_list
#['foo', 'peekaboo', 'baz', 'dwarf']

insert 메소드를 이용해서 원하는 위치에 값을 추가할 수 있다.

b_list.insert(1,'red')
b_list
#['foo', 'red', 'peekaboo', 'baz', 'dwarf']

insert 메소드는 append에 비해서 연산비용이 많이 든다. insert로 값을 추가하면 추가된 위치 이후의 원소들의 위치를 다 새로지정하기 때문이다. 꼭 필요한 경우가 아니면 쓰지 않는 것이 좋고, 굳이 써야한다면 비슷한 용도로 사용할 수 있는 양방향 큐 collections.deque를 이용하자

pop 메서드와 remove 메서드를 이용해서 원소를 삭제할 수 있다. 차이점은 pop 메서드는 삭제할 값의 위치를 지정할 수 있고, remove 메서드는 선택한 값이 2개라면, 리스트에서 제일 앞에 위치한 값부터 삭제한다.

b_list.pop(2)
b_list
#['foo', 'red', 'baz', 'dwarf']
b_list.append('foo')
b_list
['foo','red', 'baz', 'dwarf', 'foo']
b_list.remove('foo')
b_list
['red', 'baz', 'dwarf', 'foo']

또한, in, not in 예약어를 이용하여 List에 해당하는 값이 있는지 검사할 수 있다. 딕셔너리나 집합처럼 즉각적으로 반환하는 것이 아니라 많이 느리다.

'dwarf' in b_list
#True
'dwarf' not in b_list
#False

리스트 이어붙이기

튜플과 마찬가지로 +연산자를 이용하면 두 개의 리스트를 합칠 수 있다.

[4, None, 'foo'] + [7, 8, (2, 3)]
#[4, None, 'foo', 7, 8, (2, 3)]

+연산자를 이용하여 리스트를 이어붙일 수도 있지만,리스트가 이미 정의된 상태라면 extend 메소드를 사용하는 게 더 빠르다.

x = [4, None, 'foo']
x.extend([7, 8, (2, 3)])
x
#[4, None, 'foo', 7, 8, (2, 3)]

정렬

sort 함수를 이용하여 새로운 리스트를 생성하지 않고 있는 그대로 리스트를 정렬할 수 있다.

a = [7, 2, 5, 1, 3]
a.sort()
a
#[1, 2, 3, 5, 7]

sort 함수는 또한 정렬 기준으로 사용할 값을 인자로 넘겨줄 수 있다.

b = ['saw', 'small', 'He', 'foxes', 'six']
b.sort(key=len)
b
['He', 'saw', 'six', 'small', 'foxes']

이진 탐색과 정렬된 리스트 유지하기

내장 bisect 모듈은 이진 탐색과 리스트의 정렬을 유지한 채 값을 추가할 수 있는 기능을 제공한다.
bisect 메서드는 값이 추가될 때 리스트가 정렬된 상태를 유지할 수 있는 위치를 반환하고,
insort 메서드는 실제로 정렬된 상태를 유지한 채 값을 추가한다.

import bisect
c = [1, 2, 2, 2, 3, 4, 7]
bisect.bisect(c, 2)
#4
bisect.bisect(c, 5)
#6
bisect.insort(c, 6)
c
#[1, 2, 2, 2, 3, 4, 6, 7]

슬라이싱

리스트와 같은 자료형은 색인 연산자[] 안에 start:stop을 지정해서 원하는 크기만큼 잘라낼 수 있다.

seq = [7, 2, 3, 7, 5, 6, 0, 1]
seq[1:5]
#[2, 3, 7, 5]
seq[3:4] = [6, 3]
seq
[7, 2, 3, 6, 3, 5, 6, 0, 1] #[3:4]의 위치에 6,3이 들어옴

색인의 시작 값이나 끝 값은 생략할 수 있다.

seq[:5]
#[7, 2, 3, 6, 3]
seq[3:]
#[6, 3, 5, 6, 0, 1]

음수 색인은 순차 자료형의 끝에서부터의 위치를 나타낸다.

seq[-4:]
#[5, 6, 0, 1]
seq[-6:-2]
#[6, 3, 5, 6]

두 번째 콜론 다음에 간격을 지정할 수 있다

seq[::2]
#[7, 3, 3, 6, 1]
seq[::-1] #음수는 역순이다.
#[1, 0, 6, 5, 3, 6, 3, 2, 7]

내장 순차자료형 함수

파이썬에는 순차 자료형에 사용할 수 있는 매우 유용한 함수가 있기에 꼭 익혀 기회가 될 때 사용할 수 있어야한다.

enumerate()

이 함수는 순차 자료형에서 현재 아이템의 색인을 함께 처리하고자할 때 흔히 사용된다.
enumerate(iterater,startindex) 함수에는 두 가지 매개변수가 있다.
iterater: 반복할 수 있는 개체
startindex: (선택사항) 루프의 첫 번째 항목에 대해 startindex에 제공된 값으로 시작함, 지정하지 않으면 0부터 시작.

some_list=['foo','bar','baz']
mapping={}
for i,v in enumerate(some_list):
	mapping[v]=i
print(mapping)
#{'foo': 0, 'bar': 1, 'baz': 2}

sorted()

정렬된 새로운 순차 자료형을 반환한다.
sort 메소드와 sorted 함수의 차이는 sort()와는 다르게 원본은 건들지 않고, 새로 정렬된 객체(뷰)를 리스트로 반환한다는 것이다.

sorted('horse race')
#[' ', 'a', 'c', 'e', 'e', 'h', 'o', 'r', 'r', 's']

zip()

여러 개의 순차 자료형을 서로 짝지어서 튜플의 리스트로 생성한다.

seq1 = ['foo', 'bar', 'baz']
seq2 = ['one', 'two', 'three']
zipped = zip(seq1, seq2)
list(zipped) #튜플의 '리스트' 자료형이기 때문에 list() 함수를 이용하여 출력할 수 있다.
#[('foo', 'one'), ('bar', 'two'), ('baz', 'three')]

zip 함수는 여러 개의 순차 자료형을 받을 수 있으며 반환되는 리스트의 크기는 넘겨받은 순차 자료형 중 가장 짧은 크기로 정해진다.

seq3 = (False, True)
list(zip(seq1, seq2, seq3))
#[('foo', 'one', False), ('bar', 'two', True)]

마찬가지로 zip 함수를 이용하여 튜플의 리스트 자료형을 풀어줄 수도 있는데 이 경우 ' * ' 기호를 사용한다.

pitchers = [('Nolan', 'Ryan'), ('Roger', 'Clemens'),
            ('Schilling', 'Curt')]
first_name,last_name=zip(*pitchers)
first_name
#('Nolan','Roger','Schilling')
last_name
#('Ryan', 'Clemens', 'Curt')

reversed()

reversed 함수는 순차 자료형을 역순하여 반환한다. 또한, 이 함수는 제너레이터이기 때문에 list()나 for 문으로 값을 다 받아오기 전까지 순차 자로형을 생성하지 않는다.

list(reversed(range(10)))
#[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

딕셔너리

사전은 유연한 크기를 가지는 '키-값' 쌍으로, 키와 값은 모두 파이썬 객체다. 사전을 생성하는 방법은 중괄호 { } 를 사용하여 콜론으로 구분된 키와 값을 둘러싸는 것이다.

d1 = {'a' : 'some value', 'b' : [1, 2, 3, 4]}
d1
#{'a': 'some value', 'b': [1, 2, 3, 4]}
d1[7] = 'an integer' #이와 같은 방법으로 키-값을 추가할 수 있다.
d1
#{'a': 'some value', 'b': [1, 2, 3, 4], 7: 'an integer'}

사전의 메소드는 튜플, 리스트 메소드와 공유하는 점이 많다.
사전은 del 예약어나 pop 메서드를 이용하여 값을 삭제할 수 있는데 이 둘의 차이는 반환값의 유무 차이가 있다.

del d1[7]
d1
#{'a': 'some value', 'b': [1, 2, 3, 4]}
result=d1.pop('a') # a 키의 값에 해당하는 데이터를 result에 반환한다.
result 
#'some value'
'a' in d1 #in 예약어를 써서 해당 키가 사전에 존재하는지 알아볼 수 있다.
#False

keys와 values 메소드를 이용하여 각각에 해당하는 이터레이터를 반환받을 수 있다.

d1[5]='a'
list(d1.keys())
#['b',5]
list(d1.values())
#[[1,2,3,4],'a']

update 메소드를 이용하여 value를 갱신할 수도 있고, 새로운 원소를 추가할 수도 있다.

d1.update({'b' : 'foo', 'c' : 12})
d1
#{'b':[1,2,3,4],5:'a','c':12}

순차 자료형으로 사전 생성하기

두 개의 순차 자료형의 각 원소를 사전으로 만드는 일은 흔히 접하게 된다.

dict1={}
list_1=[1,2,3,4]
list_2=['a','b','c','d']
for a,b in zip(list_1,list_2):
	dict1[a]=b
dict1
#{1: 'a', 2: 'b', 3: 'c', 4: 'd'}

위 코드를 아래와 같이 간단하게 표현할 수도 있다

dict2=dict(zip(list_1,list_2))
dict2
#{1: 'a', 2: 'b', 3: 'c', 4: 'd'}

유효한 사전키

사전의 값으로는 파이썬의 어떤 객체든 가능하지만, 사전의 키는 스칼라형이나 튜플처럼 값이 바뀌지 않는(이뮤터블) 객체만 가능하다. 기술적으로 해쉬 가능해야 한다는 뜻이다. 해시 가능한 지는 hash함수를 사용해서 검사할 수 있다.

hash('string')
#822344810723539544
hash((1, 2, (2,3)))
#-9209053662355515447
hash((1, 2, [2, 3]))
#TypeError -> list는 뮤터플 객체이기 떄문.

리스트를 키로 사용하기 위한 한 가지 방법은 리스트를 튜플로 변경하는 것이다.

집합

집합은 '유일한 원소'만 담는 정렬되지 않은 자료형이다. 사전과 유사하지만, 값은 없고 키만 가지고 있다고 생각하면 된다. set 함수를 이용하거나 중괄호를 이용해서 생성할 수 있다.

set1=set([2, 2, 2, 1, 3, 3])
set1
#{1, 2, 3}
set2={2,2,2,1,3}
set2
#{1,2,3}

집합은 합집합, 교집합, 차집합 등 산술 집합 연산을 제공한다.
합집합은 union 메소드나 '|' 이항연산자를, 교집합은 intersection 메소드나 '&' 이항연산자로 구할 수 있다.

a = {1, 2, 3, 4, 5} ;b = {3, 4, 5, 6, 7, 8}
a.union(b) # 또는 a|b
#{1, 2, 3, 4, 5, 6, 7, 8}
a.intersection(b) # 또는 a&b 
#{3, 4, 5}

집합에 원소를 추가하는 add 메서드, 원소를 제거하는 remove 메서드도 있다. 차집합 메서드는 difference_update()이다.

a.add(6)
b.remove(6)
a
#{1, 2, 3, 4, 5, 6}
b
#{3, 4, 5, 7, 8}

리스트, 집합, 사전 표기법(Comprehension)

컴프리헨션은 파이썬에서 가장 사랑받는 기능 중 하나다. 이를 이용하면 간결한 표현으로 새로운 자료형(리스트, 집합, 사전)를 만들 수 있다.
기본형식은 [expr for val in collection if condition]로 구성된다.

사전과 집합도 같은 방식으로 적용될 수 있다.
{key-expr : value_expr for value in clolletion if condition}
{expr for val in collection if condition}

strings = ['a', 'as', 'bat', 'car', 'dove', 'python']
[x.upper() for x in strings if len(x)>2
#['BAT', 'CAR', 'DOVE', 'PYTHON']
unique_len={len(x) for x in strings}
unique_len
#{1, 2, 3, 4, 6} -> 집합의 특징을 이용한 것으로 집합은 유일한 값으로 구성되기 때문에 가능하다.
mapping={val:index for i,x in enumerate(strings)}
mapping
#{0: 'a', 1: 'as', 2: 'bat', 3: 'car', 4: 'dove', 5: 'python'}

중첩된 리스트 표기법

다음과 같이 영어 이름과 스페인어 이름을 담고 있는 리스트의 리스트가 있다고 하자.

all_data = [['John', 'Emily', 'Michael', 'Mary', 'Steven'],
            ['Maria', 'Juan', 'Javier', 'Natalia', 'Pilar']]

각 이름에서 알파벳 e가 2개 이상 포함된 이름의 목록을 구한다고 가정했을 때, 리스트는 for 문을 사용해서 다음처럼 구할 수 있다.

names_of_interest = []
for names in all_data:
    enough_es = [name for name in names if name.count('e') >= 2]
    names_of_interest.extend(enough_es)
names_of_interest
#['Steven']

위 코드 전체를 중첩된 리스트 표기법을 이용해서 한 줄로 구현할 수 있다.

result=[name for names in all_data for name in names if name.count('e')>=2]
result
#['Steven']

몇 단계의 중첩이라도 컴프리헨션을 사용할 수 있지만, 2단계 이상 중첩이 필요하다면 가독성이 사라지니 사용하지 않는 것이 좋다.

이렇게 내장 자료구조를 끝내고 다음 로그에 함수와 파일에 관한 내용을 다루겠다.

0개의 댓글