1.2.5. コードの再利用: スクリプトとモジュール

これまでの内容で命令は全てインタプリタに打ち込んできました. 全体が長い命令となる場合にはその方針を変え(テキストエディタを使って) スクリプトモジュール と呼ぶテキストファイルに書くことにします. テキストエディタは好みのもの (Python 用の構文ハイライト機能があるものがいいでしょう) か Python の科学ライブラリ一式に付属しているエディタ (例えば Python(x,y) に付属する Scite) を使いましょう.

1.2.5.1. スクリプト

ちなみに

まずは スクリプト を書いてみましょう, スクリプトは指示の一連の流れが書かれたファイル呼び出す度に実行されます。指示はおそらくインタプリタなどからコピーアンドペーストできます(ただし、インデント規則に注意して下さい)。

Python ファイルの拡張子は .py です. test.py という名前のファイルに以下の行を書き写すかコピーペーストしましょう:

message = "Hello how are you?"
for word in message.split():
print word

ちなみに

では, スクリプトを対話的に Ipython インタプリタ内部から実行してみましょう. これは科学技術計算用のスクリプトの最も一般的使い方でしょう.

注釈

Ipython でスクリプトを実行する構文は %run script.py です.例

In [1]: %run test.py
Hello
how
are
you?
In [2]: message
Out[2]: 'Hello how are you?'

スクリプトが実行されました。スクリプトで定義された変数(message など) はインタプリタの名前空間でも利用可能です。

ちなみに

他のインタプリタでもスクリプトを実行することができます(例 素の Python インタプリタでは execfile 等)。

また, ターミナル上(Linux/Mac のターミナル Windows のコマンドプロンプト)で実行することで 独立したプログラム としても実行できます. 例えば test.py ファイルが置かれたディレクトリにいる場合端末でこれを実行します:

$ python test.py
Hello
how
are
you?

ちなみに

独立したスクリプトはコマンドライン引数をとることもできます

file.py に:

import sys
print sys.argv
$ python file.py test arguments
['file.py', 'test', 'arguments']

警告

オプションの構文解析には利用しないようにして下さい、その場合は optparseargparse または docopt のようなモジュールを利用して下さい。

1.2.5.2. モジュールからオブジェクトをインポート

In [1]: import os
In [2]: os
Out[2]: <module 'os' from '/usr/lib/python2.6/os.pyc'>
In [3]: os.listdir('.')
Out[3]:
['conf.py',
'basic_types.rst',
'control_flow.rst',
'functions.rst',
'python_language.rst',
'reusing.rst',
'file_io.rst',
'exceptions.rst',
'workflow.rst',
'index.rst']

さらに:

In [4]: from os import listdir

略記でインポート:

In [5]: import numpy as np

警告

from os import *

これは star import と呼ばれています、そして これを使わないで下さい

  • コードが読みにくく, 理解しづらくなります: シンボルがどこから来たかわかりますか?

  • 名前と文脈から機能を予測することが不可能になります(ヒント: os.name は OS の名前), さらに tab 補完の恩恵を受けるのに便利です.

  • 命名できる変数名が制限されます: os.namename を上書きします、そしてその逆も.

  • モジュール間の名前の衝突を生みます。

  • 未定義のシンボルを静的に調べることが不可能になります.

ちなみに

モジュールはこのように階層的にコードをまとめるのに有効です。実際、私達が利用しようとしている全ての科学技術計算ツールはモジュールになっています:

>>> import numpy as np # data arrays
>>> np.linspace(0, 10, 6)
array([ 0., 2., 4., 6., 8., 10.])
>>> import scipy # scientific computing

Python(x,y) の中のソフトウェア Ipython(x,y) は起動時に以下のインポートを実行します:

>>> import numpy
>>> import numpy as np
>>> from pylab import *
>>> import scipy

つまり, これらのモジュールの再インポートは不要です.

1.2.5.3. モジュールの作成

ちなみに

いくつかのオブジェクト(変数,関数,クラス)が定義され, 何度も再利用したくなるような(単純なスクリプトに比べて)より大きなまとまったプログラムを書きたい場合には自分自身の モジュール を作成します.

demo.py に書かれた demo モジュールを作ってみましょう:

"A demo module."
def print_b():
"Prints b."
print 'b'
def print_a():
"Prints a."
print 'a'
c = 2
d = 2

ちなみに

このファイルには私達が定義した2つの関数 print_aprint_b があります. インタプリタから print_a 関数を呼び出したい仮定します. ファイルをスクリプトとして実行することもできますが print_a 関数にアクセスしたいだけなので モジュールとしてインポート しましょう. 構文は以下です.

In [1]: import demo
In [2]: demo.print_a()
a
In [3]: demo.print_b()
b

モジュールをインポートすることでそのオブジェクトに module.object 構文でアクセスすることができます. オブジェクトの名前の前にモジュールの名前を忘れないで下さい, 忘れると Python は命令を理解してくれません.

