여기 있어요: Home Dive Into Python 3

난이도: ♦♦♢♢♢

고유 자료형

모든 철학은 경이로움에서 태어나, 궁금증으로 성장하지만, 무관심으로 생을 마친다.

— Michel de Montaigne

 

뛰어들기

자료형입니다. 우리의 첫번째 파이썬 프로그램은 잠시 접어두고, 자료형에 대해서 이야기 해 봅시다. 파이썬의 모든 값은 그 타입을 갖고 있습니다. 하지만 이를 명시적으로 선언할 필요는 없습니다. 어떻게 그게 가능할까요? 파이썬에서는 각 변수에 할당되는 값의 타입을 보고 변수의 데이터 타입을 유추하기 때문입니다. 그리고 내부적으로 이를 저장해놓기 때문에, 프로그래머가 명시적으로 변수의 타입을 선언해 줄 필요가 없습니다.

파이썬은 다양한 자료형을 가지고 있습니다만, 우선 그 중 중요한 몇 가지만 살펴봅시다:

  1. BooleanTrue 또는 False입니다.
  2. Numbers 는 (1이나 2 같은) 정수일 수도 있고, (1.1이나 1.2 같은) 실수일 수도, (1/2이나 2/3 같은) 분수일 수도 있으며, 심지어 복소수일 수도 있습니다.
  3. String은 유니코드 문자의 연속입니다, 일례로 HTML 문서는 String입니다.
  4. Bytebyte array, 일례로 JPEG 이미지 파일을 들 수 있습니다.
  5. List 는 어떤 값들을 순서대로 써놓은 것입니다.
  6. Tuple 은 내용을 변경할수 없는 (immutable) 한 값들을 순서대로 써놓은 것 입니다.
  7. Set은 어떤 값들을 순서에 상관없이 모아둔 것입니다.
  8. 사전Dictionaries 키-값 쌍의 모음들을 순서에 상관없이 모아둔 것입니다.

물론 이게 전부는 아닙니다. 파이썬에선 모든 것이 객체(Object) 입니다. 따라서 모듈(module), 클래스(class), 메소드(method), 파일(file), 심지어 컴파일된 코드(compiled code)도 일종의 데이터 타입입니다. 이 중 몇 가지는 이미 앞에서 다뤘었죠? 모듈은 __name__ 이라는 특성이 있고, 함수엔 docstrings이라는 특성이 있습니다. 클래스 타입은 클래스와 반복자 챕터에서 자세히 다루도록 하고요, 파일 타입은 파일챕터에서 다루겠습니다.

문자열과 바이트는 매우 중요한데, 복잡하기도 해서 별도 챕터로 뺐습니다. 아무튼 이렇게 별도의 챕터로 빼둔 것들은 그때 다시 배우도록 하고요, 우선은 그를 제외한, 좀 쉬운 것부터 살펴보죠.

Booleans

Boolean은 참 이나 거짓 가운데 하나를 의미하는 데이터 타입입니다. 파이썬은 이를 위해 TrueFalse 라는 상수를 정의해서 사용하고, 이 상수를 변수에 직접 할당하여 Boolean 타입으로 만들 수 있습니다. 어떤 상황에서는, 주어진 표현식이 반드시 True 이거나 False 여야 합니다. if 문을 예로 들 수 있습니다. if 문에 사용된 조건식은 반드시 TrueFalse 가운데 하나로 판명나야 합니다. 이런 상황을 Boolean context 라고 부릅니다. Boolean context 에는 어떤 파이썬 표현식도 사용할 수 있습니다. 파이썬이 알아서 TrueFalse 중 하나로 평가해 주거든요. 하지만, 자료형마다 서로 다른 룰을 가지고, 참과 거짓이 판명된다는 사실은 알고 계셔야 합니다. 아리송하죠? 역시 예제를 보는 편이 낫겠네요.

humansize.py 에서 가져온 코드를 봅시다:

if size < 0:
    raise ValueError('number must be non-negative')

size는 정수, 0도 정수 입니다. < 는 수 연산자이고요. 그래서, size < 0 라는 파이썬 표현식은 언제나 참 또는 거짓입니다. 파이썬 쉘에서 테스트 해보세요.

		
>>> size = 1
>>> size < 0
False
>>> size = 0
>>> size < 0
False
>>> size = -1
>>> size < 0
True

파이썬 버전 2에서와 같이, Boolean 타입을 숫자처럼 쓸 수도 있습니다. True1, False는 0을 의미합니다.

>>> True + True
2
>>> True - False
1
>>> True * False
0
>>> True / False
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: int division or modulo by zero

아차. 깜빡하고 말씀을 안드렸네요. 0으로 나누면 안됩니다. 알고 계셨죠?.

Numbers

이번 챕터에서는 다뤄야 할 것들이 굉장히 많습니다. 일단 파이썬 3는 정수형(integer)실수형(floating point number)을 지원합니다. 앞에서 말씀드렸듯이, 파이썬은 변수 선언시 타입을 별도로 선언하지 않기 때문에, 정수형인지 실수형인지를 판단하려면 값에 소숫점이 있는지 없는지를 봐야합니다.

>>> type(1)                 
<class 'int'>
>>> isinstance(1, int)      
True
>>> 1 + 1                   
2
>>> 1 + 1.0                 
2.0
>>> type(2.0)
<class 'float'>
  1. 어떤 값이나 변수의 타입을 확인하려면 type() 함수를 사용합니다. 당연히 1정수형입니다.
  2. isinstance() 함수를 사용하면, 첫번째 파라미터로 넘겨진 값이나 변수가 두번째 파라미터로 넘겨진 타입과 같은지 아닌지를 알 수 있습니다.
  3. 정수정수를 더하면 정수입니다.
  4. 정수실수를 더하면 실수입니다. 파이썬에선 정수실수를 더하면, 먼저 정수를 실수로 변경한 후 연산을 적용합니다. 따라서 연산결과는 실수입니다.

정수와 실수간 변환하기

앞에서 본 것과 같이, 더하기 연산자 같은 연산자 들은 주어진 정수값을 그때 그때 상황에 맞게 먼저 실수값으로 변경후 연산에 적용합니다. 하지만 다음과 같이 명시적으로 타입을 변경할 수도 있습니다.

>>> float(2)                
2.0
>>> int(2.0)                
2
>>> int(2.5)                
2
>>> int(-2.5)               
-2
>>> 1.12345678901234567890  
1.1234567890123457
>>> type(1000000000000000)  
<class 'int'>
  1. float() 함수를 사용해 int형을 float형으로 강제 형 변환할 수 있습니다.
  2. int() 함수를 사용해 float형을 int형으로 바꿀 수 있습니다.
  3. int() 함수는 양수의 경우 소수점 아래는 그냥 버립니다.
  4. int() 함수는 음수의 경우도 소수점 아래는 버립니다.
  5. 실수는 소숫점 아래 15 자리까지만 표현할 수 있습니다.
  6. 정수는 임의의 큰 수를 표현하는데 사용합니다.

파이썬 2에서는 int 형과 long 형을 따로 정의해 두었습니다. int 형의 크기는 sys.maxint 에 정의되어 있고요. 물론 윈도우인지 리눅스인지, 32비트인지 64비트인지 같은 OS 플랫폼에 따라 달라지긴 하지만, int 형의 크기는 최소한 232-1 입니다. 파이썬 3에서는 long 형을 따로 두지 않고, 모든 정수는 int 형 하나로 표시합니다. 자세한 내용은 PEP 237 를 읽어 보세요.

파이썬으로 산술 연산 하기

숫자를 가지고 할 수 있는 모든 일은 역시 파이썬으로도 할 수 있답니다.

