Introducing Python(처음 시작하는 파이썬)
Chapter 5. 파이 포장하기: 모듈, 패키지, 프로그램
5.1 Standalone Programs (스탠드얼론 프로그램)
- 본서에서는 Python Interactive Interpreter에서 작성한 코드가 아닌, 파이썬 확장자(.py)를 가진 독립적인 파일(프로그램)을 스탠드얼론 프로그램이라 칭한다.
5.2 Command-Line Arguments (커맨드 라인 인자)
# test.py
import sys
print('Program arguments:', sys.argv)
# Windows Standard Shell Program (cmd창)
C:\Users\이병헌>python test2.py
Program arguments: ['test2.py']
C:\Users\이병헌>python test2.py tra la la
Program arguments: ['test2.py', 'tra', 'la', 'la']
# 마치, C/C++에서의
# int main(int argc, char *argv )를 통해 터미널에서 Inputs을 전달받는 것과 비슷하다.
5.3 Modules and the import Statement (모듈과 import 문)
- Module: 파이썬 코드 파일이다. (.py확장자를 가진 파일)
5.3.1 Import a Module (모듈 임포트하기)
* import 문
import module_name
# module_name 모듈을 임포트한다.
# .py 확장자는 생략한다.
- 다른 모듈을 본 코드에 참조시키는 Syntax이다.
- 임포트한 모듈의 코드와 변수를 해당 프로그램에서 사용할 수 있게 만들어준다.
- 해당 모듈을 특정 블럭(함수)에서만 사용한다면, import문을 블럭 내부에 위치시키는 것이 바람직하다.
(자체적인 Scope 제한을 통한 효율성 제고)
※ import한 객체를 사용할 때의 규칙
- Naming Conflict(이름 충돌)를 방지하기 위해, 임포트한 객체를 사용할 때에는 해당 모듈의 이름을 명시한다.
- from ~ import 문으로 특정 함수만을 임포트한 경우, 모듈 이름을 따로 명시할 필요는 없다.
# 모듈을 임포트한 메인 프로그램에서는,
# 해당 모듈의 객체를 사용할 때, 객체 이름 앞에 해당 모듈의 이름을 붙여야 한다.
import module_name
module_name.object # module_name의 객체인 object를 사용하는 경우
module_name.function() # module_name의 함수인 function()을 사용하는 경우
* 모듈을 같은 디렉터리에 위치시키면, 메인 프로그램에서 해당 모듈을 임포트할 수 있다.
# weather.py (메인 프로그램)
import report
description = report.get_description()
print("Today's weather:", description)
# report.py
def get_description():
"""Return random weather, just like the pros""" # docstring
from random import choice # random 모듈의 choice 함수만 임포트
possibilities = ['rain', 'snow', 'sleet', 'fog', 'sun', 'who knows']
return choice(possibilities)
# import random문을 사용했다면, random.choice() 형태로 명시해야 한다.
# 두 파일은 같은 디렉터리에 위치해야 한다.
5.3.2 Import a Module with Another Name (다른 이름으로 모듈 임포트하기)
* alias 문
import module_name as abbreviation_name
# as는 alias를 의미한다.
# module_name 모듈을 현재 Scope에서 abbreviation_name(약칭)으로 표현한다.
- 같은 이름을 사용하는 모듈들을 구분짓거나, 모듈의 이름을 단축해서 사용하고자 할 때 사용하는 Syntax이다.
- 즉, 해당 Scope에서 모듈의 이름을 임시적으로 변경해주는 역할을 한다.
(C/C++의 typedef와 비슷한 개념이다.)
* report 모듈을 wr로 줄여서 사용한다.
import report as wr
description = wr.get_description()
print("Today's weather:", description)
5.3.3 Import Only What You Want from a Module (필요한 모듈만 임포트하기)
* from ~ import ~ 문
from module_name import object_name
# module_name 모듈의 object_name 객체를 임포트한다.
from module_name import OBJ_1, OBJ_2
# 해당 모듈에 있는 여러 객체를 한꺼번에 임포트할 수도 있다.
- 해당 모듈의 특정 객체(변수, 함수)만을 임포트하는 Syntax이다.
* from ~ import ~ 문에도 Alias를 적용할 수 있다.
from report import get_description as do_it
description = do_it()
print("Today's weather:", description)
5.3.4 Module Search Path (모듈 검색 경로)
* 파이썬 표준 모듈: sys
>>> import sys
>>> for place in sys.path:
... print(place)
...
# 현재 디렉터리 -> 공백
C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.8_3.8.1520.0_x64__qbz5n2kfra8p0\python38.zip
C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.8_3.8.1520.0_x64__qbz5n2kfra8p0\DLLs
C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.8_3.8.1520.0_x64__qbz5n2kfra8p0\lib
C:\Users\이병헌\AppData\Local\Microsoft\WindowsApps\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0
C:\Users\이병헌\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\LocalCache\local-packages\Python38\site-packages
C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.8_3.8.1520.0_x64__qbz5n2kfra8p0
C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.8_3.8.1520.0_x64__qbz5n2kfra8p0\lib\site-packages
- sys 모듈의 path 변수에는 디렉터리 이름의 리스트와 ZIP Archive 파일에 대한 정보가 저장되어 있다.
- 현재 디렉터리는 공백('')으로 표현된다.
- 공백('')이 sys.path의 첫 줄에 있는 경우에 파이썬 프로그램은 임포트할 파일을 현재 디렉터리부터 찾아나간다.
※ 이름이 중복되는 모듈들의 경우, 검색 경로가 더 가까운 모듈이 임포트된다.
- 표준 라이브러리 모듈의 이름과 사용자 정의 모듈의 이름이 중복되는 경우, 사용자 정의 모듈에 우선적으로 접근한다.
(같은 디렉터리에 있는 사용자 모듈이 표준 라이브러리 모듈보다 먼저 검색되기 때문이다.)
5.4 Packages (패키지)
- Package: 다수의 파이썬 모듈들이 저장되는 파일이다.
- 보통 파이썬 프로그램은, __init__.py 파일이 포함된 디렉터리를 패키지로 간주한다.
(__init__.py 파일의 내용은 비어있어도 상관없다.)
5.5 The Python Standard Library (파이썬 표준 라이브러리)
- 파이썬 철학 중, "Batteries Included"에 부합하여, 파이썬에는 많은 표준 라이브러리 모듈들이 존재한다.
- 파이썬 프로그램을 작성할 때는 원하는 기능을 수행하는 표준 모듈이 있는지부터 확인하는 것이 바람직하다.
* 파이썬 표준 라이브러리 (URL)
* 더그 헬먼의 PyMOTW (URL)
5.5.1 Handle Missing Keys with setdefault() and defaultdict() (누락된 키 처리하기: setdefault(). defaultdict())
※ 존재하지 않는 Key를 통해 딕셔너리에 접근할 경우 예외가 발생된다.
* get() 함수
- 키가 존재하지 않으면, 딕셔너리가 기본값을 리턴하게 한다.
* setdefault(Key, Value) 함수
- 키가 존재하지 않으면, 딕셔너리에 해당 키에 대한 항목을 할당할 수 있게 한다.
- 존재하는 키에 대해 setdefault() 함수가 다른 값을 할당하려 하면, 해당 키에 대한 원래 값이 리턴되고,
아무것도 바뀌지 않는다.
>>> periodic_table = {'Hydrogen': 1, 'Helium': 2}
>>> print(periodic_table)
{'Hydrogen': 1, 'Helium': 2}
>>> carbon = periodic_table.setdefault('Carbon', 12) # 키 'Carbon'은 딕셔너리에 없던 키다.
>>> carbon
12
>>> periodic_table
{'Hydrogen': 1, 'Helium': 2, 'Carbon': 12}
>>> Helium = periodic_table.setdefault('Helium', 3) # 키 'Helium'은 기존에 존재하던 키다.
>>> Helium
2 # 변하지 않았다.
>>> periodic_table
{'Hydrogen': 1, 'Helium': 2, 'Carbon': 12} # 변하지 않았다.
* defaultdict(Function) 함수
- 딕셔너리의 새로운 Key에 대한 기본값(Value)를 설정할 때 쓰이는 함수이다.
- collections 모듈에 정의되어 있다.
- defaultdict() 함수의 Argument는 해당 키에 값을 리턴하는 함수이다.
- 기본값을 따로 설정하지 않으면 새로운 키에 대한 초깃값은 None이다.
>>> from collections import defaultdict
>>>
>>> def no_idea():
... return 'Huh?' # 누락된 키에 대한 기본값은 'Huh?'이다.
...
>>> bestiary = defaultdict(no_idea)
>>> bestiary['A'] = 'Abominable Snowman'
>>> bestiary['B'] = 'Basilisk'
>>> bestiary['A']
'Abominable Snowman'
>>> bestiary['B']
'Basilisk'
>>> bestiary['C']
'Huh?'
* Lambda를 이용하여 기본값을 리턴하는 함수를 매개변수로 넣어 코드를 더욱 간결하게 할 수 있다.
>>> bestiary = defaultdict(lambda: 'Huh?') # lambda: 'Huh?' = 'Huh?'를 리턴하는 익명 함수
>>> bestiary['E']
'Huh?'
* defaultdict() 함수와 int() 함수를 이용한 카운터 구현
>>> from collections import defaultdict
>>> food_counter = defaultdict(int)
>>> for food in ['spam', 'spam', 'eggs', 'spam']:
... food_counter[food] += 1
... # 딕셔너리가 초기화되지 않았기 때문에, defaultdict(int)구문이 없었다면,
# 이 부분에서 예외가 발생했을 것이다.
>>> for food, count in food_counter.items():
... print(food, count)
...
eggs 1
spam 3
* defaultdict() 함수를 사용하지 않고 구현한 카운터
>>> dict_counter = {}
>>> for food in ['spam', 'spam', 'eggs', 'spam']:
... if not food in dict_counter:
... dict_counter[food] = 0
... dict_counter[food] += 1
...
>>> for food, count in dict_counter.items():
... print(food, count)
...
spam 3
eggs 1
5.5.2 Count Items with Counter() (항목 세기: Counter())
- 항목을 세어주는 함수이다.
- collections 모듈에 정의된 함수이다.
* Counter() 함수를 통한 리스트의 항목 카운팅
>>> from collections import Counter
>>> breakfast = ['spam', 'spam', 'eggs', 'spam']
>>> breakfast_counter = Counter(breakfast)
>>> breakfast_counter
Counter({'spam': 3, 'eggs': 1})
* most_common([at_least]) 함수
>>> breakfast_counter.most_common()
[('spam', 3), ('eggs', 1)]
>>> breakfast_counter.most_common(1)
[('spam', 3)]
- 카운팅 결과를 내림차순으로 리턴한다.
- 매개변수 at_least에 숫자값을 부여하면, 해당 값 이상의 결과만 리턴한다. (선택사항이다.)
* + 연산자를 통한 카운터 결합
>>> breakfast_counter
>>> Counter({'spam': 3, 'eggs': 1})
>>> lunch = ['eggs', 'eggs', 'bacon']
>>> lunch_counter = Counter(lunch)
>>> lunch_counter
Counter({'eggs': 2, 'bacon': 1})
>>> breakfast_counter + lunch_counter
Counter({'spam': 3, 'eggs': 3, 'bacon': 1})
* - 연산자를 통한 카운터 차감
>>> breakfast_counter - lunch_counter
Counter({'spam': 3})
# 반대의 경우
>>> lunch_counter - breakfast_counter
Counter({'bacon': 1, 'eggs': 1})
* & 연산자를 통한 카운터 간 Intersection 구하기
>>> breakfast_counter & lunch_counter
Counter({'eggs': 1})
# 교집합에선, 더 적은 개수가 우선시된다.
* | 연산자를 통한 카운터 간 Union 구하기
>>> breakfast_counter | lunch_counter
Counter({'spam': 3, 'eggs': 2, 'bacon': 1})
# 합집합에선, 더 많은 개수가 우선시된다.
5.5.3 Order by Key with OrderedDict() (키 정렬하기: OrderedDict())
- 딕셔너리의 키 순서는 예측할 수 없다.
(입력되는 순서와 저장되는 순서 간 관계가 없다.)
* OrderedDict() 함수
- 딕셔너리에 키가 추가된 순서를 기억하고, Iterator로부터 순서대로 키 값을 리턴하게 한다.
- collections 모듈에 정의되어 있는 함수이다.
>>> 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 Stack + Queue == deque (스택 + 큐 == 데크)
* deque
- 스택과 큐의 기능을 모두 가졌으며, 출입구가 양 끝에 있는 큐다.
- 데이터를 양 끝 중 한 곳에 추가/삭제하기 용이한 구조이다.
* depue.pop() 메서드
- 데크의 오른쪽 끝에 있는 항목을 리턴한 후 제거하는 멤버 함수이다.
* deque.popleft() 메서드
- 데크의 왼쪽 끝에 있는 항목을 리턴한 후 제거하는 멤버 함수이다.
* 데크를 통해 구현된 회문 여부를 검사하는 프로그램
- 회문은 알파벳이 대칭 구조인 단어/문장을 의미한다. (level, refer 등)
>>> def palindrome(word):
... from collections import deque
... dq = deque(word)
... while len(dq) > 1:
... if dq.popleft() != dq.pop():
... return False
... return True
...
...
>>> palindrome('a')
True
>>> palindrome('racecar')
True
>>> palindrome('')
True
>>> palindrome('radar')
True
>>> palindrome('halibut')
False
* Slicing을 이용한 회문 여부를 검사하는 프로그램
>>> def another_palindrome(word):
... return word == word[::-1]
...
>>> another_palindrome('radar')
True
>>> another_palindrome('halibut')
False
5.5.5 Iterate over Code Structures with itertools (코드 구조 순회하기: itertools)
- itertools 모듈은 특수 목적용 Iterator 함수가 정의되어 있는 모듈이다.
* chain() 함수
>>> import itertools
>>> for item in itertools.chain([1, 2], ['a', 'b']):
... print(item)
...
1
2
a
b
- 순회 가능한 인자들을 하나씩 리턴하는 함수이다.
- itertools 모듈에 정의되어 있는 함수이다.
* cycle() 함수
>>> import itertools
>>> for item in itertools.cycle([1, 2]):
... print(item)
...
1
2
1
2
.
.
.
…and so on.
- 인자를 순환하는 무한 Iterator이다.
- itertools 모듈에 정의되어 있는 함수이다.
* accumulate() 함수
>>> import itertools
>>> for item in itertools.accumulate([1, 2, 3, 4]):
... print(item)
...
1
3
6
10
# 두 번째 인자 활용방법
>>> import itertools
>>> def multiply(a, b):
... return a * b
...
>>> for item in itertools.accumulate([1, 2, 3, 4], multiply):
... print(item)
...
1
2
6
24
- 축적된 값을 계산하는 함수이다.
- 기본적으로 합계를 계산한다.
- 두 번째 인자로 함수를 받아들여, 해당 함수의 연산 결과를 리턴시킬 수 있다.
- itertools 모듈에 정의되어 있는 함수이다.
* combination() 함수, permutation() 함수
- 각각, 조합과 순열을 계산하는 함수이다.
- 모두 itertools 모듈에 정의되어 있는 함수이다.
5.5.6 Print Nicely with pprint() (깔끔하게 출력하기: pprint())
- pprint() 함수는 pprint 모듈에 정의되어 있다.
- 보다 가독성 높게 출력하는 함수이다.
* print() 함수와 pprint() 함수의 출력결과
>>> 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!')])
>>> pprint(quotes)
{'Moe': 'A wise guy, huh?',
'Larry': 'Ow!',
'Curly': 'Nyuk nyuk!'}
5.6 More Batteries: Get Other Python Code (배터리 장착: 다른 파이썬 코드 가져오기)
* Python Third-Party Open Source References
- PyPI (URL)
- github (URL)
- readthedocs (URL)
- activestate (URL)
5.7 Things to Do (연습문제)
5.1 zoo.py 파일에서 'Open 9-5 daily' 문자열을 반환하는 hours() 함수를 정의하라.
그러고 나서 대화식 인터프리터에서 zoo 모듈을 임포트한 후 hours() 함수를 호출하라.
Sol 5.1
# zoo.py
def hours():
return 'Open 9-5 daily'
# Python Interactive Interpreter
>>> import zoo
>>> zoo.hours()
'Open 9-5 daily'
5.2 대화식 인터프리터에서 zoo 모듈을 menagerie라는 이름으로 임포트한 후 hours() 함수를 호출하라.
Sol 5.2
>>> import zoo as menagerie
>>> menagerie.hours()
'Open 9-5 daily'
5.3 인터프리터에서 zoo 모듈로부터 직접 hours() 함수를 임포트해서 호출하라.
Sol 5.3
>>> from zoo import hours
>>> hours()
'Open 9-5 daily'
5.4 hours() 함수를 info라는 이름으로 임포트해서 호출하라.
Sol 5.4
>>> from zoo import hours as info
>>> info()
'Open 9-5 daily'
5.5. 키:값 쌍이 'a':1, 'b':2, 'c':3인 plain 딕셔너리를 만들어서 출력하라.
Sol 5.5
>>> plain = {
... 'a':1,
... 'b':2,
... 'c':3
... }
>>> from pprint import pprint
>>> pprint(plain)
{'a': 1, 'b': 2, 'c': 3}
5.6 연습문제 5.5의 plain 딕셔너리에 있는 키:값 쌍으로 fancy라는 OrderedDict를 만들어서 출력하라.
plain 딕셔너리와 출력 순서가 같은가?
Sol 5.6
>>> import collections
>>> fancy = collections.OrderedDict(plain)
>>> pprint(fancy)
OrderedDict([('a', 1), ('b', 2), ('c', 3)])
5.7 dict_of_lists라는 defaultdict를 만들어서 list 인자를 전달하라.
리스트 dict_of_lists['a']에 'something for a' 값을 append()를 통해 추가하고, dict_of_lists['a']를 출력하라.
Sol 5.7
>>> from collections import defaultdict
>>> dict_of_lists = defaultdict(list)
>>> dict_of_lists['a'].append('something for a')
>>> print(dict_of_lists['a'])
['something for a']
>>> print(dict_of_lists[1])
[] # 디폴트는 Empty List이다. (list()함수로 인해)
Reference: Introducing Python(처음 시작하는 파이썬) (Bill Lubanovic 저, O'Reilly, 2015)