내 Python 패키지에있는 파일을 어떻게 읽을 수 있는지 말해 주시겠습니까?
내 상황
내가로드하는 패키지에는 프로그램 내에서로드하려는 여러 템플릿 (문자열로 사용되는 텍스트 파일)이 있습니다. 그러나 그러한 파일의 경로를 어떻게 지정합니까?
다음에서 파일을 읽고 싶다고 상상해보십시오.
package\templates\temp_file
어떤 종류의 경로 조작? 패키지 기본 경로 추적?
답변
[2016-06-15 추가 : 분명히 모든 상황에서 작동하는 것은 아닙니다. 다른 답변을 참조하십시오]
import os, mypackage
template = os.path.join(mypackage.__path__[0], 'templates', 'temp_file')
답변
TLDR; 아래 방법 2 번에 설명 된대로 표준 라이브러리의 importlib.resources
모듈 을 사용하십시오 .
그만큼 전통 pkg_resources
에서setuptools
더 이상 사용하지 않는 것이 좋습니다 새로운 방법 때문에 :
- 그것은이다 훨씬 더 성능이 좋은 ;
- 패키지 (경로 지정 대신)를 사용하면 컴파일 시간 오류가 발생하므로 더 안전합니다.
- 경로를 “결합”할 필요가 없기 때문에 더 직관적입니다.
- 추가 종속성이 필요하지 않으므로 개발할 때 더 빠릅니다 (
setuptools
)이 Python의 표준 라이브러리에만 의존 .
기존 코드를 이식 할 때 새 방법과의 차이점을 설명하기 위해 먼저 나열된 기존 코드를 유지했습니다 ( 여기 에서도 이식 설명 ).
템플릿이 모듈의 패키지 내에 중첩 된 폴더에 있다고 가정 해 보겠습니다.
<your-package>
+--<module-asking-the-file>
+--templates/
+--temp_file <-- We want this file.
참고 1 : 확실히, 우리는
__file__
속성을 조작 (예 : zip에서 제공 될 때 코드가 손상됨).2 주 : 이 패키지를 빌드하는 경우로 데이터 파일을 declatre 기억
package_data
또는data_files
당신을에서setup.py
.
1) pkg_resources
from 사용setuptools
(느림) 사용
setuptools 배포판의 pkg_resources
패키지를 사용할 수 있지만 성능면 에서 비용이 발생 합니다 .
import pkg_resources
# Could be any dot-separated package/module name or a "Requirement"
resource_package = __name__
resource_path = '/'.join(('templates', 'temp_file')) # Do not use os.path.join()
template = pkg_resources.resource_string(resource_package, resource_path)
# or for a file-like stream:
template = pkg_resources.resource_stream(resource_package, resource_path)
팁 :
배포판이 압축되어 있어도 데이터를 읽으므로
zip_safe=True
에서 설정setup.py
하거나 python-3.5 에서 오랫동안 기다려온zipapp
패커 를 사용하여 자체 포함 된 배포판을 만들 수 있습니다.
setuptools
런타임 요구 사항 에 추가하는 것을 잊지 마십시오 (예 : install_requires`).
… 그리고 Setuptools / pkg_resources
문서 에 따르면 다음을 사용해서는 안됩니다 os.path.join
.
기본 리소스 액세스
리소스 이름은
/
경로로 구분되어야하며 절대적 (예 : 선행 없음/
)이거나 ”..
” 와 같은 상대 이름을 포함 할 수 없습니다 . 마십시오 하지 사용os.path
은 그대로, 자원 경로를 조작하는 루틴을 하지 파일 시스템 경로.
2) Python> = 3.7, 또는 백 포트 사용 importlib_resources
라이브러리 사용
위의 보다 효율적인 표준 라이브러리 importlib.resources
모듈 을 사용하십시오 setuptools
.
try:
import importlib.resources as pkg_resources
except ImportError:
# Try backported to PY<37 `importlib_resources`.
import importlib_resources as pkg_resources
from . import templates # relative-import the *package* containing the templates
template = pkg_resources.read_text(templates, 'temp_file')
# or for a file-like stream:
template = pkg_resources.open_text(templates, 'temp_file')
주의:
기능에 대하여
read_text(package, resource)
:
- 그만큼
package
문자열이나 모듈이 될 수 있습니다.- 는
resource
더 이상 경로,하지만 기존 패키지에서 열 수있는 자원, 단지 파일 이름이 아니다; 경로 구분 기호를 포함 할 수 없으며 하위 리소스가 없을 수도 있습니다 (즉, 디렉터리가 될 수 없음).
질문에서 묻는 예의 경우 이제 다음을 수행해야합니다.
- 을
<your_package>/templates/
빈을 생성하여, 적절한 패키지에__init__.py
거기에 파일을 - 이제 우리는 간단한 (아마도 상대적인)
import
문을 (더 이상 패키지 / 모듈 이름을 구문 분석하지 않음). resource_name = "temp_file"
(경로 없음)을 요청하십시오 .
팁 :
- 현재 모듈 내부의 파일에 액세스하려면 패키지 인수를하는 설정
__package__
, 예를 들어,pkg_resources.read_text(__package__, 'temp_file')
( @ ben-mares 덕분에).- 때 상황이 재미가 될 실제 파일 이름이 함께 요구되는
path()
지금 상황에 관리자가 일시적으로 생성 된 파일 (읽기에 사용되기 때문에, 이 ).- 와, 조건부 이전의 파이를 들어, 백 포트 라이브러리 추가
install_requires=[" importlib_resources ; python_version<'3.7'"]
(확인 이 당신이 프로젝트를 패키징하는 경우setuptools<36.2.1
).- 기존 방법에서 마이그레이션 한 경우 런타임 요구 사항
setuptools
에서 라이브러리 를 제거해야합니다 .- 사용자 정의 할 기억
setup.py
이나MANIFEST
하는 정적 파일이 포함됩니다 .- 당신은 또한
zip_safe=True
당신의setup.py
.
답변
포장 전주곡 :
리소스 파일 읽기에 대해 걱정하기 전에 첫 번째 단계는 데이터 파일이 처음에 배포판에 패키징되었는지 확인하는 것입니다. 소스 트리에서 직접 쉽게 읽을 수 있지만 중요한 부분은 이러한 리소스 파일이 설치된 패키지 내의 코드에서 액세스 할 수 있는지 확인하십시오 .
다음과 같이 프로젝트를 구조화하여 데이터 파일을 패키지 내의 하위 디렉토리에 넣습니다 .
.
├── package
│ ├── __init__.py
│ ├── templates
│ │ └── temp_file
│ ├── mymodule1.py
│ └── mymodule2.py
├── README.rst
├── MANIFEST.in
└── setup.py
당신은 통과해야 include_package_data=True
에 setup()
전화를 겁니다. 매니페스트 파일은 setuptools / distutils를 사용하고 소스 배포를 빌드하려는 경우에만 필요합니다. templates/temp_file
이 예제 프로젝트 구조에 대해 패키지를 가져 오려면 매니페스트 파일에 다음과 같은 줄을 추가하십시오.
recursive-include package *
역사적인 cruft note : 기본적으로 패키지 데이터 파일을 포함하는 flit, poetry와 같은 최신 빌드 백엔드에는 매니페스트 파일을 사용할 필요가 없습니다 . 따라서 사용 중이고 파일 pyproject.toml
이없는 경우 .setup.py
MANIFEST.in
이제 포장을 벗어난 상태에서 읽기 부분에 …
추천:
표준 라이브러리 pkgutil
API를 사용하십시오 . 라이브러리 코드에서 다음과 같이 보일 것입니다.
# within package/mymodule1.py, for example
import pkgutil
data = pkgutil.get_data(__name__, "templates/temp_file")
print("data:", repr(data))
text = pkgutil.get_data(__name__, "templates/temp_file").decode()
print("text:", repr(text))
그것은 zip에서 작동합니다. Python 2 및 Python 3에서 작동합니다. 타사 종속성이 필요하지 않습니다. 나는 실제로 어떤 단점도 알고 있지 않습니다 (당신이 있다면 대답에 대해 언급하십시오).
피하는 나쁜 방법 :
나쁜 방법 # 1 : 소스 파일의 상대 경로 사용
이것은 현재 허용되는 답변입니다. 기껏해야 다음과 같이 보입니다.
from pathlib import Path
resource_path = Path(__file__).parent / "templates"
data = resource_path.joinpath("temp_file").read_bytes()
print("data", repr(data))
그게 무슨 문제입니까? 사용 가능한 파일 및 하위 디렉토리가 있다는 가정이 올바르지 않습니다. 이 접근 방식은 zip 또는 wheel로 압축 된 코드를 실행하는 경우에는 작동하지 않으며 패키지가 파일 시스템으로 추출되는지 여부에 관계없이 완전히 사용자가 제어 할 수 없습니다.
나쁜 방법 # 2 : pkg_resources API 사용
이것은 최고 투표 답변에 설명되어 있습니다. 다음과 같이 보입니다.
from pkg_resources import resource_string
data = resource_string(__name__, "templates/temp_file")
print("data", repr(data))
그게 무슨 문제입니까? setuptools 에 대한 런타임 종속성을 추가합니다. 이는 바람직하게는 설치 시간 종속성 이어야 합니다. 코드가 설치된 모든 패키지 의 작업 세트를 구축하기 때문에 가져 오기 및 사용 이 정말 느려질 수 있습니다.pkg_resources
패키지 리소스 . 설치시에는 큰 문제는 아니지만 (설치가 한 번만 종료되기 때문에) 런타임에는보기 흉합니다.
나쁜 방법 # 3 : importlib.resources API 사용
이것은 현재 최다 투표 답변의 권장 사항입니다. 최근 표준 라이브러리 추가 ( Python 3.7의 새로운 기능 )이지만 백 포트도 사용할 수 있습니다. 다음과 같이 보입니다.
try:
from importlib.resources import read_binary
from importlib.resources import read_text
except ImportError:
# Python 2.x backport
from importlib_resources import read_binary
from importlib_resources import read_text
data = read_binary("package.templates", "temp_file")
print("data", repr(data))
text = read_text("package.templates", "temp_file")
print("text", repr(text))
그게 무슨 문제입니까? 글쎄, 안타깝게도 아직 작동하지 않습니다. 이는 여전히 불완전한 API이므로을 사용 importlib.resources
하면 templates/__init__.py
데이터 파일이 하위 디렉토리가 아닌 하위 패키지 내에 상주 하도록 빈 파일을 추가해야합니다 . 또한 package/templates
하위 디렉토리를 package.templates
자체적으로 가져올 수있는 하위 패키지 로 노출합니다 . 그다지 큰 문제가 아니고 불편하지 않다면 계속해서 __init__.py
파일을 추가 하고 가져 오기 시스템을 사용하여 리소스에 액세스 할 수 있습니다. 그러나 그 동안 my_resources.py
파일 로 만들고 모듈에서 일부 바이트 또는 문자열 변수를 정의한 다음 Python 코드로 가져 오는 것이 좋습니다. 여기서 어느 쪽이든 무거운 작업을 수행하는 것은 수입 시스템입니다.
예제 프로젝트 :
github 에서 예제 프로젝트를 만들고 PyPI에 업로드했습니다 . 위에서 설명한 네 가지 접근 방식을 모두 보여줍니다. 다음과 같이 사용해보십시오.
$ pip install resources-example
$ resources-example
자세한 내용은 https://github.com/wimglenn/resources-example 을 참조 하십시오 .
답변
이 구조가있는 경우
lidtk
├── bin
│ └── lidtk
├── lidtk
│ ├── analysis
│ │ ├── char_distribution.py
│ │ └── create_cm.py
│ ├── classifiers
│ │ ├── char_dist_metric_train_test.py
│ │ ├── char_features.py
│ │ ├── cld2
│ │ │ ├── cld2_preds.txt
│ │ │ └── cld2wili.py
│ │ ├── get_cld2.py
│ │ ├── text_cat
│ │ │ ├── __init__.py
│ │ │ ├── README.md <---------- say you want to get this
│ │ │ └── textcat_ngram.py
│ │ └── tfidf_features.py
│ ├── data
│ │ ├── __init__.py
│ │ ├── create_ml_dataset.py
│ │ ├── download_documents.py
│ │ ├── language_utils.py
│ │ ├── pickle_to_txt.py
│ │ └── wili.py
│ ├── __init__.py
│ ├── get_predictions.py
│ ├── languages.csv
│ └── utils.py
├── README.md
├── setup.cfg
└── setup.py
이 코드가 필요합니다.
import pkg_resources
# __name__ in case you're within the package
# - otherwise it would be 'lidtk' in this example as it is the package name
path = 'classifiers/text_cat/README.md' # always use slash
filepath = pkg_resources.resource_filename(__name__, path)
이상한 “항상 슬래시 사용”부분은 setuptools
API 에서 비롯됩니다.
또한 경로를 사용하는 경우 Windows를 사용하는 경우에도 경로 구분 기호로 슬래시 (/)를 사용해야합니다. Setuptools는 빌드시 슬래시를 적절한 플랫폼 별 구분 기호로 자동 변환합니다.
문서가 어디에 있는지 궁금한 경우 :
답변
Python Cookbook의 “10.8. Reading Datafiles Within a Package”의 내용은 David Beazley와 Brian K. Jones가 답변을 제공합니다.
여기로 가져 오겠습니다.
다음과 같이 구성된 파일이 포함 된 패키지가 있다고 가정합니다.
mypackage/
__init__.py
somedata.dat
spam.py
이제 spam.py 파일이 somedata.dat 파일의 내용을 읽으려고한다고 가정합니다. 이를 수행하려면 다음 코드를 사용하십시오.
import pkgutil
data = pkgutil.get_data(__package__, 'somedata.dat')
결과 변수 데이터는 파일의 원시 내용을 포함하는 바이트 문자열입니다.
get_data ()의 첫 번째 인수는 패키지 이름이 포함 된 문자열입니다. 직접 제공하거나 다음과 같은 특수 변수를 사용할 수 있습니다.__package__
. 두 번째 인수는 패키지 내 파일의 상대 이름입니다. 필요한 경우 최종 디렉토리가 패키지 내에있는 한 표준 Unix 파일 이름 규칙을 사용하여 다른 디렉토리로 이동할 수 있습니다.
이런 식으로 패키지는 디렉토리, .zip 또는 .egg로 설치할 수 있습니다.
답변
패키지의 모든 Python 모듈에는 __file__
속성이 있습니다.
다음과 같이 사용할 수 있습니다.
import os
from mypackage
templates_dir = os.path.join(os.path.dirname(mypackage.__file__), 'templates')
template_file = os.path.join(templates_dir, 'template.txt')
계란 리소스는 http://peak.telecommunity.com/DevCenter/PythonEggs#accessing-package-resources를 참조하십시오.
답변
달걀 파일을 사용한다고 가정합니다. 추출되지 않음 :
나는 최근 프로젝트에서 달걀 (zip 파일)의 템플릿을 파일 시스템의 적절한 디렉토리로 추출하는 postinstall 스크립트를 사용하여이 문제를 “해결”했습니다. 작업 한 이후 가장 빠르고 신뢰할 수있는 솔루션이었습니다.__path__[0]
때때로 잘못 될 수 (이름은 기억 나지 않지만 목록 앞에 무언가를 추가 한 라이브러리가 하나 이상 있습니다!).
또한 계란 파일은 일반적으로 “달걀 캐시”라는 임시 위치로 즉석에서 추출됩니다. 스크립트를 시작하기 전이나 나중에 환경 변수를 사용하여 해당 위치를 변경할 수 있습니다.
os.environ['PYTHON_EGG_CACHE'] = path
그러나 작업을 제대로 수행 할 수있는 pkg_resources 가 있습니다.