>>> 11 / 2      
5.5
>>> 11 // 2     
5
>>> −11 // 2    
−6
>>> 11.0 // 2   
5.0
>>> 11 ** 2     
121
>>> 11 % 2      
1
  1. / 연산자는 실수 나눗셈을 합니다. 분모와 분자 모두 int형이더라도 결과는 항상 float 형이 됩니다.
  2. // 연산자는 조금 독특한 정수 나눗셈을 합니다. 만약 결과가 양수라면 소숫점 이하는 무조건 버립니다. 하지만, 조심할게 있습니다.
  3. 만약 // 연산자를 음수에 적용하면, 소숫점 이하는 역시 버리고, 결과를 가장 가까이 있는 자기보다 적은 정수값으로 만듭니다. 결과만 놓고 볼 때는, −5 보다 −6이 더 작으니까, 수학적으로 말하면 반내림과 같습니다. -5 라고 자신있게 말씀하셨던 분 많죠? 앞에서 조심하라고 말씀드렸었죠?
  4. // 연산자의 결과값이 언제나 정수형인 것은 아닙니다. 분자와 분모 중 하나가 실수형이면, 결과값도 실수입니다. 하지만 실수인 결과값 역시 가장 가까운 작은 정수로 변환됩니다.
  5. ** 연산자는 제곱을 의미합니다. 112121이죠.
  6. % 연산자는 정수 나눗셈을 하고 난 나머지를 돌려줍니다. 112로 나눈 나머지는 1이죠. 그래서 결과는 1입니다.

파이썬2 에서의 / 연산자는 정수 나눗셈을 의미했습니다만, 코드 안에 특별 지시자를 넣어 실수 나눗셈 또한 가능하도록 할 수 있었습니다. 그러나 파이썬3 에서는 / 연산자는 디폴트로 실수 나눗셈을 의미합니다. 더 자세한 내용은 PEP 238을 참고하세요.

분수(Fraction) 다루기

파이썬이 정수나 실수 연산만 할 수 있는건 아닙니다. 여러분이 중고등학교 때 배웠던, 수학의 정석에나 나올 법한 복잡한 수학공식들도 파이썬은 척척 해낼 수 있습니다.

>>> import fractions              
>>> x = fractions.Fraction(1, 3)  
>>> x
Fraction(1, 3)
>>> x * 2                         
Fraction(2, 3)
>>> fractions.Fraction(6, 4)      
Fraction(3, 2)
>>> fractions.Fraction(0, 0)      
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "fractions.py", line 96, in __new__
    raise ZeroDivisionError('Fraction(%s, 0)' % numerator)
ZeroDivisionError: Fraction(0, 0)
  1. 분수를 사용하려면 먼저 fractions 모듈을 임포트해야 합니다.
  2. 분수를 만드려면 Fraction 객체를 만들고, 파라미터로 분자와 분모를 넘겨줍니다.
  3. 파이썬은 어떤 분수 계산도 할 수 있습니다. 결과값으로는 언제나 새로운 Fraction 객체가 반환됩니다. 2 * (1/3) = (2/3)입니다.
  4. Fraction 객체는 분자와 분모를 감지해서 자동으로 크기를 맞춰줍니다. (6/4) = (3/2) 입니다.
  5. 파이썬도 분모가 0이 되면 안된다는 것 쯤은 알고 있습니다.

삼각함수(Trigonometry) 다루기

You can also do basic trigonometry in Python. 기본적인 삼각함수도 사용할 수 있습니다.

>>> import math
>>> math.pi                
3.1415926535897931
>>> math.sin(math.pi / 2)  
1.0
>>> math.tan(math.pi / 4)  
0.99999999999999989
  1. math 모듈은 원주율을 의미하는 &pi 상수를 정의하고 있습니다.
  2. math 모듈은 sin(), cos(), tan() 같은 기본 삼각함수들 뿐만 아니라, asin()같이 고급 삼각 함수도 지원합니다.
  3. tan(π / 4)1.0을 반환합니다. 0.99999999999999989가 아닙니다. 파이썬이 무한정밀도(infinite precision)을 지원하는 것은 아니거든요.

숫자도 참 또는 거짓이 될 수 있습니다

if 절 같은 곳에서는 숫자를 참/거짓을 판별할 때 사용할 수 있습니다. 0은 false로 0이 아닌 숫자는 true 로 판별됩니다.

>>> def is_it_true(anything):             
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
...
>>> is_it_true(1)                         
yes, it's true
>>> is_it_true(-1)
yes, it's true
>>> is_it_true(0)
no, it's false
>>> is_it_true(0.1)                       
yes, it's true
>>> is_it_true(0.0)
no, it's false
>>> import fractions
>>> is_it_true(fractions.Fraction(1, 2))  
yes, it's true
>>> is_it_true(fractions.Fraction(0, 1))
no, it's false
  1. 파이썬 쉘 안에서도 함수를 정의할 수 있답니다. 함수를 작성해 나갈때는 엔터키를 이용해 다음 행으로 계속할 수 있고, 함수 정의가 끝났을때는 엔터키를 두번 사용해 공백 라인이 맨 마지막에 오도록 하면 됩니다.
  2. boolean 값이 와야 하는 자리에 숫자가 사용되면 true 나 false 로 판명됩니다.
  3. 실수값도 마찬가지로 0이 아니면 True 입니다. 0.0False 입니다. 반올림 등의 연산을 실수에 적용할 때는 조심하세요. 0.0000000000001 같은 값도 0이 아니기 때문에 True로 판명됩니다.
  4. 분수도 역시 0이 아니면 True 입니다. Fraction(0, n)의 경우 n에 어떤 값을 넣더라도 False 입니다. 이를 제외한 나머지는 모두 True입니다

리스트 (List)

리스트 (List) 데이터 타입은 파이썬에서 없어서는 안 될 충실한 심복중 하나입니다. list라는 단어를 듣는 순간, 이렇게 생각하실지도 모르겠습니다. "음, 먼저 사이즈를 정해서 미리 선언해주고, 같은 데이터 타입만 집어 넣을 수 있겠군". 파이썬의 list 데이터 타입을 다른 프로그래밍 언어의 배열과 비슷할 거라고 생각하셨다면, 틀렸습니다. 파이썬 list는 그것보다 훨씬 멋지거든요.

펄과 비교하면, list는 펄 5의 배열과 유사합니다. 한편, 펄에서는 배열의 변수명이 항상 @로 시작해야 하지만, 파이썬엔 그런 제약이 없습니다. 파이썬은 내부적으로 데이터 형을 관리하기 때문에 명시적으로 나타낼 필요가 없는거죠.

파이썬 list는 다른 언어, 가령, 자바의 배열보다 훨씬 강력합니다. 물론 딱 배열처럼만 사용하려면 그렇게 사용할 수도 있습니다. 하지만, 어떤 타입의 객체도 담을 수 있고, 그 크기가 동적으로 늘어날 수 있다는 점에서는 자바의 ArrayList 클래스에 비유할 수 있겠네요.

list 생성하기

파이썬 list를 만드는 방법은 간단합니다. 꺽쇠 안에 값을 적어주고, 콤마로 구분하면 됩니다.

>>> a_list = ['a', 'b', 'mpilgrim', 'z', 'example']  
>>> a_list
['a', 'b', 'mpilgrim', 'z', 'example']
>>> a_list[0]                                        
'a'
>>> a_list[4]                                        
'example'
>>> a_list[-1]                                       
'example'
>>> a_list[-3]                                       
'mpilgrim'
  1. 다섯개의 원소를 가진 list를 정의했습니다. 출력 결과에 주목하세요. 순서가 변하지 않고 그대로입니다.
  2. list는 배열처럼 사용할 수 있다고 앞에서 말씀드렸죠? a_list[0] 은 alist 라는 list의 첫번째 원소를 가져옵니다.
  3. 맨 마지막 아이템인 5번째 아이템은 a_list[4] 로 가져옵니다.
  4. 인덱스에 음수값을 주면 뒤에서부터 카운트 해온 값을 반환합니다. a_list[-1] 는 뒤에서 첫번째 아이템, 그러니까 맨 마지막 아이템을 반환합니다.
  5. 혹시 음수값을 사용한 예가 잘 이해가 안되시는 분들은 이런 식으로 한번 생각해보세요. a_list[-n] == a_list[len(a_list) - n]. 그러니까 이 예제에서는 이렇게 됩니다. a_list[-3] == a_list[5 - 3] == a_list[2].

list 쪼개기 (Slicing List)

정의된 list에 대해 그 일부분을 떼내어 새로운 list로 정의할 수 있습니다. 이를 리스트 쪼개기 (list slicing)라고 합니다.

>>> a_list
['a', 'b', 'mpilgrim', 'z', 'example']
>>> a_list[1:3]            
['b', 'mpilgrim']
>>> a_list[1:-1]           
['b', 'mpilgrim', 'z']
>>> a_list[0:3]            
['a', 'b', 'mpilgrim']
>>> a_list[:3]             
['a', 'b', 'mpilgrim']
>>> a_list[3:]             
['z', 'example']
>>> a_list[:]              
['a', 'b', 'mpilgrim', 'z', 'example']
  1. list를 쪼갤 때는 인덱스 두개와 그 사이에 콜론을 적어줍니다. 결과로 첫번째 인덱스 아이템(예에서는 a_list[1]) 을 포함하고, 두번째 인덱스의 바로 앞 아이템까지(예에서는 a_list[3])를 포함하는 list를 반환합니다.
  2. list를 쪼갤때 인덱스로 음수값을 줘도 됩니다. 헷갈리시면 이런 식으로 생각하셔도 됩니다. 첫 번째 인덱스는 포함시키고 싶은 파트의 첫 번째 위치입니다. 두 번째 인덱스는 포함시키고 싶지 않은 파트의 첫 번째 위치이고요. 그리고 이 둘 사이의 아이템을 취한 것이 결과입니다.
  3. list의 인덱스는 0부터 시작합니다. 따라서 a_list[0:3]은 a_list 라는 list의 맨 처음부터 3개의 아이템입니다. a_list[0] 부터 시작하여 a_list[3]의 바로 앞까지 입니다.
  4. 시작 인덱스가 0인 경우엔 생략이 가능합니다. 따라서 a_list[:3]a_list[0:3]와 같은 의미입니다.
  5. 마찬가지 원리로 종료 인덱스가 list의 길이와 같은 경우엔 생략이 가능합니다. 따라서 a_list[3:]a_list[3:5] 와 같은 의미입니다. a_list에는 다섯 개의 아이템이 있기 때문입니다. list 쪼개기에서 아름다운 대칭의 미가 느껴지지 않나요? a_list[:3] 는 list의 앞부분 3개 아이템을 반환하고, a_list[3:]는 이를 제외한 뒷부분 전부의 아이템을 반환합니다. 따라서 이렇게 일반화할 수 있겠네요. a_list[:n] 는 언제나 앞쪽 n개의 아이템을 반환하고, a_list[n:]는 n번째 아이템부터 list 끝까지를 반환합니다
  6. 만약 시작과 종료 인덱스 모두를 생략하면, list 내 모든 아이템을 포함한다는 의미입니다. 원래 list랑 똑같은 복사본이 생기는 것입니다. 이를 이용하여, a_list[:] 라고 써주면 간단히 a_list list를 복사할 수 있습니다.

list에 아이템 추가하기

list에 아이템을 추가하는 방법들을 알아봅니다.

>>> a_list = ['a']
>>> a_list = a_list + [2.0, 3]    
>>> a_list                        
['a', 2.0, 3]
>>> a_list.append(True)           
>>> a_list
['a', 2.0, 3, True]
>>> a_list.extend(['four', 'Ω'])  
>>> a_list
['a', 2.0, 3, True, 'four', 'Ω']
>>> a_list.insert(0, 'Ω')         
>>> a_list
['Ω', 'a', 2.0, 3, True, 'four', 'Ω']
  1. + 연산자를 이용하여 list를 합칠 수 있고, 결과로는 새로운 list가 반환됩니다. list에 담을 수 있는 아이템 갯수에는 제한이 없는데, 정확히 말하면 사용할 수 있는 메모리에 제한이 없다면 제약이 없는 셈입니다. 만약 구현하려는 어플리케이션의 사용 가능한 메모리가 많지 않다면, 사용시 주의해야 합니다. 이 코드가 하는 일을 두 단계로 나누면, 먼저 a_list[2.0, 3]을 합치고, 그 결과를 다시 a_list 에 할당합니다. 다루고 있는 list의 크기가 크지 않다면 괜찮겠지만, 상당한 크기인 경우에는 두번째 단계인 할당이 일어나는 순간 많은 양의 메모리가 요구될 수 있습니다. 그 점을 미리 염두에 두셔야 합니다.
  2. list 내부에는 어떤 타입의 데이터도 함께 담을 수 있습니다. 꼭 같은 타입의 데이터일 필요는 없습니다. 이 예제는 string, 실수, 정수 이렇게 서로 다른 데이터 타입을 가진 list를 보여줍니다.
  3. append() 메소드를 이용해 list의 맨 끝에 아이템을 추가할 수 있습니다. 이제 4개의 서로 다른 데이터 타입이 list 안에서 공존하는군요.
  4. list는 파이썬 내부에선 클래스로 구현되있습니다. 따라서 list를 하나 만든다는 것은 list 클래스의 인스턴스를 하나 만든다는 것과 동일합니다. 모든 인스턴스에는 메소드가 있고, list 또한 예외가 아닙니다. 이 예제 코드에서는 list 클래스의 extend() 메소드를 이용하여 기존 list에 아이템을 추가하고 있습니다. 메소드 인자가 추가할 데이터입니다.
  5. insert() 메소드는 아이템 하나를 정해진 위치에 삽입할 때 사용합니다. 첫번째 인자는 삽입하고 싶은 위치이고, 동작이 성공적이면 뒤쪽에 위치한 아이템들은 자리가 하나씩 뒤로 밀리게 됩니다. 삽입하는 데이터가 기존 list에 이미 존재하더라고 상관없습니다. list는 중복된 데이터도 허용하기 때문입니다. 예제 코드에서는 'Ω' 라는 값이 첫번째 인덱스와 마지막 인덱스, 이렇게 두 군데에서 보이고 있습니다.

a_list.insert(0, value)는 펄 함수 중 unshift() 함수와 비슷합니다. list의 맨 앞에 아이템을 삽입하고 나머지 아이템들은 전부 뒤로 한 칸씩 이동시킵니다.

이번에는 append()extend()의 차이점에 대해 알아 봅시다.

