４０代おっさんPythonを勉強する（パッケージとモジュール編）

## 本記事について

この記事はプログラミング初学者の私が学んでいく中でわからない単語や概要を分かりやすくまとめたものです。
もし不正などありましたらコメントにてお知らせいただければ幸いです。

## パッケージとモジュール

### スタンドアロンプログラム

- 現実的な大規模のプログラムはファイルに保存してスタンドアロンプログラムを作成しなければならない
- filename.pyのプログラムを書いてみよう

```python
print('こんにちは')
```

上記のPthyonコードをtest1.pyというファイルに保存
ターミナルウィンドウで開いてPythonのコマンドで実行

```bash
python3 test1.py
```

### コマンドライン引数

コマンドライン引数とは、コンピューターのコマンド入力画面（コマンドライン）からプログラムを起動する際に指定する文字列のこと

- test2.pyを作成

```python
import sys
print('プログラム引数:', sys.argv)
```

```bash
python3 test2.py
```

プログラム引数:['test2.py']

- sys.argv属性
  - Pythonのスクリプトを実行する際には、その実行に必要な情報を与えたり、振る舞いを変更したりするためにコマンドライン引数として何らかの値を渡せる。

- sys.argvでコマンドラインの引数を保存している

```bash
python3 test2.py inputfile bla bla
```

- sys.argv[0]は自身のプログラム名で、その後は引数になる
- 入力ファイルの読み込みの時や、オプションを付けるときに役に立つ

### モジュール(module)とimport文

- プログラムが大きくなると、1つのファイルで収まるのは読みにくくなるし、メンテナンスも難しくなる
- Pythonコードを複数のファイルで作り、メインプログラムからそれらを使うときimportで読み込むと良い
- Pythonの標準ライブラリや、サードパーティにより作ったモジュールも同じくimportできる
- 上のコードで書いたimport sysもPythonのsysモジュールを読み込むこと

例えば、report.pyにこのような関数を定義している

```python
#  report.py

from random import choice
#  Pythonの標準ライブラリrandomを使う
#  choiceは無作為で選択する関数

possibilities = ['sunny', 'rainy', 'snow', 'cloudy', 'fog']

def get_today():
    return choice(possibilities)

def get_tomorrow():
    return choice(possibilities)

if __name__ == "__main__":
    print(get_today())
    print(get_tomorrow())
```

2)そして、メインプログラムforecast.pyにこのように呼び出す

```python
#  forecast.py

import report # report モジュールをインポートする

today = report.get_today() # reportモジュールの関数を呼び出す
print('Today\'s weather: ', today)
tomorrow = report.get_tomorrow() # reportモジュールの関数を呼び出す
print('Tomorrow\'s weather: ', tomorrow)
```

3)ターミナルでforecast.pyを実行

```bash
python3 forecast.py
```

- モジュールをインポートする時いろんな呼び方がある

```python
import report
#  一番簡単なインポートの呼び方
#  関数を呼び出す時モジュールの名前で内容を修飾することで、コードが長くなる
#  間違いがない
report.get_today()
report.get_tomorrow()
```

```python
import report
from report import get_today, get_tomorrow
#  モジュールからその2つの関数しか使わない場合
#  コードが短くなるが、他に同じ名前の関数がある場合は優先順位に要注意
get_today()
get_tomorrow()
```

```python
import report
from report import get_today, get_tomorrow
#  モジュールからその2つの関数しか使わない場合
#  コードが短くなるが、他に同じ名前の関数がある場合は優先順位に要注意
get_today()
get_tomorrow()
```

```python
import report as r
#  モジュールの名前が長い時に役に立つ
r.get_today
r.get_tomorrow
```

```python
from report import get_today as today
#  関数名を好きな名前で呼び出す
today()
```

```python
from report import *
#  モジュール内で全ての関数を読み込む
#  構文上には可能だが、良くない習慣
#  未知の関数が読み込まれ、定義済みの関数を上書きしてしまう可能性がある
#  また、可読性に乏しいコードになるから、避けた方がよい
get_today
get_tomorrow
```

### モジュールをスクリプトとして実行する

- Pythonモジュールをメインプログラムとして実行することが可能
- 上のreport.pyの中で出てきたこのコード

```python
if __name__ == "__main__":
    print(get_today())
    print(get_tomorrow())
```

- このコードを追加することで、このファイルがimportできるモジュールであると同時にメインプログラムとしても使えるようになる
- モジュールが"main"ファイルとして起動された時だけ、この部分のコードが実行されるから
- 別のプログラムでこのモジュールがimportされた場合は、このコードは実行されない
- このコードはモジュールを作成する時テストのためにとても役に立つ

### モジュール検索パス

- インポートする時、ファイルを探しに行くかは、優先順位がある
- sysモジュールで調べられる

```python
import sys
sys.path
```

- sys.path変数に格納されているリストを使って、ファイルを探す
- もし同じ名前の関数を自分のディレクトリでも定義したら、他のライブラリの関数がアクセスできなくなる。

### パッケージ

- パッケージとは階層構造に組織するモジュール群のこと
- サードパーティによるたくさんのライブラリがあって、それぞれ同じ名前のモジュールや関数などを避けることは不可能
- パッケージにすると、トップレベルの名前だけ違って、モジュールは階層的に呼び出すので、曖昧はなくなる

例えば、こんな階層のディレクトリや、モジュールがあるとする

```test
sound/                          Top-level package
      __init__.py               Initialize the sound package
      formats/                  Subpackage for file format conversions
              __init__.py
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...
      effects/                  Subpackage for sound effects
              __init__.py
              echo.py
              surround.py
              reverse.py
              ...
      filters/                  Subpackage for filters
              __init__.py
              equalizer.py
              vocoder.py
              karaoke.py
              ...
```

- パッケージをimportする際、トップレベルのパッケージの入ったサブディレクトリを探しに行く

```python
import sound.effects.echo # echoモジュール
from sound.effects import echo # echoモジュール
from sound.effects.echo import echofilter # echofilter関数だけ
```

- パッケージが入ったディレクトリとして扱わせるには、ファイル`__init__.py`が必要
- `__init__.py`のコードに`__all__`という名前のリストに全てのモジュールの名前が書いてある
- 例えば、`sound/effects/__init__.py`には、次のようなコードは入っている

```python
__all__ = ["echo", "surround", "reverse"]
```

- `__init__.py`はただの空ファイルでも構わないが、*をimportすることができなくなる

参考：[https://docs.python.jp/3/tutorial/modules.html](https://docs.python.jp/3/tutorial/modules.html)

## 参考

https://www.kitakyu-u.ac.jp/news/2022/08/003215.html