본문 바로가기

개발하자

Python 3을 사용하여 Jupyter Notebook에서 상대적으로 가져온 다른 디렉터리에 있는 모듈에서 로컬 함수 가져오기

반응형

Python 3을 사용하여 Jupyter Notebook에서 상대적으로 가져온 다른 디렉터리에 있는 모듈에서 로컬 함수 가져오기

나는 다음과 유사한 디렉토리 구조를 가지고 있다

meta_project
    project1
        __init__.py
        lib
            module.py
            __init__.py
    notebook_folder
        notebook.jpynb

에서 작업할 때 상대 가져오기를 사용하여 다음을 사용하여 함수에 액세스하려는 경우:

from ..project1.lib.module import function

다음 오류가 발생합니다:

SystemError                               Traceback (most recent call last)
<ipython-input-7-6393744d93ab> in <module>()
----> 1 from ..project1.lib.module import function

SystemError: Parent module '' not loaded, cannot perform relative import

상대적인 수입품을 사용하여 이것을 작동시킬 수 있는 방법이 있습니까?

노트북 서버는 디렉터리 수준에서 인스턴스화되므로 해당 파일의 정보에 액세스할 수 있어야 합니다.

또한, 적어도 원래 의도했던 대로는 모듈로 생각되지 않았기 때문에 파일을 가지고 있지 않으며, 단지 파일 시스템 디렉터리로 의도되었다. 문제 해결을 위해 모듈로 처리하고 파일(빈 파일이라도 포함)을 포함해야 하는 경우에는 문제가 발생하지 않습니다.

나는 이 디렉터리를 기계 간에 공유하고 상대 가져오기를 통해 어디에서나 동일한 코드를 사용할 수 있으며, 빠른 프로토타이핑을 위해 노트북을 자주 사용하기 때문에 절대 경로를 함께 해킹하는 제안은 도움이 되지 않을 것 같다.


편집: 와는 달리 일반적으로 Python 3에서 상대적으로 가져온 것, 특히 패키지 디렉터리 내에서 스크립트를 실행하는 것에 대해 이야기한다. 이것은 다른 일반적인 측면과 특정한 측면을 모두 가진 다른 디렉터리의 로컬 모듈에서 함수를 호출하려고 시도하는 주피터 노트북 내에서 작업하는 것과 관련이 있다.




나는 인접한 모듈의 기능을 건식으로 사용하는 방법을 설명하고자 하는 경우에 너와 거의 동일한 예를 가지고 있다.

제 솔루션은 다음과 같은 스니펫을 노트북에 추가하여 Python에게 추가 모듈 가져오기 경로를 알려주는 것이었습니다:

import os
import sys
module_path = os.path.abspath(os.path.join('..'))
if module_path not in sys.path:
    sys.path.append(module_path)

이를 통해 모듈 계층에서 원하는 기능을 가져올 수 있습니다:

from project1.lib.module import function
# use the function normally
function(...)

및 폴더가 없는 경우 빈 파일을 에 추가해야 합니다.




이 프로젝트 구조를 사용합니다:

my_project
├── nb
│   ├── 20170609-Examine_Database_Requirements.ipynb
│   └── 20170609-Initial_Database_Connection.ipynb
└── my_package
    ├── __init__.py
    └── postgres.py

