2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ゼロから始めるPython(6)

Posted at

ゼロから始めるPythonの6回目になります。
5回目はこちら

#おさらい
前回は主にリストやシーケンス型の詳細についてまとめていました。
引き続き、分割の投稿となります。

#チュートリアル
Python3.7 チュートリアル

#13. モジュール(module)

2回目ではそれとなく触れていましたが、Pythonの処理はスクリプトファイルに定義できます。

ファイル名.py

このスクリプトファイルをモジュールと呼びます。
モジュールの中身はこれまで説明してきた内容の定義やプロパティ、変数や式などを入力しておきます。

##13.1. import

###13.1.1. 通常のimport

作成されたスクリプトは「import」により別のスクリプトで機能を取り込むことが出来ます。
この時、必須ではありませんがインポートをモジュールの先頭部に列挙して記述することが推奨されます。

import モジュール名
(実行)モジュール名.メソッド()

下記の例では、greetingモジュールを作成してモジュール内のメソッドhelloを呼び出します。

greeting.py
def hello(name):
    """ 引数の名前に対して挨拶 """
    print('hello,', name, '!')
console
>>> import greeting
>>> greeting.hello('taro')
hello, taro !

また、関数呼び出しを簡略化するために、関数自体をローカルな変数に代入できます。

>>> import greeting
>>> hello = greeting.hello
>>> hello('ziro')
hello, ziro !

###13.1.2. 部分的なimport

モジュール内の一部の機能のみをインポートしたい場合、次の記述によって部分的(正しくは指定した機能すべて)なインポートが出来ます。

from モジュール名 import 機能

次の例では、モジュールのうちの一つの機能のみインポートすることを検証します。

fibo.py
def fib(n):
    a, b  = 0, 1
    while a < n :
        print(a, end=' ')
        a, b = b, a+b
    print()

def fib_2(n):
    result = []
    a, b = 0, 1
    while a < n:
        result.append(a)
        a, b = b, a+b
    return result 
>>> from fibo import fib
>>> fib(1000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
>>> fib_2(1000)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'fib_2' is not defined

前述の形式と異なり、インポート対象のモジュール名は記述せずとも機能を記述するだけで実行できましたね。

これは、インポート対象のモジュールをどのような形でシンボルテーブルに保持しておくかにより変わってきます。
モジュール全体をインポートする場合は

インポート対象のモジュール名のみをシンボルテーブルに書き込む(参照を持つ)」ので、モジュール内の機能はシンボルテーブルに格納されていません。

一方、モジュール内の指定した機能のみをインポートする場合は

インポートした機能のみをシンボルテーブルに書き込む(参照を持つ)」ので、機能のみシンボルテーブルに格納されます。

###13.1.3. 全体のimportと部分的なimportの違い

検証してみましょう。まずはモジュール全体でインポートします。(機能は前述のとおりです)

>>> import fibo
>>> locals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__':<class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'fibo': <module 'fibo' from '~~/fibo.py'>}

最後に「~~/fibo.py」がありますね。(~~はスクリプトファイルまでのディレクトリを示しています)

では、機能だけをインポートしてみると。。。

>>> locals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'fib': <function fib at 0x7fb46e799ae8>}

こちらは「fib」のみ書き込まれていますね。
このように、モジュールのインポートはどのようにインポートするのかを指定することが出来ます。

###13.1.4. 非推奨のimport

Pythonプログラマ(=Pythonista)は基本的に次のようなインポート方法はほとんどしません。
予期しない機能が含まれていることにより、定義済みの機能を上書きしてしまうことがあります。

from 機能 import *

また、このインポートはモジュール全体のインポートのように取り込まれてしまい、
機能内部の定義のみならず変数までも取り込んでしまうのです。

では検証してみます。
greeting.pyに変数を作成してimportしてみましょう。

greeting.py
global_var = 'global'

def hello(name):
    """ 引数の名前に対して挨拶 """
    print('hello,', name, '!')
console
>>> from greeting import *
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'global_var', 'hello']
>>>

変数global_varが取り込まれていることがわかります。

###13.1.5. import対象モジュールの別名定義

import対象のモジュールに対して別名を定義することも出来ます。

import モジュール as 別名
from 機能名 import モジュール as 別名

呼び出す時は別名で定義された名前で参照します。

##13.2. __name__属性

モジュール内では、そのモジュール名を「__name__」というグローバル変数を用いて取得できます。

用途はいろいろあるかと思いますが、大きな用途としては
メインの処理として実行された場合の動作を定義する」ことです。

