2021.03.17   2021.05.16

【Python】デコレータの使い方

Python    


今回はデコレータの使い方について解説したいと思います。

後半では、アットマーク(@)を使用したデコレータについて紹介します。

・Python標準デコレータ
@classmethod
@staticmethod
@property

・Webフレームワークで使われるデコレータ
@login_required

・単体テストで使用するデコレータ
@patch

・データクラスを作成する際に使用するデコレータ
@dataclass

デコレータとは

ひとことで言うと「すでにある関数を書き換えることなく、その関数に処理の追加や変更を行うことができる関数」のことです。

ちなみにdecorator(デコレーター)は装飾する者という意味です。

公式ドキュメント
https://docs.python.org/ja/3/glossary.html#term-decorator

使用例

以下はデコレータを使用したコードの例です。

deco_test.py
def deco(func): # (3)
    def wrapper(*args, **kwargs): # (4)
        print('>>>>>>>>>>>')
        func(*args, **kwargs) # (5)
        print('<<<<<<<<<<<')
    return wrapper # (6)

@deco # (2)
def hoge(): 
    print('decorator!')

hoge() # (1)
実行結果
>>>>>>>>>>>
decorator!
<<<<<<<<<<<


処理の流れ

(1) プログラムを実行すると、まず関数hogeが実行されます。

(2) 次に関数hogeの上のデコレータが実行されます。
関数(hoge)を装飾(デコレート)しているのでデコレータと言います。

(3) 今回使用するデコレーターを定義しています。
ここの引数 func にデコレートする関数が入ります。
したがって今回はhoge()が入ります。
funcの部分の名前はなんでも大丈夫です。ただし(5)と名前を合わせて下さい。

(4)上記のfuncが引数を持っていた場合は、wrapper (*args, **kwargs)で受け取ります。今回で言うfuncはhoge()のことです。

hogeは引数を持っていないのでここの部分はwrapper()と書き直しても動作します。
ただし、そうする場合は(5)のfuncもfunc()と定義して下さい。

(5) print('>>>>>>>>>>>') が実行された後に、func つまりhoge()関数が実行されます。
hoge関数の中身はprint('decorator!')なのでこちらが実行されます。
その後、print('<<<<<<<<<<<')が実行されます。

(6)実行結果がコンソールに出力されます。結果、

>>>>>>>>>>>
decorator!
<<<<<<<<<<<

となるわけです。

もっと複雑な使い方をしたいという方は、こちらのサイトが参考になると思います。
https://qiita.com/mtb_beta/items/d257519b018b8cd0cc2e

標準デコレータ

Pythonには標準のデコレーターが用意されています。
以下がその代表的なものです。

@classmethod
@staticmethod
@property


@classmethod -クラスメソッド

インタンス化していないクラスからメソッドを呼び出すことのできる関数のことです。

記述例
class DecoClass:
    @classmethod # クラスメソッドを使用
    def func(cls):
        print("クラスメソッド")


DecoClass.func()
実行結果
クラスメソッド

クラスメソッドを使用せずにこの処理を実行しようとすると、次のようにインスタンス化が必要となります。

class DecoClass:
    # クラスメソッドを削除
    def func(self):
        print("クラスメソッド無し")


# インスタンス化を実行
NoDeco = DecoClass()
NoDeco.func()
実行結果
クラスメソッド無し


@staticmethod -スタティックメソッド

スタティックメソッドは、クラスメソッドと同様、インタンス化していなくてもクラスからメソッドを呼び出すことができる関数です。
使い方もクラスメソッドとほとんど同じです。

class DecoClass:
    @staticmethod # スタティックメソッドを使用
    def func():
        print("スタテックメソッド")


DecoClass.func()
実行結果
スタテックメソッド

では、classmethodとstaticmethod にはどうのような違いがあるのでしょうか。

この2つのメソッドには、簡単に言うと次のような違いがあります。

classmethod : 第一引数にクラスを受け取る
staticmethod : 第一引数にクラスを受け取らない

これだけでは分かりにくいので以下に例を出します。

class Bird:
    name = '鳥'
    @classmethod
    def class_method(cls): # クラスメソッド
        print(cls.name)
    @staticmethod # スタティックメソッド
    def static_method():
        print(Bird.name)


Bird.class_method()
Bird.static_method()
実行結果
鳥
鳥

上記でクラスメソッドとスタティックメソッドを両方用意したクラスを定義し、それぞれメソッドを実行すると、どちらも「鳥」と表示されます。

では、今度はBirdクラスを継承したHumanBirdクラスを作成して、メソッドを実行します。

class HumanBird(Bird):
    name = '鳥人間'


HumanBird.class_method()
HumanBird.static_method()
実行結果
鳥人間
鳥

スタティックメソッドの方は、Birdクラスのメソッドをそのまま呼び出しているのに対し、クラスメソッドはclsがHumanBirdクラスとなったので、鳥人間と表示されています。

このように、継承を行うと2つのメソッドの動きが変わるので注意が必要です。


@property - プロパティ

@propertyは、関数を読み取りしかできないようにするデコレータです。

class DecoClass:
    def __init__(self):
        self._x = 1

    @property # プロパティ
    def x(self):
        return self._x


deco = DecoClass()
print(deco.x) # 読み取り
deco.x = 2 # 書き込み
実行結果
1
AttributeError: can't set attribute

print(deco.x)でprint(読み取り)を行なった場合、1が出力され、
deco.x = 2 でxに2を代入(書き込み)しようとするとエラーとなりました。

