A Byte of Python - Data Structures Python


Data Structures

자료 구조는 기본적으로 어떤 데이터를 함께 저장할 수 있는 구조이다. 다른 말로 연관 데이터의 콜렉션을 저장하는데 사용되는 것이다.

파이썬에는 4가지의 빌트인 자료구조가 있는데 - 리스트, 튜플, 딕셔너리 그리고 셋이다. 이들을 어떻게 사용하는지 보고 이들이 어떻게 쉽게 만들어주는지를 보게 될 것이다.

리스트

리스트는 아이템의 순서로된 콜렉션을 담는 자료구조로서 리스트에 아이템의 연속을 저장할 수 있다. 이 것은 쇼핑리스트에 구매할 아이템을 차례로 저장하는 것으로 생각할 수 있는데 쇼핑리스트에는 구별된 라인에 한 아이템이 있지만 파이선에서는 이들사이에 콤마가 있다.

아이템의 리스트는 스퀘어 브라켓으로 감싸는데 파이썬은 이들이 아이템을 나타내는 것이라고 이해한다. 일단 리스트를 생성하면 추가, 제거 또는 검색할 수 있다. 아이템을 추가제거할 수 있으므로 리스트는 수정될 수 있어 뮤터블 데이터 형식이라 할 수 있다.

객체와 클래스에 대한 짧은 소개

비록 객체와 클래스를 지금까지 미뤄왔지만 리스트를 더 쉽게 이해할 수 있게 여기서 간단한 설명을 한다. 이는 나중에 더 자세히 살펴볼 것이다.

객체와 클래스의 사용 예시중의 하나가 리스트다. 변수 i를 사용하고 여기에 값을 할당한다. 5를 할당한다면 int 클래스 i 객체를 생성한다고 할 수 있다. 실제로 help(int) 를 통해 이에대해 더 자세히 이해할 수 있다.

클래스는 메소드를 가질 수 있는데 이는 클래스에서만 사용한다고 기대되는 미리지정된 함수들이다. 클래스의 객체를 가질 때만 그 기능성의 일부를 사용하게 된다. 예를 들어, 파이썬은 list클래스에 append 메소드를 제공하는데 이는 리스트의 끝에 아이템을 추가하는 것이다. 예를 들어, myList.append('an item') 은 리스트 myList에 문자열을 추가하는 것이다. 객체의 메소드에 접근하기 위해 . 를 사용한다.

클래스는 또한 필드를 가질 수 있는데 클래스에서만 사용되는 미리정의된 변수이다. 클래스를 가졌을때 이런 변수/이름을 사용할 수 있다. 필드도 또한 . 으로접근한다.

예시 (ds_using_list.py):

shoplist = ['apple','mango','carrot','banana']
print('I have', len(shoplist), 'items to purchase.')
print('These items are: ', end=' ')
for item in shoplist:
  print(item, end=' ')

print('\nI also have to buy rice.')
shoplist.append('rice')
print('My shopping list is now', shoplist)

print('I will sort my list now')
shoplist.sort()
print('Sorted shopping list is', shoplist)

print('The first item I will buy is', shoplist[0])
olditem = shoplist[0]
del shoplist[0]
print('I bought the' olditem)
print('My shopping list is now', shoplist)

Output:

$ python ds_using_list.pyI have 4 items to purchase.These items are: apple mango carrot bananaI also have to buy rice.My shopping list is now ['apple', 'mango', 'carrot', 'banana', 'rice']I will sort my list nowSorted shopping list is ['apple', 'banana', 'carrot', 'mango', 'rice']The first item I will buy is appleI bought the appleMy shopping list is now ['banana', 'carrot', 'mango', 'rice']

동작 분석

변수 shoplist 는 마켓에서 구매하는 누군가를 위한 쇼핑리스트이다. 구매할 아이템의 이름을 가진 문자열을 저장하지만 객체의 다른 형식인 갯수나 다른 리스트는 포함하지 않는다.

for..in 루프를 통해 리스트의 아이템을 열거한다. 지금 리스트가 또한 시퀀스임을 나타낸다. 시퀀스는 이후에 다시 살펴볼 것이다.

