작년에 Python을 주로 사용하는 것스마트스터디로 이직했다. 난 전에 ASP, Java, JavaScript 등을 했지만 사실 Python은 코드만 가끔 본 거 외에는 실제로 사용해 본 적은 한 번도 없다. Python 때문에 이직을 한 건 아니지만 일을 하려면 Python을 알아야 하므로 입사 전에 공부를 시작하려고 했었다.
당시에는 내가 아는 가장 유명한 Python 책인 Learn Python the hard way를 꺼내 들었는데 바로 SNS에서 이 책은 프로그래밍을 모르는 초급자를 위한 책이라서 적합하지 않다고 하면서 Dive Into Python 3을 추천받았다. 덤으로 Python 2로 공부하려고 했더니 왜 2016년에 Python 2를 공부하냐고 혼났다. 몇 년 전에 콘퍼런스에서 아직 Python 2로도 충분하다는 발표를 들었었는데 1, 2년 사이에 달라졌나 보다. 그래서 Python 3으로 갈아탔다.
Mark Pilgrim의 Dive Into Python 3가 원서라서 고민하던 중 파이썬 3에 뛰어들기로 번역된 자료가 있다는 걸 알게 되었다. GitHub에 올라와 있어서 보기 쉽게 PDF로 변환했지만 읽던 중 일부는 번역이 적용 안 된 것을 알게 되어 결국 위 웹사이트에서 책을 읽었다. 책을 1년 동안 읽은 것은 아니고 입사 전에 공부를 하다가 막상 입사하니까 Python 프로젝트에 내가 발을 담그는 게 그리 급한 일이 아니라서 다른 일을 하다 보니 멈춰뒀다가 올해 와서 다시 읽었다. 일단 코드 리뷰를 하려고 해도 어느 정도 알아야 하므로 틈틈이 읽기는 했는데 생각보다 훨씬 더 오래 읽게 됐다.
언어의 문법을 안다고 코딩을 할 수 있는 건 아니지만 처음에 할 때 한번 문법을 점검하고 공부하고 싶어서 문법 관련 책을 먼저 봤다. 다른 Python 문법책을 또 볼 생각은 없다.(너무 오래 걸리므로...) 웹이라서 약간 아주 깔금하게 편집되어 있지 않은 부분 외에는 내용이 나쁘지 않다. 기본적인 Python 3의 문법을 읽힐 수 있고 2와의 비교 내용도 잘 나와 있어서 어떻게 달라졌는지도 어느 정도 파악할 수 있다. 다른 Python 책은 아직 안 봐서 비교는 안 되고 생각해 보면 문법책을 본 것도 너무 오랜만이라서 상대 기준으로 비교가 되진 않는다.
아래 내용은 나중에 참고하려고 책을 읽으면서 간단히 정리한 내용이다. 박스친 부분은 책의 내용이 아니라 내 생각을 적은 부분이다.
-1. "파이썬 3로 뛰어들기"에서 달라진 점
- Python 2에서는 문자열과 유니코드 문자열이 있었지만, Python 3에서는 바이트와 문자열만 있다.
- Python 공부하면서 2에서 유니코드가 안된다는 걸 알았을 때는 정말 놀랐다.
0. 파이썬 설치하기
- REPL에서
help()
를 입력하면 파이썬 명령어의 도움말을 입력해서 볼 수 있다.
- 손에 익을 때까지
help()
쓰는 습관을 들여야 할것 같다.
1. 첫 파이썬 프로그램
1024 if a_kilobyte_is_1024_bytes else 1000
처럼 작성할 수 있다.- 문자열에 값을 넣을 때는
'{0:.1f} {1}'.format(size, suffix)
와 같이 쓴다. - 함수는
def
를 사용한다.
- 함수에
return
문이 없다면None
을 반환한다. def approximate_size(size, a_kilobyte_is_1024_bytes=True):
처럼 파라미터의 기본값을 지정한다.- 함수를 호출할 때
approximate_size(a_kilobyte_is_1024_bytes=False, size=4000)
처럼 이름을 지정해서 호출할 수 있다. 이름이 가진 파라미터가 있으면 그다음부터는 모두 이름을 주어야 한다. - 함수는 first-class object다.
- 함수에
- 함수에 설명을 다는 것을 docstring(documentation string)이라고 한다.
'''
로 묶어서 docstring을 작성한다.'''
가 주석 문법이므로 아무 곳에서나 사용할 수 있지만 docstring에 가장 많이 쓰인다.- docstring은 함수 선언 다음 줄에 위치한다.
method.__doc__
를 프린트하면 해당 함수의 docstring을 볼 수 있다.
import
를 할 때는sys.path
(import sys
)의 모든 경로를 검색한다.
- 여기엔 현재 경로도 맨 앞에 추가된다.
sys.path.insert(0, new_path)
하면 경로의 맨 앞에 새로운 경로를 추가하므로 다른 경로보다 우선 검색하게 할 수 있다.
:
과 들여쓰기만으로 함수를 정의한다.
- 들여쓰기하면 코드 블록이 시작된다.
- 한 코드에서는 들여쓰기를 같이 사용해야 한다.
- 파이썬 함수는 어떤 예외를 발생시킬 수 있는지 선언하지 않는다. 어떤 예외를 발생시키고 처리할지는 개발자에게 달려있다.
- 예외처리는
raise ValueError('number must be non-negative')
와 같이 한다. - 스택트레이스를
traceback
이라고 부른다.
try:
import chardet
except ImportError:
chardet = None
- 위처럼 임포트 에러를 처리할 수 있고 모듈의 존재 여부는
if chardet:
로 확인할 수 있다. from lxml import etree
와 같이하면lxml
에서etree
를 임포트한다.- 선언하지 않고 할당하는 변수를 unbound 변수라고 부른다. 이건 괜찮지만 할당하지 않은 변수를 참조하면
NameError
가 발생한다. multiple = 1024 if a_kilobyte_is_1024_bytes else 1000
와 같이 상황에 따라 할당하는 게 가능하다.- 모든 이름은 대소문자를 구별한다.
if __name__ == '__main__':
부분은 커맨드라인에서 파이썬 파일을 실행할 때만 실행된다.
- 모든 모듈은
__name__
속성을 가지고 있어서 모듈을 사용하는 방식에 따라 이 값이 달라진다. - import 하면 파일명이
__name__
에 들어간다.(확장자 제외)
- 모든 모듈은
- 함수나 변수명에
_
를 쓰는 게 아직 어색하다. - 함수나 조건문 마지막에
:
를 잊지 말자. 자꾸 까먹어... - 세미콜론 쓰지 말자.(오류는 안 나는데 습관이 들어서...)
- 선언없이 변수를 선언하는 게 어색하다.
2. 고유 자료형
- boolean 타입은
True
,False
를 쓸 수 있다.True
는1
,False
는0
이 된다.
- if 문 등에서는
0
은 False이고 그 외의 숫자는 모두 True이다.
- if 문 등에서는
- 타입 검사는
type(1)
처럼 사용한다. 이 결과는<class 'int'>
가 된다. isinstance(1, int)
처럼 소속 클래스를 판별할 수 있다.- numbers 타입에서는
float(2)
나int(2.5)
처럼 변환을 할 수 있다.
- 실수는 소수점 아래 15자리까지만 정확하게 표현할 수 있다.
- Python 2에서는 int와 long이 따로 있었지만 Python 3에서는 int만 사용한다.
/
는 실수 나눗셈이지만//
는 실수 나눗셈을 하는데 결과가 양수라면 소수점 이하는 모두 버리고 결과가 음수라면 자기보다 작은 정수가 된다.-11//2
는 -5.5이므로 -6이 된다.**
는 지수를 의미한다. 즉11**2
는11^2
가 된다.- 수학 함수는
import math
에서 사용할 수 있다.
- List
- 리스트는
[]
로 정의하고 콤마로 각 요소를 구별하고 데이터 타입을 통일할 필요는 없다. list[0]
는 첫 번째 요소를 반환한다.list[1:6]
는 2번째부터 6번째까지 요소를 반환하고 length가 넘어도 오류 나진 않는다.list[1:]
는 2번째부터 끝까지 가져오고list[:4]
는 처음부터 4번째까지 가져온다. 그래서list[:]
는 다 가져온다.list[-1]
는 뒤에서 첫 번째 아이템을 가져온다.list + ['f']
처럼 요소를 추가할 수 있다.list.append(6)
는 마지막에6
을 추가한다.list.extend([7])
는 마지막에7
을 추가한다.list.insert(2, 10)
를 하면 2번 위치에 10을 추가하고 다른 요소를 뒤로 미룬다.list.count('e')
는 리스트에서e
의 개수를 반환한다.'c' in list
처럼c
가 리스트에 있는지를 판단할 수 있다.list.index('e')
는e
의 첫 번째 위치를 반환한다. 찾지 못하면ValueError
가 발생한다.del list[2]
를 하면 리스트의 두 번째 아이템을 제거한다.list.remove('e')
처럼 원하는 객체를 제거할 수 있다. 첫 번째 아이템만 제거한다. 존재하지 않는 값을 지우려고 하면 오류가 발생한다.list.pop()
는 마지막 아이템을 제거하고list.pop(3)
는 3번째 아이템을 제거한다. 빈 리스트라면 오류가 발생한다.- 빈리스트는
False
이다.
- 리스트는
- Tuple
- **내부아이템 수정이 불가능한 리스트이다.*** 그래서,
append()
,extend()
,insert()
,remove()
,pop()
등이 없다. - 대괄호 대신 괄호를 이용해서 정의한다.
("a", "b", "mpilgrim", "z", "example")
- 리스트와 같이 순서가 있고 리스트처럼 아이템을 가져올 수 있다.
- 특징
** 리스트보다 빠르다. tuple()
은 리스트를 튜플로 바꿔주고list()
는 튜플을 리스트로 바꿔준다.- 빈 튜플은 False이다.
- 1개짜리 튜플을 만들 때는
("a",)
와 같이 해주어야 한다. 마지막에 콤마가 없으면 그냥 괄호다.
- **내부아이템 수정이 불가능한 리스트이다.*** 그래서,
v = ('a', 2, True)
(x, y, z) = v
- 위처럼 디스럭쳐링을 할 수 있다.
(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)
같은 방식으로 활용 가능하다.
- Set
- 유일 값을 가진다. 정렬되지 않았다.
{1}
나{1, 2}
처럼 Set을 만든다.set()
함수로 리스트를 Set으로 변환할 수 있다.a_set = set()
처럼 빈 Set을 만들 수 있다.len(a_set)
로 길이를 검사한다.{}
이렇게 하면 빈 Set이 아니라 딕션어리가 만들어진다.add()
함수로 아이템을 추가할 수 있다.update()
는 인자로 Set을 받아서 Set에 추가한다. set을 2개이상 전달하는 것도 가능하다. 리스트를 전달해도 된다.discard()
와remove()
로 특정 아이템을 제거할 수 있지만remove()
는 없는 값을 지정하면 오류가 발생한다.pop()
은 아이템을 제거하지만, 어느 아이템 제거될지는 알 수 없다.clear()
는 모든 요소를 제거한다.union()
,intersection()
,difference()
,symmetric_difference()
의 함수도 있다.
- 딕션어리
- 정렬되지 않은 키-값의 쌍
{'server': 'db.diveintopython3.org', 'database': 'mysql'}
로 생성하고a_dict['server']
같이 조회한다.- 없는 키를 조회하면 오류가 난다.
- 기존 키에 할당하면 덮어써 지고 새로운 키에 할당하면 추가된다.
- None
- 특수 상수로 null을 의미하지만, False나 0과는 다르다.
NoneType
이고 딱None
하나 뿐이다.
list.index()
에서 값이 없으면 오류가 발생시킨다는 게 신기하게 느껴졌다. JS를 오래 해서 그런지-1
검사가 좀 귀찮은 건 사실이지만 오류를 잡아내는 게 처리가 편하려나.... Java에서는 어떻게 했더라...- 빈 리스트(
[]
)가 False라는 건 직관적이지만 JavaScript는 안 그래서 난 좀 헷갈릴 듯... - 1개짜리 튜플을 만들 때 마지막에 콤마를 찍는 건 기억해 둬야 할 듯...
3. 컴프리헨션
os
모듈로 디렉터리, 파일, 프로세스, 환경변수를 다룰 수 있다.
os.path
에는 파일과 디렉터리를 다루는 함수가 있다.os.path.expanduser()
는os.path.expanduser('~'')
처럼 해서 홈 디렉터리로 바꿔준다.os.path.split
는 경로와 파일명을 튜플로 분리하고os.path.splitext
는 파일과 확장자를 튜플로 분리한다.os.path.realpath('feed.xml')
로 파일의 절대 경로를 얻을 수 있다.
glob
모듈로 디렉터리를 검색한다.
glob.glob('examples/*.xml')
처럼 파일을 검색할 수 있다.os.stat('feed.xml')
로 파일의 메타데이터를 추출할 수 있다.
time
모듈로 시간을 다룬다.- 리스트 컴프리헨션
- 리스트의 각 요소에 함수를 적용 후 반환 값을 새 리스트를 얻을 수 있다.
[elem * 2 for elem in a_list]
오른쪽에서 왼쪽으로 읽으면 이해가 쉽다.- 기존 리스트는 변경되지 않는다.
[f for f in glob.glob('*.py') if os.stat(f).st_size > 6000]
같이 하면if
조건이 참인 경우만 리스트에 포함된다.
- 딕션어리 컴프리헨션
{f:os.stat(f) for f in glob.glob('*test*.py')}
와 같이 만든다.
- 셋 컴프리헨션
{x for x in a_set if x % 2 == 0}
처럼 만든다.
4. 문자열
- Python 3의 모든 문자열은 유니코드이다.
len(str)
로 문자열의 길이를 알 수 있다."{0}'s password is {1}".format(username, password)
처럼 문자열을 치환할 수 있다.'1000{0[0]} = 1{0[1]}'.format(arr)
처럼arr
이 배열이면 이렇게도 가능하다.format specifier
'{0:.2f} {1}'.format(698.249, 'GB')
처럼 할 때:
은 형식지정자의 시작을 알리고.2f
가 형식 지정자이다..2
는 소수 셋째 자리에서 반올림하라는 것이고f
는 부동소수점을 의미한다. 그래서698.25GB
가 된다.- Format Specification Mini-Language 참고
'''
를 사용하면 멀티라인 문자열을 정의할 수 있다.str.splitlines()
로 뉴라인(\n
)기준으로 분리해서 배열로 만들 수 있다.str.lower()
로 소문자로 만든다.str.lower().count('f')
로f
의 개수를 셀 수 있다.str.split('&')
를 사용하면&
기준으로 분리한다.query = 'name=outsier&s=blog&date=2017-01-01'
가 있을 때
query.split('&')
를 하면['name=outsier', 's=blog', 'date=2017-01-01']
를 얻을 수 있다.query.split('&', 1)
처럼 숫자를 줄 수 있는데 이는 몇 번 스플릿 할지를 정한다.[v.split('=', 1) for v in query.split('&') if '=' in v]
처럼 나눌 수 있다. 이건[['name', 'outsier'], ['s', 'blog'], ['date', '2017-01-01']]
가 되는데 뒤에if
까지 붙어서 URL 나눌 때 아주 편할 듯...
a_string[3:11]
처럼 리스트와 마찬가지로 자를 수 있다.by = b'abcd\x65'
로 바이트 정의할 수 있다. 이는byte literal
이다. 객체의 타입은 바이트이다.(<class 'bytes'>
)- 내장함수
bytearray(by)
로 바이트어레이(<class 'bytearray'>
)로 바꿀 수 있다. 바이트의 메서드를 같이 쓸 수 있다. 바이트는 생성하고 나면 바꿀 수 없으므로 이때 바이트 어레이를 사용한다. - 바이트와 문자열을
+
하면 오류가 난다. 섞어 쓰면 안된다. - 바이트객체의
decode()
로 문자열로 바꿀 수 있고 문자열에서encode('utf-8')
로 바이트로 바꿀 수 있다. - Python 3에서
.py
는 UTF-8로 인코딩되어 있다고 가정한다. 다른 인코딩을 사용하려면 파일 첫 줄에서# -*- coding: windows-1252 -*-
로 명시해야 한다.
- 멀티라인은 되는데 heredoc은 없나.
str.lower().count('f')
이런건 꽤 편할 듯...
5. 정규표현식
import re
re.sub('ROAD$', 'RD.', s)
로 치환한다.- 특수문자의 경우
re.sub('\\bROAD$', 'RD.', s)
로 이스케이프 할 수 있지만, raw string을 써서re.sub(r'\bROAD$', 'RD.', s)
처럼 할 수 있다. 파이썬 정규표현식은r
을 쓰기를 권장한다. re.search(pattern, 'MM')
로 검색한다. 발견하면 발견 정보 객체를 반환하고 없으면None
이다.- 정규표현식에 주석을 달 수 있다. -> verbose 정규표현식
pattern = '''
^ # 문자열의 시작
M{0,3} # 천의 자리 - 0-3 개의 M
(CM|CD|D?C{0,3}) # 백의 자리 - 900(CM), 400(CD), 0-300(0-3 개의 C),
# 500-800(D 하나와, 뒤이은 0 to 3 개의 C)
(XC|XL|L?X{0,3}) # 십의 자리 - 90(XC), 40(XL), 0-30(0-3 개의 X),
# 50-80(L 하나와, 뒤이은 0-3 개의 X)
(IX|IV|V?I{0,3}) # 일의 자리 - 9(IX), 4(IV), 0-3(0-3 개의 I),
# 5-8(V 하나와 뒤이은 0-3 개의 I)
$ # 문자열의 끝
'''
- verbose를 쓰면
re.search(pattern, 'M', re.VERBOSE)
처럼re.VERBOSE
를 전달해야 한다. p = re.compile(r'^(\d{3})-(\d{3})-(\d{4})$')
처럼 정의해서 쓸 수 있다.p.search('800-555-1212-1234').groups()
처럼 서치의 결과에 그룹을 쓰면 정규표현식 그룹을 얻을 수 있다. 하지만 검색결과가 없으면None
이 되므로 그룹을 붙일 때는 조심해야 한다.
- raw string은 편해 보인다.
e.VERBOSE
를 써야 하는 건 어색하지만 주석 달 수 있는건 편해 보인다.- 정규표현식 그룹을 가져오는 건 좀 불편해 보이는데 써봐야...
6. 클로저와 제너레이터
re.sub('([^aeiou])y$', r'\1ies', 'vacancy')
처럼 정규식의 그룹은\1
로 사용한다.open('plural4-rules.txt', encoding='utf-8')
파일은 이렇게 연다.with open('plural4-rules.txt', encoding='utf-8') as pattern_file:
블록에서는pattern_file
을 변수로 쓸 수 있다. 여기서with
컨텍스트가 끝나면 알아서 파일을 닫는다.line.split(None, 3)
와 같이하면None
이 아무 공백으로나 나누라는 의미다.
def make_counter(x):
print('entering make_counter')
while True:
yield x #1
print('incrementing x')
x = x + 1
- 위처럼 제너레이터를 만들 수 있다.
counter = make_counter(2)
로 만든 후next(counter)
로 사용한다. fib
를 제너레이터로 만들었을 때for n in fib(1000):
로 하면 순회하면서n
에 다음 제너레이터 값이 계속 들어온다.list(fib(1000))
와 같이하면 제너레이터가 바로 리스트가 된다.
True
로 반환하는 게 어색하다.with
를 열심히 쓰는 게 흥미롭다.- 제너레이터를 리스트로 만들거나 바로 for 문을 돌리는 건 엄청 좋아 보인다.
7. 클래스와 반복자
class Fib:
처럼 클래스를 정의한다. 보통은 파스칼로 이름을 작성한다.
class Fib:
psss
- 위처럼 빈 클래스를 정의할 수 있다.
__init__()
- 생성자와 비슷한 역할로 인스턴스가 생성될 때 호출된다.
- 생성자라고 부르면 안 된다. 왜냐하면, 이미 인스턴스가 만들어져 있기 때문이다.
- 관습적으로 클래스에서 가장 먼저 정의한다.
- 첫 인자는 생성된 인스턴스 자신을 가리키는 참조로 보통
self
로 쓴다.
- 모든 클래스 개체는
__class__
속성이 개체의 클래스를 가리킨다. 개체.__doc__
로 docstring을 가져올 수 있다.__iter__()
- 이 메서드가 정의되어 있으면 반복자이다.
iter(fib)
호출할 때마다 이 메서드가 호출된다.- for 문은 알아서
iter
를 호출한다. __iter__()
는__next__()
메서드를 가진 객체를 반환한다.
__next__()
next()
를 호출할 때 호출된다.raise StopIteration
으로 예외를 발생시키면 반복이 끝났다. 이 예외는 다른 예외와 달리 오류가 아니다.
8. 고급 반복자
assert 1 <= 10, 'Too many letters'
처럼 assert를 사용할 수 있다. 이는 다음과 같다.
if 1 <= 10:
raise AssertionError('Too many letters')
- 제너레이터 표현식(generator expression)은 함수가 없는 제너레이터 함수다. 즉 값을 yield 해주는 이름 없는 함수다.
(ord(c) for c in unique_characters)
처럼 쓰는데 리스트 컴프리헨션과 비슷하지만, 대괄호가 아니라 소괄호를 사용했다.- 이는
next()
를 호출할 때마다 다음 값이 나온다. tuple(ord(c) for c in unique_characters)
와 같이하면 한꺼번에 튜플로 바꾸어준다.- 리스트 컴프리헨션 대신 제너레이터 표현식을 사용하면 CPU, 메모리를 절약할 수 있다.
rstrip()
으로 스트링 우측의 공백을 제거한다.lstrip()
이나 양쪽을 제거하는strip()
도 있다.sorted()
로 정렬할 수 있다.sorted(names)
처럼 쓰지만sorted(names, key=len)
을 쓰면len
으로 정렬한다.- 문자열의
translate()
는 문자열을 순회하다가 키에 맞는 값이 나오면 해당 값으로 바꾼다. eval()
로 코드를 실행할 수 있다. 입력을 신뢰할 수 없다면 사용하면 안 된다.eval("x*5", {}, {})
와 같이하면 주변에 접근 못 하게 샌드박스로 막을 수 있다.
- 특정 부분만 열어주려면
eval("x*5", {"x":5}, {})
와 같이한다. - 전역을 접근할 수 있고 당연히
__import__()
도 접근할 수 있으므로 위험하다. - 전역도 막으려면
eval("__import__('math').sqrt(5)", {"__builtins__":None}, {})
와 같이한다. 물론 여기서도 DOS 공격은 받을 수 있다.
- 특정 부분만 열어주려면
9. 단위 테스트
import unittest
모듈을 사용한다.- 테스트 케이스는
class KnownValues(unittest.TestCase):
처럼unittest.TestCase
를 상속받아서 정의한다. - 테스트 케이스는 함수명이 반드시
test
로 시작해야 한다.
10. 리팩토링
- 3.1부터는 서식 지정자(format specifier)에 위치 인덱스를 숫자로 표시 하지 않아도 된다. 그래서
Invalid {}'.format(s)
처럼 쓸 수 있다. - 모듈은
import
하면 캐싱 된다. if _name_ == '_main_
블록이 없이 있는 코드는import
할 때 호출된다.
11. 파일
a_file = open('test.txt', encoding='utf-8')
로 파일을 연다.locale.getpreferredencoding()
으로 기본 인코딩을 알 수 있다.a_file
에서a_file.encoding
이나a_file.mode
로 정보를 읽을 수 있다.a_file.read()
로 파일 내용을 읽는다. 이 함수는 스트림 객체를 반환한다. 그래서 여러 번 호출해도 오류는 발생하지 않는다.a_file.read(10)
하면 문자 10개를 읽는다.a_file.seek(0)
를 실행하면 0 바이트 위치로 옮겨간다.a_file.tell()
은 현재 위치를 알려준다.seek
,tell
은 모두 바이트를 다루지만, 파일은 텍스트로 열면 문자를 다룬다.- 바이트를 다룰 때 유니코드는 멀티바이트이므로 중간 위치를 지정해서 읽으려고 하면
UnicodeDecodeError
가 발생한다. a_file.close()
로 파일을 닫는다.
with open('test.txt', encoding='utf-8') as a_file:
a_file.seek(17)
a_character = a_file.read(1)
- 위처럼 사용하면 자동으로 파일을 닫을 수 있다. 코드가 비정상적으로 종료되더라도 닫힌다.
with
은 런타임 컨텍스트를 생성하는데 이러면 스트림 객체는 콘텍스트 매니저처럼 동작해서 with에서 빠져나올 때close()
를 호출한다.
with
는 런타임 컨텍스트를 생성하고 객체에 런타임 콘텍스트에 진입하거나 빠져나온다는 사실을 알려줘야 할 때 쓰이는 범용 프레임워크다.- 스트림 객체가 iterator이므로
for a_line in a_file
처럼 순회할 수 있다. - 서식 지정자
{:>4}
는 공백 네 칸에 우측 정렬해서 출력하라는 의미이다. 그래서'{:>4}'.format(line_number)
를 하면 4칸 크기에 우측으로 숫자가 찍힌다.
with open('test.log', mode='w', encoding='utf-8') as a_file:
a_file.write('test succeeded')
- 위와 같이 파일에 내용을 작성할 수 있다.
mode='a'
이면 추가를 한다. - 바이너리 파일을 열 때는
mode='rb'
처럼b
를 추가한다. io.StringIO(a_string)
을 하면 문자열을 파일 스트림객체처럼 사용할 수 있다.- 표준 입출력
sys.stdout.write('')
,sys.stderr.write('')
로 한다.- 파일스트림이므로 파이프로 연결할 수 있다.
with open('out.log', mode='w', encoding='utf-8') as a_file, RedirectStdoutTo(a_file):
는 아래와 같다.
with open('out.log', mode='w', encoding='utf-8') as a_file:
with RedirectStdoutTo(a_file):
with
를 잘 이해하고 쓰는 게 중요하다.
12. XML
- XML을 파싱하는데 표준 라이브러리
ElementTree
를 쓸 수 있다. libxml2
위에 만든lxml
도 있다.ElementTree
와 API가 100% 호환된다.
13. 파이썬 객체 직렬화
- pickle 모듈
- 표준라이브러리에 포함되어 있다.
- C로 작성되어 빠르다.
- 파이썬의 모든 자료형을 저장할 수 있다.
- 함수, 클래스 개체도 저장할 수 있다.
import pickle
with open('entry.pickle', 'wb') as f:
pickle.dump(entry, f)
entry
라는 객체가 있을 때 위처럼 저장할 수 있다.
- pickle 프로토콜로 직렬화한다.
- 이는 파이썬 전용인데 지정하지 않으면 최신 프로토콜을 사용한다.
- 이진 바이너리 형식으로 저장한다.
with open('entry.pickle', 'rb') as f:
entry = pickle.load(f)
- 불러올 때는 위처럼 불러온다.
e
과e2
가 같은 데이터이지만 객체는 다를 때e == e2
는True
이지만e is e2
를 하면False
가 된다.b = pickle.dumps(entry)
만 사용하면 메모리상에서 직렬화를 하고pickle.loads(b)
로 불러올 수 있다. 파일에 쓰는 함수와 달리 뒤에 s가 붙어 있다.- 파이썬 3.0의 새로운 pickle 프로토콜을 사용하고 바이트 객체와 바이트 배열을 명시적으로 지원하는 이진 형식이다.
- pickletools
import pickletools
를 하면 pickle 파일의 내용을 볼 수 있다.
import json
with open('basic.json', mode='w', encoding='utf-8') as f:
json.dump(basic_entry, f)
- 위처럼 JSON으로 직렬화를 할 수 있다.
json.dump(basic_entry, f, indent=2)
처럼 하면 좀 더 보기 좋은 들여쓰기로 볼 수 있다.- 파이썬 객체를 JSON으로 매핑하지만 튜플과 바이트는 매핑되지 않는다.
def to_json(python_object):
if isinstance(python_object, bytes):
return {'__class__': 'bytes',
'__value__': list(python_object)}
raise TypeError(repr(python_object) + ' is not JSON serializable')
- 위처럼 직렬화 함수를 직접 작성해서
json.dump(entry, f, default=customserializer.to_json)
처럼 사용할 수 있다.
def from_json(json_object):
if '__class__' in json_object:
if json_object['__class__'] == 'time.asctime':
return time.strptime(json_object['__value__'])
if json_object['__class__'] == 'bytes':
return bytes(json_object['__value__'])
return json_object
- 읽어올 때도 직렬화 함수를 작성해서
json.load(f, object_hook=customserializer.from_json)
처럼 사용할 수 있다.
14. HTTP 웹서비스
- Python 3에는 두 가지 라이브러리가 있다.
http.client
는 HTTP 프로토콜인 RFC 2616을 구현한 저수준(low-level) 라이브러리다.urllib.request
는 http.client를 기반으로 만들어진 보다 추상화된 라이브러리다. HTTP와 FTP 서버에 접근하는 표준 API를 제공하고, HTTP 리다이렉션(redirect)를 자동으로 따라가며, 기본적인 HTTP 인증(authentication) 방식을 처리할 수 있다.
- 하지만 오픈소스
httplib2
를 사용하면 된다.
- python의 HTTP 라이브러리는 캐싱을 지원하지 않지만 httplib2는 지원한다.
from http.client import HTTPConnection
HTTPConnection.debuglevel = 1
- 위처럼 하면 HTTP에 대한 디버그 로그를 볼 수 있다.
import httplib2
h = httplib2.Http('.cache')
response, content = h.request('http://www.diveintopython3.net/examples/feed.xml')
print(response.status)
## 200
print(content[:52])
## b"<?xml version='1.0' encoding='utf-8'?>\r\n<feed xmlns="
print(len(content))
## 3070
- 이렇게 httplib2를 사용한다.
response.fromcache
로 캐시에서 가져왔는지를 알 수 있다.h.request(url, headers={'cache-control':'no-cache'})
와 같이하면 강제로 캐시를 사용 안 한다.httplib2.debuglevel = 1
로 디버깅 레벨을 조정한다.response.previous
로 리다이렉트된 정보를 볼 수 있다.urllib.parse.urlencode()
로 url 인코딩을 할 수 있다.
15. 사례 연구: chardet을 파이썬 3로 이식하기
- chardet이라는 디렉터리를 하나 만들고 그 디렉터리에
__init__.py
파일 하나를 생성했다. 파이썬은 디렉터리에__init__.py
라는 파일이 있으면 그 디렉터리에 있는 모든 파일이 같은 모듈 일부분이라고 인식한다. 모듈의 이름은 디렉터리 이름이 된다. 디렉터리 내의 파일은 같은 디렉터리 내, 혹은 그 하위 디렉터리 내에 있는 다른 파일을 참조할 수 있다. __init__.py
파일이 들어있는 디렉터리는 항상 여러 .py 파일로 이루어진 하나의 모듈로 취급된다.__init__.py
파일이 없으면 그 디렉터리는 그냥 일반적인 디렉터리이고, 그 안에 든.py
파일은 서로 독립적으로 존재한다.from . import universaldetector
-> 현 위치에서universaldetector
를 임포트
- 파이썬 2에서는 그냥
import constants
라고 쓰면 chardet/ 디렉터리를 먼저 검색. - 파이썬 3에서는 모든 import 문은 기본적으로 절대(absolute) import다. 만약 파이썬 3에서 상대 import를 사용하고 싶다면 위처럼 명시적으로 표현을 해주어야 한다.
- 파이썬 2에서는 그냥
- python2 에서는
file()
가open()
의 별칭이지만 python 3에서는open()
만 있다.
16. 파이썬 라이브러리 패키징하기
- 파이썬 패키지 색인(Python Package Index, "PyPI")
- Distutil은 setup.py로 설정한다.
- 모듈은 폴더로 만들고
__init__.py
를 안에 둔다. setup.py
의 첫 줄은from distutils.core import setup
로 시작한다.
- pyhton 스크립트이지만 보통은 최소한의 일만 해야 한다.
- 보통은
setup()
을 실행하는 것으로 끝난다.
from distutils.core import setup
setup(
name = 'chardet',
packages = ['chardet'],
version = '1.0.2',
description = 'Universal encoding detector',
author='Mark Pilgrim',
# ...
)
- 여기서
packages
는chardet/
안에 있는__init__.py
와 모든.py
를 포함하라는 의미이다. classifier
를 통해서 PyPI에서 검색을 할 수 있다. 선택사항이지만 하는 게 좋다. 다음과 같이 지정한다.
classifiers = [
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Development Status :: 4 - Beta",
"Environment :: Other Environment",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
"Operating System :: OS Independent",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Text Processing :: Linguistic",
],
- Distutil은
README.txt
,setup.py
,packages
로 지정한 모듈의.py
파일,py_modules
에 지정한.py
파일들을 기본으로 포함한다. - 다른 파일을 포함하고 싶다면
MANIFEST.in
을 사용해야 한다. 프로젝트 루트에 둔다.
include COPYING.txt #1
recursive-include docs *.html *.css *.png *.gif
python setup.py check
로 검사할 수 있다.python setup.py sdist
로 소스 배포판을 만들 수 있다.python setup.py bdist_wininst
로 그래픽 설치 파일을 만들 수 있다.python setup.py register sdist bdist_wininst upload
로 PyPI에 등록한다.- 설치 과정에 초점을 맞춘 프레임워크: Setuptools, Pip, Distribute
- 테스트/개발에 초점을 맞춘 프레임워크: virtualenv, zc.buildout, Paver, Fabric, py2exe
- Distutil을 포함한 배포 도구는 따로 최신자료라 함께 검색해봐야 할 것 같다. pip이랑은 어떻게 다른지를 여기선 잘 모르겠다.
파이썬 입문자입니다! 정리 잘해주셔서 너무좋네요 감사합니다 :D