>>> a_list = ['a', 'b', 'c']
>>> a_list.extend(['d', 'e', 'f'])  
>>> a_list
['a', 'b', 'c', 'd', 'e', 'f']
>>> len(a_list)                     
6
>>> a_list[-1]
'f'
>>> a_list.append(['g', 'h', 'i'])  
>>> a_list
['a', 'b', 'c', 'd', 'e', 'f', ['g', 'h', 'i']]
>>> len(a_list)                     
7
>>> a_list[-1]
['g', 'h', 'i']
  1. extend() 메소드는 list 타입의 인자를 받아서, 그 내부의 아이템들을 a_list안에 추가함으로써, 기존의 list를 그만큼 연장해(extend)줍니다.
  2. 처음 3개의 아이템을 가진 list에 3개의 아이템이 있는 또 다른 list를 이용해 늘렸으므로, 이제 아이템의 갯수는 6개가 되었습니다.
  3. 한편 append() 메소드는 인자로 들어온 값을 list에 추가합니다. 인자는 하나이며, 인자의 타입은 어떤 타입이라도 괜찮습니다. 예제코드에서는 3 개의 아이템이 있는 list를 인자로 넘겨주고 있습니다.
  4. 앞에서 list 내 아이템의 갯수가 6개 였으니까, 이제 9개가 되어야겠군요. 앗. 그런데, 파이썬 쉘은 7개라고 나오네요. 왜죠? 이게 바로 extend 메소드와 append 메소드 간의 차이점입니다. extend 메소드는 인자로 넘어온 list내 아이템 하나 하나를 추가해주지만, append 메소드는 인자가 list건 뭐건 상관없이 하나의 객체로 판단하고 이를 기존 list 끝에 추가합니다. 그래서 예제코드에선 ['g', 'h', 'i'] 가 3개의 개별적인 아이템이 아닌, 전체 list 하나가 통째로 list 안에 들어가게 된거죠. list 객체는 다른 list 객체도 내부 아이템으로 가질 수 있다는 사실을 잊지마세요.

리스트 검색하기

>>> a_list = ['a', 'b', 'new', 'mpilgrim', 'new']
>>> a_list.count('new')       
2
>>> 'new' in a_list           
True
>>> 'c' in a_list
False
>>> a_list.index('mpilgrim')  
3
>>> a_list.index('new')       
2
>>> a_list.index('c')         
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
ValueError: list.index(x): x not in list
  1. 특정 값이 list 내에 몇 개나 있는지 그 횟수를 알고 싶을때는 count() 메소드를 사용하세요.
  2. 그냥 어떤 값이 들어 있는지 아닌지만 알고 싶다면 in 연산자를 이용하는 편이 간단하고, 성능도 좀 더 빠릅니다. 결과로 TrueFalse가 반환됩니다.
  3. 특정 값이 list내에서 몇 번째 있는지 알고 싶다면 index() 메소드를 사용하세요. 이 함수는 list 전체를 검색하여 해당 위치(0 부터 시작합니다) 를 반환합니다. 검색을 수행할 위치도 지정할 수 있는데요, 두번째 파라미터가 검색을 시작할 위치를 의미하고, 세번째 파라미터가 검색을 끝낼 위치를 의미합니다.
  4. 찾고자 하는 값이 list 내에 여러 번 등장하더라도, index() 메소드는 처음으로 발견한 위치를 반환합니다. 예에서 'new' 라는 문자열은 a_list[2] 에 하나, a_list[4]에 다른 하나, 이렇게 모두 두 번 등장합니다. 하지만, index() 메소드는 첫번째 위치인 2만 반환합니다.
  5. 검색대상이 발견되지 않으면, 예외를 일으킵니다.

잠깐만요. 뭔가 맘에 안듭니다. 보통 다른 언어같은 경우엔 검색하는 값이 없으면 -1 같은 값을 리턴해서 에러를 나타내잖아요? 파이썬도 그렇게 하면 안되나요? 예외까지 발생 시키는건 좀 오바 같은데요. 이렇게 생각하시는 분도 분명 계실겁니다. 하지만 한번 더 생각해보세요. 우리 프로그래머들은 대부분 게으릅니다. 만약 -1을 리턴받았는데, 에러값 조사도 꼼꼼히 하지 않고 그 위치에 있는 데이터를 읽으려고 하면 어떤 일이 생길까요? 아마 프로그램이 꽥 소리를 내며 죽을 수도 있습니다. 이런 코드는 잘 돌다가 꼭 금요일 밤에 비명횡사하기도 합니다. 금요일 밤 11시에 이런 코드를 디버깅하며 밤을 지새우기 보다, 차라리 언어레벨에서 예외를 던져주는 편이 훨씬 낫습니다. 물론, 예외 처리도 잘 해주지 않았다면 프로그램이 죽는건 마찬가지겠지만, 디버깅은 훨씬 더 쉬울겁니다. 예외를 맨 처음 던진 곳을 찾아가면 되니까요. 파이썬의 이런 디자인, 정말 멋지지 않나요?

리스트에서 아이템 삭제하기

list는 배열과 달리 아이템을 추가하면 그 크기가 동적으로 늘어나고, 삭제하면 동적으로 줄어듭니다. 앞에서는 list에 아이템을 추가해 list가 동적으로 확장시키는 방법을 알아봤으니, 이제부턴 list내 아이템을 삭제하는 방법을 알아봅시다. 아이템을 추가할 때와 마찬가지로 몇 가지 다른 방법을 사용할 수 있습니다. Lists can expand and contract automatically. You’ve seen the expansion part. There are several different ways to remove items from a list as well.

>>> a_list = ['a', 'b', 'new', 'mpilgrim', 'new']
>>> a_list[1]
'b'
>>> del a_list[1]         
>>> a_list
['a', 'new', 'mpilgrim', 'new']
>>> a_list[1]             
'new'
  1. del 구문을 이용하여 list 내 특정 아이템을 삭제할 수 있습니다.
  2. list에서 특정 위치의 아이템을 삭제했다고 해서, 그 자리가 마치 이빨빠진 자리처럼 계속 비워져 있지는 않습니다. 그 뒷부분에 위치한 아이템들이 자동으로, 차례대로 이동하여 빈자리를 메꿔줍니다. 따라서 여전히 그 자리는 다른 아이템에 의해 채워지게 됩니다.

어떤 아이템을 지우고 싶은데 위치를 모른다고요? 문제 없습니다. 위치 인덱스 대신 삭제하고 싶은 값을 직접 입력하면 됩니다.

>>> a_list.remove('new')  
>>> a_list
['a', 'mpilgrim', 'new']
>>> a_list.remove('new')  
>>> a_list
['a', 'mpilgrim']
>>> a_list.remove('new')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: list.remove(x): x not in list
  1. remove() 메소드도 삭제에 사용하는 메소드 입니다. 삭제하고 싶은 아이템의 값을 파라미터로 받아 list내에서 첫번째로 발견되는 해당 아이템을 삭제합니다. 다시 한번 말씀드리지만, 특정 아이템이 삭제된 자리는 그 뒤의 아이템들이 자동으로 차례차례 메꿔줍니다.
  2. list 내 존재하지 않는 값을 삭제하려고 하면 예외가 발생됩니다.

리스트에서 아이템 삭제하기: 보너스

Another interesting list method is pop(). The pop() method is yet another way to remove items from a list, but with a twist. pop()이라는 메소드를 이용하여 list에서 아이템을 제거할 수도 있습니다. 하지만, 주의할 점이 있습니다.

>>> a_list = ['a', 'b', 'new', 'mpilgrim']
>>> a_list.pop()   
'mpilgrim'
>>> a_list
['a', 'b', 'new']
>>> a_list.pop(1)  
'b'
>>> a_list
['a', 'new']
>>> a_list.pop()
'new'
>>> a_list.pop()
'a'
>>> a_list.pop()   
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: pop from empty list
  1. pop() 메소드를 인자없이 호출하면 list의 맨 마지막 아이템을 반환하고, 해당 아이템은 삭제됩니다.
  2. pop() 메소드에 인자로 위치 인덱스를 주면, 해당 아이템을 반환후 삭제합니다.
  3. 비어있는 list에 호출하면 예외가 발생됩니다.

