CodeOnWeb
로그인

챕터 2. 고유 자료형

파이썬에서 제공하는 기본적인 데이터 형태(datatype)에 대해 알아봅니다.

Park Jonghyun 2015/09/04, 17:45

내용

파이썬 3에 뛰어들기 (22)

챕터 -1. “파이썬 3로 뛰어들기”에서 달라진 점 챕터 0. 파이썬 설치하기 챕터 1. 첫 파이썬 프로그램 챕터 2. 고유 자료형 챕터 3. 컴프리헨션(Comprehensions) 챕터 4. 문자열(Strings) 챕터 5. 정규표현식(Regular Expressions) 챕터 6. 클로저와 제너레이터(Closures & Generators) 챕터 7. 클래스와 반복자(Classes & Iterators) 챕터 8. 고급 반복자(Advanced Iterators) 챕터 9. 단위 테스트(Unit Testing) 챕터 10. 리팩토링(Refactoring) 챕터 11. 파일 챕터 12. XML 챕터 13. 파이썬 객체 직렬화 챕터 14. HTTP 웹서비스 챕터 15. 사례 연구: chardet을 파이썬 3로 이식하기 챕터 16. 파이썬 라이브러리 패키징하기 부록 A. 2to3를 이용해서 코드를 파이썬 3로 이식하기 부록 B. 특수 함수 이름 부록 C. 이제 어디로 가야 할까요? 부록 D. 문제 해결

"모든 철학은 경이로움에서 태어나, 궁금증으로 성장하지만, 무관심으로 생을 마친다."
— 미셸 드 몽테뉴(Michel de Montaigne)

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

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

  1. Boolean은 True나 False 값을 가집니다.
  2. Number는 정수(1, 2 등)나 실수(1.1, 1.2), 분수(1/2, 2/3), 또는 복소수 값을 가질 수 있습니다.
  3. String은 유니코드 문자의 연속입니다. 예를 들어 html 문서는 스트링입니다.
  4. Bytebyte array의 예로는 jpeg 이미지 파일 같은 걸 들 수 있겠네요.
  5. List에는 여러 개의 값이 순서대로 배열될 수 있습니다.
  6. Tuple은 리스트와 비슷하지만 값을 바꿀 수 없습니다(immutable).
  7. Set은 정렬되지 않은 값의 집합입니다.
  8. Dictionary 정렬되지 않은 키-값(key-value) 쌍의 모음.

물론 이게 전부는 아닙니다. 파이썬에서는 모든 것이 객체 이므로, 모듈, 클래스, 메소드, 파일, 심지어 컴파일된 코드도 일종의 데이터 타입입니다. 이 중 몇 가지는 이미 앞에서 다뤘죠? 모듈은 이름을 가지고 있고 함수에는 docstring이 있었죠. 클래스에 대해서는 클래스와 반복자 챕터에서 자세히 다루도록 하고요, 파일은 파일 챕터에서 다루겠습니다.

문자열과 바이트는 매우 중요한데 그만큼 복잡하기도 해서 별도의 챕터로 뺐습니다. 우선은 다른 것부터 살펴보죠.

Booleans

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

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

size는 정수이고 0도 정수 입니다. <는 연산자이고요. size < 0 이라는 파이썬 표현식의 결과는 항상 boolean 입니다. 파이썬 대화형 쉘에서 테스트 해보세요.

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

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

Numbers

이번 챕터에서는 다뤄야 할 것이 굉장히 많습니다. 파이썬 3는 정수형(integer)과 실수형(floating point number)을 지원합니다. 파이썬은 변수 선언시 타입을 별도로 선언하지 않으므로, 소수점의 유무에 따라 정수형과 실수형으로 갈라집니다.

  1. 어떤 값이나 변수의 타입을 확인하려면 type() 함수를 사용합니다. 1은 당연히 정수형입니다.
  2. isinstance() 함수를 사용하면 첫번째 인수의 타입이 두번째 인수와 같은지 알아볼 수 있습니다.
  3. 정수에 정수를 더하면 정수입니다.
  4. 정수에 실수를 더하면 실수입니다. 파이썬은 정수와 실수의 덧셈을 만나면, 정수를 실수형으로 변환하고 결과값은 실수로 반환합니다.

정수와 실수 사이의 변환

방금 본 것과 같이, (덧셈과 같은) 어떤 연산자는 필요하다면 정수를 실수로 강제변환할 수 있습니다. 물론 여러분이 직접 변환할 수도 있습니다.

  1. float() 함수를 사용해 int 형을 float 형으로 변환할 수 있습니다.
  2. int() 함수를 사용하면 float형을 int형으로 바꿀 수도 있습니다. 별로 놀라운 일은 아니죠?
  3. int() 함수는 소수점 아래는 그냥 버립니다. 반올림하지 않습니다.
  4. int() 함수는 음수의 경우도 소수점 아래는 버립니다. 0과 가까운 쪽으로 숫자를 변환하네요.
  5. 실수는 소숫점 아래 15 자리까지만 정확하게 표현할 수 있습니다.
  6. 정수는 임의의 큰 수를 표현할 수 있습니다.

