Python 패키지 내부에서 (정적) 파일을 읽는 방법은 무엇입니까?


106

내 Python 패키지에있는 파일을 어떻게 읽을 수 있는지 말해 주시겠습니까?

내 상황

내가로드하는 패키지에는 프로그램 내에서로드하려는 여러 템플릿 (문자열로 사용되는 텍스트 파일)이 있습니다. 그러나 그러한 파일의 경로를 어떻게 지정합니까?

다음에서 파일을 읽고 싶다고 상상해보십시오.

package\templates\temp_file

어떤 종류의 경로 조작? 패키지 기본 경로 추적?



답변:


-12

[2016-06-15 추가 : 분명히 모든 상황에서 작동하는 것은 아닙니다. 다른 답변을 참조하십시오]


import os, mypackage
template = os.path.join(mypackage.__path__[0], 'templates', 'temp_file')

175

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_resourcesfrom 사용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.

1
str.join 시퀀스 resource_path을 = '/'.join(('templates', 'temp_file')) 얻어
알렉스 Punnen

1
계속 NotImplementedError: Can't perform this operation for loaders without 'get_data()'아이디어가 떠오르나요?
leoschet

그 참고 importlib.resources하고 pkg_resources있습니다 반드시 호환되지 않습니다 . , setuptools에 importlib.resources추가 된 zip 파일과 함께 작동하며 에그 파일 자체가 추가되는 디렉토리에 저장된 zip 파일 인 egg 파일과 함께 작동합니다 . 예를 들어 , 에그는에 들어가 지만에있는 패키지 도 가져올 수 있습니다. .NET의 패키지에서 데이터를 추출하는 데 사용할 수 없습니다 . setuptools 가 계란 작업에 필요한 로더를 등록하는지 확인하지 않았습니다 . sys.pathpkg_resourcessys.pathsys.path = [..., '.../foo', '.../bar.zip'].../foobar.zippkg_resourcesbar.zipimportlib.resources
Martijn Pieters

오류 Package has no location가 발생하면 추가 setup.py 구성이 필요 합니까?
zygimantus

1
현재 모듈 ( templates예제 와 같은 하위 모듈이 아님)에있는 파일에 액세스 하려면 package인수를로 설정할 수 있습니다 __package__. 예pkg_resources.read_text(__package__, 'temp_file')
Ben Mares

42

포장 전주곡 :

리소스 파일 읽기에 대해 걱정하기 전에 첫 번째 단계는 데이터 파일이 처음에 배포판에 패키징되었는지 확인하는 것입니다. 소스 트리에서 직접 쉽게 읽을 수 있지만 중요한 부분은 이러한 리소스 파일이 설치된 패키지 내의 코드에서 액세스 할 수 있는지 확인하십시오 .

다음과 같이 프로젝트를 구조화하여 데이터 파일을 패키지 내의 하위 디렉토리에 넣습니다 .

.
├── package
   ├── __init__.py
   ├── templates
      └── temp_file
   ├── mymodule1.py
   └── mymodule2.py
├── README.rst
├── MANIFEST.in
└── setup.py

당신은 통과해야 include_package_data=Truesetup()전화를 겁니다. 매니페스트 파일은 setuptools / distutils를 사용하고 소스 배포를 빌드하려는 경우에만 필요합니다. templates/temp_file이 예제 프로젝트 구조에 대해 패키지를 가져 오려면 매니페스트 파일에 다음과 같은 줄을 추가하십시오.

recursive-include package *

역사적인 cruft note : 기본적으로 패키지 데이터 파일을 포함하는 flit, poetry와 같은 최신 빌드 백엔드에는 매니페스트 파일을 사용할 필요가 없습니다 . 따라서 사용 중이고 파일 pyproject.toml이없는 경우 .setup.pyMANIFEST.in

이제 포장을 벗어난 상태에서 읽기 부분에 ...

추천:

표준 라이브러리 pkgutilAPI를 사용하십시오 . 라이브러리 코드에서 다음과 같이 보일 것입니다.