内観

In [4]: demo?
Type: module
Base Class: <type 'module'>
String Form: <module 'demo' from 'demo.py'>
Namespace: Interactive
File: /home/varoquau/Projects/Python_talks/scipy_2009_tutorial/source/demo.py
Docstring:
A demo module.
In [5]: who
demo
In [6]: whos
Variable Type Data/Info
------------------------------
demo module <module 'demo' from 'demo.py'>
In [7]: dir(demo)
Out[7]:
['__builtins__',
'__doc__',
'__file__',
'__name__',
'__package__',
'c',
'd',
'print_a',
'print_b']
In [8]: demo.
demo.__builtins__ demo.__init__ demo.__str__
demo.__class__ demo.__name__ demo.__subclasshook__
demo.__delattr__ demo.__new__ demo.c
demo.__dict__ demo.__package__ demo.d
demo.__doc__ demo.__reduce__ demo.print_a
demo.__file__ demo.__reduce_ex__ demo.print_b
demo.__format__ demo.__repr__ demo.py
demo.__getattribute__ demo.__setattr__ demo.pyc
demo.__hash__ demo.__sizeof__

main の名前空間にオブジェクトをインポートする

In [9]: from demo import print_a, print_b
In [10]: whos
Variable Type Data/Info
--------------------------------
demo module <module 'demo' from 'demo.py'>
print_a function <function print_a at 0xb7421534>
print_b function <function print_b at 0xb74214c4>
In [11]: print_a()
a

警告

モジュールのキャッシュ

モジュールはキャッシュされます: demo.py を変更して変更前のセッションで再インポートした場合, 変更前のモジュールがインポートされます.

解決法:

In [10]: reload(demo)

Python3 では reload は組み込み関数ではなくなっています、そのため importlib オジュールを最初に呼び出して以下のようにする必要があります:

In [10]: importlib.reload(demo)

1.2.5.4. ‘__main__’ とモジュールのロード

ちなみに

しばしば、モジュールを直接走らせたときにはそのまま実行し、他のモジュールから import されたときには実行しないコードを書きたいことがあります。 if __name__ == '__main__' を使うことで直接走らせているかを確認することができます。

ファイル demo2.py:

def print_b():
"Prints b."
print 'b'
def print_a():
"Prints a."
print 'a'
# print_b() runs on import
print_b()
if __name__ == '__main__':
# print_a() is only executed when the module is run directly.
print_a()

インポートする:

In [11]: import demo2
b
In [12]: import demo2

実行する:

In [13]: %run demo2
b
a

1.2.5.5. スクリプトそれともモジュール? ソースコードのまとめ方

注釈

確かな経験則

  • コードの再利用性を高めるために, たくさん呼ばれる命令の組は 関数 内に書くべきです.

  • いくつかのスクリプトから呼び出される関数(もしくはソースコードの一部分)は モジュール の中に書くべきです, そうすることで異なるスクリプトから呼び出されるのはそのモジュールだけになります(違うスクリプトに関数をコピーペーストするのはやめましょう!)

1.2.5.5.1. どうモジュールは探索、インポートされるか

import mymodule 文が実行されるとモジュール mymodule が与えられたディレクトリのリストから探し出されます。このリストは Python をインストールした場所に依存するデフォルトのパス(例 /usr/lib/python) を含み、環境変数 PYTHONPATH で指定したディレクトリも含んでいます。

Python が検索するディレクトリの一覧は sys.path 変数で与えられます

In [1]: import sys
In [2]: sys.path
Out[2]:
['',
'/home/varoquau/.local/bin',
'/usr/lib/python2.7',
'/home/varoquau/.local/lib/python2.7/site-packages',
'/usr/lib/python2.7/dist-packages',
'/usr/local/lib/python2.7/dist-packages',
...]

モジュールは検索パス内に配置されていなければいけません、そのためにできることは:

  • 検索パスとして既に定義されているディレクトリ (例 $HOME.local/lib/python2.7/dist-packages) に自作モジュールを書く。コードを別の場所に置いておくために(Linux では)シンボリックリンクを使うことも可能です。

  • 環境変数 PYTHONPATH を編集しユーザ定義モジュールのあるディレクトリを含むようにします。

    ちなみに

    Linux/Unix では、シェルの起動時に読みだされるファイル(例 /etc/profile, .profile) に以下の行を加えます

    export PYTHONPATH=$PYTHONPATH:/home/emma/user_defined_modules
    

    Windows では, http://support.microsoft.com/kb/310519 で環境変数の扱い方が説明されています。

  • または, Python スクリプトの中で sys.path 変数を変更する。

    ちなみに

    import sys
    
    new_path = '/home/emma/user_defined_modules'
    if new_path not in sys.path:
    sys.path.append(new_path)

    この方法は (ユーザによって変わるパスを設定するため)ソースコードの可搬性が悪く, ディレクトリからモジュールをインポートする度 sys.path を変更しなければならないため、変更が少ない頑強な方法ではありません.