파이썬 2에서는 int 형과 long 형을 따로 정의해 두었습니다. int 형이 표현할 수 있는 최대 크기는 sys.maxint에 정의되어 있는데, 플랫폼에 따라 다르긴 하지만 보통 2^32-1 이었습니다. 파이썬 3에는 long 형이 따로 없고 모든 정수는 int로 표시합니다. 자세한 내용은 PEP 237 를 참고하세요.

파이썬으로 산술 연산 하기

숫자로 많은 일을 할 수 있습니다.

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

파이썬 2에서 / 연산자는 정수 나눗셈이었는데, 코드 안에 특별한 지시자를 넣어 실수 나눗셈으로 동작하게 할 수 있었습니다. 그러나 파이썬3 에서 / 연산자는 항상 실수 나눗셈입니다. 더 자세한 내용은 PEP 238을 참고하세요.

분수(Fraction)

파이썬이 정수나 실수 연산만 할 수 있는건 아닙니다. 여러분이 중고등학교 때 배웠던(하지만 이제는 다 잊어버린) 복잡한 수학도 할 수 있습니다.

  1. 분수를 사용하려면 먼저 fractions 모듈을 임포트해야 합니다.
  2. 분수를 만드려면 Fraction 객체를 만들고 인수로 분자와 분모를 넘겨줍니다.
  3. 일반적인 분수 계산을 수행할 수 있습니다. 결과값으로는 새로운 Fraction 객체가 반환됩니다. 2 * (1/3) = (2/3)입니다.
  4. Fraction 객체는 자동으로 약분합니다. (6/4) = (3/2)입니다.
  5. 파이썬도 분모가 0이 되면 안된다는 것 쯤은 알고 있습니다.

삼각함수(Trigonometry) 다루기

기본적인 삼각함수도 사용할 수 있습니다.

  1. math 모듈은 원주율을 의미하는 π 상수를 정의하고 있습니다.
  2. math 모듈은 sin(), cos(), tan() 같은 기본 삼각함수들 뿐만 아니라, asin()같이 고급 삼각함수도 지원합니다.
  3. 하지만 파이썬이 무한한 정밀도를 가지지는 않으니 조심하세요. tan(π / 4)는 1.0을 반환해야 하지만 실제로는0.99999999999999989를 반환합니다.

Boolean context에서의 숫자

if 문과 같이 boolean context에서 숫자가 사용되면, 0은 거짓이고 0이 아닌 숫자는 참입니다.

  1. 파이썬 쉘 안에서도 함수를 정의할 수 있답니다. 줄의 마지막 부분에서 엔터키를 치면 다음 행으로 계속할 수 있고, 함수 정의를 끝내려면 빈 칸에서 엔터키를 치면 됩니다.
  2. Boolean 값이 와야 하는 자리에 숫자가 사용되면 true(0이 아닌 정수) 나 false(0)로 판명됩니다.
  3. 실수값도 0이 아니면 true 입니다. 0.0만 false 입니다. 반올림 등의 연산을 실수에 적용할 때는 조심하세요. 0.0000000000001 같은 값도 0이 아니기 때문에 True로 판명됩니다.
  4. 분수도 역시 0이 아니면 true 입니다. Fraction(0, n)의 경우 n에 어떤 값을 넣더라도 false 입니다. 나머지는 모두 true가 되겠죠.

리스트 (List)

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

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

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

리스트 생성하기

파이썬 리스트를 만드는 방법은 간단합니다. 꺽쇠(대괄호) 안에 값을 적어주고, 콤마로 구분하면 됩니다.

  1. 다섯개의 원소를 가진 리스트를 정의했습니다. 출력을 하면 순서가 변하지 않고 그대로인 것에 주목하세요. 이것은 우연이 아닙니다. 리스트에는 아이템이 정렬된 채로 저장됩니다.
  2. 리스트는 첫 번째 인덱스가 0인 배열처럼 사용할 수 있습니다. a_list[0]은 a_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] 식이 되겠지요.

리스트 쪼개기 (Slicing List)