인자없는 pop() 메소드 호출은 펄의 pop() 함수와 유사합니다. list 맨 마지막 원소를 반환하고, list 내에서는 삭제합니다. 펄과 다른 점은, list의 맨 앞부분 원소를 삭제할 때, 펄은 shift()라는 메소드를 따로 두고 있지만, 파이썬은 인자로 0을 주면 된다는 점입니다.

list로 참, 거짓 판단하기

if문과 같이 참, 거짓을 판별하는 구문에서도 list를 사용할 수 있습니다.

>>> def is_it_true(anything):
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
...
>>> is_it_true([])             
no, it's false
>>> is_it_true(['a'])          
yes, it's true
>>> is_it_true([False])        
yes, it's true
  1. 빈 list는 false를 의미합니다.
  2. list내에 아이템이 하나라도 들어있으면 true로 판명됩니다.
  3. list내에있는 아이템의 값과는 관계없이, 비어있는 list가 아니라면 true로 판명됩니다.

튜플 (Tuple)

튜플은 list의 일종입니다. 일반 list와 다른점은 내부 아이템의 수정이 불가능합니다. 튜플은 일단 생성되고 나면 어떤 방식으로든 수정이 불가능 합니다.

>>> a_tuple = ("a", "b", "mpilgrim", "z", "example")  
>>> a_tuple
('a', 'b', 'mpilgrim', 'z', 'example')
>>> a_tuple[0]                                        
'a'
>>> a_tuple[-1]                                       
'example'
>>> a_tuple[1:3]                                      
('b', 'mpilgrim')
  1. 튜플을 정의하는 방법은 list와 동일합니다. 다만 list에서는 둥근괄호를 사용하였지만, 튜플에서는 각진 괄호를 사용합니다.
  2. 튜플도 list와 마찬가지로 첫번째 원소는 0부터 시작합니다.
  3. list와 마찬가지로 인덱스에 마이너스 값이 사용되면 뒤쪽 위치에서부터 카운트합니다.
  4. list와 마찬가지로 쪼개기(slicing)도 가능합니다. list에서도 쪼개기를 하면, 새로운 list가 만들어지는 것처럼 튜플에서도 쪼개기를 하면 그 결과로 새로운 튜플이 생성됩니다.

튜플이 list와 다른점 중 눈여겨 볼 점은 일단 만들어지고 난 다음엔 수정이 불가능하다는 것입니다. 튜플이란 결국 읽기 전용 list인 셈이고, 이런 특징을 고상하게 말하면 immutable 하다고 합니다. 튜플은 읽기 전용 list이므로 뭔가 변경을 할때 사용할 만한 메소드를 하나도 지원하지 않습니다. append(), extend(), insert(), remove(), and pop() 와 같은 메소드들이 하나도 없습니다. 다만 쪼개기 (slicing) 정도는 지원하기 때문에, 어떤 값이 들어있는지 검사할 때 사용할 수 있겠습니다.

# 앞 예제에서 계속 이어집니다
>>> a_tuple
('a', 'b', 'mpilgrim', 'z', 'example')
>>> a_tuple.append("new")               
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
AttributeError: 'tuple' object has no attribute 'append'
>>> a_tuple.remove("z")                 
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
AttributeError: 'tuple' object has no attribute 'remove'
>>> a_tuple.index("example")            
4
>>> "z" in a_tuple                      
True
  1. 이미 만들어진 튜플에 새로운 아이템을 추가할 수는 없습니다. append()extend() 같은 메소드는 없다구요.
  2. 이미 만들어진 튜플로부터 어떤 값을 삭제할 수는 없습니다. remove() or pop() 같은 메소드는 없거든요.
  3. 튜플안에 들어있는 어떤 값을 검색하는 것은 문제가 없습니다. 검색은 수정이 아니잖아요.
  4. 튜플내의 어떤 값을 검색할 때 in 연산자를 사용해도 됩니다.

그래서, 튜플로 뭘 할 수 있을까요?

튜플을 list로 전환하거나, 꺼꾸로 list를 튜플로 전환하는 것도 가능합니다. 이를 위해 파이썬의 내장함수인 tuple() 은 list를 튜플로 변경할 때 사용할 수 있습니다. 그리고 list() 는 튜플을 list로 변경할 때 사용합니다. list나 튜플 내부에 들어있던 아이템에는 전혀 영향을 미치지 않습니다. 다만 list에서 튜플로, 또는 튜플에서 list로 타입을 변경할 뿐입니다. 우리가 어렸을 적 놀던 얼음 땡을 기억하시나요? 이 함수들이 바로 파이썬의 얼음 땡 함수입니다. tuple() 은 list에 얼음을 걸어서 읽기전용인 튜플로 만들고, list()는 꼼짝없이 얼어붙어있던 읽기 전용 튜플을 읽기 쓰기가 가능한 list로 만들어 줍니다.

튜플로 참, 거짓 판단하기

if문과 같이 참, 거짓을 판별하는 구문에서도 튜플을 사용할 수 있습니다.

>>> def is_it_true(anything):
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
...
>>> is_it_true(())             
no, it's false
>>> is_it_true(('a', 'b'))     
yes, it's true
>>> is_it_true((False,))       
yes, it's true
>>> type((False))              
<class 'bool'>
>>> type((False,))
<class 'tuple'>
  1. 빈 튜플은 false를 의미합니다.
  2. 튜플내에 원소가 하나라도 있으면 true 입니다.
  3. 튜플내에 원소가 하나라도 있으면 true 입니다. 원소의 값이 무엇인지는 상관없고요. 그건 그렇고 콤마는 무슨뜻일까요?
  4. 원소가 하나뿐인 튜플을 만들때는 그 원소뒤에 콤마를 써줘야 합니다. 콤마가 없으면 파이썬은 그냥 괄호가 두번 연달아 사용됬다고 인식할 뿐, 그중 하나가 튜플이라고 판단하지 못합니다.

여러 값을 한꺼번에 할당하기

파이썬 튜플이 가진 멋진 기능중 하나는, 여러 값을 한꺼 번에 할당해야 하는 상황에서 튜플을 사용할 수 있다는 점입니다.

>>> v = ('a', 2, True)
>>> (x, y, z) = v       
>>> x
'a'
>>> y
2
>>> z
True
  1. v 는 세 개의 원소로 이루어진 튜플입니다. (x, y, z) 는 세 개의 변수를 가진 튜플이구요. 서로 짝이 맞기 때문에 한쪽에서 다른 쪽으로 할당하면, 변수가 선언된 순서대로 할당이 이루어집니다.

튜플의 이런 특징은 꽤 쓸모가 많습니다. 일례로 특정 범위 내에 있는 값들을 변수들에 할당해야 할 때, 파이썬 내장함수인 range() 와 튜플을 함께 사용하여 간단하게 해결할 수 있습니다. 아래 예제코드를 보시죠.

>>> (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)  
>>> MONDAY                                                                       
0
>>> TUESDAY
1
>>> SUNDAY
6
  1. 내장함수 range() 는 일련의 정수들을 만들어 냅니다. (정확히 말하면 range() 는 list나 tuple이 아닌 반복자(iterator)를 반환합니다. 이들의 차이점은 뒤에서 따로 다룰겁니다) MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, and SUNDAY 와 같은 변수들을 정의했고요. (이 예제 코드는 사실 calendar 모듈에서 가져왔습니다. 이 모듈은 달력과 관련된 기능들을 모아둔 모듈인데요, UNIX 운영체제를 보면cal 이라고 하는 날짜를 출력하는 프로그램이 있는데, 이와 비슷한 기능을 가진 모듈입니다. 이 calendar 모듈은 월요일부터 일요일까지를 정수의 상수값으로 정의해두고 있습니다.)
  2. 자 이제 모든 변수가 값을 갖게되었습니다. MONDAY 는 0, TUESDAY1, 이런식으로 값이 할당됩니다.