그리고 노트북에서:

    In [1]: import os
            os.chdir(os.path.expanduser("~/location_of/my_project")

    In [2]: from my_package.postgres import database_connection



지금까지 받아들여진 답변이 나에게 가장 효과적이었다. 그러나 항상 걱정되는 것은 디렉토리를 하위 디렉토리로 재팩터링하여 모든 노트북에서 를 변경해야 하는 경우가 있다는 것입니다. 각 노트북 디렉터리에 파이썬 파일을 추가하여 필요한 모듈을 가져오기로 했습니다.

따라서 다음과 같은 프로젝트 구조를 갖는다:

project
|__notebooks
   |__explore
      |__ notebook1.ipynb
      |__ notebook2.ipynb
      |__ project_path.py
   |__ explain
       |__notebook1.ipynb
       |__project_path.py
|__lib
   |__ __init__.py
   |__ module.py

각 노트북 하위 디렉터리( 및 )에 파일을 추가했습니다. 이 파일에는 (@metakermit에서) 상대 가져오기에 대한 코드가 포함되어 있습니다:

import sys
import os

module_path = os.path.abspath(os.path.join(os.pardir, os.pardir))
if module_path not in sys.path:
    sys.path.append(module_path)

이렇게 하면 노트북이 아니라 파일 내에서 상대 가져오기만 하면 됩니다. 노트북 파일은 가져오기 전에 가져오기만 하면 됩니다. 예를 들어:

import project_path
import lib

여기서 주의할 점은 수입을 역전시키는 것은 효과가 없을 것이라는 것이다. 이것은 작동하지 않습니다:

import lib
import project_path

따라서 수입 시 주의가 필요합니다.




이 항목을 직접 조사하고 답변을 읽은 후 현재 작업 디렉토리를 변경하기 위한 컨텍스트 관리자를 제공하므로 을 사용하는 것이 좋습니다.

그러면 다음과 같은 것이 있습니다

import path
if path.Path('../lib').isdir():
    with path.Path('..'):
        import lib

하지만, 당신은 그 진술을 그냥 생략할 수도 있습니다.

여기에 발생하는 상황을 쉽게 추적할 수 있도록 인쇄 설명을 추가합니다

import path
import pandas

print(path.Path.getcwd())
print(path.Path('../lib').isdir())
if path.Path('../lib').isdir():
    with path.Path('..'):
        print(path.Path.getcwd())
        import lib
        print('Success!')
print(path.Path.getcwd())

이 예에서 출력하는 값(여기서 lib는 ):

/home/jovyan/shared/notebooks/by-team/data-vis/demos/custom-chart
/home/jovyan/shared/notebooks/by-team/data-vis/demos
/home/jovyan/shared/notebooks/by-team/data-vis/demos/custom-chart

솔루션은 컨텍스트 관리자를 사용하므로 커널이 셀 이전에 어떤 상태에 있었는지, 라이브러리 코드를 가져오면서 어떤 예외가 발생했는지에 관계없이 이전 작업 디렉터리로 돌아갈 수 있습니다.




나는 방금 이 멋진 해결책을 찾았다:

import sys; sys.path.insert(0, '..') # add parent folder path where lib folder is
import lib.store_load # store_load is a file on my library folder

당신은 단지 그 파일의 몇 가지 기능을 원할 뿐이다

from lib.store_load import your_function_name

python 버전 >= 3.3이면 폴더에 init.py 파일이 필요하지 않습니다




내 2센트 줄게:

import sys
# map the path where the module file is located. In my case it was the desktop
sys.path.append('/Users/John/Desktop')

# Either import the whole mapping module BUT then you have to use the .notation to map the classes like mapping.Shipping()

import mapping #mapping.py is the name of my module file

shipit = mapping.Shipment() #Shipment is the name of the class I need to use in the mapping module

# Or import the specific class from the mapping module
from mapping import Mapping

shipit = Shipment() #Now you don't have to use the .notation



나는 그것이 이 문제를 꽤 효과적으로 해결하는 데 도움이 된다는 것을 알았다. 프로젝트 구조는 약간 변경되지만 노트북의 코드는 노트북 전체에서 조금 더 단순하고 일관성이 있습니다.

프로젝트를 위해 약간의 설치를 수행합니다.

pipenv install python-dotenv

그러면 프로젝트가 다음으로 변경됩니다:

├── .env (this can be empty)
├── ipynb
│   ├── 20170609-Examine_Database_Requirements.ipynb
│   └── 20170609-Initial_Database_Connection.ipynb
└── lib
    ├── __init__.py
    └── postgres.py

마지막으로 가져오기가 다음으로 변경됩니다:

import os
import sys

from dotenv import find_dotenv


sys.path.append(os.path.dirname(find_dotenv()))

이 패키지의 +1은 노트북의 디렉터리가 여러 개일 수 있다는 것입니다. python-dotenv는 부모 디렉토리에서 가장 가까운 것을 찾아서 사용할 것이다. 이 접근법의 +2는 주피터가 시작할 때 .env 파일에서 환경 변수를 로드한다는 것이다. 이중고.




여기서 다른 모든 답변은 노트북 코드 추가(!)에 따라 달라집니다

제 생각에는 노트북 코드에 특정 경로를 하드 코딩하거나 위치에 따라 다른 방식으로 코딩하는 것은 좋지 않은 관행입니다. 왜냐하면 이것은 나중에 코드를 다시 만드는 것을 매우 어렵게 만들기 때문입니다. 대신 주피터 노트북 서버를 시작할 때 루트 프로젝트 폴더를 PYONPATH에 추가하는 것이 좋습니다. 프로젝트 폴더에서 직접

env PYTHONPATH=`pwd` jupyter notebook

또는 다른 곳에서 시작하는 경우에는 절대 경로를 사용하십시오

env PYTHONPATH=/Users/foo/bar/project/ jupyter notebook




나처럼 해결책을 이해하지 못하는 사람들을 위해, 당신은 당신의 특정 문제에 필요한 만큼 깊은 디렉토리로 가야 한다. 이 오류에 대한 해결 방법:

'your_folder'라는 이름의 모듈이 없습니다

작업하는 노트북은 다음과 같습니다:

C:\Users\vojte\projects\predicta\EDA\apartments\EDA.ipynb

이 노트북을 가져오려고 합니다:

C:\Users\vojte\projects\functions\functions.ipynb

나는 위의 솔루션을 이 솔루션에 @metakermit로 수정해야 했다:

import os
import sys
module_path = os.path.abspath(os.path.join('..\..\..'))
if module_path not in sys.path:
    sys.path.append(module_path)

from functions import functions as f

추가된 디렉터리가 있는지 확인할 수 있습니다. 이 예제에서는 이 행이 추가되었습니다:

C:\\Users\\vojte\\projects




를 사용하여 @joshua-cook을 개선하여 셀을 다시 실행하지 않도록 하고 사용하지 않고 디렉토리를 엉망으로 만들지 않도록 하려면 대신 다음 코드를 사용하십시오:

if 'NOTEBOOK_INITIATED_FLAG' not in globals():
    NOTEBOOK_INITIATED_FLAG = True
    %cd ..
%pwd

는 커널이 이미 실행 중임을 표시하는 자리 표시자로 사용되므로 디렉터리를 변경할 필요가 없습니다.

상위 폴더에서 파일을 작업하고 실행하려는 경우 다음과 같이 매우 복잡한 보일러 플레이트를 참조하십시오:

import os
import sys

if 'NOTEBOOK_INITIATED_FLAG' not in globals():
    NOTEBOOK_INITIATED_FLAG = True
    
    try:
        # not in notebook
        module_path = os.path.join(os.path.dirname(__file__), os.pardir)
    except:
        # in notebook
        module_path = os.path.abspath(os.path.join('..'))
        %cd ..
        %pwd

    if module_path not in sys.path:
        sys.path.append(module_path)



저는 짜증이 나서.

그리고 넌 할 수 있어.

이는 호출 스택을 통해 가져온 파일을 확인한 다음 위쪽 디렉터리를 이동하여 파일이 누락된 첫 번째 디렉터리를 찾는 방식으로 작동합니다.

따라서 실제로 Python 패키지를 작동시키듯이 노트북 디렉터리에 파일을 추가해야 합니다.




VS Code에서 Jupyter 확장자를 통해 작업하고 있다면 파일에서 이 경로를 설정할 수 있습니다.

{
    "jupyter.notebookFileRoot": "/path/to/your/module/root"
}

편집: 또는 작업 공간 루트에서 보다 일반적으로 설정하려면:

{
    "jupyter.notebookFileRoot": "${workspaceFolder}"
}



다음은 상위 폴더 이름을 지정할 필요도 없고 현재 작업 디렉터리를 변경할 필요도 없는 기본 솔루션입니다.

상대 가져오기가 시작되는 상위 수준 수로 업데이트하고 각 하위 패키지에 해당 수준이 있는지 확인하십시오.

if "PKG" not in globals(): # `PKG` is used just to avoid re-excuting the cell more than once
  root_parent_level = 2
  import importlib, sys, pathlib
  PKG = %pwd
  PKG = pathlib.Path(PKG)
  root = PKG
  full_pkg = f"{root.name}"
  for _ in range(root_parent_level):
    root = root.parent
    full_pkg = f"{root.name}.{full_pkg}"
    MODULE_PATH = f"{root}{pathlib.os.path.sep}__init__.py"
    MODULE_NAME = f"{root.name}"
    spec = importlib.util.spec_from_file_location(MODULE_NAME, MODULE_PATH)
    module = importlib.util.module_from_spec(spec)
    sys.modules[spec.name] = module 
    spec.loader.exec_module(module)
  __package__ = full_pkg

반응형