리스트를 정의하고 나면 그 일부분을 떼내어 새로운 리스트로 만들 수 있습니다. 이를 리스트 쪼개기(slicing the list)라고 합니다.

  1. 리스트의 일부분을 얻고 싶을 때는 대괄호 안에 두 개의 인덱스를 콜론으로 구분지어 적어줍니다. 그러면 시작 인덱스부터(이 예에서는 a_list[1]) 종료 인덱스 아이템(a_list[3]) 바로 전까지를 포함하는 새 리스트가 반환됩니다.
  2. 리스트를 쪼갤때 음수 인덱스를 줘도 됩니다. 헷갈리시면 이런 식으로 생각하셔도 됩니다. 리스트를 왼쪽에서 오른쪽으로 읽을 때, 첫 번째 인덱스는 포함시키고 싶은 아이템의 첫 번째 위치입니다. 두 번째 인덱스는 포함시키고 싶지 않은 아이템의 첫 번째 위치고요. 반환값은 그 사이의 모든 아이템입니다.
  3. 리스트의 인덱스는 0부터 시작하므로, a_list[0:3]은 a_list 리스트의 첫 세 개의 아이템을 반환합니다. a_list[0]부터 a_list[3] 전까지요.
  4. 시작 인덱스가 0인 경우에는 생략이 가능합니다. 따라서 a_list[:3]는 a_list[0:3]와 같은 의미입니다.
  5. 마찬가지로 종료 인덱스가 리스트의 길이와 같은 경우에는 생략이 가능합니다. a_list에는 다섯 개의 아이템이 있으므로, a_list[3:]는 a_list[3:5] 와 같습니다. 리스트 쪼개기에서 아름다운 대칭의 미가 느껴지지 않나요? a_list[:3] 는 리스트의 첫 세 개 아이템을 반환하고, a_list[3:]는 이를 제외한 뒷 아이템 두 개를 모두 반환합니다. 리스트의 길이와 상관없이 이렇게 일반화할 수 있겠네요. a_list[:n]는 언제나 첫 n 개의 아이템을 반환하고, a_list[n:]는 n번째 아이템부터 리스트 끝까지를 반환합니다
  6. 만약 시작과 종료 인덱스를 모두 생략하면, 리스트의 모든 아이템을 반환합니다. 원래의 리스트와 같은 값을 가지는 복사본이 생기는 것입니다. 이를 이용하여, a_list[:] 라고 써주면 간단히 a_list 리스트를 복사할 수 있습니다.
    list에 아이템 추가하기#

리스트에 아이템 추가하기

  1. + 연산자는 두 리스트를 합쳐 새 리스트를 반환합니다. 리스트에 담을 수 있는 아이템의 숫자에는 특별한 제한이 없으므로, 메모리가 허락하는 한 몇 개든 담을 수 있습니다. 만약 메모리를 신경써야 하는 상황이라면, + 연산 시 메모리에 두 번 째 리스트가 새로 생성된다는 사실에 주의해야 합니다. 이 코드는 실제로 두 단계의 일을 하는데, 먼저 a_list에 [2.0, 3]을 합치고 그 결과를 다시 a_list에 할당합니다. 합치는 과정에서 a_list와는 다른 이름 없는 새로운 리스트가 만들어지므로, 순간적으로 메모리 소비량이 두 배 이상 증가할 수 있습니다. 다루는 리스트의 크기가 크다면 조심할 필요가 있습니다.
  2. 리스트에는 여러 타입의 데이터를 함께 담을 수 있습니다. 꼭 같은 타입의 데이터로 통일할 필요가 없습니다. 여기서는 문자열, 실수, 정수 이렇게 서로 다른 데이터 타입을 가진 리스트를 보여줍니다.
  3. append() 메소드를 이용해 리스트의 맨 끝에 아이템을 추가할 수 있습니다. (이제 네 개의 서로 다른 데이터 타입이 리스트 안에서 공존하는군요!)
  4. 리스트는 클래스입니다. 따라서 리스트를 하나 만든다는 것은 리스트 클래스의 인스턴스를 하나 만든다는 것과 동일한 이야기입니다. 모든 인스턴스에는 메소드가 있고 리스트 또한 예외가 아닙니다. 이 예제 코드에서는 리스트 클래스의 extend() 메소드를 이용하여 기존 리스트에 아이템을 추가하고 있습니다. 메소드 인자가 추가할 데이터입니다.
  5. insert() 메소드는 아이템 하나를 정해진 위치에 삽입합니다. 첫번째 인자는 삽입하고 싶은 위치이고, 동작이 성공적이면 뒤쪽에 위치한 아이템들은 자리가 하나씩 뒤로 밀리게 됩니다. 삽입하는 데이터가 기존 리스트에 이미 존재하더라도 상관없습니다. 리스트는 중복된 데이터도 허용하기 때문입니다. 예제 코드에서는 'Ω' 라는 값이 첫번째 인덱스와 마지막 인덱스, 이렇게 두 군데에서 보이고 있습니다.

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

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

  1. extend() 메소드는 리스트 타입의 인자 하나를 받아서 그 내부의 아이템을 기존의 a_list안에 추가합니다.
  2. 세 개의 아이템을 가진 리스트에 세 개의 아이템이 추가되었으니 이제 아이템의 수는 여섯 개가 되었습니다.
  3. 한편 append() 메소드는 인자로 들어온 값 자체를 리스트에 추가합니다. 인자는 하나이며 타입에 제한은 없습니다. 예제코드에서는 세 개의 아이템이 있는 리스트를 인자로 넘겨주고 있습니다.
  4. 리스트 내 아이템의 갯수가 여섯 개 였으니까, 이제 아홉 개가 되어야겠군요. 앗. 그런데, 파이썬 쉘은 일곱 개라고 하네요. 왜죠? 이것이 바로 extend와 append 메소드 간의 차이점입니다. extend 메소드는 인자로 넘어온 리스트내 아이템 하나 하나를 추가해주지만, append 메소드는 인자가 리스트건 뭐건 상관없이 하나의 객체로 판단하고 이를 기존 리스트 끝에 추가합니다. 그래서 예제코드에서는 ['g', 'h', 'i'] 가 3개의 개별적인 아이템이 아닌, 전체 리스트 하나로써 기존 리스트 안에 들어가게 된거죠. 리스트 객체는 다른 리스트 객체를 내부 아이템으로 가질 수 있다는 사실, 잊지마세요.