튜플을 이용하면 둘 이상의 값을 인자로 받아, 둘 이상의 값을 반환하도록 하는 함수도 작성할 수 있습니다. 여러 값들을 묶어서 하나의 튜플 변수로 처리하면 되니까요. 실제로 파이썬 표준 라이브러리의 수 많은 함수들이 이 테크닉을 즐겨 이용하고 있답니다. (궁금하신 분은 os 모듈 소스코드를 들여다 보세요.)

Sets

set은 마치 유일한 값을 가진 데이터들을 뒤죽박죽인 채로 넣어둔 자루와 같습니다. set 안에 들어갈 수 있는 데이터는 추후 변경이 불가능 (immutable) 해야 합니다. 두 개의 set 이 정의되 있다면, 마치 수학에서의 합집합 (union), 교집합 (intersection) 그리고 교집합을 제외한 나머지 (difference) 등과 같은 개념을 표준 set 함수를 이용해 구할 수 있습니다.

Set 생성

먼저 set 을 만들어봅시다. 간단합니다.

>>> a_set = {1}     
>>> a_set
{1}
>>> type(a_set)     
<class 'set'>
>>> a_set = {1, 2}  
>>> a_set
{1, 2}
  1. 어떤 하나의 값을 가지고 set을 만드는 경우엔, 그 값 주변을 코드에서와 같이 curly brace ({})로 감싸주면 됩니다.
  2. set의 타입은 클래스입니다. 클래스에 대해서는 나중에 따로 설명해 드리겠습니다. 걱정마세요.
  3. 두 개 이상의 값을 가지고 set을 만드는 경우엔, 각각의 값들 사이에 콤마를 넣어주고, curly brace ({})로 감싸주면 됩니다.

list를 set으로 변환할 수도 있습니다.

>>> a_list = ['a', 'b', 'mpilgrim', True, False, 42]
>>> a_set = set(a_list)                           
>>> a_set                                         
{'a', False, 'b', True, 'mpilgrim', 42}
>>> a_list                                        
['a', 'b', 'mpilgrim', True, False, 42]
  1. list를 set으로 변환할때는 set()함수를 사용하면 됩니다. 혹시 어떤 깐깐한 분들께서는 "잠깐만요, 이건 사실 함수호출이 아니라 객체 생성 (class instantiation) 아닌가요?" 라고 질문하실수도 있겠습니다. 약속드릴게요. 제가 나중에 다시 설명드리겠습니다. 일단 지금은 set() 이 함수처럼 동작하고, set 을 반환한다고 알고 넘어갑시다.
  2. set 내부에는 어떤 데이터 타입도 포함될 수 있다고 말씀드렸죠? 그리고 뒤죽박죽인 채로 (unordered) 들어있다고 말한것도 기억하시나요? 비록 이 set 은 list 를 이용해 생성되었지만, list 에 들어있던 원소들의 순서는 깡그리 무시됩니다. 그리고 또 다른 아이템들을 이 set 안에 추가하더라도, 추가되는 순서또한 무시된다는 걸 기억하세요.
  3. 원본 list 는 그대로 있습니다.

set 안에 집어넣을 아이템이 아직 준비되지 않았다구요? 괜찮습니다. 빈 set을 만들면 되거든요.

>>> a_set = set()    
>>> a_set            
set()
>>> type(a_set)      
<class 'set'>
>>> len(a_set)       
0
>>> not_sure = {}    
>>> type(not_sure)
<class 'dict'>
  1. To create an empty set, call set() with no arguments. 빈 set 을 만드려면 set()라고 입력합니다. 함수안에 어떤 인자도 주지 않습니다.
  2. 비어있는 set을 출력하면 좀 의외의 결과가 나오죠? 뭐 {} 랑 비슷한 것이 나와야 하지 않을까라고 생각하셨을지도 모르겠습니다. 하지만 {} 는 빈 dictionary 를 의미합니다. dictionary 에 대해서는 조금있다가 배우도록 하겠습니다.
  3. 어찌됬던 간에 이게 바로 set입니다…
  4. …그리고 텅텅 비어있는 set이죠.
  5. Python 2에서 정의된 룰을 따르다 보니, curly brace 로는 빈 set이 아닌 빈 dictionary를 만들게 됩니다.

Set 변경하기

set에 새로운 아이템을 추가하는 데는 두 가지 방법이 있습니다. 하나는 add() 메소드를 호출하는 것이고, 다른 하나는 update() 메소드를 호출하는 겁니다.

>>> a_set = {1, 2}
>>> a_set.add(4)  
>>> a_set
{1, 2, 4}
>>> len(a_set)    
3
>>> a_set.add(1)  
>>> a_set
{1, 2, 4}
>>> len(a_set)    
3
  1. add() 메소드는 하나의 인자를 받습니다. 어떤 데이터 타입이든 상관없이 그 인자를 주어진 set에 추가합니다.
  2. 이제 이 set은 3개의 아이템을 가지게 되었습니다.
  3. set 은 유일한 값들을 집합이라고 말씀드렸습니다. 만약 중복되는 아이템을 추가하면 아무일도 생기지 않습니다. 에러도 생기지 않구요, 그냥 아무 일도 안 일어납니다.
  4. 여전히 3개의 아이템을 가지고 있습니다.
>>> a_set = {1, 2, 3}
>>> a_set
{1, 2, 3}
>>> a_set.update({2, 4, 6})                       
>>> a_set                                         
{1, 2, 3, 4, 6}
>>> a_set.update({3, 6, 9}, {1, 2, 3, 5, 8, 13})  
>>> a_set
{1, 2, 3, 4, 5, 6, 8, 9, 13}
>>> a_set.update([10, 20, 30])                    
>>> a_set
{1, 2, 3, 4, 5, 6, 8, 9, 10, 13, 20, 30}
  1. 예제 코드에서 update() 메소드는 하나의 인자를 입력으로 받았는데, 그 인자 역시 set 이군요. 이 코드는 입력으로 주어진 set 을 대상이 되는 set에 추가합니다. 입력 set 내 아이템들 하나 하나에 add() 메소드를 호출하는 것과 같은 동작을 합니다.
  2. set 은 유일한 값만 받아들이므로 중복되는 값은 추가되지 않습니다.
  3. update() 메소드의 인자로 두 개 이상의 set 을 전달하는 것도 가능합니다. 타겟 set 내 아이템과 중복되는 것은 무시하고, 모든 아이템을 타겟 set 에 추가합니다.
  4. update() 메소드는 인자로 어떤 데이터 타입도 넘겨줄 수 있습니다. 예제에서는 인자의 타입으로 list를 넘겨주고 있습니다. list가 인자로 건네지면 list 내 모든 아이템을 타겟 set에 추가하게 됩니다.

Set 아이템 삭제하기

set 내 아이템 삭제에는 모두 세 가지 방법이 있습니다. 우선 첫번째 방법인 discard() 메소드를 사용하는 방법과 두번째 방법인remove() 메소드를 사용하는 방법에 대해 알아봅시다. 이 둘 간에 아주 미묘한 차이가 있거든요.

>>> a_set = {1, 3, 6, 10, 15, 21, 28, 36, 45}
>>> a_set
{1, 3, 36, 6, 10, 45, 15, 21, 28}
>>> a_set.discard(10)                        
>>> a_set
{1, 3, 36, 6, 45, 15, 21, 28}
>>> a_set.discard(10)                        
>>> a_set
{1, 3, 36, 6, 45, 15, 21, 28}
>>> a_set.remove(21)                         
>>> a_set
{1, 3, 36, 6, 45, 15, 28}
>>> a_set.remove(21)                         
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 21
  1. discard() 메소드는 값 하나를 인자로 받아 해당 값을 set에서 제거합니다.
  2. 만약 set 내에 존재하지 않는 값을 제거하려 하면, 아무일도 발생하지 않습니다. 에러도 없구요. 진짜 아무일도 안합니다.
  3. remove() 메소드 또한 값 하나를 인자로 받아 해당 값을 set에서 제거합니다.
  4. 바로 이 점이 다릅니다. 존재하지 않는 값을 remove() 메소드로 제거하려고 하면 KeyError라고 하는 예외가 발생합니다.