print함수의 end 파라미터는 라인 브레이크 대신 마지막에 쓸 문자를 나타낸다.

다음, append 메소드를 사용해 리스트에 아이템을 추가한다. 그러면 print 함수에 리스트를 전달하여 내용응ㄹ 출력한다.

그리고, sort메소드를 사용해 리스트를 정렬한다. 기억해야 할 점은 소트된 것을 반환하지 않고 리스트 자체를 소트한다는 점으로서 문자열이 작동하는 것과는 다르다. 이를 통해 리스트는 뮤터블하고 스트링은 뮤터블하지 않음을 보여준다.

다음으로, 마켓에서 아이템을 구매를 마침으로서 리스트에서 제거한다. 이는 del 문법으로 수행했다. 제거할 아이템을 del 한다. 리스트의 첫번째 아이템을 제거하기 위해 del shoplist[0] 을 썼다.

리스트 객체에 대한 모든 메소드를 보려면 help(list) 를 살펴본다.

튜플

튜플은 다중 객체를 한꺼번에 담기 위한 객체이다. 리스트와 비슷하게 생각할 수 있지만 이 것은 확장 성은 없는 리스트 같은 것이다. 튜플의 주요 기능중에 하나는 스트링 처럼 임뮤터블로서 수정할 수 없다.

튜플은 괄호에서 콤마로 구분해 지정한다.

튜플은 문장이나 사용자 지정함수에서 안전하게 값의 콜렉션이 변경되지 않음을 나타낼 때 사용되곤 한다.

예(ds_using_tuple.py)

# 괄호가 선택적이지만 명시적으로 사용하는 것이 좋다
zoo = ('python','elephant','penguin')
print('Number of animals in the zoo is', len(zoo))

new_zoo = 'monkey','camel', zoo # 괄호가 생략되어 있다
print('Number of cages in the new zoo is', len(new_zoo))
print('All animals in new zoo are',new_zoo)
print('Animals brought from old zoo are', new_zoo[2])
print('Last animal brought from old zoo is', new_zoo[2][2])
print('Number of animals in the new zoo is', len(new_zoo)-1+len(new_zoo[2]))

Output:

$ python ds_using_tuple.pyNumber of animals in the zoo is 3Number of cages in the new zoo is 3All animals in new zoo are ('monkey', 'camel', ('python', 'elephant', 'penguin'))Animals brought from old zoo are ('python', 'elephant', 'penguin')Last animal brought from old zoo is penguinNumber of animals in the new zoo is 5


동작 분석

변수 zoo는 아이템의 튜플을 나타낸다. len 함수는 튜플의 길이를 얻는데 사용할 수 있다. 이는 또한 튜플이 시퀀스임을 나타낸다.

이전 동물원이 닫음에 따라 이 동물들을 새로운 동물원으로 건낸다. 그러므로, new_zoo 튜플은 이전 동물원에서 가져온 동물들을 포함한다. 실제로 튜플 속의 튜플은 그 동등성을 잃지 않는다.

튜플 내의 아이템은 스퀘어 브라켓을 통한 아이템의 위치로 접근한다. 이를 인덱싱 연산자라고 한다. new_zoo는 new_zoo[2] 로 접근하고 new_zoo의 세번째 아이템 내의 세번째 아이템은 new_zoo[2][2] 로 접근한다. 

0 또는 1 아이템의 튜플
빈 튜플은 myempty=() 로 정의한다. 그러나 하나로된 튜플은 그렇게 단순하지 않다. 첫 번째 아이템 뒤에만 콤마를 사용할 수 있으므로 파이썬이 튜플과 괄호의 짝을 구별할 방법이 필요하다. 아이템 2만을 가지는 튜플을 정의하려면 singleton=(2,)과 같이 지정해야 한다.

펄 프로그래머에게 일러두기

리스트내의 리스트는 그 특성을 잃지 않는다. 펄 처럼 리스트가 플래턴되지 않는다. 튜플내의 튜플, 리스트 내의 튜플 또는 튜플 내의 리스트가 될 수 있다. 파이썬에서는 다른 객체로 저장될 뿐이다.

딕셔너리

