モジュールによるPythonの機能拡張
プログラミングにおいて頻繁に使われるデータ型や関数は、組み込み型や組み込み関数として Python 本体に内蔵されていて、特別なことをしなくても使えるようになっています。
組み込み型や組み込み関数だけを使って、目的のプログラムを作ることが難しいと感じたときは、部品の自作を始める前に Python に付属している標準ライブラリを探します。Anaconda をインストールしている環境であれば、標準ライブラリ以外にも numpy や scipy といった豊富なサードパーティーのライブラリ群が使えるようになっています。
ライブラリとはモジュールの集合体です。モジュールとは拡張子.py のついたファイルのことです。拡張子.py といえば Python のプログラムを保存しておくファイルの形式ですから、モジュールとはつまり、Python のコードで記述されたクラスや変数、関数などのオブジェクト (Python では全てがオブジェクトです) が入ったファイルのことです。
外部の Python コードを読み込むことで、そこに定義されているクラスや関数を使えるようにすることを、モジュールをインポートするといいます。使いたいモジュール名の前に import を添えると、指定モジュールがインポートされます。
import モジュール名
一般に、1つのモジュールには複数のクラスや関数がまとめて記述されています。このモジュールに含まれている関数を使うときは、
モジュール名.関数名
と記述します。例として数学関数をまとめてあるmathモジュールをインポートして、平方根を計算する sqrt関数を使ってみます。
# PYTHON_MODULE # In[1] # mathモジュールをインポート import math # 100の平方根を計算 x = math.sqrt(100) print(x) # 10.0
特定の関数をインポートする
多くの場合、モジュールにはたくさんの種類の関数が含まれていますが、その中から1つか2つの関数だけを使うというケースもあります。関数を使うたびにモジュール名を添えるのは煩わしいと思うのであれば、
from モジュール名 import 関数名
と記述することで、指定した関数を関数名の記述だけで使えるようになります。先ほどのコードを少し変えて、mathモジュールからsqrt関数を選んでインポートしてみます。
# In[2] # mathモジュールからsqrt関数をインポート from math import sqrt # 100の平方根を計算 x = sqrt(100) print(x) # 10.0
複数の関数を読み込むときには、import のあとに関数名をカンマ (,) で区切って記述します。
# In[3] # mathモジュールから定数eとpiの近似値をインポート # eは自然対数の底(ネイピア数), piは円周率の近似値 from math import e, pi print(e, pi) # 2.718281828459045 3.141592653589793
import の後ろにワイルドカード (*) を添えることで、モジュールに収められている関数をまとめてインポートすることも可能です。
# In[4] # mathモジュールのすべてのオブジェクトをインポート from math import* print(e, pi, log(e)) # 2.718281828459045 3.141592653589793 1.0
ただし、モジュール名の省略には大きな副作用を伴う可能性があります。モジュールに収められた関数の中には e や pi のように非常に短い名前のものがあるので、インポートしたときに、すでにプログラムの中で使われている関数名や変数名がかぶってしまうことがあるからです。そうすると変数名などが上書きされて思わぬ誤作動が生じるかもしれません。これはエラーとして扱われるわけではないので、知らないうちにプログラムが予想とは異なる動作をしている可能性があるのです。特に1つのプログラムで複数のモジュールをインポートする場合、その危険性は一気に増します。それぞれのモジュールに共通して使われている関数名は非常に多いからです。たとえば、Math と NumPy には sin や cos という同名の関数があります。ですから、安全第一に考えるのならば、基本通りにモジュール名.関数名と記述したほうがよいでしょう。モジュール名が長すぎる場合には、次に説明するように、自分でモジュールの名前を定義するという方法もあります。
モジュールに新しい名前をつける
モジュールの中には、名前がやたらと長いものがあります。そのようなモジュールの機能を使うたびに名前を記述するのは面倒だと思えば、自分で勝手な名前を定義することもできます。
import モジュール名 as 読み込む名前
下のサンプルで乱数を生成する関数をまとめた randomモジュールを rd という名前で読み込んで、randint関数で整数乱数をつくってみます。
# In[5] # randomモジュールをrdという名前でインポート import random as rd # 1~100の乱数を生成 x = rd.randint(1, 100) print(x) # 47
いくつかのモジュールにおいては、読み込むときの名前が慣例的に決まっているものもあります。たとえばグラフ描画の機能をまとめた matplotlib.pyplot モジュールをインポートしたときには plt という名前で取り扱うことが(世界中で)暗黙のルールとなっています。数学関数や行列を扱う numpy は np という名前で扱うことになっています。
クラスのインポート
次は外部からクラスを読み込んでみましょう。標準ライブラリの datetime モジュールには日付や時刻を扱うための datetimeクラスが用意されています。クラスとはオブジェクトを生み出すための設計図のような概念で、要は datetimeクラスを読み込めば、datetime型オブジェクト(日付型データ)を利用できるようになると考えてください。次のサンプルでは datetime型オブジェクトに備わっている nowメソッドを使って現在の日付と時刻を取得しています。
# In[6] # datetimeモジュールからdatetimeクラスをdtという名前でインポート from datetime import datetime as dt # datetimeオブジェクトのnowメソッドで現在の日付と時刻を取得 x = dt.now() print(x) # 2018-09-30 08:15:51.063449
少しややこしいのですが、最初に datetimeモジュールからdatetimeクラスを dt という名前でインポートしています。これによって以降のコードでは、datetime型オブジェクトは dt という名前で扱うことができます。nowメソッドを使うときは dt.now と記述します。上のコードを実行するとマイクロ秒(百万分の一秒)の精度で日付と時刻を返してきます。
関数内部のインポート
def文を使って関数を定義するときに、関数ブロックでインポートしたモジュールは関数内部に限って有効です。たとえば、
def inner_sin(x): import math return math.sin(x)
のような関数を定義して、
inner_sin(0)
を実行すると「 0.0 」という値が返りますが、
math.sin(0)
を実行しても NameError (“そんな名前の関数はありませんよ”というエラー) が返ります。関数の外側ではモジュールがインポートされていないからです。
つまり、関数内部でインポートされた関数もローカル変数と似たような スコープのルール が適用されます。このようなルールがあるので、関数内部でモジュールのインポートを実行しておけば、ユーザーがその関数を利用するための環境に気をとらわれずに済むという利点があります。
もう少し深く調べていくと、関数内部のインポートが面白い性質を示すことに気づきます。たとえば、次のような関数を定義します。
def inner_sqrt(x): import numpy as np return np.sqrt(x)
np.sqrt(x) は配列 (ndarray) を受け取って各要素の平方根を返す関数です。inner_sqrt() は関数内部で numpy をインポートしているので、関数の外で配列を生成することはできません:
a = np.array([1, 2, 3]) # NameError: name 'np' is not defined
しかし、inner_sqrt() にリストを渡すと、ちゃんと配列が返ってくるのです:
inner_sqrt([1, 2, 3]) # array([1. , 1.41421356, 1.73205081])
本来ならば関数の外に存在しないはずの NumPy 特有のオブジェクトがローカルスコープを抜けて外に出てきています。NumPy 配列の演算機能もちゃんと使えます:
f = inner_sqrt([2, 4]) g = inner_sqrt([9, 16]) print(f + g) # [4.41421356 6. ]
つまり、関数の戻り値として設定しておけば、インポートもしていないのに、モジュール特有のオブジェクトは使えるという不思議な状態が実現するのです。昔のファミコンの裏技(← 古い)みたいで面白いですね。
コメント
「関数内部のインポート」は面白かったので、ランタイムを再起動してから f と g について四則演算や比較演算をやってみましたが、うまく動作しました。
面白いですよね! この面白さを理解してくれる人に会えて嬉しいです。たぶん、実務では使っちゃいけない書き方なんでしょうけど、たまにこうやって遊んでみると色々な発見があります。(^^)