このように、外から簡単に値を変更させたくないけど、取り出しは簡単に行いたいという時に、@property使用します。

ちなみにプログラム中で self._x = 1のように変数の前にselfをつける書き方をインスタンス変数といいます。

Web開発で使用するデコレータ

@login_required

@login_requiredは、ユーザーがログイン状態かどうかを判定するデコレータです。

ログイン状態であった場合はデコレーターでが設定されている関数を実行します。

こちらは主にDjangoやFlaskといったWebフレームワークで使用されます。

実装方法については、それぞれ以下のようになります。


Djangoの場合

views.pyで次のように定義します。

from django.contrib.auth.decorators import login_required

@login_required
def test(request):
    return render(request, 'hello.html')

ユーザーがログイン状態であった場合は、hello.htmlに遷移します。
ログインしていない場合はhello.htmlに遷移せず、ログインページを表示させます。


Flaskの場合

from flask_login import login_required
...
@app.route('/test')
@login_required
def logout():
    return Response('''
    hogehoge-page<br />
    <a href="/logout/">logout</a>
    ''')

ログインした状態で/testにアクセスすると、hogehoge-pagelogoutのリンクが画面に表示されます。

ログインしていない状態で/testにアクセスすると、hogehoge-pageのみ表示されlogoutのリンクは画面に表示されません。

単体テストで使用するデコレータ

@patch

@patchは、Pythonの単体テストで使用するデコレータです。

テストケースの各メソッドに記述することで、テスト対象ファイルの中で定義されている未完成の関数に戻り値を設定することができます。

詳細な使い方については、当記事に記載するには少し文量が多いため、こちらの記事にまとめています。

データクラスを作成する際に使用するデコレータ

@dataclass -データクラス-

データクラスとは、その名の通りデータ格納専用クラスのことです。

@dataclassを使用することでデータクラスを作成することができます。

この機能はPython3.7から導入されました。

通常のクラスで、データ格納クラスを作成すると以下のようになります。

class data:
    def __init__(self, ip=192.168.22.3, host="hoge_server",port=22):
        self.ip = ip
        self.host = host
        self.port = port

これをデータクラスデコレータを使用して記述すると次のようになります。

@dataclass
class data:
    ip: str = "192.168.22.3"
    host: str = "hoge_server"
    port: int = 22

データクラスを使用すると以下のメリットがあります。
・データ専用のクラスであることが明示できる。
・ソースがすっきりする
・型情報を記述しておくと、インスタンスをprintするだけで中身を確認できる

3つ目のprintについては、次のように表示されます。

@dataclass
class data:
    ip: str = "192.168.22.3"
    host: str = "hoge_server"
    port: int = 22

print(data())
実行結果
data(ip="192.168.22.3", host="hoge_server", port=22)

マニュアル
参考サイト

その他の用語

args
可変長の引数を使う関数を作りたい場合に使用します。
次のように、関数の引数に
argsを定義すると好きな個数の変数を定義できます。
引数はタプルの要素としてまとめられます。(タプルはリストや辞書型などの仲間)

具体例

args.py
def hoge(*args):
    print(args)

hoge('hoge1')
hoge('hoge1', 'hoge2')
hoge('hoge1', 'hoge2', 'hoge3')
実行結果
('hoge1')
('hoge1', 'hoge2')
('hoge1', 'hoge2', 'hoge3')



**kwargs

argsと同じく、可変長の引数を使う関数を作りたい場合に使用します。
argsでは、引数はタプルの要素としてまとめられましたが、**kwargsの場合はdict(辞書型)でまとめられます。

def func(**kwargs):
    for key, value in kwargs.items():
        print(key + ':' + value)


func(key1='1', key2='2', key3='3')
実行結果
key1:1
key2:2
key3:3



インスタンス変数
インスタンス変数とは、クラスからインスタンスを生成する際に、インスタンスごとに定義される変数のことです。
インスタンス変数を定義するときは、変数の前にself.をつけます。
以下に例を記載します。

処理の例
class MessageClass:
    def __init__(self, msg):
        self._msg = msg #self._msgがインスタンス変数

    @property
    def print_msg(self):
        return print(self._msg)


MsgA = MessageClass("aaa") # インスタンスMsgAを生成
MsgB = MessageClass("bbb") # インスタンスMsgBを生成

MsgA.print_msg # MsgAのインスタンス変数「aaa」が出力される
MsgB.print_msg # MsgBのインスタンス変数「bbb」が出力される

参考動画
Python+FlaskでのWebアプリケーション開発講座!!~0からFlaskをマスターしてSNSを作成する~

参考サイト
https://qiita.com/mtb_beta/items/d257519b018b8cd0cc2e
https://www.atmarkit.co.jp/ait/articles/1911/26/news018.html
https://www.sejuku.net/blog/25130
https://blog.pyq.jp/entry/Python_kaiketsu_190205
https://qiita.com/ysk24ok/items/848daec3886f1030f587
http://blog.yuku-t.com/entry/20100328/1269774244
https://qiita.com/Sylba2050/items/d6f23ac13a0cc5da0c17>https://qiita.com/Sylba2050/items/d6f23ac13a0cc5da0c17
https://qiita.com/cardene/items/8a59d576d360b7568c3a
https://qiita.com/mocha_xx/items/8e1aa096ceae93d2b60c

コメント
現在コメントはありません。
コメントする
コメント入力

名前 (※ 必須)

メールアドレス (※ 必須 画面には表示されません)

送信