Post

(python) import 관련 - 모듈, 패키지, \_\_init\_\_.py, \_\_all\_\_

모듈 단독 실행 시 import 경로 문제

1
2
3
4
5
6
7
8
9
10
11
├── common
│   ├── \_\_init\_\_.py
│   ├── copy.py
│   └── clean.py
├── T1036
│   ├── \_\_init\_\_.py
│   ├── bar.py
│   ├── foo.py
├── run.py

우선, 기본적인 import 동작 방식

python run.py 커맨드로 실행하는 경우 , foo.py 파일 내에서 모듈 참조는 아래와 같다.

1
2
3
4
5
from .bar import BarCls
## .으로 참조하는 경우 그 파일 자체의 위치(T1036) 기준 찾는다
from common.copy import CopyNoMetaCls
## 절대 경로로 명시하는 경우는 python 커맨드 대상이 되는 파일(run.py 디렉터리) 위치 기준 찾는다.

foo.py에서 다른 hierarch에 있는 common.copy에 접근하기
1
2
3
4
5
6
7
8
9
10
11
import sys, os
sys.path.append(os.path.dirname(os.path.abspath(os.path.dirname(\_\_file\_\_))))
## sys.path.insert(0, os.path.abspath('..')) 이건 별로 좋은 방법이 아니다.
## python foo.py 하면 제대로 되는데, run.py에서 subprocess.run("python T1036/foo.py")하면
## run.py가 위치한 디렉토리에서 ".."이 적용되어 common을 못찾는다.
## 그래서 위와 같이 지정하는게 더 확실하다.


import common
common.copy.copyNoMeta()

common.__init__.py 설정
1
2
3
4
5
6
7
8
9
10
from .copy import \*
from .clean import \*

#### from common import \* 했을 때도 import 되도록 동작하게 하려면 \_\_all\_\_을 정의.
## \_\_all\_\_ = ['copyNoMeta', 'copyWithMeta', 'remove']
#### 위처럼 하는 것 보다, 아래 처럼 각각의 모듈에 \_\_all\_\_을 정의하고
#### \_\_init\_\_.py에서 이를 한 번에 종합하는 식으로 구성하는 것이 좋다.
## \_\_all\_\_ = (copy.\_\_all\_\_ +
##            clean.\_\_all\_\_)

외부에 공개하고 싶지 않은 내용이 있다면 \_\_all\_\_ = []로 비워두어야 한다. 따로 설정하지 않으면 기본적으로 그 모듈에서 import한 다른 모듈이나, 함수등이 모두 노출되도록 되어 있어서 dir()로 조회해보면 다 나온다.

dynamic module & package import

동적으로 모듈 임포트하기.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
############ Dynamic module & package import ##############
from importlib import import\_module
from pathlib import Path

p = {}
def dynamicImport():
path = Path('.')
attack\_packages = [x for x in path.iterdir()
if x.is\_dir()
and x.name not in ("\_\_pycache\_\_", "common", "T1097A\_Pass\_the\_Ticket")]
for package in attack\_packages:
module\_py = list(package.glob("\*.py"))
modules = list(map(lambda x: x.stem, filter(lambda x: x.name not in ("\_\_init\_\_.py", "test.py"), module\_py)))
try:
name\_as = package.name[:package.name.index("\_")]
except:
name\_as = package
p[name\_as] = {}
for m in modules:
p[name\_as][m] = import\_module("." + m, package.name)

###########################################################

pkgutil을 사용해 패키지 내의 모듈 리스트 가져오기

1
2
3
4
package = import\_module(package.name)
for importer, modname, ispkg in pkgutil.iter\_modules(package.\_\_path\_\_):
print("Found submodule %s (is a package: %s)" % (modname, ispkg))

그 외 정보를 조회하려면 inspect 모듈을 사용한다.

1
2
from inspect import Signature

모듈을 싱글턴 처럼 쓸 수는 있지만 이렇게 쓰는건 별로다.

파이썬 모듈은 참조된 횟수에 상관없이 단 하나의 복사본만 불러온다. 따라서 모듈 자체를 하나의 싱글턴으로 사용할 수 있다. 그래서 모듈 하나에 전역 변수 선언해놓고, 다른데서 이 변수를 import해서 쓰더라도 값이 복사되는게 아니라 공유된다. 여기서도 써야하고 저기서도 써야하는 파라미터를 이리저리 넘기는게 지저분한 경우가 있는데, 그럴 때 사용할 수 있다. 근데 이 변수에 다시 값을 할당 하는 경우는 import를 어떻게 했느냐에 따라서 동작이 달라진다!!!!!!!

헷갈리는 모듈 스코프 변수, 전역 변수처럼 쓸 수 있을까? - 아니 그냥 class level 변수를 쓰자.

일반적으로 다음과 같은 순서로 import해야 보기좋다.

1
2
3
4
5
6
7
8
import 표준 모듈


import 서드파티 모듈/패키지


import 로컬 어플리케이션 모듈/패키지

  • 설치된 package list는 list또는 pip freeze로 확인할 수 있다.
This post is licensed under CC BY 4.0 by the author.