スマホ版は別ページ→

≪いっしょにPython≫ プログラマーの実験ノート

 このページは,プログラム言語Pythonをこれから学ぼうと考えている筆者の備忘録です.
 そこそこ調べて書いていますが,仕様を精密に調べたものではありません.どちらかと言えば,実装されたPython 3を使ってみてどうなったかということを中心に書いたものです.いわば,実験ノートの記録です.(最終更新年月日:2019.5.1 / by Python 3.7.3)
Pythonの練習には,
(1) IDLEというメニューから入って,>>>というプロンプトが表示される状態で使う場合
• 画面の上端がPython 3.7.x Shellとなっていて,2行目がFile, Edit, Shell, Debug, ...となっている
• 1行入力するたびに,エラーを確かめながら処理を進める,対話型インタプリタの形になっている
(2) IDLEというメニューから入って,左上端のFileをクリック→New Fileと進む場合
• 画面の上端がファイル名(初めはUntitled)となっていて,2行目がFile, Edit, format, Run, ...となっている
(3) Python 3.7.x という真っ黒なコマンドプロンプトの画面(ターミナルウィンドウ)から入るもの
の3通りが使えるが,以下は(2)のNew Fileから,テキストエディタを使って,プログラムを書く場合を考える.

1. デコレータ

(1)デコレータの概要
• デコレータを利用すると,自作の関数,ビルドイン関数,インポートした関数などを変更せずに,その関数の呼び出しのときに,その前後に補助的な作業を追加することができる.
• デコレータ(decorator)を直訳すると装飾するものとなるが,主な作業を行う関数が修飾される方であるのに対して,付加的・補助的な装飾を行う関数の方をデコレータという.例えば,手紙の本文を主な関数とすると,これを送るときにある封筒に相当するデコレータに包んで送ることにすると,「あいさつ文」「送った回数」「送り先」「引数に使った言葉」「送信に要した時間」などが自動で記録される仕組みにできる.
• 主な作業を行う修飾される方の関数の定義式の前に@デコレータ関数名,または@デコレータ関数名(引数)と書いておくと,修飾される方の関数を呼び出すたびに,デコレータ関数を通して実行されるようになる.
【書式】
def デコレータ関数名()
	…

@デコレータ関数名
def 主人公の関数名()
	…
@の働き(予備実験1)
次のようにfun1()の定義式の前に@deco1と書いておくと,fun1()を実行したときに,
deco1() takes 0 positional arguments but 1 was given
というエラーメッセージが返される.
【例 1.1】
IDLE → New File → Save as .. → Run
def deco1():
    print('d')
    return
@deco1
def fun1():
    print('f')
fun1()
これは,deco1()という関数を引数なしで定義しているいるのに,fun1が引数として渡されたために起きたエラーだと考えられる.
そこで,次のようにdeco1()の方に引数を受け止められるように変更する.(このデコレータをこの関数fun1()だけに使うとは限らない.他のいろいろな関数の装飾に使えることが分かるように,仮引数は仮の名前fxで受ける)

【例 1.2】
IDLE → New File → Save as .. → Run
def deco1(fx):
    print('d')
    return
@deco1
def fun1():
    print('f')
fun1()
fun1()
TypeError: 'NoneType' object is not callable
今度は,deco1()のreturn文が何も指定していないから,fun1()が呼び出せないということらしい.
そこで,次のようにdeco1()のreturn文に引数の関数名を書く.

【例 1.3】
IDLE → New File → Save as .. → Run
def deco1(fx):
    print('d')
    return fx
@deco1
def fun1():
    print('f')
fun1()

d
f
これにより,deco1()を行ってからfun1()が行われるようになる.
(ここまでのまとめ)
(1) 次のように,主人公の関数名fun1()の定義式の前に@deco1と書くと,deco1()関数の引数として主人公の関数名fun1が渡される:deco1(fun1).デコレータ関数の引数として主人公の関数名が渡されるとは,主人公の関数のアドレスが渡されるということ
(2) そのとき,さらにfun1()関数を呼ぶようにdeco1()関数の戻り値をfun1に代入する.
 このように,fun1()を呼び出すときに,deco1()経由で呼び出すようにするとは,次の形と同じになる.
fun1 = deco1(fun1)
 デコレータを付けると,簡単に@deco1と書くだけで,上記と同じ効果が出せる.