리스트 검색하기

  1. 특정 값이 리스트 내에 몇 개 있는지 알고 싶으면 count() 메소드를 사용하세요.
  2. 그냥 어떤 값이 리스트에 들어 있는지 여부만 알고 싶다면 in 연산자를 이용하는 편이 간단합니다. count() 메소드보다 약간 더 빠르기도 하지요. in 연산자는 참이나 거짓만을 반환하므로 아이템이 몇 개 들었는지 알 수는 없습니다.
  3. 특정 값이 리스트 내의 몇 번째에 위치하는지 알고 싶다면 index() 메소드를 사용하세요. 이 함수는 기본적으로 리스트 전체를 검색하여(0 부터 시작합니다) 아이템이 있는 위치 를 반환합니다. 선택사항이긴 하지만, 두 번째 인자를 줘서 검색을 시작할 인덱스를 지정할 수 있고, 세 번째 인자로 검색을 종료할 인덱스도 지정할 수 있습니다.
  4. 찾고자 하는 값이 리스트 내에 여러 번 등장하더라도 index() 메소드는 처음으로 발견한 항목의 위치만을 반환합니다. 위 예에서 'new' 라는 문자열은 a_list[2]에 하나, a_list[4]에 하나, 이렇게 두 번 등장하지만, index() 메소드는 첫번째 위치인 2만 반환합니다.
  5. 여러분이 예상 못했을 수도 있는데, 검색하는 값이 발견되지 않으면 index() 메소드는 예외를 발생시킵니다.

잠깐만, 뭐라고요? ... 예, 제대로 읽으셨어요. index() 메소드는 리스트에서 검색하는 값을 찾지 못할 경우 예외를 발생시킵니다. 이는 유효하지 않은 인덱스(예를 들어 -1)를 반환하는 대부분의 다른 언어와 큰 차이가 나는 부분입니다. 처음에는 좀 귀찮게 여길 수도 있지만, 조금 지나면 왜 이렇게 설계했는지 이해하실 수 있을거에요. 문제가 발생한 지점에서 그냥 이상한 반환값을 얻고 조용히 지나가는 것보다는(그리고 나중에 알 수 없는 문제가 발생하겠지요), 프로그램이 아예 멈추는 것이 낫습니다. 또한, 파이썬에서 -1은 유효한 인덱스라는 것을 명심하세요. 만약 index()가 -1을 반환한다면 디버깅할 때 골치 깨나 아플거에요!

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

리스트는 자동으로 늘어나고 줄어듭니다. 늘어나는 경우는 위에서 보셨지요? 리스트에서 항목을 지우는 방법도 살펴 봅니다. 몇 가지 있어요.

  1. del 구문을 이용하여 리스트 내 특정 아이템을 삭제할 수 있습니다.
  2. 리스트에서 1번 인덱스 아이템을 지우고 다시 1번 인덱스의 값에 접근해도 오류가 나지 않습니다. 그 뒤에 위치한 모든 아이템이 위치를 차례대로 이동하여 빈 자리를 메꿔줍니다.

어떤 아이템을 지우고 싶은데 위치를 모른다고요? 문제 없습니다. 값을 써서 아이템을 지울 수도 있습니다.

  1. remove() 메소드로 아이템을 삭제할 수 있습니다. 삭제하고 싶은 아이템의 값을 인자로 넘겨주면, 리스트에서 첫 번째로 발견되는 해당 아이템을 삭제합니다. 다시 한번 말씀드리지만 특정 아이템이 삭제된 자리는 그 뒤의 아이템들이 자동으로 차례차례 메꿔줍니다.
  2. 리스트에 존재하지 않는 값을 삭제하려고 하면 예외가 발생됩니다.

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

pop()이라는 재미있는 메소드를 이용해서 리스트 아이템을 제거할 수도 있습니다. 하지만 주의할 점이 있습니다.

  1. 인자없이 pop() 메소드를 호출하면 리스트의 맨 마지막 아이템을 반환한 후 삭제합니다.
  2. pop() 메소드의 인자로 위치 인덱스를 넘기면 해당 위치의 아이템을 반환한 후 삭제합니다. 삭제된 아이템이 있던 자리는 역시 뒷 아이템을의 이동으로 채워지게 됩니다.
  3. 비어있는 리스트에 pop() 메소드를 호출하면 예외가 발생됩니다.

인자없이 호출하는 pop() 메소드는 펄의 pop() 함수와 유사합니다. 리스트 맨 마지막 항목을 반환하고 삭제합니다. 펄은 shift()라는 함수를 이용해서 리스트의 첫 번째 항목을 삭제하지만, 파이썬은 pop()의 인자로 0을 주기만 하면 된다는 점은 차이가 있습니다.