次のコードを見てください。ファイルは上述の「fibo.py」です。

fibo.py
def fib(n):
    a, b  = 0, 1
    while a < n :
        print(a, end=' ')
        a, b = b, a+b
    print()

def fib_2(n):
    result = []
    a, b = 0, 1
    while a < n:
        result.append(a)
        a, b = b, a+b
    return result

if __name__ == "__main__" :
    print('loaded')
    fib(2000)
console
>>> import fibo
>>> fibo.fib(1000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
通常のコンソール
> python fibo.py
loaded
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597

いかがでしょうか?
スクリプトファイルがメインファイルとして起動されたときに実行される」ことがおわかりかと思います。

この方法は、モジュール単位の単体テストなどに非常に有効です。

##13.2. 標準モジュール

Pythonインタプリター内部にビルトインされている標準的なモジュールライブラリです。
これらは主に効率やOS機能の利用のために同梱されているものです。

次の例は、環境変数PYTHONPATHにパスを追加しています。

>>> import sys
>>> sys.path.append('/ufs/guido/lib/python')

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

モジュールをインポートする時、インタプリタでは次のように探索が行われます。
・組み込みモジュールから同一の名前のモジュールを検索
・sys.pathにあるディレクトリリストから同一の名前のモジュールを検索
なお、sys.pathは以下の場所に初期化されます。

・入力されたスクリプトのあるディレクトリ (あるいはファイルが指定されなかったときはカレントディレクトリ)。
・PYTHONPATH (ディレクトリ名のリスト。シェル変数の PATH と同じ構文)。
・インストールごとのデフォルト。

また、Pythonプログラムでsys.pathを修正できますので、プログラム内で動的にsys.pathにディレクトリを追加できます。

##13.4. コンパイル済みPythonファイル
Pythonでは、モジュール読み込み速度を高速化するために、コンパイル済みのモジュールをファイルにキャッシュします。
ファイルは__pychche__ディレクトリに格納されます。

ファイル名:module.version.pyc
ex.) example.cpython-36/pyc

ソースの変更日時をキャッシュファイルと比較して、必要に応じて再コンパイルします。
そしてキャッシュファイルはプラットフォームにも依存しないので、異なるアーキテクチャのシステムで共有ができます。

#14. パッケージ

参考サイト(Python にまつわるアイデア: Python のパッケージとモジュールの違い)

まず、パッケージとは
複数のモジュールをまとめたモジュール群
を指します。

この時、パッケージには2つの種類があります。

▼ 通常のパッケージ
 「__init__.py」というファイルが格納されたディレクトリ。
 __init__.pyはディレクトリをimport可能にするためのファイル。
 では例としてこのような構成でパッケージを作成しました。

構成

/sample_package/
 +-- parent/
       +-- __init__.py
       +-- module_parent.py
       +-- child1/
       |     +-- __init__.py
       |     +-- module_a.py
       |     +-- module_a_.py
       +-- child2/
       |     +-- __init__.py
       |     +-- module_b.py
       |     +-- module_b_.py
       +-- module_child.py

ファイル

module_parent.py
def call():
    print('module_parent called')
module_child.py
def call():
    print('module_child called')
module_a.py
def call():
    print('module_a called')
module_a_.py
module_var = 'example_a_'
def call():
    print('module_a_ called', module_var)
module_b.py
def call():
    print('module_b called')
module_b_.py
module_var = 'example_b_'
def call():
    print('module_a called',module_var)

実行結果

>>> from sample_package.parent.child1 import module_a
>>> from sample_package.parent.child2 import module_b
>>> module_a.call()
module_a called
>>> module_b.call()
module_b called

▼ 名前空間パッケージ
 PYTHONPATHの通っている別々のディレクトリに、同じパッケージ構造をもつディレクトリ。
「__init__.py」が含まれないことと、対すようパッケージの上位ディレクトリが異なることが条件。
なお、Python3.3以降でこの仕様が標準化されました。
それ以前の場合は__init__.pyにちょっと仕組みを入れなければならなかったそうです。

構成

+-- sample_namespace_package/
       +-- father/
       |     +-- child/
       |           +-- module_c.py
       +-- mother/
             +-- child/
                   +-- module_d.py  

(ファイル内容は割愛)

実行

>>> import sys
# モジュール検索パスに「child」ディレクトリの親ディレクトリを追加する
>>> sys.path.append('sample_namespace_package/father')
>>> sys.path.append('sample_namespace_package/mother')
>>> import child.module_c as c
>>> import child.module_d as d
>>> c.call()
module_c called
>>> d.call()
module_d called

