2014年12月16日火曜日

Python高度なimport

同じフォルダにhogehoge.pyというファイルがあったら
import hogehoge
と書くことでインポートできるし、
from hogehoge import *
と書けば、本文中でhogehoge.メソッド名なんて書かずに直接メソッド名だけでメソッドを呼び出せる。asで別名とか、まあPythonのモジュールインポートのしくみでも見てもらえるといいと思う。

以下にパッケージの話が出てこないのは話したい話を単純にするための仕様です。特に「モジュールを探す範囲を拡張」なんかは普通はパッケージの仕組みを使ったほうが簡単にできると思う。でもそれだけでは出来ないことをここでは解説したい。

同じディレクトリに置きたくない!ってこともあると思います。そういうときは
sys.path.append('パス')
としてやればそこも探してくれるようになります。基本的には絶対パス。
import sys,os
sys.path.append(os.pardir) #親フォルダを追加
from i3r import printnum, printstr
とか
import sys,os
dirpath = os.path.dirname(os.path.abspath(__file__))
datadir = 'data'
sys.path.append(dirpath+'/'+datadir)
from i3k import *
とか自由にどうぞ。os.path.dirname(os.path.abspath(__file__))を使ってディレクトリの絶対パスを取得することで、ユーザによる Python コードの変更が必要となるため、sys.path.append() の使用はお勧めしません。なんて言いがかりも避けられる。
メソッドの上書き
たとえばi3r.pyで

def printnum():
print(3)
def printstr():
print("r")
と書いていて、i3k.pyで
from i3r import printnum, printstr

def printstr():
print('k')
とするとprintstr()の結果は上書きされてkになり、printnum()はそのまま3が出力される。
変数でモジュール名を指定
import式と等価な組み込み関数に__import__がある。

これを使えば、動的に生成されたモジュールを呼び出すことができ、メタプログラミングの際に有用だ。例えば下記の様になる。

modname = 'i3k'
module = __import__(modname)

module.printnum()
module.printstr()
ではfrom ... import ... はどうやって書くかというと、
modname = 'i3k'
methods = ['printnum','printstr']
module = __import__(modname, globals(), locals(), methods, -1)

module.printnum()
module.printstr()
こんな感じでimportする関数を指定できる。問題はmodule.を付けなければいけない事。さっきみたいに上書きとかしたい時はどうするか?
modname = 'i3k'
methods = ['printnum','printstr']
module = __import__(modname, globals(), locals(), methods, -1)

printnum = module.printnum #module.無しで使いたかったらコレ
printstr = getattr(module,'printstr') #この書き方でも同等

printnum()
printstr()
こんな感じでローカル変数に持って来れる。しかし、こうやって各個上書きするのは面倒だろう。for文とかでできないか?
modname = 'i3k'
methods = ['printnum','printstr']
module = __import__(modname, globals(), locals(), methods, -1)
for x in methods:
locals()[x]=getattr(module,x)

printnum()
printstr()
そう。「ローカル変数に持って来」ればいいのだからこの手が使える。ここまで来たらfrom i3k import *のようにメソッド一括みたいなこともできるかな?
modname = 'i3k'
module = __import__(modname)
exceplist = ['__builtins__', '__doc__', '__file__', '__name__', '__package__']
for x in dir(module):
if x not in exceplist:
locals()[x]=getattr(module,x)

printnum()
printstr()
可読性の面からは全然お勧めできないね。dir(モジュール名)でモジュールの全attributeを列挙してくれるから、その中から一般的に上書きしちゃいけないモノリストを挙げて、それ以外を上書きしている。まあ、あくまでこういうこともできるってことで。こういうメタなことをする時に重要なのはBuilt-in Functionsの知識。__import__もgetattrもdirも組み込み関数。

Pythonで可変関数
さっきの
locals()[x]=getattr(module,x)
ってのは前述のように「ローカル変数に持って来」てるのだが、可変関数を作っていると見ることができる。同様に、右辺に来るのをただの値にすれば可変変数も作れる。PHPは可変変数が楽だよとよく言われるが、Pythonも難しくないです。
ちなみに「可変変数」って変だなあと思ったが、英語でも"Variable variables"らしくて"Dynamic variables"とも言うが「動的変数」って別物について使うらしいし、召喚変数とかどうだろう。

0 件のコメント:

コメントを投稿