리스트로 참, 거짓 판단하기

if 문과 같은 boolean context에서도 리스트를 사용할 수 있습니다.

  1. 빈 리스트는 거짓입니다.
  2. 리스트에 아이템이 하나라도 있으면 참입니다.
  3. 리스트에 아이템이 하나라도 있으면 참입니다. 아이템의 값과는 관계없습니다.

튜플 (Tuple)

튜플은 내부 아이템의 수정이 불가능한(immutable) 리스트입니다. 튜플은 일단 생성되고 나면 어떤 방식으로든 수정할 수 없습니다.

  1. 튜플을 정의하는 형식은 리스트와 같습니다. 다만 각진 대괄호 대신 일반 둥근 괄호를 사용한다는 차이가 있습니다.
  2. 튜플의 원소도 리스트의 경우와 마찬가지로 순서를 가집니다. 첫번째 원소가 0부터 시작한다는 점도 같습니다. 그래서 비어있지 않은 튜플의 첫 번째 아이템은 a_tuple[0]이 됩니다.
  3. 리스트와 마찬가지로 인덱스에 음수가 사용되면 뒤쪽부터 시작합니다.
  4. 리스트처럼 쪼개기(slicing)도 가능합니다. 튜플에서도 쪼개기를 하면 새로운 튜플이 생성됩니다.

아니 그럼 뭐가 다른가요 저자 양반.튜플이 리스트와 다른 점은 수정을 할 수 없다는 것입니다. 좀 유식하게 말하자면 immutable 하다고 하지요. 좀 더 알아듣기 쉽게 말하면, 튜플은 항목을 변화시킬 수 있는 메소드를 제공하지 않습니다. 리스트에는 append(), extend(), insert(), remoe(), pop()과 같은 함수가 있지만 튜플에는 없습니다. 튜플을 쪼갤 수도 있고(새로운 튜플을 만드니까요) 아이템의 값을 읽을 수도 있지만(값을 변화시키지는 않지요) 그게 다입니다.

  1. 이미 만들어진 튜플에 새로운 아이템을 추가할 수 없습니다. append()나 extend() 같은 메소드는 없어요.
  2. 이미 만들어진 튜플의 항목을 삭제할 수 없습니다. remove()나 pop() 같은 메소드가 없거든요.
  3. 튜플안에 들어있는 값을 검색하는 것은 문제가 없습니다. 검색은 수정이 아니잖아요.
  4. 튜플내의 어떤 값을 검색할 때 in 연산자를 사용해도 됩니다.

그럼 튜플을 왜 쓸까요?

  • 튜플은 리스트보다 빠릅니다. 여러분이 만약 상수값을 가진 리스트가 필요하고 추후 변경없이 검색에만 사용한다면 리스트를 대신 튜플을 쓰세요.
  • 튜플은 읽기전용이므로 변경하면 안 되는 데이터가 실수로 바뀌는 일을 막아줍니다. 마치 assert 문을 이용하여 데이터를 방어하는 것과 같은 효과를 볼 수 있습니다.
  • 튜플은 변경 불가능하므로(immutable) 딕셔너리(dictionary)의 키 값으로 사용할 수 있습니다. 리스트는 딕셔너리의 키 값으로 사용할 수 없습니다.

튜플을 리스트로 전환하거나, 거꾸로 리스트를 튜플로 변환하는 것도 가능합니다. 내장 함수인 tuple()은 리스트를 인자로 받아 같은 항목을 갖는 튜플을 반환하고, list()는 튜플을 인자로 받아 리스트를 반환합니다. tuple()은 리스트를 얼리고 list()는 튜플을 녹인다고 표현할 수도 있겠네요. 문학적이네요.

튜플로 참, 거짓 판단하기

if 문과 같은 boolean context에서 튜플을 사용할 수 있습니다.

  1. 빈 튜플은 거짓입니다.
  2. 튜플내에 원소가 하나라도 있으면 참입니다.
  3. 튜플내에 원소가 하나라도 있으면 참입니다. 원소의 값습니다 상관없습니다. 그건 그렇고 False 뒤의 False 뒤의 콤마는 무슨 뜻일까요?4. 원소가 하나밖에 없는 튜플을 만들때는 그 원소뒤에 콤마를 써줘야 합니다. 콤마가 없으면 파이썬은 그냥 괄호가 두번 연달아 사용됬다고 인식할 뿐 튜플이라고 판단하지 못합니다.

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

여러 값을 한꺼번에 할당해야 하는 상황에서 튜플을 멋지게 사용할 수 있습니다.

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