異なるディレクトリ位置の「child」がまとめられていることがわかります。
では、__init__.pyをfather/child にいれてみます。

>>> import sys
# モジュール検索パスに「child」ディレクトリの親ディレクトリを追加する
>>> sys.path.append('sample_namespace_package/father')
>>> sys.path.append('sample_namespace_package/mother')
# 通常のパッケージとして認識される
>>> import child.module_c as c
>>> import child.module_d as d
# __init__.pyがないのでパッケージとして認識されない
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'child.module_d'

##14.1. __init__.py

__init__.pyが含まれるディレクトリはパッケージとみなされることは説明しました。
では、__init__.pyは何者で、どのような役割があるのでしょうか?

まずは前述の通り、ファイルの存在によって格納ディレクトリをパッケージとして認識するという役割があります。

もう一つ、パッケージ内のモジュールをimportするときに初期化処理を行う役割があります。
まずはパッケージの構成とimportのおさらい。

上述のsample_packageを確認してください。

再掲
# パッケージ構成がわからないとimportできない
>>> from sample_package.parent.child1 import module_a
>>> from sample_package.parent.child2 import module_b
>>> module_a.call()
module_a called
>>> module_b.call()
module_b called

__init__.pyには何も入力していない状態でmodule_a,module_bをimportするには、そのモジュールまですべて指定しなければなりません。
では、この処理を初期化処理に組み込んでみるとどうでしょうか。
まずは、sample_package/__init__.pyに入力してみます。

__init__.py
from sample_package.parent.child1 import module_a
from sample_package.parent.child2 import module_b

そして、これらをimportして使用してみます。

>>> import sample_package
>>> sample_package.module_a.call()
module_a called
>>> sample_package.module_b.call()
module_b called

このように、一部のパスが省略されていることがわかります。
また、このように指定することで対象モジュールのみをimportできます。

>>> from sample_package import module_a
>>> from sample_package import module_b
>>> module_a.call()
module_a called
>>> module_b.call()
module_b called

段々と効用がわかってきましたね。

__init__.pyによって、パッケージ読み込み時の初期化処理も行えると。

##14.2. パッケージから * をimport
13.1.4.では、非推奨のimportがあることを説明しました。

from 機能 import *

では、**「非推奨だけど13.1.4.のimportが行われる」**シーンを想定した初期化処理も見てみます。

下記で扱う__init__.pyは、次の場所のファイルです。

/sample_package/
+-- parent/
       +-- __init__.py
       +-- module_parent.py
       +-- child1/
       |     +-- __init__.py ★ こいつ
       |     +-- module_a.py
       |     +-- module_a_.py
       +-- child2/
       |     +-- __init__.py
       |     +-- module_b.py
       |     +-- module_b_.py
       +-- module_child.py
sample_package\parent\child1__init__.py
__all__ = ['module_a','module_a_']

このグローバル変数__all__を用いることにより、importした場合に読み込まれるモジュールを制御することができます。

では結果を見てみましょう。

>>> from sample_package.parent.child1 import *
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'module_a', 'module_a_']
>>> from sample_package.parent.child2 import *
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']

child1の方のモジュールは明示的にimport対象を指定しているので、シンボルテーブルに追加されています。

でも、パッケージに新しくモジュールを追加するたびに更新しなければならないのは面倒ですよね。
そこで、次のように実装するとよいです。
参考

import os, glob
__all__ = [
    os.path.split(os.path.splitext(file)[0])[1]
    # かな漢字、≠などを対応させる場合は別の正規表現を用います
    for file in glob.glob(os.path.join(os.path.dirname(__file__), '[a-zA-Z0-9]*.py')
]

##14.3. パッケージ内の参照

現在のモジュールから相対的に上位階層のモジュールを参照(import)する場合、ドットを用いることができます。
例えば、現在のモジュールをmodule_aとし、module_a_をimportしてみます。

from . import モジュール

module_a.py
def call():
    print('module_a called')

# モジュール単体として実行された時
if __name__ == '__main__' :
    from . import module_a_
    print(module_a_.call())

そして、モジュールとして実行。

~package/sample_package/parent$ python -m child1.module_a
module_a_ called example_a_
None

#まとめ
Pythonの「モジュール」と「パッケージ」の概念について抑えることが出来ました。
もちろん、チュートリアルレベルで掘り下げていますので
「ここはこうしたほうがいいぜ!!!」や「そこ間違っとるやろ」など
コメントいただければ嬉しいです。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?