여기 있어요: Home ‣ Dive Into Python 3 ‣
난이도: ♦♦♢♢♢
❝ 모든 철학은 경이로움에서 태어나, 궁금증으로 성장하지만, 무관심으로 생을 마친다.
❞
— Michel de Montaigne
자료형입니다. 우리의 첫번째 파이썬 프로그램은 잠시 접어두고, 자료형에 대해서 이야기 해 봅시다. 파이썬의 모든 값은 그 타입을 갖고 있습니다. 하지만 이를 명시적으로 선언할 필요는 없습니다. 어떻게 그게 가능할까요? 파이썬에서는 각 변수에 할당되는 값의 타입을 보고 변수의 데이터 타입을 유추하기 때문입니다. 그리고 내부적으로 이를 저장해놓기 때문에, 프로그래머가 명시적으로 변수의 타입을 선언해 줄 필요가 없습니다.
파이썬은 다양한 자료형을 가지고 있습니다만, 우선 그 중 중요한 몇 가지만 살펴봅시다:
True
또는 False
입니다.
1
이나 2
같은) 정수일 수도 있고, (1.1
이나 1.2
같은) 실수일 수도, (1/2
이나 2/3
같은) 분수일 수도 있으며, 심지어 복소수일 수도 있습니다.
물론 이게 전부는 아닙니다. 파이썬에선 모든 것이 객체(Object) 입니다. 따라서 모듈(module), 클래스(class), 메소드(method), 파일(file), 심지어 컴파일된 코드(compiled code)도 일종의 데이터 타입입니다. 이 중 몇 가지는 이미 앞에서 다뤘었죠? 모듈은 __name__ 이라는 특성이 있고, 함수엔 docstrings
이라는 특성이 있습니다. 클래스 타입은 클래스와 반복자 챕터에서 자세히 다루도록 하고요, 파일 타입은 파일챕터에서 다루겠습니다.
문자열과 바이트는 매우 중요한데, 복잡하기도 해서 별도 챕터로 뺐습니다. 아무튼 이렇게 별도의 챕터로 빼둔 것들은 그때 다시 배우도록 하고요, 우선은 그를 제외한, 좀 쉬운 것부터 살펴보죠.
⁂
Boolean은 참 이나 거짓 가운데 하나를 의미하는 데이터 타입입니다. 파이썬은 이를 위해 True
와 False
라는 상수를 정의해서 사용하고, 이 상수를 변수에 직접 할당하여 Boolean 타입으로 만들 수 있습니다. 어떤 상황에서는, 주어진 표현식이 반드시 True
이거나 False
여야 합니다. if 문을 예로 들 수 있습니다. if 문에 사용된 조건식은 반드시 True
나 False
가운데 하나로 판명나야 합니다. 이런 상황을 Boolean context 라고 부릅니다. Boolean context 에는 어떤 파이썬 표현식도 사용할 수 있습니다. 파이썬이 알아서 True
나 False
중 하나로 평가해 주거든요. 하지만, 자료형마다 서로 다른 룰을 가지고, 참과 거짓이 판명된다는 사실은 알고 계셔야 합니다. 아리송하죠? 역시 예제를 보는 편이 낫겠네요.
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 타입을 숫자처럼 쓸 수도 있습니다. True
는 1
, 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으로 나누면 안됩니다. 알고 계셨죠?.
⁂
이번 챕터에서는 다뤄야 할 것들이 굉장히 많습니다. 일단 파이썬 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'>
type()
함수를 사용합니다. 당연히 1
은 정수형
입니다.
isinstance()
함수를 사용하면, 첫번째 파라미터로 넘겨진 값이나 변수가 두번째 파라미터로 넘겨진 타입과 같은지 아닌지를 알 수 있습니다.
정수
에 정수
를 더하면 정수
입니다.
정수
에 실수
를 더하면 실수
입니다. 파이썬에선 정수
와 실수
를 더하면, 먼저 정수를 실수
로 변경한 후 연산을 적용합니다. 따라서 연산결과는 실수입니다.
앞에서 본 것과 같이, 더하기 연산자 같은 연산자 들은 주어진 정수값을 그때 그때 상황에 맞게 먼저 실수값으로 변경후 연산에 적용합니다. 하지만 다음과 같이 명시적으로 타입을 변경할 수도 있습니다.
>>> float(2) ① 2.0 >>> int(2.0) ② 2 >>> int(2.5) ③ 2 >>> int(-2.5) ④ -2 >>> 1.12345678901234567890 ⑤ 1.1234567890123457 >>> type(1000000000000000) ⑥ <class 'int'>
float()
함수를 사용해 int
형을 float
형으로 강제 형 변환할 수 있습니다.
int()
함수를 사용해 float
형을 int
형으로 바꿀 수 있습니다.
int()
함수는 양수의 경우 소수점 아래는 그냥 버립니다.
int()
함수는 음수의 경우도 소수점 아래는 버립니다.
☞ 파이썬 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
/
연산자는 실수 나눗셈을 합니다. 분모와 분자 모두 int
형이더라도 결과는 항상 float
형이 됩니다.
//
연산자는 조금 독특한 정수 나눗셈을 합니다. 만약 결과가 양수라면 소숫점 이하는 무조건 버립니다. 하지만, 조심할게 있습니다.
//
연산자를 음수에 적용하면, 소숫점 이하는 역시 버리고, 결과를 가장 가까이 있는 자기보다 적은 정수값으로 만듭니다. 결과만 놓고 볼 때는, −5
보다 −6
이 더 작으니까, 수학적으로 말하면 반내림과 같습니다. -5 라고 자신있게 말씀하셨던 분 많죠? 앞에서 조심하라고 말씀드렸었죠?
//
연산자의 결과값이 언제나 정수형인 것은 아닙니다. 분자와 분모 중 하나가 실수형이면, 결과값도 실수입니다. 하지만 실수인 결과값 역시 가장 가까운 작은 정수로 변환됩니다.
**
연산자는 제곱을 의미합니다. 112
은 121
이죠.
%
연산자는 정수 나눗셈을 하고 난 나머지를 돌려줍니다. 11
을 2
로 나눈 나머지는 1
이죠. 그래서 결과는 1
입니다.
☞ 파이썬2 에서의
/
연산자는 정수 나눗셈을 의미했습니다만, 코드 안에 특별 지시자를 넣어 실수 나눗셈 또한 가능하도록 할 수 있었습니다. 그러나 파이썬3 에서는/
연산자는 디폴트로 실수 나눗셈을 의미합니다. 더 자세한 내용은 PEP 238을 참고하세요.
파이썬이 정수나 실수 연산만 할 수 있는건 아닙니다. 여러분이 중고등학교 때 배웠던, 수학의 정석에나 나올 법한 복잡한 수학공식들도 파이썬은 척척 해낼 수 있습니다.
>>> 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)
fractions
모듈을 임포트해야 합니다.
Fraction
객체를 만들고, 파라미터로 분자와 분모를 넘겨줍니다.
Fraction
객체가 반환됩니다. 2 * (1/3) = (2/3)
입니다.
Fraction
객체는 분자와 분모를 감지해서 자동으로 크기를 맞춰줍니다. (6/4) = (3/2)
입니다.
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
math
모듈은 원주율을 의미하는 &pi 상수를 정의하고 있습니다.
sin()
, cos()
, tan()
같은 기본 삼각함수들 뿐만 아니라, asin()
같이 고급 삼각 함수도 지원합니다.
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
True
입니다. 0.0
만 False
입니다. 반올림 등의 연산을 실수에 적용할 때는 조심하세요. 0.0000000000001
같은 값도 0이 아니기 때문에 True
로 판명됩니다.
True
입니다. Fraction(0, n)
의 경우 n에 어떤 값을 넣더라도 False
입니다. 이를 제외한 나머지는 모두 True
입니다
⁂
리스트 (List) 데이터 타입은 파이썬에서 없어서는 안 될 충실한 심복중 하나입니다. list라는 단어를 듣는 순간, 이렇게 생각하실지도 모르겠습니다. "음, 먼저 사이즈를 정해서 미리 선언해주고, 같은 데이터 타입만 집어 넣을 수 있겠군". 파이썬의 list 데이터 타입을 다른 프로그래밍 언어의 배열과 비슷할 거라고 생각하셨다면, 틀렸습니다. 파이썬 list는 그것보다 훨씬 멋지거든요.
☞ 펄과 비교하면, list는 펄 5의 배열과 유사합니다. 한편, 펄에서는 배열의 변수명이 항상
@
로 시작해야 하지만, 파이썬엔 그런 제약이 없습니다. 파이썬은 내부적으로 데이터 형을 관리하기 때문에 명시적으로 나타낼 필요가 없는거죠.
파이썬 list는 다른 언어, 가령, 자바의 배열보다 훨씬 강력합니다. 물론 딱 배열처럼만 사용하려면 그렇게 사용할 수도 있습니다. 하지만, 어떤 타입의 객체도 담을 수 있고, 그 크기가 동적으로 늘어날 수 있다는 점에서는 자바의
ArrayList
클래스에 비유할 수 있겠네요.
파이썬 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'
a_list[0]
은 alist 라는 list의 첫번째 원소를 가져옵니다.
a_list[4]
로 가져옵니다.
a_list[-1]
는 뒤에서 첫번째 아이템, 그러니까 맨 마지막 아이템을 반환합니다.
a_list[-n] == a_list[len(a_list) - n]
. 그러니까 이 예제에서는 이렇게 됩니다. a_list[-3] == a_list[5 - 3] == a_list[2]
.
정의된 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']
a_list[1]
) 을 포함하고, 두번째 인덱스의 바로 앞 아이템까지(예에서는 a_list[3]
)를 포함하는 list를 반환합니다.
a_list[0:3]
은 a_list 라는 list의 맨 처음부터 3개의 아이템입니다. a_list[0]
부터 시작하여 a_list[3]
의 바로 앞까지 입니다.
a_list[:3]
는 a_list[0:3]
와 같은 의미입니다.
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 끝까지를 반환합니다
a_list[:]
라고 써주면 간단히 a_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', 'Ω']
+
연산자를 이용하여 list를 합칠 수 있고, 결과로는 새로운 list가 반환됩니다. list에 담을 수 있는 아이템 갯수에는 제한이 없는데, 정확히 말하면 사용할 수 있는 메모리에 제한이 없다면 제약이 없는 셈입니다. 만약 구현하려는 어플리케이션의 사용 가능한 메모리가 많지 않다면, 사용시 주의해야 합니다. 이 코드가 하는 일을 두 단계로 나누면, 먼저 a_list 에 [2.0, 3]을 합치고, 그 결과를 다시 a_list 에 할당합니다. 다루고 있는 list의 크기가 크지 않다면 괜찮겠지만, 상당한 크기인 경우에는 두번째 단계인 할당이 일어나는 순간 많은 양의 메모리가 요구될 수 있습니다. 그 점을 미리 염두에 두셔야 합니다.
append()
메소드를 이용해 list의 맨 끝에 아이템을 추가할 수 있습니다. 이제 4개의 서로 다른 데이터 타입이 list 안에서 공존하는군요.
extend()
메소드를 이용하여 기존 list에 아이템을 추가하고 있습니다. 메소드 인자가 추가할 데이터입니다.
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']
extend()
메소드는 list 타입의 인자를 받아서, 그 내부의 아이템들을 a_list안에 추가함으로써, 기존의 list를 그만큼 연장해(extend)줍니다.
append()
메소드는 인자로 들어온 값을 list에 추가합니다. 인자는 하나이며, 인자의 타입은 어떤 타입이라도 괜찮습니다. 예제코드에서는 3 개의 아이템이 있는 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
count()
메소드를 사용하세요.
in
연산자를 이용하는 편이 간단하고, 성능도 좀 더 빠릅니다. 결과로 True
나 False
가 반환됩니다.
index()
메소드를 사용하세요. 이 함수는 list 전체를 검색하여 해당 위치(0 부터 시작합니다) 를 반환합니다. 검색을 수행할 위치도 지정할 수 있는데요, 두번째 파라미터가 검색을 시작할 위치를 의미하고, 세번째 파라미터가 검색을 끝낼 위치를 의미합니다.
index()
메소드는 처음으로 발견한 위치를 반환합니다. 예에서 'new'
라는 문자열은 a_list[2]
에 하나, a_list[4]
에 다른 하나, 이렇게 모두 두 번 등장합니다. 하지만, index()
메소드는 첫번째 위치인 2만 반환합니다.
잠깐만요. 뭔가 맘에 안듭니다. 보통 다른 언어같은 경우엔 검색하는 값이 없으면 -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'
어떤 아이템을 지우고 싶은데 위치를 모른다고요? 문제 없습니다. 위치 인덱스 대신 삭제하고 싶은 값을 직접 입력하면 됩니다.
>>> 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
remove()
메소드도 삭제에 사용하는 메소드 입니다. 삭제하고 싶은 아이템의 값을 파라미터로 받아 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
pop()
메소드를 인자없이 호출하면 list의 맨 마지막 아이템을 반환하고, 해당 아이템은 삭제됩니다.
pop()
메소드에 인자로 위치 인덱스를 주면, 해당 아이템을 반환후 삭제합니다.
인자없는
pop()
메소드 호출은 펄의pop()
함수와 유사합니다. list 맨 마지막 원소를 반환하고, list 내에서는 삭제합니다. 펄과 다른 점은, list의 맨 앞부분 원소를 삭제할 때, 펄은shift()
라는 메소드를 따로 두고 있지만, 파이썬은 인자로0
을 주면 된다는 점입니다.
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
⁂
튜플은 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')
튜플이 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
append()
나 extend()
같은 메소드는 없다구요.
remove()
or pop()
같은 메소드는 없거든요.
in
연산자를 사용해도 됩니다.
그래서, 튜플로 뭘 할 수 있을까요?
assert
를 이용하여 데이터를 방어하는 것과 같은 효과를 볼 수 있습니다.
☞튜플을 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'>
파이썬 튜플이 가진 멋진 기능중 하나는, 여러 값을 한꺼 번에 할당해야 하는 상황에서 튜플을 사용할 수 있다는 점입니다.
>>> v = ('a', 2, True)
>>> (x, y, z) = v ①
>>> x
'a'
>>> y
2
>>> z
True
(x, y, z)
는 세 개의 변수를 가진 튜플이구요. 서로 짝이 맞기 때문에 한쪽에서 다른 쪽으로 할당하면, 변수가 선언된 순서대로 할당이 이루어집니다.
튜플의 이런 특징은 꽤 쓸모가 많습니다. 일례로 특정 범위 내에 있는 값들을 변수들에 할당해야 할 때, 파이썬 내장함수인 range()
와 튜플을 함께 사용하여 간단하게 해결할 수 있습니다. 아래 예제코드를 보시죠.
>>> (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7) ① >>> MONDAY ② 0 >>> TUESDAY 1 >>> SUNDAY 6
range()
는 일련의 정수들을 만들어 냅니다. (정확히 말하면 range()
는 list나 tuple이 아닌 반복자(iterator)를 반환합니다. 이들의 차이점은 뒤에서 따로 다룰겁니다) MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, and SUNDAY 와 같은 변수들을 정의했고요.
(이 예제 코드는 사실 calendar
모듈에서 가져왔습니다. 이 모듈은 달력과 관련된 기능들을 모아둔 모듈인데요, UNIX 운영체제를 보면cal
이라고 하는 날짜를 출력하는 프로그램이 있는데, 이와 비슷한 기능을 가진 모듈입니다. 이 calendar
모듈은 월요일부터 일요일까지를 정수의 상수값으로 정의해두고 있습니다.)
1
, 이런식으로 값이 할당됩니다.
튜플을 이용하면 둘 이상의 값을 인자로 받아, 둘 이상의 값을 반환하도록 하는 함수도 작성할 수 있습니다. 여러 값들을 묶어서 하나의 튜플 변수로 처리하면 되니까요. 실제로 파이썬 표준 라이브러리의 수 많은 함수들이 이 테크닉을 즐겨 이용하고 있답니다. (궁금하신 분은 os
모듈 소스코드를 들여다 보세요.)
⁂
set은 마치 유일한 값을 가진 데이터들을 뒤죽박죽인 채로 넣어둔 자루와 같습니다. set 안에 들어갈 수 있는 데이터는 추후 변경이 불가능 (immutable) 해야 합니다. 두 개의 set 이 정의되 있다면, 마치 수학에서의 합집합 (union), 교집합 (intersection) 그리고 교집합을 제외한 나머지 (difference) 등과 같은 개념을 표준 set 함수를 이용해 구할 수 있습니다.
먼저 set 을 만들어봅시다. 간단합니다.
>>> a_set = {1} ① >>> a_set {1} >>> type(a_set) ② <class 'set'> >>> a_set = {1, 2} ③ >>> a_set {1, 2}
{}
)로 감싸주면 됩니다.
{}
)로 감싸주면 됩니다.
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]
set()
함수를 사용하면 됩니다. 혹시 어떤 깐깐한 분들께서는 "잠깐만요, 이건 사실 함수호출이 아니라 객체 생성 (class instantiation) 아닌가요?" 라고 질문하실수도 있겠습니다. 약속드릴게요. 제가 나중에 다시 설명드리겠습니다. 일단 지금은 set()
이 함수처럼 동작하고, set 을 반환한다고 알고 넘어갑시다.
set 안에 집어넣을 아이템이 아직 준비되지 않았다구요? 괜찮습니다. 빈 set을 만들면 되거든요.
>>> a_set = set() ① >>> a_set ② set() >>> type(a_set) ③ <class 'set'> >>> len(a_set) ④ 0 >>> not_sure = {} ⑤ >>> type(not_sure) <class 'dict'>
set()
with no arguments.
빈 set 을 만드려면 set()
라고 입력합니다. 함수안에 어떤 인자도 주지 않습니다.
{}
랑 비슷한 것이 나와야 하지 않을까라고 생각하셨을지도 모르겠습니다. 하지만 {}
는 빈 dictionary 를 의미합니다. dictionary 에 대해서는 조금있다가 배우도록 하겠습니다.
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
add()
메소드는 하나의 인자를 받습니다. 어떤 데이터 타입이든 상관없이 그 인자를 주어진 set에 추가합니다.
>>> 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}
update()
메소드는 하나의 인자를 입력으로 받았는데, 그 인자 역시 set 이군요. 이 코드는 입력으로 주어진 set 을 대상이 되는 set에 추가합니다. 입력 set 내 아이템들 하나 하나에 add()
메소드를 호출하는 것과 같은 동작을 합니다.
update()
메소드의 인자로 두 개 이상의 set 을 전달하는 것도 가능합니다. 타겟 set 내 아이템과 중복되는 것은 무시하고, 모든 아이템을 타겟 set 에 추가합니다.
update()
메소드는 인자로 어떤 데이터 타입도 넘겨줄 수 있습니다. 예제에서는 인자의 타입으로 list를 넘겨주고 있습니다. list가 인자로 건네지면 list 내 모든 아이템을 타겟 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
discard()
메소드는 값 하나를 인자로 받아 해당 값을 set에서 제거합니다.
remove()
메소드 또한 값 하나를 인자로 받아 해당 값을 set에서 제거합니다.
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'
pop()
메쏘드는 set 내의 임의의 아이템 하나를 제거한후 이를 반환합니다. 특이한 점은 이 메소드를 통해 어떤 원소를 반환하게 될지 미리 알 수 없다는 점입니다.
clear()
메쏘드는 set의 모든 아이템을 제거합니다. 예제 코드는 a_set = set()
와 같은 효력을 지니는데, 새로운 빈 set을 만든후 이를 a_set 변수에 덮어씌우는 것과 같습니다.
pop()
메쏘드를 적용하면 KeyError
예외가 반환됩니다.
파이썬 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}
in
연산자를 사용합니다.
union()
메쏘드는 대상이 되는 두 개 set의 합집합을 결과로 반환합니다.
intersection()
메쏘드는 대상이 되는 두 개 set의 교집합을 결과로 반환합니다.
difference()
메쏘드는 a_set 에는 있지만 b_set에는 없는 아이템을 담은 새로운 set을 반환합니다.
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
symmetric_difference()
메쏘드를 적용하던 b_set을 대상으로 a_set에 symmetric_difference()
을 적용하던 그 결과는 동일합니다. 출력이 다르게 나오는 이유는 set 이 정렬되지 않은 (unordered) 컨테이너이기 때문이고요, 순서와 관계없이 동일한 아이템을 포함하고 있는 set은 동일하다고 취급됩니다.
자자, 이제 몇 개 안남았습니다. 힘을 내세요.
>>> 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
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
⁂
dictionary는 정렬되지 않은 (unordered) 키-값의 쌍 (key-value pair) 입니다. dictionary에 새로운 key 값을 입력할때 반드시 key 값에 해당하는 value 도 함께 입력해야 합니다. value 는 언제든지 새로운 값으로 다시 변경할 수 있습니다. dictionary는 key 값을 가지고 이에 해당하는 value를 조회할때 최고의 성능을 낼 수 있도록 최적화되 있습니다만, 그 반대는 아닙니다.
☞ 파이썬의 dictionary 는 Perl 5의 해쉬 (hash)와 여러 모로 유사합니다. Perl 5에서 hash 값을 저장하고 있는 변수를 지정할때는
%
문자로 시작합니다만, 파이썬에서는 그럴 필요가 없습니다. 내부적으로 변수이름을 추적하도록 설계되있으므로, 어떤 문자든 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'
'server'
는 key 값이므로, 해당하는 value 를 조회하기 위해서는 a_dict['server']
를 입력하면 됩니다. 결과로 'db.diveintopython3.org'
가 반환됩니다.
'database'
는 key 값이므로, 해당하는 value 를 조회하기 위해서는 a_dict['database']
를 입력하면 됩니다. 결과로 'mysql'
가 반환됩니다.
a_dict['server']
는 'db.diveintopython3.org'
라는 값을 반환하지만, a_dict['db.diveintopython3.org']
의 경우 예외를 발생시킵니다.
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'}
'user'
:'mark'
는 dictionary 중간에 위치했습니다. dictionary 는 정렬되지 않기 (unordered) 때문에 추가하는 순서대로 맨 뒤에 나오거나 하지 않습니다. 아이템 순서에는 큰 의미가 없습니다.
user
의 value가 "mark" 라는 값으로 다시 되돌아갈까요? 아닙니다. 다시 key 값을 자세히 보면, 대문자로 시작하는 "User" 임을 알 수 있습니다. 기존에 있던 key 는 소문자이구요. dictionary 는 대소문자를 구분합니다. 따라서 이 예제코드는 새로운 key-value 쌍을 만들어 냅니다. 사람눈엔 비슷해 보여도 파이썬 입장에선 완전 다른 key 입니다.
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'
len()
함수는 딕셔너리의 길이를 반환합니다. key의 갯수이기도 합니다.
in
연산자도 특정 key 가 dictionary 안에 존재하는지 검사할 때 사용할 수 있고요.
1000
은 SUFFIXES
라는 dictionary의 key 로 사용됬습니다. 해당 value는 list로 그안에는 모두 8개의 아이템이 들어있고요.
1024
은 SUFFIXES
dictionary 의 key 이고, 해당 value는 8개의 아이템이 있는 list 입니다.
SUFFIXES[1000]
는 list를 반환하므로 배열방식을 이용하면, 반환된 list 내 각각의 아이템의 값을 구할 수 있습니다.
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
⁂
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
⁂
fractions
module
math
module
© 2001–11 Mark Pilgrim