딕셔너리는 주소록 같은 것으로서 사람의 이름으로 주소와 같은 연락처 세부사항을 찾을 때 쓰는 것으로서 키와 값으로 구성된다. 키는 반드시 유일해야 하며 같은 이름으로 두 사람이 등록되면 정확한 정보를 찾을 수 없을 것이다.

딕셔너리의 키를 위해서는 임뮤터블 객체만 사용할 수 있지만 값에는 뮤터블 또는 임뮤터블을 사용할 수 있다. 이는 기본적으로 키에는 단순한 객체를 사용해야 함을 나타낸다.

딕셔너리에서 키와 값의 짝은 d={key1:value1,key2=value2} 와 같은 표시로 사용한다. 키-값 은 콜론으로 구별하고 짝자체는 콤마로 구별하며 이는 컬리 브레이스로 감싼다.

딕셔너리에서의 키-값 짝은 순서화되어 있지 않다. 만약 특정 순서를 원한다면 자체적으로 소트해야한다.

딕셔너리는 dict 클래스 이다.

예 (ds_using_dict.py)
ab = {
  'Swaroop':'swaroop@swaroopch.com',
  'Larry':'larry@wall.org',
  'Matsumoto':'matz@ruby-lang.org',
  'Spammer':'spammer@hotmail.com'
}

print("Swaroop's address is", ab['Swaroop'])

del ab['Spammer']

print('\nThere are {} contacts in the address-book\n'.format(len(ab)))

for name,address in ab.items():
  print('Contact {} at {}'.format(name,address))

ab['Guido'] = 'guido@python.org'