튜플의 이런 특징은 꽤 쓸모가 많습니다. 예를 들어, 일정 범위의 값에 이름을 붙이고 싶다면, 파이썬 내장 함수인 range()와 튜플을 사용하여 간단하게 해결할 수 있습니다.

  1. 내장 함수인 range()는 일정한 범위의 정수를 만듭니다. (정확히 말하면, range()는 리스트나 튜플이 아닌 반복자(iterator)를 반환합니다. 그 차이에 대해서는 뒤에서 따로 다룰겁니다) MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY와 같은 변수를 정의했고요. (이 예제 코드는 사실 calendar 모듈에서 가져왔습니다. 이 모듈은 달력을 출력하는 작고 귀여운 모듈인데요, unix 운영체제의 cal 이라고 하는 프로그램과 비슷합니다. 이 calendar 모듈은 월요일부터 일요일까지를 정수의 상수값으로 정의해두고 있습니다.)
  2. 자 이제 모든 변수가 값을 갖게되었습니다. MONDAY 는 0, TUESDAY 는 1, 이런식으로 값이 할당됩니다.

튜플의 이런 성질을 이용하면 둘 이상의 값을 반환하는 함수도 작성할 수 있습니다. 여러 값을 묶어서 하나의 튜플 변수로 처리하면 되니까요. 실제로 os 모듈을 포함해 많은 파이썬 표준 라이브러리 함수가 이런 테크닉을 사용하고 있습니다. 다음 챕터에서 배우게 될거에요.

Sets

집합은 마치 유일한 값을 가진 데이터를 뒤죽박죽인 채로 넣어둔 자루와 같습니다. 집합의 항목이 될 데이터는 추후 변경이 불가능(immutable) 해야 합니다. 두 개의 집합이 있다면 합집합(union), 교집합(intersection), 차집합(difference) 등의 일반적인 연산을 수행할 수 있습니다.

Set 생성

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

  1. 원소가 하나인 집합을 만들려면 값 주변을 중괄호({})로 감싸주면 됩니다.
  2. 집합도 클래스입니다. 일단 지금은 신경쓰지 마세요.
  3. 여러 개의 원소를 가지는 집합을 만들려면 각 값을 콤마로 구분해주고 중괄호로 감싸주세요.

리스트를 집합으로 변환할 수도 있습니다.

  1. 리스트를 집합으로 변환할때는 set()함수를 사용하면 됩니다. (혹시 어떤 깐깐한 분들께서는 "잠깐만요, 이건 사실 함수 호출이 아니라 객체 생성(class instantiation) 아닌가요?" 라고 질문하실 수도 있겠습니다. 약속드릴게요. 제가 나중에 다시 설명드리겠습니다. 일단 지금은 set()이 함수처럼 동작하고, 집합을 반환한다고 알고 넘어갑시다.)
  2. 앞서 말했들이 집합은 어떤 데이터 타입도 원소로 가질 수 있습니다. 그리고 정렬되지 않은 채로(unordered) 들어있다고 말한 것도 기억하시나요? 이 집합이 리스트를 이용해 생성되었음에도 불구하고 원소들의 순서는 깡그리 무시됩니다. 다른 아이템을 이 집합에 추가하더라도 추가되는 순서 또한 무시된다는 걸 기억하세요.
  3. 원본 리스트는 그대로 있습니다.

집합을 만들긴 해야 하는데, 어떤 항목을 집어넣야 할지 아직 모르겠다고요? 괜찮습니다. 빈 집합을 만들면 되거든요.

1.빈 집합을 만드려면 인자 없이 set()이라고 입력하면 됩니다.
2. 비어있는 집합을 출력하면 좀 의외의 결과가 나오죠? 뭐 {}와 비슷한 것이 나와야 하지 않을까라고 생각하셨을지도 모르겠습니다. 하지만 {}는 빈 사전(dictionary)입니다. 사전에 대해서는 조금 있다가 배우도록 하겠습니다.
3. 좀 이상하긴 하지만 이게 바로 집합입니다...
4. ... 그리고 텅텅 비어있는 집합이죠.
5. 파이썬 2에서 정의된 규칙을 따르다 보니 중괄호는 빈 집합이 아닌 빈 사전을 만들게 됩니다.

집합 변경하기

이미 존재하는 집합에 새로운 아이템을 추가하는 두 가지 방법이 있습니다. 하나는 add() 혹은 update() 메소드를 호출하는 겁니다.

  1. add() 메소드는 하나의 인자를 받습니다. 어떤 데이터 타입이든 상관없이 그 인자를 집합에 추가합니다.
  2. 이제 이 집합은 세 개의 아이템을 가지게 되었습니다.
  3. 집합은 중복된 값을 가지지 않습니다. 만약 중복되는 아이템을 추가하면 아무일도 생기지 않습니다. 에러도 생기지 않구요. 그냥 아무 일도 일어나지 않습니다.
  4. 여전히 세 개의 아이템을 가지고 있습니다.

  1. 예제 코드에서 update() 메소드는 하나의 인자를 입력 받았는데, 그 인자 역시 집합이군요. 인자에 있는 집합의 모든 항목을 대상이 되는 집합에 추가합니다. 집합 내 아이템 하나 하나에 add() 메소드를 호출하는 것과 같은 동작을 합니다.
  2. 중복된 값은 무시됩니다.
  3. update() 메소드의 인자로 두 개 이상의 집합을 전달하는 것도 가능합니다. 중복되는 항목은 무시하고 나머지 모든 아이템을 메소드를 호출한 집합에 추가합니다.
  4. update() 메소드의 인자로 어떤 데이터 타입도 넘겨줄 수 있습니다. 예제에서는 리스트를 넘겨주고 있는데 모든 리스트 항목을 집합에 추가하는군요.

