Closed hcn1519 closed 8 months ago
Another way to import modules is modifying module search path. When Python interpreter detects import statement in scripts, it searches three paths, current running script path, PYTHONPATH
, and path in sys.path
, to import modules that users import. So, if we modify one of them to include where our modules are located, we can use them.
PYTHONPATH
: PYTHONPATH
is environment variable which searches module when python interpreter is executed. Like other environment variables, you can update it for current session by exporting the path. Or, if you want to apply the changes permanently, adding the export command it ./zshrc
or ./bashrc
.export PYTHONPATH=/path/to/msgs:$PYTHONPATH
sys.path
in runtimePython also searches module in sys.path
. So, if we append new paths for the modules, it also finds modules we want to import.
import sys
sys.path.append("path/of/msgs")
from msgs import Message
class Sender:
def send(self):
msg = Message()
msg.foo()
print("Send from script2")
if __name__ == "__main__":
sender = Sender()
sender.send()
Modifying
기반의 프로젝트가
5. 같은 디렉토리
6. 다른 디렉토리
7. -m 옵션
Python은
Python 다른 파일 import하기
import math as m
m.pi
from math import pi as PI
PI
좀 더 자세한 Python의 import system에서 확인할 수 있습니다.
Python 프로젝트에서 다른 소스코드의 기능을 import하는 과정에서 잦은 에러를 마주치는 것은 흔한 일입니다. Python import system에 익숙하지 않은 개발자들은 이러한 에러를 피하기 위해, 모든 소스코드를 하나의 파일에 넣거나, 스크립트를 모두 하나의 디렉토리에 넣는 작업을 했을 수도 있습니다.
이 글은 위와 같은 경험이 있는 개발자들의 Python의 import system에 대한 이해를 돕기 위해 작성되었습니다.
파이썬을 사용할 때 개발자는 경로 제약 없이 스크립트를 실행할 수 있습니다. 이는 큰 장점이지만, 한편으로는 여러 파일의 참조를 구성할 때 주요한 기준점인 스크립트 수행 경로를 쉽게 바꾼다는 말을 의미하기도 합니다. 예를 들어서, 아래와 같은 디렉토리를 생각해보겠습니다.
foo
├── bar
│ └── sample2.py
└── sample.py
여기에서 foo
에서 python3 sample.py
를 수행하는 것과 bar
디렉토리 안에서 python3 ../sample.py
는 동일한 스크립트 파일을 실행합니다. 하지만, 둘의 스크립트 수행 경로는 /foo
와 /foo/bar
로 다르고 이는 큰 차이를 야기합니다. 그렇기 때문에 우리는 여러 소스 파일이 다양한 하위 디렉토리에 나누어진 프로젝트를 진행하고 있다면, 일반적으로 프로젝트 루트에서 프로젝트를 실행하는 것을 전제하고 프로젝트 코드를 구성합니다.
Python은 import
문을 통해 다른 스크립트의 기능을 가져올 수 있습니다. Python 스크립트는 동시에 여러 스크립트와 컴파일되지 않기 때문에, 인터프리터는 가져온 정의나 문을 찾을 때 디렉토리 구조에 의존합니다.
이 글에서는 import 문을 정의하는 다양한 방법을 다루고자 합니다.
다른 정의를 가져오는 가장 쉬운 방법은 모든 스크립트 파일을 동일한 디렉토리에 두는 것입니다. 예를 들어, 아래와 같은 프로젝트가 있다고 가정해보겠습니다.
directory
├── message.py
└── sender.py
각 스크립트는 아래 코드를 포함합니다.
# message.py
class Message:
def foo(self):
print("Message from script1")
# sender.py
from message import Message
class Sender:
def send(self):
msg = Message()
msg.foo()
print("Send from script2")
if __name__ == "__main__":
sender = Sender()
sender.send()
$ python3 script2.py
Message from script1
Send from script2
이 예제에서 sender.py
는 message
에서 Message
클래스를 from message import Message
문을 사용하여 가져옵니다. 위의 예제와 같이, 우리는 간단히 from module import name
형식으로 다른 스크립트의 정의나 문을 추가할 수 있습니다. 여기서 module은 우리가 가져오고자 하는 기능을 갖춘 확장자(.py)가없는 파일입니다.
왜 import name
형식으로 import 문을 정의하지 않는지 궁금할 수 있습니다. 이는 사용 가능하지만, import
후에 오는 [name]
부분은 모듈 또는 패키지(모듈의 모음)이어야 합니다. 그렇기 때문에 module.name
(예: msg = message.Message()
)과 같이 모듈 내의 기능을 사용할 때, 각 함수 또는 클래스의 전체 네임스페이스를 작성해야 접근이 가능합니다. 반면 from message import Message
형태의 statement는 별도의 네임스페이스 없이 모듈 내 기능에 접근할 수 있게 합니다.
프로젝트가 커지면 모든 스크립트 파일을 하나의 디렉토리에 넣는 것이 어려워집니다. 아래 디렉토리 구조를 생각해보겠습니다.
directory
├── msgs
│ └── message.py
└── sender.py
이 예에서 sender.py
와 message.py
는 서로 다른 디렉토리에 있습니다. 그래서 위의 예시 파일의 sender.py
를 실행하면 ModuleNotFoundError
가 발생합니다.
$ python3 sender.py
Traceback (most recent call last):
File "path/sender.py", line 2, in <module>
from message import Message
ModuleNotFoundError: No module named 'message'
이 문제를 해결하는 몇 가지 방법이 있습니다. 가장 간단한 방법은 import 문을 from msgs.message import Message
로 변경하면 됩니다. 이는 msgs 디렉토리를 package로 간주하여 내부의 모듈을 접근하는 방식입니다. Package는 모듈 네임스페이스를 지원하는 Python의 구조적인 방식으로 .
을 연결하는 방식(package.sub_package.module
)으로 내부 모듈 접근을 지원합니다. Python은 디렉토리가 Python 소스코드 파일을 가지고 있을 때 이를 package로 인식합니다. 이는 __init__.py
파일을 디렉토리에 추가하여 명시적으로 이를 보여줄 수 있습니다.
directory
├── msgs
│ ├── __init__.py
│ └── message.py
└── sender.py
# sender.py
from msgs.message import Message # changed
class Sender:
def send(self):
msg = Message()
msg.foo()
print("Send from script2")
if __name__ == "__main__":
sender = Sender()
sender.send()
__init__.py
는 import statement를 단순화하는 다양한 syntax sugar를 정의하는 데에 사용되기도 합니다. 예를 들어서,__init__.py
에from message import Message
를 추가할 경우,from msgs import Message
형태로sender.py
에 statement를 작성할 수 있습니다.
Another way to import modules is modifying module search path. When Python interpreter detects import statement in scripts, it searches three paths, current running script path, PYTHONPATH
, and path in sys.path
, to import modules that users import. So, if we modify one of them to include where our modules are located, we can use them.
PYTHONPATH
: PYTHONPATH
is environment variable which searches module when python interpreter is executed. Like other environment variables, you can update it for current session by exporting the path. Or, if you want to apply the changes permanently, adding the export command it ./zshrc
or ./bashrc
.export PYTHONPATH=/path/to/msgs:$PYTHONPATH
sys.path
in runtimePython also searches module in sys.path
. So, if we append new paths for the modules, it also finds modules we want to import.
import sys
sys.path.append("path/of/msgs")
from msgs import Message
class Sender:
def send(self):
msg = Message()
msg.foo()
print("Send from script2")
if __name__ == "__main__":
sender = Sender()
sender.send()
I personally think modifying search paths is the least preferable way to solve module import issue. Because, changing environement variable would have side-effect to the other projects.
또한, sys.path를 활용하는 것은 import line 코드가 실행되는 것에 module import를 의존합니다. 즉, 해당 기능에 의존하여 프로그램을 구성한다면, 우리는 사용하는 모든 path 정보가 있는 코드에 프로젝트가 의존해야 합니다.
Also, leveraging sys.path would make importing modules complicated, because projects should rely on the execution of the sys.path.append()
statement.
It means that sys.path varies depending on what source code we executed. Having single sys.path.append()
collection source would resolve this issue, but it will introduce unnecessary dependency to all source codes, and we need to update that file whenever we want to create new directory in the project.
Python Import Mechanism
module import import system venv
Introduction
Python 프로젝트에서 다른 소스코드의 기능을 import하는 과정에서 잦은 에러를 마주치는 것은 흔한 일입니다. Python import system에 익숙하지 않은 개발자들은 이러한 에러를 피하기 위해, 모든 소스코드를 하나의 파일에 넣거나, 스크립트를 모두 하나의 디렉토리에 넣는 작업을 했을 수도 있습니다. 혹은 import를 하기 위해 공식 문서를 보아도, 다소 헷갈리는 부분들이 있을
이 글은 Python의 import system에 대한 이해를 돕기 위해 작성되었습니다.
왜 import 과정에서 에러가 자주 발생할까?
1. Flexibility of Script Execution
모든 프로그래밍 언어의 import system은 우리가 import하고자 하는 소스 코드의 위치를 알고 있다는 전제로 동작합니다. 우리가 이런 작업을 하지 않았다면, 우리가 사용하는 IDE 혹은 에디터가 이를 대신해주고 있는 경우가 많습니다. 만약, 이 툴들이 이러한 작업을 해주지 않았다면, 우리는 언어 컴파일/인터프리팅의 과정에서 이를 명시해주어야 합니다.
마찬가지로, Python에서도 다른 소스코드를 import하려면 룰에 맞게 import statement를 작성해야 합니다. 그런데 이 룰이 실행하는 스크립트 파일의 위치에 따라 다른 의미를 가지게 되는 경우가 있습니다. 예를 들어서, 아래와 같은 디렉토리를 생각해보겠습니다.
여기에서
foo
에서python3 sample.py
를 수행하는 것과bar
디렉토리 안에서python3 ../sample.py
는 동일한 스크립트 파일을 실행합니다. 하지만, import system은 다른 input 값을 가지고 실행이 되어, import 에러가 발생할 수 있습니다.2. No chances to resolve module dependencies before script execution
이러한 에러를 이해하기 위해서는 Python의 Module과 Package에 대한 이해가 필요합니다.
그렇다면, Python에서 다른 소스코드의 기능은 어떻게 import할 수 있을까요?
Python 파일에 다른 파일 import하여 사용하기
Python supports importing different script's functionality through
import
statement. Since Python script does not compile with different scripts at the same time, interpreter relies on directory structure when it finds imported definitions or statements.1. Putting all modules in the same directory
Easiest way to import other definitions is locating all script files in the same directory. For example, let's say we have project like below.
Each script contains code below.
In this example,
sender.py
importMessage
class frommessage
usingfrom message import Message
statement. Like above example, we can simply add other scripts definitions or statements infrom module import name
form. From the form, module is just a file without extension(.py
) that has functionalities we want to import.You might wonder why not define import statement like
import message
form. Yes, you can utilize it, but[name]
part coming after import should be module or package(collection of module). And you need to describe full namespace of each function or class like,module.name
(e.g.msg = message.Message()
). Since Python does not import2. Use Package to resolve dependency
As the project becomes larger, it is hard to put all script files in single directory. Think about below directory structure.
In this example,
sender.py
andmessage.py
are in different directories. If you runsender.py
you will getModuleNotFoundError
.There are a couple of ways to resolve this issue. If you change import statement
from msgs.message import Message
, it works.First, if you change msgs directory into package, we can import
Message
into sender file. Package is a structuring way for module namespace by chaining dots inpackage.sub_package.module
form. If a directory has__init__.py
, Python considers the directory as a package. So, above case, if we add empty__init__.py
file inmsgs
, we can import Message usingfrom msgs.message import Message
.but it makes directory as a package and creates namespace for module in it.