参考

モジュールに関するさらなる情報は https://docs.python.org/tutorial/modules.html を見てください。

1.2.5.6. パッケージ

多くのモジュールを含むディレクトリは パッケージ と呼ばれます。パッケージはサブモジュールを持つモジュールのことです(サブモジュールは自身のサブモジュールを持っていてもかまいません)。 __init__.py と呼ばれる特殊なファイル(これは空でもかまいません)は Python にこのディレクトリは Python パッケージであること、どのモジュールがインポートできるのか、を伝えます

$ ls
cluster/ io/ README.txt@ stsci/
__config__.py@ LATEST.txt@ setup.py@ __svn_version__.py@
__config__.pyc lib/ setup.pyc __svn_version__.pyc
constants/ linalg/ setupscons.py@ THANKS.txt@
fftpack/ linsolve/ setupscons.pyc TOCHANGE.txt@
__init__.py@ maxentropy/ signal/ version.py@
__init__.pyc misc/ sparse/ version.pyc
INSTALL.txt@ ndimage/ spatial/ weave/
integrate/ odr/ special/
interpolate/ optimize/ stats/
$ cd ndimage
$ ls
doccer.py@ fourier.pyc interpolation.py@ morphology.pyc setup.pyc
doccer.pyc info.py@ interpolation.pyc _nd_image.so
setupscons.py@
filters.py@ info.pyc measurements.py@ _ni_support.py@
setupscons.pyc
filters.pyc __init__.py@ measurements.pyc _ni_support.pyc tests/
fourier.py@ __init__.pyc morphology.py@ setup.py@

Ipython から:

In [1]: import scipy
In [2]: scipy.__file__
Out[2]: '/usr/lib/python2.6/dist-packages/scipy/__init__.pyc'
In [3]: import scipy.version
In [4]: scipy.version.version
Out[4]: '0.7.0'
In [5]: import scipy.ndimage.morphology
In [6]: from scipy.ndimage import morphology
In [17]: morphology.binary_dilation?
Type: function
Base Class: <type 'function'>
String Form: <function binary_dilation at 0x9bedd84>
Namespace: Interactive
File: /usr/lib/python2.6/dist-packages/scipy/ndimage/morphology.py
Definition: morphology.binary_dilation(input, structure=None,
iterations=1, mask=None, output=None, border_value=0, origin=0,
brute_force=False)
Docstring:
Multi-dimensional binary dilation with the given structure.
An output array can optionally be provided. The origin parameter
controls the placement of the filter. If no structuring element is
provided an element is generated with a squared connectivity equal
to one. The dilation operation is repeated iterations times. If
iterations is less than 1, the dilation is repeated until the
result does not change anymore. If a mask is given, only those
elements with a true value at the corresponding mask element are
modified at each iteration.

1.2.5.7. 良い習慣

  • 意味がある オブジェクト を使う

  • インデント: 選択の余地なし!

    ちなみに

    Python ではインデントは強制されます. コロンに続く全てのコマンドブロックは前のコロンの行より1段インデントが深くなります. なので def f():while: の後はインデントしなければいけません. この論理ブロックが終ると, インデントが浅くなります(そして新しいブロックに入るとまた深くなります。)

    インデントを厳格に扱うことで他の言語で論理ブロックを示す文字 {; を取り除くことができます. 不適切なインデントはこのようなエラーを起こします

    ------------------------------------------------------------
    
    IndentationError: unexpected indent (test.py, line 2)

    はじめはこのインデント作業で少し混乱するかもしれませんが, はっきりとインデントすることで余計な文字列がなくなり, 他の言語と比べて読みやすいすてきなソースコードになります.

  • インデントの深さ: テキストエディタでは, インデントのスペースの数を任意の正の数 (1, 2, 3, 4, ...) にできるかもしれません. しかし, インデントにはスペース4つ分 がよい習慣とされています. エディタの設定で Tab キーをインデントのための4文字分のスペースに割り当てることができるでしょう. Python(x,y) ではエディタでは既にそのように設定されています.

  • スタイルガイド

    長い行: とても長い、(例えば)80文字以上に越える行は書いてはいけません。長い行は \ 文字で分割することができます:

    >>> long_line = "Here is a very very long line \
    
    ... that we break in two parts."

    スペース

    うまくスペースを入れてソースコードを書きましょう:カンマの後や算術演算子の周り等に空白を入れましょう:

    >>> a = 1 # yes
    
    >>> a=1 # too cramped

    “きれいな”ソースコードを書くためのいくつかの規則が Style Guide for Python Code に与えられています (みんなで同じ規則を使う, という意味でもより重要です)。


急いで読みたい人は

エコシステムを知るために、講義を素早く進めたい場合、次の NumPy: 数値データの作成と処理 まで読み飛ばしてもかまいません。

この入門章の残りの部分は必ず必要というわけではありません。しかし、後に戻ってきて完了することになるでしょう。