집합에서 아이템 삭제하기

집합에서 아이템을 제거하는 방법은 세 가지가 있습니다. 우선 discard()와 remove() 두 메소드에 대해 알아봅시다. 미묘한 차이가 있습니다.

  1. discard() 메소드는 값 하나를 인자로 받아 해당 값을 집합에서 제거합니다.
  2. 만약 집합에 존재하지 않는 값을 제거하면 아무일도 발생하지 않습니다. 에러도 없구요. 진짜 아무일도 안합니다.
  3. remove() 메소드 또한 값 하나를 인자로 받아 해당 값을 집합에서 제거합니다.
  4. 바로 이 점이 다릅니다. 존재하지 않는 값을 remove() 메소드로 제거하면 KeyError라고 하는 예외가 발생합니다.

집합에서도 리스트와 마찬가지로 pop() 메소드를 가지고 있습니다.

  1. pop() 메소드는 집합의 아무 아이템 하나를 제거하고 반환합니다. 집합은 정렬되어 있지 않으므로 pop()이 어떤 항목을 선택해서 제거할지 알 수 없습니다.
  2. clear() 메소드는 집합의 모든 아이템을 제거합니다. 예제 코드에서 a_set = set()와 같은 효력을 지니는데, 새로운 빈 집합을 만든 후 이를 a_set 변수에 덮어 씌우는 것과 같습니다.
  3. 빈 집합에 pop() 메소드를 적용하면 KeyError 예외가 반환됩니다.

일반적인 집합 연산

파이썬의 집합은 다음과 같은 일반적인 연산자를 제공합니다.

  1. 어떤 값이 집합에 존재하는지 여부를 알아보려면 리스트의 경우와 마찬가지로 in 연산자를 사용합니다.
  2. union() 메소드는 대상이 되는 두 집합의 합집합을 결과로 반환합니다.
  3. intersection() 메소드는 대상이 되는 두 집합의 교집합을 결과로 반환합니다.
  4. difference() 메소드는 a_set에는 있지만 b_set에는 없는 아이템 만을 담은 새로운 집합을 반환합니다.
  5. symmetric_difference() 메쏘드는 두 집합의 합집합에서 교집합을 제외한 부분을 새로운 집합으로 반환합니다.

이 중 세 개의 메소드는 대칭적입니다. 인자의 위치를 바꾸어도 결과가 변하지 않는다는 것이지요.

  1. 앞의 예제와 출력 결과가 달라서 조금 헷갈릴 수 있지만, a_set을 대상으로 b_set에 symmetric_difference() 메소드를 적용하든지, 혹은 b_set을 대상으로 a_set에 symmetric_difference()을 적용하든지 그 결과는 같습니다. 출력이 다른 이유는 집합이 정렬되지 않은(unordered) 원소들의 모임이기 때문입니다. 순서와 관계없이 같은 아이템을 가지고 있는 두 집합은 동일하게 취급됩니다.
  2. 혼동하기 쉬울 때 이렇게 프로그램적으로 확인하면 좀 더 확실합니다. a_set 과 b_set 이 동일한 아이템을 가지고 있으므로 두 set은 같습니다.
  3. 두 집합의 합집합 역시 대칭적입니다.
  4. 두 집합의 교집합 역시 대칭적입니다.
  5. 두 집합의 차(difference)는 대칭적이지 않습니다. 뺄셈에서와 같은 원칙이 적용됩니다. 무엇에서 무엇을 빼느냐에 따라 결과도 달라집니다.

마지막으로 함수 간의 포함관계에 대해 알아볼 수도 있습니다.

  1. 만약 a_set의 모든 항목이 b_set에도 있으면 a_set은 b_set의 부분집합(subset)입니다.
  2. 바꿔서 말하면 b_set은 a_set의 초집합(superset)이라고 할 수도 있습니다.
  3. a_set에 b_set에는 없는 아이템을 추가하는 순간 subset - superset관계는 끝나버리고, 모두 False를 반환합니다.

집합으로 참거짓 판별하기

if 문과 같이 참, 거짓을 판별해야하는 상황(boolean context)에도 집합을 사용할 수 있습니다.

  1. 빈 집합은 거짓입니다.
  2. 아이템이 하나라도 들어 있는 집합은 참입니다.
  3. 아이템의 값이 무엇인지는 상관없습니다. 집합에 아이템이 하나라도 들어있다면 참입니다.

사전(Dictionaries)

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

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

