소개
더 큰 프로그램을 작성하는 경우, 최상위 수준에서 독립 실행형 파일의 큰 모음으로 구성하는 것은 바람직하지 않습니다. 이 섹션에서는 패키지 (package) 의 개념을 소개합니다.
더 큰 프로그램을 작성하는 경우, 최상위 수준에서 독립 실행형 파일의 큰 모음으로 구성하는 것은 바람직하지 않습니다. 이 섹션에서는 패키지 (package) 의 개념을 소개합니다.
모든 Python 소스 파일은 모듈입니다.
## foo.py
def grok(a):
...
def spam(b):
...
import 문은 모듈을 로드하고 실행합니다.
## program.py
import foo
a = foo.grok(2)
b = foo.spam('Hello')
...
더 큰 코드 모음의 경우, 모듈을 패키지로 구성하는 것이 일반적입니다.
## From this
pcost.py
report.py
fileparse.py
## To this
porty/
__init__.py
pcost.py
report.py
fileparse.py
이름을 선택하고 최상위 디렉토리를 만듭니다. 위의 예에서 porty (분명히 이 이름을 선택하는 것이 가장 중요한 첫 번째 단계입니다).
__init__.py 파일을 디렉토리에 추가합니다. 비어 있을 수 있습니다.
소스 파일을 디렉토리에 넣습니다.
패키지는 import 를 위한 네임스페이스 (namespace) 역할을 합니다.
이는 이제 다단계 import 가 있다는 것을 의미합니다.
import porty.report
port = porty.report.read_portfolio('portfolio.csv')
import 문에는 다른 변형이 있습니다.
from porty import report
port = report.read_portfolio('portfolio.csv')
from porty.report import read_portfolio
port = read_portfolio('portfolio.csv')
이 접근 방식에는 두 가지 주요 문제점이 있습니다.
그래서 기본적으로 모든 것이 깨집니다. 하지만, 그 외에는 작동합니다.
동일한 패키지 내 파일 간의 import 는 이제 import 에 패키지 이름을 포함해야 합니다. 구조를 기억하세요.
porty/
__init__.py
pcost.py
report.py
fileparse.py
수정된 import 예시.
from porty import fileparse
def read_portfolio(filename):
return fileparse.parse_csv(...)
모든 import 는 *절대적 (absolute)*이며, 상대적 (relative) 이지 않습니다.
import fileparse ## BREAKS. fileparse not found
...
패키지 이름을 직접 사용하는 대신, .을 사용하여 현재 패키지를 참조할 수 있습니다.
from . import fileparse
def read_portfolio(filename):
return fileparse.parse_csv(...)
구문:
from . import modname
이렇게 하면 패키지 이름을 쉽게 변경할 수 있습니다.
패키지 서브모듈을 메인 스크립트로 실행하면 작동하지 않습니다.
$ python porty/pcost.py ## BREAKS
...
이유: 단일 파일에서 Python 을 실행하고 있으며, Python 은 나머지 패키지 구조를 올바르게 인식하지 못합니다 (sys.path가 잘못되었습니다).
모든 import 가 깨집니다. 이를 해결하려면 -m 옵션을 사용하여 프로그램을 다른 방식으로 실행해야 합니다.
$ python -m porty.pcost ## WORKS
...
__init__.py 파일이 파일들의 주요 목적은 모듈을 함께 묶는 것입니다.
예시: 함수 통합
## porty/__init__.py
from .pcost import portfolio_cost
from .report import portfolio_report
이렇게 하면 import 시 이름이 최상위 레벨에 나타납니다.
from porty import portfolio_cost
portfolio_cost('portfolio.csv')
다단계 import 를 사용하는 대신.
from porty import pcost
pcost.portfolio_cost('portfolio.csv')
앞서 언급했듯이, 이제 패키지 내에서 스크립트를 실행하려면 -m package.module을 사용해야 합니다.
$ python3 -m porty.pcost portfolio.csv
또 다른 대안이 있습니다: 새로운 최상위 레벨 스크립트를 작성합니다.
#!/usr/bin/env python3
## pcost.py
import porty.pcost
import sys
porty.pcost.main(sys.argv)
이 스크립트는 패키지 외부에 위치합니다. 예를 들어, 디렉토리 구조를 살펴보면 다음과 같습니다:
pcost.py ## 최상위 레벨 스크립트 (top-level-script)
porty/ ## 패키지 디렉토리 (package directory)
__init__.py
pcost.py
...
코드 구성과 파일 구조는 애플리케이션의 유지 관리 가능성에 핵심입니다.
Python 에는 "모두를 위한 하나의 해결책 (one-size fits all)" 접근 방식은 없습니다. 하지만, 많은 문제에 적용되는 구조는 다음과 같습니다.
porty-app/
README.txt
script.py ## 스크립트 (SCRIPT)
porty/
## 라이브러리 코드 (LIBRARY CODE)
__init__.py
pcost.py
report.py
fileparse.py
최상위 레벨의 porty-app은 문서, 최상위 레벨 스크립트, 예제 등을 위한 컨테이너입니다.
다시 말하지만, 최상위 레벨 스크립트 (있는 경우) 는 코드 패키지 외부, 한 단계 위에 존재해야 합니다.
#!/usr/bin/env python3
## porty-app/script.py
import sys
import porty
porty.report.main(sys.argv)
이 시점에서, 여러 프로그램이 있는 디렉토리가 있습니다:
pcost.py ## 포트폴리오 비용 계산 (computes portfolio cost)
report.py ## 보고서 생성 (Makes a report)
ticker.py ## 실시간 주식 시세 표시 (Produce a real-time stock ticker)
다양한 기능을 가진 여러 지원 모듈이 있습니다:
stock.py ## 주식 클래스 (Stock class)
portfolio.py ## 포트폴리오 클래스 (Portfolio class)
fileparse.py ## CSV 파싱 (CSV parsing)
tableformat.py ## 서식 있는 테이블 (Formatted tables)
follow.py ## 로그 파일 추적 (Follow a log file)
typedproperty.py ## 타입 지정 클래스 속성 (Typed class properties)
이 연습에서는 코드를 정리하고 공통 패키지에 넣을 것입니다.
porty/라는 디렉토리를 만들고 위의 모든 Python 파일을 그 안에 넣습니다. 또한 빈 __init__.py 파일을 생성하여 디렉토리에 넣습니다. 다음과 같은 파일 디렉토리가 있어야 합니다:
porty/
__init__.py
fileparse.py
follow.py
pcost.py
portfolio.py
report.py
stock.py
tableformat.py
ticker.py
typedproperty.py
디렉토리에 있는 __pycache__ 파일을 제거합니다. 이 파일에는 이전의 사전 컴파일된 Python 모듈이 포함되어 있습니다. 우리는 처음부터 시작하고 싶습니다.
패키지 모듈 중 일부를 가져오기를 시도해 봅니다:
>>> import porty.report
>>> import porty.pcost
>>> import porty.ticker
이러한 가져오기가 실패하면, 해당 파일로 이동하여 모듈 가져오기를 패키지 상대 가져오기를 포함하도록 수정합니다. 예를 들어, import fileparse와 같은 문은 다음과 같이 변경될 수 있습니다:
## report.py
from . import fileparse
...
from fileparse import parse_csv와 같은 문이 있는 경우, 코드를 다음과 같이 변경합니다:
## report.py
from .fileparse import parse_csv
...
모든 코드를 "패키지"에 넣는 것만으로는 애플리케이션에 충분하지 않은 경우가 많습니다. 때로는 지원 파일, 문서, 스크립트 및 기타 항목이 있습니다. 이러한 파일은 위에서 만든 porty/ 디렉토리 밖에 존재해야 합니다.
porty-app이라는 새 디렉토리를 만듭니다. 연습 9.1 에서 만든 porty 디렉토리를 해당 디렉토리로 이동합니다. portfolio.csv 및 prices.csv 테스트 파일을 이 디렉토리로 복사합니다. 또한 자신에 대한 몇 가지 정보가 포함된 README.txt 파일을 만듭니다. 이제 코드는 다음과 같이 구성되어야 합니다:
porty-app/
portfolio.csv
prices.csv
README.txt
porty/
__init__.py
fileparse.py
follow.py
pcost.py
portfolio.py
report.py
stock.py
tableformat.py
ticker.py
typedproperty.py
코드를 실행하려면 최상위 레벨의 porty-app/ 디렉토리에서 작업하고 있는지 확인해야 합니다. 예를 들어, 터미널에서:
$ cd porty-app
$ python3
>>> import porty.report
>>>
이전 스크립트 중 일부를 메인 프로그램으로 실행해 봅니다:
$ cd porty-app
$ python3 -m porty.report portfolio.csv prices.csv txt
Name Shares Price Change
---------- ---------- ---------- ----------
AA 100 9.22 -22.98
IBM 50 106.28 15.18
CAT 150 35.46 -47.98
MSFT 200 20.89 -30.34
GE 95 13.48 -26.89
MSFT 50 20.89 -44.21
IBM 100 106.28 35.84
$
python -m 명령어를 사용하는 것은 종종 약간 이상합니다. 패키지의 특이성을 처리하는 최상위 스크립트를 작성하고 싶을 수 있습니다. 위의 보고서를 생성하는 print-report.py 스크립트를 만듭니다:
#!/usr/bin/env python3
## print-report.py
import sys
from porty.report import main
main(sys.argv)
이 스크립트를 최상위 porty-app/ 디렉토리에 넣습니다. 해당 위치에서 실행할 수 있는지 확인합니다:
$ cd porty-app
$ python3 print-report.py portfolio.csv prices.csv txt
Name Shares Price Change
---------- ---------- ---------- ----------
AA 100 9.22 -22.98
IBM 50 106.28 15.18
CAT 150 35.46 -47.98
MSFT 200 20.89 -30.34
GE 95 13.48 -26.89
MSFT 50 20.89 -44.21
IBM 100 106.28 35.84
$
최종 코드는 이제 다음과 같은 구조를 가져야 합니다:
porty-app/
portfolio.csv
prices.csv
print-report.py
README.txt
porty/
__init__.py
fileparse.py
follow.py
pcost.py
portfolio.py
report.py
stock.py
tableformat.py
ticker.py
typedproperty.py
축하합니다! 패키지 (Packages) 랩을 완료했습니다. LabEx 에서 더 많은 랩을 연습하여 기술을 향상시킬 수 있습니다.