이제 set의 아이템을 제거하는 방법가운데 마지막, 세번째 방법 입니다. set 도 list처럼 pop() 메소드를 통해 아이템을 제거할 수 있습니다.

>>> a_set = {1, 3, 6, 10, 15, 21, 28, 36, 45}
>>> a_set.pop()                                
1
>>> a_set.pop()
3
>>> a_set.pop()
36
>>> a_set
{6, 10, 45, 15, 21, 28}
>>> a_set.clear()                              
>>> a_set
set()
>>> a_set.pop()                                
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'pop from an empty set'
  1. pop() 메쏘드는 set 내의 임의의 아이템 하나를 제거한후 이를 반환합니다. 특이한 점은 이 메소드를 통해 어떤 원소를 반환하게 될지 미리 알 수 없다는 점입니다.
  2. clear() 메쏘드는 set의 모든 아이템을 제거합니다. 예제 코드는 a_set = set() 와 같은 효력을 지니는데, 새로운 빈 set을 만든후 이를 a_set 변수에 덮어씌우는 것과 같습니다.
  3. 만약 빈 set에 pop() 메쏘드를 적용하면 KeyError 예외가 반환됩니다.

공통 set 연산

파이썬 3의 set 타입은 다음과 같은 공통 연산자들을 제공합니다.

>>> a_set = {2, 4, 5, 9, 12, 21, 30, 51, 76, 127, 195}
>>> 30 in a_set                                                     
True
>>> 31 in a_set
False
>>> b_set = {1, 2, 3, 5, 6, 8, 9, 12, 15, 17, 18, 21}
>>> a_set.union(b_set)                                              
{1, 2, 195, 4, 5, 6, 8, 12, 76, 15, 17, 18, 3, 21, 30, 51, 9, 127}
>>> a_set.intersection(b_set)                                       
{9, 2, 12, 5, 21}
>>> a_set.difference(b_set)                                         
{195, 4, 76, 51, 30, 127}
>>> a_set.symmetric_difference(b_set)                               
{1, 3, 4, 6, 8, 76, 15, 17, 18, 195, 127, 30, 51}
  1. 특정 값이 set에 존재하는지 여부를 확인할 때는, list의 경우처럼 in 연산자를 사용합니다.
  2. union() 메쏘드는 대상이 되는 두 개 set의 합집합을 결과로 반환합니다.
  3. intersection() 메쏘드는 대상이 되는 두 개 set의 교집합을 결과로 반환합니다.
  4. difference() 메쏘드는 a_set 에는 있지만 b_set에는 없는 아이템을 담은 새로운 set을 반환합니다.
  5. symmetric_difference() 메쏘드는 두 set 간의 합집합에서 교집합을 제외한 부분을 새로운 set 으로 반환합니다.

방금 다룬 4개의 메쏘드 가운데 3개는 타겟 set 과 인자로 넘겨주는 set의 위치를 변경해도 그 결과가 같습니다. 이를 조금 어렵게 표현하면 symmetric (좌우가 대칭인) 하다고 말합니다.

# 앞의 예제에서 계속 이어집니다.
>>> b_set.symmetric_difference(a_set)                                       
{3, 1, 195, 4, 6, 8, 76, 15, 17, 18, 51, 30, 127}
>>> b_set.symmetric_difference(a_set) == a_set.symmetric_difference(b_set)  
True
>>> b_set.union(a_set) == a_set.union(b_set)                                
True
>>> b_set.intersection(a_set) == a_set.intersection(b_set)                  
True
>>> b_set.difference(a_set) == a_set.difference(b_set)                      
False
  1. 앞의 예제와 출력된 결과가 달라서 조금 헷갈리시겠지만, a_set을 대상으로 b_set에 symmetric_difference() 메쏘드를 적용하던 b_set을 대상으로 a_set에 symmetric_difference()을 적용하던 그 결과는 동일합니다. 출력이 다르게 나오는 이유는 set 이 정렬되지 않은 (unordered) 컨테이너이기 때문이고요, 순서와 관계없이 동일한 아이템을 포함하고 있는 set은 동일하다고 취급됩니다.
  2. 사람의 눈이란 혼동하기 쉽기 때문에 이렇게 프로그램적으로 확인하면 좀 더 확실합니다. a_set 과 b_set 이 동일한 아이템들을 가지고 있으므로 두 set은 동일하다고 간주됩니다.
  3. 두 set의 합집합 역시 symmetric 합니다.
  4. 두 set의 교집합 역시 symmetric 합니다.
  5. 두 set의 차(difference)는 symmetric 하지 않습니다. 뺄셈에서와 같은 원칙이 적용됩니다. 무엇에서 무엇을 빼느냐에 따라 결과도 달라집니다.

자자, 이제 몇 개 안남았습니다. 힘을 내세요.

>>> a_set = {1, 2, 3}
>>> b_set = {1, 2, 3, 4}
>>> a_set.issubset(b_set)    
True
>>> b_set.issuperset(a_set)  
True
>>> a_set.add(5)             
>>> a_set.issubset(b_set)
False
>>> b_set.issuperset(a_set)
False
  1. 만약 a_set의 모든 아이템이 b_set에 들어있는 때, 파이썬에서는 a_setb_setsubset 이라고 표현합니다.
  2. 바꿔서 말하면 b_seta_setsuperset 이라고 말할 수도 있습니다.
  3. 하지만 a_setb_set에 없는 새로운 아이템을 추가하는 순간 subset - superset관계는 끝나버리고, False를 리턴합니다.

Set 으로 참거짓 판별하기

if 절 처럼 참거짓을 판별해야하는 상황에도 set 사용이 가능합니다.

>>> def is_it_true(anything):
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
...
>>> is_it_true(set())          
no, it's false
>>> is_it_true({'a'})          
yes, it's true
>>> is_it_true({False})        
yes, it's true
  1. 빈 set은 False 입니다.
  2. 아이템이 하나라도 들어있는 set은 True 입니다.
  3. 아이템의 값이 무엇인지는 상관없습니다. set에 아이템이 하나라도 들어있다면 True 입니다.

Dictionaries (딕셔너리)

dictionary는 정렬되지 않은 (unordered) 키-값의 쌍 (key-value pair) 입니다. dictionary에 새로운 key 값을 입력할때 반드시 key 값에 해당하는 value 도 함께 입력해야 합니다. value 는 언제든지 새로운 값으로 다시 변경할 수 있습니다. dictionary는 key 값을 가지고 이에 해당하는 value를 조회할때 최고의 성능을 낼 수 있도록 최적화되 있습니다만, 그 반대는 아닙니다.

파이썬의 dictionary 는 Perl 5의 해쉬 (hash)와 여러 모로 유사합니다. Perl 5에서 hash 값을 저장하고 있는 변수를 지정할때는 % 문자로 시작합니다만, 파이썬에서는 그럴 필요가 없습니다. 내부적으로 변수이름을 추적하도록 설계되있으므로, 어떤 문자든 dictionary 변수를 지칭할 수 있습니다.

dictionary 생성하기

dictionary를 생성하는 법은 간단합니다. set을 생성하는 것과 유사한데요, set과 다른 점은 임의의 값(value) 대신 키-값 쌍 (key-value pair)을 입력해야 한다는 것 정도입니다. dictionary 를 생성하고 나면 key 값을 이용해 해당 value를 조회할 수 있습니다.

>>> a_dict = {'server': 'db.diveintopython3.org', 'database': 'mysql'}  
>>> a_dict
{'server': 'db.diveintopython3.org', 'database': 'mysql'}
>>> a_dict['server']                                                    
'db.diveintopython3.org'
>>> a_dict['database']                                                  
'mysql'
>>> a_dict['db.diveintopython3.org']                                    
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'db.diveintopython3.org'
  1. 두개의 아이템을 가진 dictionary 하나를 만들고, 이를 a_dict이라는 변수에 할당하고 있습니다. 각 아이템은 키-값의 쌍 (key-value pair)이고, 전체 아이템을 curly brace 로 감싸고 있습니다.
  2. 'server'는 key 값이므로, 해당하는 value 를 조회하기 위해서는 a_dict['server']를 입력하면 됩니다. 결과로 'db.diveintopython3.org'가 반환됩니다.
  3. 'database'는 key 값이므로, 해당하는 value 를 조회하기 위해서는 a_dict['database']를 입력하면 됩니다. 결과로 'mysql'가 반환됩니다.
  4. key 값을 이용해 value 를 조회하는 것은 가능하지만, 그 반대로 value 를 이용해 key 값을 조회하는 것은 불가능합니다. 따라서, a_dict['server']'db.diveintopython3.org'라는 값을 반환하지만, a_dict['db.diveintopython3.org'] 의 경우 예외를 발생시킵니다.

dictionary 수정하기

dictionary 의 크기에는 제한이 없습니다. 언제든지 key-value 쌍으로 이루어진 아이템을 dictionary 에 추가할 수 있고, 이미 존재하는 key 의 해당 value 값은 언제든지 수정할 수 있습니다. 앞의 예제에서 계속됩니다.

>>> a_dict
{'server': 'db.diveintopython3.org', 'database': 'mysql'}
>>> a_dict['database'] = 'blog'  
>>> a_dict
{'server': 'db.diveintopython3.org', 'database': 'blog'}
>>> a_dict['user'] = 'mark'      
>>> a_dict                       
{'server': 'db.diveintopython3.org', 'user': 'mark', 'database': 'blog'}
>>> a_dict['user'] = 'dora'      
>>> a_dict
{'server': 'db.diveintopython3.org', 'user': 'dora', 'database': 'blog'}
>>> a_dict['User'] = 'mark'      
>>> a_dict
{'User': 'mark', 'server': 'db.diveintopython3.org', 'user': 'dora', 'database': 'blog'}
  1. dictionary의 key는 그 안에서 유일한 값이어야 합니다. 이미 존재하는 key에 value 를 할당하면 이전 value를 덮어 씌웁니다.
  2. 새로운 key-value 쌍을 언제든지 추가할 수 있습니다. 새롭게 추가하는 문법은 존재하는 key 값에 value를 덮어씌울 때와 같습니다.
  3. 방금 추가된 key-value 쌍인 'user':'mark' 는 dictionary 중간에 위치했습니다. dictionary 는 정렬되지 않기 (unordered) 때문에 추가하는 순서대로 맨 뒤에 나오거나 하지 않습니다. 아이템 순서에는 큰 의미가 없습니다.
  4. 이미 존재하는 key값을 이용해 새로운 value 를 할당하면 이전 value를 덮어 씌운다고 말씀드렸었죠?
  5. user 의 value가 "mark" 라는 값으로 다시 되돌아갈까요? 아닙니다. 다시 key 값을 자세히 보면, 대문자로 시작하는 "User" 임을 알 수 있습니다. 기존에 있던 key 는 소문자이구요. dictionary 는 대소문자를 구분합니다. 따라서 이 예제코드는 새로운 key-value 쌍을 만들어 냅니다. 사람눈엔 비슷해 보여도 파이썬 입장에선 완전 다른 key 입니다.

dictionaries에 여러 데이터 타입 사용하기

dictionary 아이템에 꼭 문자열을 사용해야할 이유는 없습니다. 어떤 데이터 타입이든 가능합니다. 정수 타입, boolean 타입, 임의의 객체타입, 심지어는 dictionary 자신도 dictionary 의 아이템에 사용할 수 있답니다. dictionary의 value로 이런 다양한 타입들을 동시에 섞어서 사용하는 것이 가능합니다. key는 조금 제약이 있는데, string 이나 정수 그리고 몇몇 다른 타입들을 사용할 수 있습니다. 물론 섞어서 사용하는 것도 가능하고요.

사실, 앞에 다뤘던 your first Python program라는 파트에서 string을 사용하지 않는 dictionary 예제를 보여드렸었죠.

SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
            1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']}

하나하나 찬찬히 뜯어서 살펴 봅시다.

>>> SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
...             1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']}
>>> len(SUFFIXES)      
2
>>> 1000 in SUFFIXES   
True
>>> SUFFIXES[1000]     
['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
>>> SUFFIXES[1024]     
['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
>>> SUFFIXES[1000][3]  
'TB'
  1. listset 의 경우와 마찬가지로 len() 함수는 딕셔너리의 길이를 반환합니다. key의 갯수이기도 합니다.
  2. in 연산자도 특정 key 가 dictionary 안에 존재하는지 검사할 때 사용할 수 있고요.
  3. 1000SUFFIXES라는 dictionary의 key 로 사용됬습니다. 해당 value는 list로 그안에는 모두 8개의 아이템이 들어있고요.
  4. 마찬가지로 1024SUFFIXES dictionary 의 key 이고, 해당 value는 8개의 아이템이 있는 list 입니다.
  5. SUFFIXES[1000] 는 list를 반환하므로 배열방식을 이용하면, 반환된 list 내 각각의 아이템의 값을 구할 수 있습니다.

dictionary 로 참, 거짓 판별하기

dictionary 는 if 문과 같이 참, 거짓의 판별을 요구하는 구문에도 사용할 수 있습니다.

>>> def is_it_true(anything):
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
...
>>> is_it_true({})             
no, it's false
>>> is_it_true({'a': 1})       
yes, it's true
  1. 빈 dictionary 는 false 입니다.
  2. key-value 쌍이 하나라도 들어있으면 true 입니다.

None

None은 파이썬에서 정의된 특별한 상수값입니다. 이 상수값은 null 을 의미합니다만, False랑은 다르고, 0 을 의미하는 것도 아닙니다. 빈 문자열을 의미하지도 않습니다. 따라서 None 을 이런 값들과 비교하는 구문에 사용하는 실수를 저지르지 마세요. 어차피 False가 리턴될거니까요.

파이썬에서 null 값을 의미하는 것은 None 하나 뿐입니다. 이 상수의 타입은 NoneType 이고요, NoneType 의 객체 (파이썬에선 모든것이 객체라구요) 는 None 하나 뿐입니다. 변수가 None 으로 할당되면 그게 어떤 타입이던지 간에 동일하다고 간주됩니다.

>>> type(None)
<class 'NoneType'>
>>> None == False
False
>>> None == 0
False
>>> None == ''
False
>>> None == None
True
>>> x = None
>>> x == None
True
>>> y = None
>>> x == y
True

None 으로 참, 거짓 판별하기

None 이 if 문과 같이 참, 거짓의 판별이 요구되는 구문에 사용되면 false 를 의미합니다. not None 은 true 를 의미합니다.

>>> def is_it_true(anything):
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
...
>>> is_it_true(None)
no, it's false
>>> is_it_true(not None)
yes, it's true

더 읽어볼 것들

© 2001–11 Mark Pilgrim