def deco1(fx):
  …
  return fx
@deco1
def fun1():
  …
引数の渡し方(予備実験2)
• 主人公の関数を呼び出すときに,常にデコレータ経由となるようにできても,関数によっては,引数がない場合も,可変長の場合も,辞書型引数になっている場合もある.どの場合でもデコレータが受け止められるようにするには,次のように関数を入れ子にすればよい.
【例 1.4】
IDLE → New File → Save as .. → Run
def deco1(fx):
    def inner(*args, **dics):
        print('before:')
        return fx(*args, **dics)
    return inner
@deco1
def fun1():
    print('f')
• このようにすると,主人公の関数fun1()に引数がない場合,可変長の場合,辞書型引数の場合のいずれの場合でも,*args, **dicsが受け止めることができる.また,関数fun1()は,return fx(*args, **dics)によって実行される.
• return innerは,fun1 = deco1(fun1)における左辺への代入に対応しており,このreturn innerがないと,fun1() TypeError: 'NoneType' object is not callableというエラーメッセージが表示される.
• 例1.4によって,目標とするデコレータとしては正常に作動するが,return fx(*args, **dics)によって,主人公の関数fun1()を実行するところが難点となる.なぜなら,そのようにすると,必ず最後にfx()が実行されることになり,前処理'before'には対応できても,後処理を書く余地がなくなる.そこで,次の形に書きなおすと,後処理もできるようになる.'after'の箇所に後処理を書く.
(デコレータ関数の完成系1)
【例 1.5】
IDLE → New File → Save as .. → Run
def deco1(fx):
    def wrapper(*args, **dics):
        print('before:')
        tmp = fx(*args, **dics)
        print('after')
        return tmp
    return wrapper
@deco1
def fun1():
    print('f')
• 多くの教科書で,デコレータ関数の入れ子の関数は,修飾される方の主人公となる関数fx()を包み込む関数という意味で,ラッパーという用語が使われているので,ここでもその用語にした…サランラップ,クレラップのラップ(wrap)は「包むもの」という名詞にもなり,「包む」という動詞にもなる.
(UNIXのシステムでは,TCP wrapperが使われていて,何らかのコマンドを受け付けるための前処理やアクセスログの記録などを行うらしい.)
次のようにデコレータを使うと,アクセスした,日時,関数名,引数がc:/???/log1.txtというファイルに記録される.
【例 1.6】
IDLE → New File → Save as .. → Run
import datetime as dt
dt1 = dt.datetime

def deco1(fx):
    def wrapper(*args, **dics):
        tmp = fx(*args, **dics)
        with open('c:/???/log1.txt','a') as file1:
            file1.write(str(dt1.now())+',')
            file1.write(fx.__name__+',')
            file1.write(str(args)+'\n')
        return tmp    
    return wrapper
@deco1
def fun1(str1):    
    print(str1)
@deco1
def fun2(str2):    
    print(str2)
fun1('abc')
fun2('xy')
(2)複数個のデコレータ指定
• 関数定義の前に@デコレータの形で書き込むと,デコレータを指定できるが,このデコレータ指定は2つ以上付けることができる.
• 複数個のデコレータを付けた場合,デコレータは関数定義に近い場所に書かれたものから順に(後ろから順に)行われる.後ろから順に行われるとは,具体的には,次の例2.1のようになるということ.
【例 2.1】
IDLE → New File → Save as .. → Run
def deco1(fx):
    def wrapper(*args, **dics):
        print('before1:')
        tmp = fx(*args, **dics)
        print('after1')
        return tmp    
    return wrapper
def deco2(fx):
    def wrapper(*args, **dics):
        print('before2:')
        tmp = fx(*args, **dics)
        print('after2')
        return tmp    
    return wrapper
def deco3(fx):
    def wrapper(*args, **dics):
        print('before3:')
        tmp = fx(*args, **dics)
        print('after3')
        return tmp    
    return wrapper
@deco1
@deco2
@deco3
def fun1():
    print('f')
fun1()

before1:
before2:
before3:
f
after3
after2
after1
• まず初めに,@deco3が実行され,before3, f, after3となる.次に,@deco2が実行され,その前後にbefore2, after2が付く.最後に,@deco1が実行され,その前後にbefore3, after3が付く.
• 関数fun1()は1回だけ実行される.(3回ではない)
○== メニューに戻る ==