사전 생성하기

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

  1. 두 개의 아이템을 가진 사전 하나를 만들고 a_dict이라는 변수에 할당 했습니다. 각 아이템은 키-값의 쌍(key-value pair)이고 전체 아이템을 중괄호로 감싸고 있습니다.
  2. 'server'는 key이므로 그에 해당하는 값을 조회하기 위해서 a_dict['server']를 입력하면 됩니다. 결과로 'db.diveintopython3.org'가 반환됩니다.
  3. 'database'도 key이므로, 해당 값을 조회하기 위해 a_dict['database']를 입력합니다. 결과로 'mysql'가 반환됩니다.
  4. 키를 이용해 값을 조회하는 것은 가능하지만, 그 반대로 값을 이용해 키를 조회하는 것은 불가능합니다. 따라서, a_dict['server']는 'db.diveintopython3.org'라는 값을 반환하지만 a_dict['db.diveintopython3.org'] 의 경우 예외를 발생시킵니다.

사전 수정하기

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

  1. 사전의 키는 중복될 수 없습니다. 이미 존재하는 키에 새로운 값을 할당하면 이전 값은 사라집니다.
  2. 새로운 키-값 쌍을 언제든지 추가할 수 있습니다. 문법은 이미 존재하는 키에 값을 덮어 씌울 때와 같습니다.
  3. 방금 추가된 키-값 쌍인 'user':'mark'는 사전 중간에 위치하고 있습니다. 사전은 정렬되지 않기(unordered) 때문에 추가하는 순서대로 맨 뒤에 나오거나 하지 않습니다. 아이템 순서에는 큰 의미가 없습니다.
  4. 이미 존재하는 키에 새로운 값을 할당하면 이전 값을 덮어 씌운다고 말씀드렸죠?
  5. user라는 키의 값이 "mark"라는 값으로 돌아갈까요? 아닙니다. 다시 키를 자세히 보면 대문자로 시작하는 "User" 임을 알 수 있습니다. 기존에 있던 키는 소문자이네요. 사전은 대소문자를 구분합니다. 따라서 이 예제코드는 새로운 키-값 쌍을 만들어 냅니다. 사람 눈에는 비슷해 보여도 파이썬 입장에서는 완전히 다른 키입니다.

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

사전에 꼭 문자열만 사용할 필요는 없습니다. 사전에는 어떤 값이든 들어갈 수 있습니다. 정수, boolean, 임의의 객체, 심지어는 다른 사전도 사전의 아이템으로 사용할 수 있습니다. 또한 한 사전 내에서도 다양한 타입을 섞어서 사용할 수 있습니다. 키에 조금 제약이 있긴 하지만, 그래도 문자열, 정수 그리고 몇몇 다른 타입을 사용할 수 있습니다. 물론 섞어서 사용하는 것도 가능하고요.

사실 첫 파이썬 프로그램 챕터에서 문자열이 아닌 키-값 쌍을 이미 한 번 봤습니다.

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

  1. 리스트나 집합의 경우와 마찬가지로 len() 함수는 사전의 길이를 반환합니다. 키 혹은 값의 수이기도 합니다.
  2. 리스트나 집합의 경우와 마찬가지로 in 연산자를 이용해 특정 키가 사전에 존재하는지 검사할 수 있습니다.
  3. 1000이 SUFFIXES라는 사전의 키로 사용되었습니다. 출력된 값은 여덟 개의 아이템(정확히는 문자열)을 포함하는 리스트 하나이고요.
  4. 마찬가지로 1024는 SUFFIXES 사전의 키이고 여덟 개의 아이템(역시 정확히 하자면 문자열)이 있는 리스트를 반환했습니다.
  5. SUFFIXES[1000]은 리스트를 반환하므로 인덱스를 주면 개별 아이템에 접근할 수 있습니다.

사전으로 참, 거짓 판별하기

사전 또한 if 문과 같이 참, 거짓의 판별을 요구하는 구문(boolean context)에 사용할 수 있습니다.

  1. 빈 사전은 거짓입니다.
  2. 키-값 쌍이 하나라도 있으면 참입니다.

None

None은 파이썬 정의된 특별한 상수입니다. 이 상수는 null을 의미하는데, False나 0과는 다릅니다. 빈 문자열을 의미하지도 않습니다. 따라서 None을 이런 값과 비교하는 구문에 사용하는 실수를 저지르지 마세요. 항상 False가 돌아올겁니다.

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

None 으로 참, 거짓 판별하기

if 문과 같이 참, 거짓의 판별을 요구하는 구문(boolean context)에서 None은 거짓이고 not None은 참입니다.

더 읽을거리

Boolean operations
Numeric types
Sequence types
Set types
Mapping types
fractions module
math module
PEP 237: Unifying Long Integers and Integers
PEP 238: Changing the Division Operator

이 강의는 영어로 된 원문한글화 프로젝트를 기초로 작성되었으며, Creative Commons Attribution Share-Alike 라이센스하에 자유로운 변경, 배포가 가능합니다

5484 읽음
이전 챕터 1. 첫 파이썬 프로그램
다음 챕터 3. 컴프리헨션(Comprehensions)

저자

토론이 없습니다

Please log in to leave a comment

16.5.11618.20190612.allo
문제 보고 · 사용조건 · 개인정보보호
래블업 주식회사 · 대한민국 서울 테헤란로 145 · 대표: 신정규 · 사업자번호: 864-88-00080 · +82 70-8200-2587

거절 확인

닫기
좋아요 책갈피 토론

아래 주소를 복사하세요