Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.

[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

Docker for Mac에서 No space left on device 오류

Chrome의 headless_shell을 Docker로 컴파일하다 보니 컴파일하는 도중 No space left on device라는 오류가 발생하면서 이미지 빌드가 멈춰버렸다. 맥북의 디스크 용량은 충분히 남아있어서 오류의 원인을 알기가 어려웠다. docker volume ls -qf dangling=true 같은 명령어로 Volume도 확인해 봤는데 남아있는 볼륨도 존재하지 않았다.

검색을 하다가 Docker Community Forums에서 비슷한 글을 발견했다.

정리하자면 이는 Docker의 이슈라기보다는 Docker for Mac의 이슈다. 내부 구조를 자세히 알진 못하지만, Docker for Mac에서 사용할 디스크로 sparse image를 만들어서 사용하고 있다. sparse image는 macOS의 디스크 이미지 파일로 별도의 공간을 만들어서 여기에 용량을 할당해서 사용할 수 있다. 원래 sparse image는 용량을 동적으로 늘려서 사용할 수 있지만 윗글에 따르면 현재 Docker for mac은 여기에 64GB를 할당하고 이를 동적으로 늘리거나 하진 않고 있다.

이 이미지 파일은 ~/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/Docker.qcow2에 있다. 내가 no space left on device 오류를 보았을 때 이 용량을 확인해 보니 이미 60G였다.

$ ls -lah ~/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/Docker.qcow2
-rw-r--r--  1 outsider  staff    60G  5 31 03:58 /Users/outsider/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/Docker.qcow2

이 파일을 지우고 Docker 데몬을 재시작하면 알아서 다시 만들기 때문에 필요한 경우 이 파일을 제거해도 된다. 대신 로컬에 이미 받아놓은 Docker 이미지나 띄워놓은 Docker 컨테이너는 모두 제거된다.

2017/06/10 15:21 2017/06/10 15:21