# 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 을 참조 하십시오 .


1
지난 5 월에 편집되었습니다. 하지만 인트로의 설명을 놓치기 쉬운 것 같습니다. 여전히, 당신은 표준에 반대하는 사람들에게 조언합니다-그것은 물기 어려운 총알입니다 :-)
ankostis

1
@ankostis 대신 질문을 드리겠습니다. importlib.resources이미 지원 중단 대기중인 불완전한 API로 이러한 모든 단점에도 불구하고 왜 추천 하시겠습니까? 새로운 것이 반드시 더 나은 것은 아닙니다. 나에게 실제로 제공합니까 어떤 장점 당신의 대답은에 대한 언급을하지 않는 다음 stdlib pkgutil, 이상?

1
@wim에게, 확인 된 내 직감 사용에 대한 Brett Canon의 마지막 응답 입니다. pkgutil.get_data()이는 저개발되고 더 이상 사용되지 않을 API입니다. 즉, 나는 당신에게 동의 importlib.resources하지만, PY3.10이이 문제를 해결할 때까지 나는이 선택을지지합니다. Heving은 그것이 문서에서 권장하는 또 다른 "표준"이 아니라는 것을 알게되었습니다.
ankostis

1
@ankostis 나는 Brett의 의견을 소금 한 알로 받아 들일 것입니다. PEP 594-표준 라이브러리에서 방전 된 배터리 제거pkgutil 의 지원 중단 일정에 전혀 언급되지 않았 으며 정당한 이유없이 제거 될 가능성이 낮습니다. Python 2.3 이후로 사용되었으며 PEP 302 에서 로더 프로토콜의 일부로 지정되었습니다 . "과소 정의 된 API"를 사용하는 것은 대부분의 Python 표준 라이브러리를 설명 할 수있는 설득력있는 답변이 아닙니다!

2
추가하겠습니다 : importlib 리소스도 성공하는 것을보고 싶습니다! 저는 엄격하게 정의 된 API를 모두 사용합니다. 현재 상태에서는 실제로 추천 할 수 없습니다. API는 여전히 변경 중이며 많은 기존 패키지에서 사용할 수 없으며 비교적 최근의 Python 릴리스에서만 사용할 수 있습니다. 실제로 pkgutil는 거의 모든면에서 보다 더 나쁩니다 . 당신의 "직감"과 권위에 대한 호소 는 저에게 무의미합니다. 만약 get_data로더에 문제가 있다면 증거와 실제 사례를 보여 주세요 .

15

이 구조가있는 경우

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)

이상한 "항상 슬래시 사용"부분은 setuptoolsAPI 에서 비롯됩니다.

또한 경로를 사용하는 경우 Windows를 사용하는 경우에도 경로 구분 기호로 슬래시 (/)를 사용해야합니다. Setuptools는 빌드시 슬래시를 적절한 플랫폼 별 구분 기호로 자동 변환합니다.

문서가 어디에 있는지 궁금한 경우 :


간결한 답변에 감사드립니다
Paolo

8

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로 설치할 수 있습니다.



-2

달걀 파일을 사용한다고 가정합니다. 추출되지 않음 :

나는 최근 프로젝트에서 달걀 (zip 파일)의 템플릿을 파일 시스템의 적절한 디렉토리로 추출하는 postinstall 스크립트를 사용하여이 문제를 "해결"했습니다. 작업 한 이후 가장 빠르고 신뢰할 수있는 솔루션이었습니다.__path__[0] 때때로 잘못 될 수 (이름은 기억 나지 않지만 목록 앞에 무언가를 추가 한 라이브러리가 하나 이상 있습니다!).

또한 계란 파일은 일반적으로 "달걀 캐시"라는 임시 위치로 즉석에서 추출됩니다. 스크립트를 시작하기 전이나 나중에 환경 변수를 사용하여 해당 위치를 변경할 수 있습니다.

os.environ['PYTHON_EGG_CACHE'] = path

그러나 작업을 제대로 수행 할 수있는 pkg_resources 가 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.