if 'Guido' in ab {
  print("\n"Guido's address is", ab['Guido'])

Output:

$ python ds_using_dict.pySwaroop's address is swaroop@swaroopch.comThere are 3 contacts in the address-bookContact Swaroop at swaroop@swaroopch.comContact Matsumoto at matz@ruby-lang.orgContact Larry at larry@wall.orgGuido's address is guido@python.org

동작 방식

딕셔너리 ab를 정의했다. 특정 키를 인덱싱 연산자를 사용해 키, 값 짝에 접근한다. 

del 문을 사용해 키-값 짝을 제거할 수 있다. 딕셔너리를 지정하고 키에 대한 인덱신 연산자로 제거할 키를 지정함으로서 수행할 수 있다. 이 연산에 대해 키에 대한 연관 값은 알 필요가 없다.

다음으로, 튜플의 리스트를 반환하는 items 메소드로 각 키-값에 접근한다. 이 짝을 얻고 이를 변수 name과 address에 할당할 때 for..in 루프를 사용한다.

새로운 키-값 짝은 단순히 인덱신 연산자로 키에 접근해 값을 할당하여 처리한다.

if문에서 in 연산자를 사용해 해당 키-값 짝이 있는지 체크한다.

dict 클래스에 대한 세부 사항은 help(dict) 를 확인한다.

키워드 매개변수와 딕셔너리

함수에 키워드 매개변수를 사용했다면 이미 딕셔너리를 사용한 것이다! 키-값 짝은 함수정의의 파라미터 리스트에 있으며 함수 내에서는 해당 변수에 접근하기 위해 그저 딕셔너리의 키 접근을 하는 것이다.

시퀀스

리스트, 튜플, 그리고 문자열은 시퀀스의 한 예이다. 하지만 무엇이 시퀀스의 특별한 차이는 무엇일까?
주요 기능은 멤버 테스트(in, not in 연산)와 인덱신 연산자로서 시퀀스내에 직접적으로 특정 아이템을 얻도록 한다.

위에 언급된 시퀀스의 세가지 형식은 슬라이싱 연산을 제공하는데 시퀀스의 일부를 반환하는 것이다.

예 (ds_seq.py)
shoplist=['apple','mango','carrot','banana']
name='swaroop'

print('item 0 is',shoplist[0])
print('item 1 is',shoplist[1])
print('item 2 is',shoplist[2])
print('item 3 is',shoplist[3])
print('item -1 is',shoplist[-1])
print('item -2 is',shoplist[-2])
print('Character 0 is',name[0])

print('item 1 to 3 is', shoplist[1:3])
print('item 2 to end is', shoplist[2:])
print('item 1 to -1 is', shoplist[1:-1])
print('item start to end is',shoplist[:])

print('characters 1 to 3 is', name[1:3])
print('characters 2 to end is', name[2:])
print('characters 1 to -1 is', name[1:-1])
print('characters start to end is', name[:])

Output:

$ python ds_seq.pyItem 0 is appleItem 1 is mangoItem 2 is carrotItem 3 is bananaItem -1 is bananaItem -2 is carrotCharacter 0 is sItem 1 to 3 is ['mango', 'carrot']Item 2 to end is ['carrot', 'banana']Item 1 to -1 is ['mango', 'carrot']Item start to end is ['apple', 'mango', 'carrot', 'banana']characters 1 to 3 is wacharacters 2 to end is aroopcharacters 1 to -1 is waroocharacters start to end is swaroop

동작분석

먼저 어떻게 인덱스를 사용해 시퀀스의 각 아이템에 접근하는지 살펴본다. 이는 또한 서브스크립션 연산자라고 한다. 위에서보여진 스퀘어브라켓내에 숫자를 지정한다. 파이썬은 해당 위치의 아이템을 반환한다. 파이썬이 0 부터 시작함을 기억하자. 그래서 shoplist[0]은 첫번째 아이템 3은 네번째 아이템이다.

인덱스는 -값이 될 수도 있다. 위치는 시퀀스의 마지막으로부터 계산된다. 그러므로, shoplist[-1] 은 시퀀스의 마지막 아이템이고 shoplist[-2] 는 시퀀스의 마지막에서 두번째 아이템이다.

슬라이싱 연산은 스퀘어 브라켓 내에 콜론으로 구별된 숫자의 짝으로 지정해 나타낸다. 숫자는 옵셔널이지만 콜론은 그렇지 않다.

첫번째 숫자(콜론전의) 는 슬라이스 시작이고 두번째는 슬라이스 끝이다. 만약 두번째 위치가 넘어선다면 시퀀스 마지막에서 끝난다. 시작 위치에서 시작하고 마지막 위치의 바로 직전것까지 이다. 시작위치는 포함되지만 끝 위치는 포함되지 않는다.

그래서 shoplist[1:3] 은 위치 1에서 시작해 2는 포함하지만 3은 포함되지 않아 2개가 반환된다. 비슷하게 shoplist[:] 은 전체 시퀀스의 복사본이 반환된다.

음수 값으로 슬라이싱할 수도 있는데, 음의 값은 시퀀스의 마지막에서부터 시작한다. 예를들어, shoplist[:-1] 은 마지막 아이템을 제외한 슬라이스를 반환한다.

슬라이스를 위한 세번째 매개변수를 지정할 수도 있는데 이는 슬라이싱의 스텝합이다. (기본값은 1이다)

>>> shoplist = ['apple', 'mango', 'carrot', 'banana']>>> shoplist[::1]['apple', 'mango', 'carrot', 'banana']>>> shoplist[::2]['apple', 'carrot']>>> shoplist[::3]['apple', 'banana']>>> shoplist[::-1]['banana', 'carrot', 'mango', 'apple']

스텝이 2이면 위치 0,2 스텝이 3이면 0,3을 얻는다.

파이썬 인터프리터를 사용해 슬라이스 스펙의 다양한 조합을 시도해보자. 시퀀드의 훌륭한점은 튜플, 리스트, 스트링에 똑같은 방식으로 접근할 수 있다는 점이다.

세트

세트는 단순객체의 비순서화된 콜렉션이다. 콜렉션에서의 객체의 존재가 순서나 얼마나 자주있는지 보다 중요할 때 사용된다.

세트를 사용하면 멤버십을 테스트하고 이 세트가 다른 세트의 서브세트인지 두 세트의 교집합은 무엇인지 알 수 있다.

>>> bri = set(['brazil','russia','india'])
>>> 'india' in bri
True
>>> 'usa' in bri
False
>>> bric=bri.copy()
>>> bric.add('china')
>>> bric.issuperset(bri)
True
>>> bri.remove('russia')
>>> bri & bric # OR bir.intersection(bric)
{'brazil','india'}

동작 분석

학교에서 배운 기본적인 집합 연산을 기억하면 예시는 자체로 이해될 것이다. 만약 그렇지 않다면 "set theory" , "Venn diagram" 을 구글해보면 알 수 있을 것이다.

레퍼런스

객체를 생성하고 그 것을 변수에 할당할 때 변수는 객체에만 참조할 뿐 객체 자체는 아니다. 즉, 변수이름은 객체가 저장된 컴퓨터의 메모리를 가리키고 있다. 이 것을 객체에 이름을 바인딩한다고 한다.

일반적으로, 이에 대해 걱정할 필요는 없지만 이를 잘 몰라서 발생하는 문제점이 있다.

예 (ds_reference.py)

print('Simple Assignment')
shoplist=['apple','mango','carrot','banana']
# mylist는 같은 객체를 가리키는 다른 이름일 뿐이다.
mylist=shoplist 

# 첫번째 아이템을 구매하여 리스트에서 제거했다.
del shoplist[0]

print('shoplist is', shoplist)
print('mylist is', mylist)
# shoplist, mylist는 apple이 없는 같은 리스트내용을 표시한다.

print('Copy by making a full slice')
mylist=shoplist[:]
del mylist[0]

print('shoplist is',shoplist)
print('mylist is',mylist)
# 이제 두 리스트가 다름을 알 수 있다.

Output:

$ python ds_reference.pySimple Assignmentshoplist is ['mango', 'carrot', 'banana']mylist is ['mango', 'carrot', 'banana']Copy by making a full sliceshoplist is ['mango', 'carrot', 'banana']mylist is ['carrot', 'banana']

동작 분석

대부분의 설명은 주석에 달려있다.

리스트나 시퀀스나 컴플렉스 객체 복사본을 만들려면 슬라이싱 연산을 사용한다. 그냥 다른 변수에 할당하면 그저 같은 객체를 가리키는 다른 변수일 뿐이다.

펄 프로그래머를 위한 일러두기

리스트를위한 할당연산이 복사본을 생성하는 것이 아님을 기억하자. 시퀀스의 복사본을 만드려면 슬라이싱을 사용해야 한다.

문자열에 대한 추가 내용

문자열에 대해서는 이전에 살펴봤지만 문자열 또한 객체이며 문자열에 대한 메소드를 가짐을 알아둘 필요가있다. 실제로 문자열에대한 메소드를 사용했는데 format 메소드이다.

프로그래메서 사용하는 문자열은 클래스 str 이다. 다음 예시에서 쓸만한 메소드를 소개한다. 이 메소드들의 전체 리스트는 help(str) 를 통해 볼 수 있다.

예(ds_str_methods.py)

name = 'Swaroop'
if name.startswith('Swa'):
  print('Yes, the string starts with "Swa"')

if 'a' in name:
  print('Yes, it contains the string "a"')

if name.find('war') != -1:
  print('Yes, it contains the string "war"')

delimeter='_*_'
mylist=['Brazil','Russia','India','China']
print(delimeter.join(mylist))

Output:

$ python ds_str_methods.pyYes, the string starts with "Swa"Yes, it contains the string "a"Yes, it contains the string "war"Brazil_*_Russia_*_India_*_China

동작 설명

실행을 통해 다양한 문자열 메소드를 살펴본다. startswith 메소드는 문자열이 주어진 문자열로 시작하는지 확인한다. in 연산자는 주어진 문자열이 문자열의 일부분인지 확인한다.

find메소드는 문자열의 부분문자의 위치를 알기 위해 사용한다. find가 -1을 반환하면 부분 문자열이 없다는 뜻이다. str 클래스는 join이라는 메소드를 갖는데 시퀀스의 각 아이템사이의 델리미터와 같이 행동하며 이를 통해 생성된 큰 문자열을 반환한다.

갈무리

우리는 파이썬의 세부적인 빌트인 자료구조에 대해 살펴봤다. 이들 자료구조는 규모있는 프로그램을 작성하는데는 필수적이다.

이제는 실제적인 파이썬 프로그램을 설계하고 작성하는 방법을 살펴볼 것이다.



덧글

댓글 입력 영역