Pythonのモジュール（Python学習メモ④）

## Pythonのモジュールについて
ポイント

* モジュール=Pythonの定義や文が入ったファイル
* ファイル名は`*.py`
* モジュールの中では、グローバル変数`__name__`の値としてモジュール名が設定されている

```python:sample_module.py
def func1(n):
    return n

def func2(n):
    return n

```
```python:Pythonのインタープリタにて
>>> import sample_module
>>> sample_module.func1('kawauso') 
'kawauso'
```

## モジュールのimportについて
ポイント

* モジュールには関数定義の他実行可能な文を入れることもできる
* これらの文はimport時に実行されるため初期化として使う
* モジュールはそれぞれがプライベートなシンボル表を持つため、呼び出し側との名前の衝突を気にする必要はない
* モジュール内のグローバル変数にアクセスするには、modulename.itemnameの形で書けばよい
* モジュールから他のモジュールも呼べる
* 慣例としてimport文はファイルの先頭に記載する
* モジュール内に定義されている名前を個別に呼び出すことも可能 

```python
>>> from sample_module import func1, func2
>>> func1('kawauso')
'kawauso'
>>> func2('kawauso')
'kawauso'
```
* `*`を使用すれば、モジュールが定義する_で始まる以外のすべての名前を取り込む
* 一般的には何がimportされるのか一見して分かりづらいため、好まれない書き方である

```python
>>> from sample_module import *
>>> func1('kawauso')
'kawauso'
```

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

* `python sample_module.py 引数`にモジュールを実行することもできる
* この時、`__name__`は`__main__`になる
* これを利用して、スクリプトとして実行された場合の分岐が書ける

```python:sample_module.py
def func1(n):
    return n

def func2(n):
    return n

# モジュールとしてインポートされたときは、このコードは走らない
if __name__ == "__main__":
    import sys
    func1(sys.argv[1])
```
```bash
$ python sample_module.py kawauso
kawauso
```

### モジュールの検索パス
ポイント

* モジュールがインポートされるとき、Pythonのインタープリタはまずビルトインモジュールを探しにいく
* 次にsys.path変数で得られるディレクトリのリストを使ってファイル名を検索する
* sys.pathは初期化後にプログラムから改変できる
* 実行中のスクリプトのあるディレクトリは、検索パスの最初に置かれる
* そのため、標準ライブラリと同名のモジュールがあれば、そちらが優先的にロードされる


### コンパイル済みのPythonファイルについて
ポイント

* モジュール読み込みの高速化のため、Pythonはコンパイル済みのモジュールを`__pycache__`ディレクトリにキャッシュする
* コンパイル済みのモジュールは`module.バージョン名.pyc`の名前でキャッシュされる
* この命名規約によって異なるリリース、異なるバージョンのPythonでも共存できるようになっている
* Pythonはソースファイルの最終変更日時をコンパイル済みのバージョンと比較し、再コンパイルが必要かどうか判断している

## 標準モジュール
ポイント

* pythonには標準モジュールのライブラリが付属する
* モジュールにはインタープリタに組み込みのものもあり、性能上の必要性やOSプリミティブな処理を行うために組み込まれる
* これらはインストール時の設定や、プラットフォームによって決まる
* たとえば`winreg`モジュールはWindowsシステムにのみ提供される
* こういったモジュールでは`sys`が代表的(これはすべてのPythonインタープリタで組み込みになっている)
* `sys.ps1`, `sys.ps2`などの変数はインタープリタが対話モードの時のみ定義されている
* `sys.path`はインタープリタのモジュール検索パスを指定する文字列のリスト
* `sys.path`は`PYTHONPATH`か、組み込みのデフォルトパスとなっている
* これは標準のリスト操作で改変可能

## dir()関数
* ビルトイン関数`dir()`は、モジュールが定義している名前を確認するのに使う(返り値は文字列のリスト)

```python
>>> import module
>>> dir(module) 
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'func1', 'func2']
```

* 引数なしの`dir()`は現在のレベルで定義されている名前のリストを返す

```python

>>> hoge = 'hoge' 
>>> dir()
['__annotations__', '__builtins__', 
'__doc__', '__loader__', '__name__', '__package__', '__spec__', 'hoge', 
'module']
>>>
```

* `dir()`はビルトインの関数名と変数名をリストアップしない
* 確認したかったら、標準モジュール`builtins`を確認する

```python
>>> import builtins
>>> dir(builtins) 
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 
'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 
'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 
'RecursionError', 'ReferenceError', 
'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError', '_', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 
'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 
'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']
```

## パッケージ
ポイント

* パッケージは「ドット区切りモジュール名」を使ってPythonのモジュールを構築する方法
* 例えばモジュール名`A.B`は、パッケージ「A」にあるサブモジュール「B]を指す
* あるディレクトリをパッケージを含むものと扱わせるには`__init__.py`ファイルが必要
* `__init__.py`は単なる空ファイルとすることもできるが、パッケージの初期化コードを実行したり、`__all__`変数をセットすることもできる

```:パッケージ構成の例
A/ -> トップレベルパッケージ
    __init__.py -> パッケージの初期化
    A1/  -> サブパッケージ1
        __init__.py
        hoge1.py
        hoge2.py
        hoge3.py
    A2/ -> サブパッケージ2
        __init__.py
        hoge1.py
        hoge2.py
        hoge3.py
```

```python:パッケージの読み込み
import A.A1.hoge1
# これに対する参照はフルネームで行う必要がある
A.A1.hoge1.func1()
```

また、別のロード方法もある

```python:パッケージの読み込み2
from A.A1 import hoge1
hoge1.func1()
```

さらに、目的の関数や変数を直接インポートする方法もある

```python
from A.A1.hoge1 import func1
func1()
```

### パッケージから*をインポート
ポイント

* `from A.A1 import *`とするとき、パッケージの`__init__.py`のコードが`__all__`変数を定義していた場合、そこに定義されたパッケージをimportする
* `__all__`が定義されていないときは、`A.A1`のインポートと、パッケージ本体で定義されているすべての名前のインポートのみ
* `__init__.py`で定義された名前もインポートされる

```python:__init__.py
__all__ = ["module1", "module2", "module3"]
module4 = func4() # module4もインポートされる
```

* それまでのimport文で明示的にロードされていたサブモジュールがあれば、それもインポートされる

```python
# echoおよびsurroundモジュールがインポートされる
import sound.effects.echo
import sound.effects.surround
from sound.effects import *
```

### パッケージ内の相互参照
ポイント

* パッケージがサブパッケージにより構成されているとき、姉妹パッケージの参照は絶対インポートの他、相対インポートも可能
* 相対インポートはカレントのモジュールに基づいて行われるので、メインモジュールからは必ず絶対インポートを使うこと
* メインモジュールの名前は常に`__main__`となる

```python
from . import echo # 同じ階層
from .. import formats # 1つ上の階層
from ..filters import equalizer # 1つ上の階層のfiltersサブモジュール内
```


### 複数のディレクトリにまたがるパッケージ
ポイント

* `__path__`は書き換えることでパッケージに含まれるモジュールやサブパッケージの検索に影響を与える
* パッケージが持つモジュールの集合を拡張したいときに使える