etc/introducing python: 처음 시작하는 파이썬

5장: 파이 포장하기- 모듈, 패키지, 프로그램

seul chan 2017. 1. 18. 14:56

5.1 스탠드얼론 프로그램

대화식 인터프리터가 아니라 standalone 프로그램을 만들어보자. 컴퓨터에 test1.py 파일을 생성하고 다음 파이썬 코드를 입력한다.

> print("This standalone program works!"

5.2 커맨드라인 인자

컴퓨터에 test2.py 파일을 생성하고 다음 두 줄의 코드를 입력한다.

> import sys
> print('Program arguments:', sys.argv)

5.3 모듈과 import 문

여러개의 파일에 파이썬 코드를 작성하여 레벨업을 해보자. (레벨업?) 모듈은 단지 파이썬 코드의 파일이다. 책에 단어, 문장, 단락, 장 등의 hierarchy가 있듯이 코드 또한 이와 비슷한 상향식 구조이다. 단어는 data type, 문장은 statement, 단락은 function, 장은 module에 비유할 수 있다.

import를 통해 다른 모듈의 코드를 참조할 수 있다.

5.3.1 모듈 임포트하기

import를 사용하여 간단하게 모튤을 임포트할 수 있다. 모듈은 .py 확장자가 없는 파이썬 파일의 이름이다. 다음 예시에서 기상 관측소, 날씨 리포트를 시뮬레이션 해 보자.

메인 프로그렘은 리포트를 출력한다.
함수가 있는 다른 모듈에서는 날씨 정보를 반환한다

다음 weatherman.py는 메인 프로그램이다

import report
description = report.get_description()
print("Today's weather:", description)

다음은 모듈이다. (report.py)

def get_description():
"""return random weather, just like the pros"""
from random import choice
possibilities = ['rain', 'snow', 'sleet', 'fog', 'sun', 'who knows']
return choice(possibilities)

두 파일을 같은 디렉토리에 저장하고, 메인 프로그램의 weatherman.py를 실행하면 메인 프로그램은 report.py의 모듈에 접근해서 get_description() 함수를 실행한다. 이 함수는 possibilities 문자열 리스트로부터 임의의 결과를 반환받고, 결과를 실행한다.

위의 예제에서는 import 문을 두 번 사용했다.

  • 메인 프로그램인 weatherman.py에서 report 모듈을 임포트했다.

      import report 
      report.get_description() # report의 get_description실행
  • 모듈 파일인 report.py에서 표준 random 모튤로부터 choice 함수를 임포트했다.

      from random import choice 
      choice(possibilities)

5.3.2 다른 이름으로 모듈 임포트 하기

weatherman.py 프로그램에서 import report로 report 모듈을 호출했다. 그런데 만약 같은 이름을 사용하는 다른 모듈이 있다면? 혹은 모듈을 짧게 쓰고 싶다면? 이런 경우에는 엘리어스(alias)를 사용한다.

    import report as wr
    description = wr.get_description()
    print("Today's weather:", description)

5.3.3 필요한 모듈만 임포트하기

모듈의 전체 외에 필요한 부분만 임포트 할 수 있다. 모듈 각 부분에서 원래 이름이나 앨리어스를 사용할 수 있다.

    from report import get_description as do_it
    description = do_it()

5.3.4 모듈 검색 경로

파이썬은 임포트할 파일을 어디서 찾을까? 디렉토리 이름의 리스트와 표준 sys 모듈에 저장되어 있는 ZIP 아카이브 파일을 변수 path로 사용한다. 이 리스트를 접근하여 수정할 수 있다.

In [1]:
# 5.3.4 검색 경로
import sys
for place in sys.path:
    print(place)
/home/seul/.pyenv/versions/3.5.2/lib/python35.zip
/home/seul/.pyenv/versions/3.5.2/lib/python3.5
/home/seul/.pyenv/versions/3.5.2/lib/python3.5/plat-linux
/home/seul/.pyenv/versions/3.5.2/lib/python3.5/lib-dynload
/home/seul/.pyenv/versions/3.5.2/envs/study/lib/python3.5/site-packages
/home/seul/.pyenv/versions/3.5.2/envs/study/lib/python3.5/site-packages/IPython/extensions
/home/seul/.ipython

위의 결과를 보면, 첫 번째 라인의 공백은 현재 디렉토리를 뜻하는 빈 문자열 ('')이다. 이와 같이 sys.path의 첫 라인에 빈 문자열이 있으면 파이썬은 현재 디렉토리에서 임포트할 파일을 먼저 찾는다. 즉, 위에서 아래 순서대로 모듈을 검색한다.

5.4 패키지

한 라인의 코드, 여러개의 함수, 스탠드얼론 프로그램, 여러 모듈을 넘어 파이썬 애플리케이션을 더 확장 가능하게 만들기 위해 모듈을 패키지라는 계층구조에 구성할 수 있다.

한 번은 다음날, 한번은 다음 주와 같이 다른 타입의 날씨 정보가 필요하다고 하자. 코드를 구성하는 한 방법은 sources 디렉토리를 만든 뒤 daily.py, weekly.py 두가지 모듈을 생성하는 것이다. 각 모듈에 forecast 함수를 주어 daily버전은 한 문자열을, weekly는 일곱개의 문자열을 반환하게 하면 된다.

메인 프로그램을 만든다. (boxes/weather.py)

    from sources import daily, weekly
    print("Daily forecast:", daily.forecast())
    print("Weekly forecast:", weekly.forecast())
    for number, outlook in enumerate(weekly.forecast(), 1):
        print(number, outlook)


첫번째 모듈이다. (boxes/sources/daily.py)

    def forecast():
        'fake daily forecast'
        return 'like yesterday'

두번째 모듈이다. (boxes/sources/weekly.py)

def forecast():
    'Fake weekly forecast'
    return ['snow', 'more snow', 'sleet', 'freezing rain', 'rain', 'fog', 'hail']

sources 디렉토리에 init.py 파일이 필요하다. 이 파일은 내용이 없어도 되지만, 파이썬은 이 파일을 포함하는 디렉토리를 패키지로 간주한다.

이제 메인 프로그램의 weather.py를 실행하여 결과를 살펴보자

5.5 파이썬 표준 라이브러리

파이썬은 batteries included라는 철학으로(ㅋㅋ) 유용한 작업을 처리하는 많은 표준 라이브러리 모듈이 있다. 또한 튜토리얼 과 함께 각 모듈에 대한 공식 문서들을 제공한다.

다음은 웹, 시스템, 데이터베이스 등에 대한 표준 모듈을 소개하겠다.

5.5.1 누락된 키 처리하기: setdefault(), defaultdict()

존재하지 않는 키로 딕셔너리에 접근하면 예외가 발생한다. 기본 값을 반환하는 딕셔너리의 get() 함수를 사용하면 예외를 피라 수 있는데, setdefault() 함수는 이와 같지만 키가 누락된 경우에 딕셔너리에 항목을 할당할 수 있다.

In [2]:
# 5.5.1 예제
periodic_table = {
    'Hydrogen':1,
    'Helium':2,
}
print(periodic_table)
{'Helium': 2, 'Hydrogen': 1}
In [4]:
#딕셔너리에 키가 없는 경우에 새 값이 사용된다.
carbon = periodic_table.setdefault('Carbon', 12)
carbon
print(periodic_table)
# 존재하는 키에 다른 기본값을 할당하려고 하면 아무것도 바뀌지 않는다.
{'Carbon': 12, 'Helium': 2, 'Hydrogen': 1}
In [5]:
# defualtdict() 함수예제
from collections import defaultdict
periodic_table = defaultdict(int)
# 이제 모든 누락된 기본값은 0이된다
In [7]:
periodic_table['Hydrogen'] = 1
periodic_table['Lead']
periodic_table
Out[7]:
defaultdict(int, {'Hydrogen': 1, 'Lead': 0})
In [9]:
# defaultdict()의 인자는 값을 누락된 키에 할당하여 반환하는 함수이다. 
# 다음 예제에서 no_idea() 함수는 필요할 때 값을 반환하기 위해 실행된다
def no_idea():
    return 'Huh?'
besitary = defaultdict(no_idea)
besitary['A'] = 'Abominable Snowman'
besitary['B'] = 'Basilisk'
print(besitary['A'])
print(besitary['B'])
print(besitary['C']) # 값을 지정하지 않아 no_idea 함수가 실행되고, Huh?가 나온 것을 볼 수 있따. 
Abominable Snowman
Basilisk
Huh?

defaultdict()에서 빈 기본값을 반환하기 위해서 int()함수는 0, list()함수는 [], dict()함수는 빈 딕셔너리{}를 반환한다. 이를 입력하지 않으면 초기값이 None으로 설정된다.

또한 lambda를 사용하여 인자에 기본값을 만드는 함수를 바로 정의할 수 있고

        besitary = defaultdict(lambda:'Huh?')

카운터를 만들기 위해 아래와 같이 int 함수를 사용할 수 있다.

In [10]:
besitary = defaultdict(lambda: "WHAT?")
besitary['E']
Out[10]:
'WHAT?'
In [13]:
# 카운터 만들기
from collections import defaultdict
food_counter = defaultdict(int)
for food in ['spam', 'spam', 'egg', 'spam']:
    food_counter[food] += 1
# for food, count in food_counter.items()
for food, count in food_counter.items():
    print(food, count)
egg 1
spam 3
In [18]:
# 위의 예제를 defaultdict()가 아니라 일반 딕셔너리였다면 예외가 발생한다. 
dict_counter = {}
for food in ['spam', 'spam', 'egg', 'spam']:
    if not food in dict_counter:
        dict_counter[food] = 0
    dict_counter[food] +=1
dict_counter
Out[18]:
{'egg': 1, 'spam': 3}

5.5.2 항목 세기: Counter()

표준 라이브러리에는 항목을 셀 수 있는 함수가 여러개 있다.

In [20]:
from collections import Counter
breakfast =['spam', 'spam', 'egg', 'spam']
breakfast_counter = Counter(breakfast)
print(breakfast_counter)
type(breakfast_counter) # collections.counter 타입을 갖는다.
Counter({'spam': 3, 'egg': 1})
Out[20]:
collections.Counter
In [29]:
# most_common() 함수는 모든 요소를 내림차순으로 반환한다. 
breakfast_counter.most_common()
Out[29]:
[('spam', 3), ('egg', 1)]
In [31]:
#또한 카운터를 결합할 수 있다. lunch 카운터를 만들어서 + 연산자를 통해 결합 가능
lunch = ['egg', 'egg', 'bacon']
lunch_counter = Counter(lunch)
print(lunch_counter)
lunch_counter + breakfast_counter
Counter({'egg': 2, 'bacon': 1})
Out[31]:
Counter({'bacon': 1, 'egg': 3, 'spam': 3})
In [32]:
# + 외에도 -를 통해 다른 카운터를 뺼 수 있다. 
print(lunch_counter - breakfast_counter)
print(breakfast_counter - lunch_counter)
Counter({'egg': 1, 'bacon': 1})
Counter({'spam': 3})
In [33]:
# set과 마찬가지로 & 연산자를 사용하여 공통된 항목을 얻울수 있다.
print(lunch_counter & breakfast_counter)
Counter({'egg': 1})

5.5.3 키 정렬하기: OrderedDict()

딕셔너리의 키 순서는 예측할 수 없다. OrderedDict() 함수는 키의 추가 순서를 기억하고 이터레이터로부터 순서대로 키 값을 반환한다.

In [34]:
from collections import OrderedDict
quotes = OrderedDict([
        ('Moe', 'A wise guy, huh?'),
        ('Larry', 'Ow!'),
        ('Curly', 'Nyuk nyuk'),
    ])
for stooge in quotes:
    print(stooge)
Moe
Larry
Curly

5.5.4 스택 + 큐 == 데크

deque(데크)는 스택과 큐의 기능을 모두 가진 출입구가 양 끝에 있는 큐다. 데크는 시퀀스의 양 끝으로부터 항목을 추가하거나 삭제할 때 유용하게 쓰인다. 여기에서 앞, 뒤로 읽으나 똑같은 단어인지 확인하기 위해 양쪽 끝에서 중간까지 문자를 확인할 것이다.

popleft() 함수는 데크로부터 왼쪽 끝의 항목을 제거한 후 그 항목을 반환한다.

pop() 함수는 오른쪽 끝의 항목을 제거한 후 그 항목을 반환한다.

양쪽 문자가 일치한다면 단어 중간에 도달할 때 까지 데크를 팝한다.

In [36]:
# deque 예제
def palindrome(word):
    from collections import deque
    dq = deque(word)
    while len(dq) > 1:
        if dq.popleft() != dq.pop():
            return False
    return True
In [37]:
palindrome('a')
Out[37]:
True
In [38]:
palindrome('토마토')
Out[38]:
True
In [39]:
palindrome('aaba')
Out[39]:
False
In [40]:
# palindrome을 간단하게 하기 위해서는 slice로 문자열을 반전할 수 있다.
def another_palindrom(word):
    return word == word[::-1]
In [41]:
another_palindrom("토마토마토")
Out[41]:
True

5.5.5 코드 구조 순회하기: itertools

itertools는 특수 목적의 이터레이터 함수를 포함하고 있다. for문에서 이터레이터 함수를 호출할 때 함수는 한번에 한 항목을 반환하고, 호출 상태를 기억한다

In [43]:
# chain() 함수는 순회 가능한 인자들을 하나씩 반환한다. 
# 아래 예는 리스트 안의 인자들을 모두 반환하는 것을 볼 수 있다.
import itertools
for item in itertools.chain([1,2], ['a','b']):
    print(item)
1
2
a
b
In [44]:
# 그냥 반환하면 리스트가 두번 반환된다. 
for item in ([1,2], ['a','b']):
    print(item)
[1, 2]
['a', 'b']
In [45]:
# cycle()함수는 인자를 순환하는 무한 이터레이터이다. (쓸일이 있을까?)
for item in itertools.cycle([1,2]):
    print(item)
    break
1
In [46]:
# accumulate() 함수는 축적된 값을 계산한다. (기본적으로 합계)
for item in itertools.accumulate([1,2,3,4]):
    print(item)
1
3
6
10
In [48]:
# accumulate() 함수의 두 번째 인자로 다른 함수를 전달하여
# 합계 대신 이 함수를 사용할 수 있다.
def multiply(a, b):
    return a*b
for item in itertools.accumulate([1,2,3,4], multiply):
    print(item)
1
2
6
24

5.5.6 깔끔하게 출력하기: pprint()

멋진프린터?

In [49]:
from pprint import pprint
quotes = OrderedDict([
        ('Moe', 'A wise guy, huh?'),
        ('Larry', 'Ow!'),
        ('Curly', 'Nyuk nyuk'),
    ])
print(quotes)
OrderedDict([('Moe', 'A wise guy, huh?'), ('Larry', 'Ow!'), ('Curly', 'Nyuk nyuk')])
In [50]:
pprint(quotes)
OrderedDict([('Moe', 'A wise guy, huh?'),
             ('Larry', 'Ow!'),
             ('Curly', 'Nyuk nyuk')])

5.6 배터리 장착: 다른 파이썬 코드 가져오기

5.7 연습문제

In [2]:
# 5.1 zoo.py 파일에서 Open 9-5 daily 문자열을 반환한느 hours() 함수를 정의
# zoo 모듈을 임포트한 후 hours() 함수를 호출하라
from zoo import hours
hours()
Open 9-5 daily
In [4]:
# 5.2 zoo 모듈을 menagerie라는 이름으로 import 한 후 함수 호출
import zoo as menagerie
menagerie.hours()
Open 9-5 daily
In [5]:
# 5.3 zoo 모듈로부터 직접 hours() 임포트
zoo.hours()
Open 9-5 daily
In [6]:
# 5.4 hours() 함수를 info라는 이름으로 임포트해서 호출하라
from zoo import hours as info
info()
Open 9-5 daily
In [8]:
# 5.5 a:1, b:2, c;3인 plain 딕셔너리를 출렦하라
plain = {
    'a': 1,
    'b': 2,
    'c': 3,
}
print(plain)
{'b': 2, 'a': 1, 'c': 3}
In [14]:
# 5.6 plain을 통해 fancy라는 OrderedDict를 만들어 출력하라. 순서가 같읁지?
from collections import OrderedDict
fancy = OrderedDict([
    ('a', 1),
    ('b', 2),
    ('c', 3),
])
fancy
Out[14]:
OrderedDict([('a', 1), ('b', 2), ('c', 3)])
In [17]:
# 5.7 dict_of_lists라는 defaultdict을 만들어 list 인자를 전달
# dict_of_lists['a']에 something for a 값을 추가하라
from collections import defaultdict
dict_of_lists = defaultdict()
dict_of_lists['a'] = 'something for a'
dict_of_lists
Out[17]:
defaultdict(None, {'a': 'something for a'})
In [ ]: