Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

[Book] 파이썬 3에 뛰어들기

작년에 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를 쓸 수 있다. True1, False0이 된다.

    • if 문 등에서는 0은 False이고 그 외의 숫자는 모두 True이다.
  • 타입 검사는 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**211^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)
  • 불러올 때는 위처럼 불러온다.
  • ee2가 같은 데이터이지만 객체는 다를 때 e == e2True이지만 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를 사용하고 싶다면 위처럼 명시적으로 표현을 해주어야 한다.
  • 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',
    # ...
)
  • 여기서 packageschardet/안에 있는 __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이랑은 어떻게 다른지를 여기선 잘 모르겠다.


2017/06/12 04:22 2017/06/12 04:22