【Python入門】デコレータの使い方をわかりやすく解説!

今回はPythonで使われるデコレータの基本的な使い方を、やさしく解説していきたいと思います。

デコレータの仕組みや使用方法を理解していると、Pythonプログラミングの幅がとても広がります。

この記事では

・デコレータとは
・デコレータの基本的な使い方

といった基本的な内容から、

・classmethod、staticmethod、propertyの使い方
・様々なデコレータの使い方

などといった、より実践的な内容に関してもわかりやすく解説していきたいと思います。

なお、Pythonの記事については、こちらにまとめています。

目次

デコレータとは

デコレータとは、すでにある関数に処理の追加や変更を行う為の機能です。

デコレータを使うことによって、既存の関数の中身を直接変更することなく、それらに機能を追加したり変更したりすることが出来るようになります。

たとえば、ある関数があって処理によって関数の動作を変更させていときにデコレータを定義しておけば、わざわざ処理ごとに似たような関数を作る手間が省けます。

デコレータの基本的な使い方

では、基本的な使い方からご紹介したいと思います。

デコレータには関数の知識も必要になります。

関数について詳しく知りたい方は、こちらの記事をご覧ください。

こちらのコードをご覧ください。

def sample_decorator(myfunc):
    print("I am the decorator!")
    return 0 

@sample_decorator
def myfunc():
    pass

実行結果

I am the decorator!

こちらのコードでは、sample_decoratorというデコレータを作成しました。

「@」の後にデコレータの名前を記述し、その下に新たな関数myfuncを定義しました。

デコレータの基本構文は以下のようになります。

@デコレータ名

このサンプルコード内では関数を実行したわけでもないのに、「I am the decorator!」という文字列が出力されました。

ではどうして、実行していないのに文字列が表示されたのでしょうか。

それは、先程のコードに出てきたような構文は、以下のようなコードのシンタックスシュガー(=糖衣構文。よりシンプルでわかりやすい書き方)だからです。

実際、先ほどのコードは以下のようにも書けます。

def myfunc():
    pass
myfunc = sample_decorator(myfunc)

上のコードでは、sample_decorator関数が呼び出されていることが分かります。

先ほどお伝えしたように、デコレータには既存の関数に機能を追加する働きがあります。

つまり、その名の通りデコレート(decorate=装飾)するのです。

このサンプルコードでは、sample_decoratorによりmyfuncが装飾されました。

実際、myfuncは関数ではなく数値0を格納している変数になりましたね。

print("myfunc:{}".format(x))

実行結果

myfunc:0

先ほどのコードでは、デコレータを使用して関数を定数に変換するという操作を行いました。

しかしもちろん、関数を変化させて別の関数にする事も出来ます。

以下のコードをご覧ください。

def sample_decorator(myfunc):
    def inner_func(text):
        return "I am the decorator!"
    return inner_func

@sample_decorator
def myfunc(text):
    return text

print(myfunc("Blabla"))

実行結果

I am the decorator!

上のコードでは、myfuncを実行したにも関わらず「Blabla」とは表示されず、反対に「I am the decorator!」という文字列が出力されました。

このサンプルコードでは関数を返すデコレータを使用しました。

その為、先ほどのサンプルコードでは装飾後myfuncはint型の0を格納しましたが、今回myfuncの値は関数からまた別の関数に差し替えられたのです。

また,myfuncの結果をそのままデコレータ内で使用することもできます。

以下のコードをご覧ください。

def sample_decorator(myfunc):
    def inner_func(*args):
        print("I am the decorator!")
        myfunc(*args)
    return inner_func

@sample_decorator
def myfunc(text):
    print(text)

myfunc("Blabla")

実行結果

I am the decorator!
Blabla

このように、myfuncに与えられた引数を使用するときは、inner_funcに*arg(可変長引数)として渡してあげます。

Python標準のデコレータ

Pythonには便利なデコレータが標準で用意されています。

最も一般的なデコレータは

@classmethod
@staticmethod
@property

になります。

では早速、ご紹介したいと思います。

@classmethodの使い方

一般的にクラスメソッドと呼ばれるものは、インスタンス化しなくても直接呼び出せるメソッドの事です。

大体は、

オブジェクト=クラス名()
オブジェクト.メソッド()

このようにメソッドを呼び出しますよね。

クラスメソッドとは、このように呼び出す事が出来る便利なものなんです!

関数をクラスメソッド化させたい場合は、@classmethodを使用しましょう。

以下のコードをご覧ください。

class MyClass():
    @classmethod
    def myfunc(cls):
        print("I am a class method")
 
MyClass.myfunc()

実行結果

I am a class method

上記コードでは、MyClassという名のクラス内にmyfuncという関数を定義しました。

myfuncをデコレートしたので、myfuncはクラスメソッドとなります。

このようにすると、クラスメソッドを呼び出せる形でmyfuncを呼び出す事が出来ました。

見ての通りMyClassのインスタンスを作成しなくても、きちんと文字列が出力されています。

注意したい点は、myfuncの引数です。

クラスメソッドは第一引数にクラスを受け取ります。

引数名は特に決まりはありませんが、今回のようにclsを指定するのが一般的です。

@staticmethodの使い方

一般的にスタティックメソッド(静的メソッド)と呼ばれているものは、クラスメソッドと同様にインスタンス化しなくても呼び出し可能な関数の事です。

では、実際に関数を静的メソッドに装飾してみましょう。

こちらのコードをご覧ください。

class MyClass():
    @staticmethod
    def myfunc():
        print("I am a static method")
 
MyClass.myfunc()

実行結果

I am a static method

こちらのコードでは、MyClassという名のクラス内にmyfuncという関数を定義しました。

その関数をデコレートし、静的メソッドにしました。

ご覧いただけるように、先ほどと同じような形でmyfuncを呼び出す事ができました。

クラスメソッドとは違い、静的メソッドの場合myfuncにclsのような引数を与える必要はありません。

@classmethodと@staticmethodの違い

結論から言ってしまうと、二つの違いはクラスに依存しているかどうかという事です。

では、これがどういう事が詳しく見て行きましょう。

classmethodとstaticmethodの違いは引数を受け取るかどうかだけではありません。

これらの違いはクラスで継承を行った時に現れます。

実際、コードで確認してみましょう。

こちらをご覧ください。

class ParentClass():
    test_str = "Hello, I am Parent Class"
    
    @classmethod
    def myclassmethod(cls):
        print("classmethod: " + cls.test_str)
 
    @staticmethod
    def mystaticmethod():
        print("staticmethod: " + ParentClass.test_str)

ParentClass.myclassmethod()
ParentClass.mystaticmethod()

実行結果

classmethod: Hello, I am Parent Class
staticmethod: Hello, I am Parent Class

上記コードでは、ParentClassという名のクラス内にmyclassmethodとmystaticmethodを定義し、呼び出しました。

すると、ParentClassが保有する文字列を出力しました。

しかし、親クラスを継承した子クラスから呼び出すと、少し変わります。

以下のコードをご覧ください。

class ChildClass(ParentClass):
    test_str = "Hello, I am Child Class"

ChildClass.myclassmethod()
ChildClass.mystaticmethod()

実行結果

classmethod: Hello, I am Child Class
staticmethod: Hello, I am Parent Class

上記コードでは、ChildClassといったParentClassを継承したクラスを作成しました。

同じようにふたつの関数を呼び出してみると、myclassmethodの方は子クラスが保有している文字列を表示しました。

mystaticmethodは先ほどと変わらず、親クラス保有の文字列を表示しています。

これは、classmethodは継承した後、clsが子クラスに差し替えられる為です。

したがって、ChildClassで宣言したstrが優先されてしまうのです。

その点、静的メソッドはクラスに依存をしない為、これらを使い分けることが出来ると便利です。

@propertyの使い方

Pythonのクラスは通常、プロパティ(属性)に直接読み書きを行う事が可能です。

クラス作成時に実行される__init__メソッドの引数としてselfが設定されていますよね、

プロパティとは「self.〜」の形の変数のことです。

@propertyを使うとこのデコレータで装飾された関数はgetterという読み取りしかできないプロパティになります。

読み取ることしかできないということは、新たに値を変更することなどができないということです。

こちらのコードをご覧ください。

class MyClass:
    def __init__(self):
        self._x = 12345
    @property
    def x(self):
        return self._x

instance = MyClass()
instance.x

実行結果

12345

上記コードでは、xという読み取り専用プロパティを作成しました。

@propertyというデコレートを使って、xというメソッドを装飾しました。

ということは、こちらのコードでは以下のようにも書くことが出来ます。

x = property(x)

property()関数は引数にgetter関数を受け取り、その属性の値を返します

その為、xに格納されている値12345を出力しました。

仮に、xプロパティに値を設定してみると、読み取り専用プロパティの為エラーが発生します。

以下をご覧ください。

instance.x = 1234

実行結果

AttributeError: can't set attribute

こちらエラーは、Python2系では発生しません。

このことからプロパティを定義する時は、Python3系を使用することをおすすめします。

デコレータの応用的な使い方

ここからは、様々なデコレータの使い方について解説していきます。

元の関数のメタデータを維持する

実は先ほどまで解説してきたデコレータの定義方法では、元の関数のメタデータ(名前等)がデコレータで使用された関数のメタデータに置き換わってしまっていました。

元の関数の名前は__name__メソッドで確かめることができます。

以下のコードをご覧ください。

def sample_decorator(myfunc):
    def inner_func():
        return "I am the decorator!"
    return inner_func
 
@sample_decorator
def myfunc(text):
    return text
 
 
print(myfunc.__name__)

実行結果

inner_func

このように、本来myfuncと表示したいところが、デコレータの内部の関数inner_funcに置き換わっています。

そこで、myfuncのメタデータに変更を加えたくないときには、functools.wrapsを使えば解決できます。

以下のコードをご覧ください。

from functools import wraps
def sample_decorator(myfunc):
    @wraps(myfunc)
    def inner_func():
        return "I am the decorator!"
    return inner_func
 
@sample_decorator
def myfunc(text):
    return text
 
print(myfunc.__name__)

実行結果

myfunc

このように、ちゃんとmyfuncが返ってくるように書き直せました。

functools.wrapsはもとの関数のメタデータに変更を加えないようにするためのデコレータです。

なので、inner_funcの前に変更を加えたくない関数(今回はmyfunc)を引数として渡してあげることで、メタデータに変更を加えないように処理してくれます。

複数のデコレータを使う

デコレータは一つの関数に対して、複数使用することもできます。

以下のコードをご覧ください。

def A(myfunc):
    def inner_func():
        print("I am the A decorator!")
        myfunc()
        print("I am the A decorator!")
    return inner_func
 
def B(myfunc):
    def inner_func():
        print("I am the B decorator!")
        myfunc()
        print("I am the B decorator!")
    return inner_func
 
@B
@A
def myfunc():
    print("Hello, decorator")
 
myfunc()

実行結果

I am the B decorator!
I am the A decorator!
Hello, decorator
I am the A decorator!
I am the B decorator!

このように、複数のデコレータがA→Bと実行されていることがわかります。

まずは、デコレータAで処理されたあとに、その結果を再度デコレータBに入力値として使用しています。

複数の場合、元の関数(今回はmyfunc)に近い方から実行されるので、注意が必要です。

まとめ

今回はPythonにおけるデコレータの基本的な使い方と、その活用法について解説しました。

デコレータの仕組みを理解していると、様々なシチュエーションなどで使えてとても便利です。

この記事でご紹介したサンプルコードなどを実際に試してながら、理解を深めてくださいね!

この記事を書いた人

イタリア在住15年目の22歳です。イタリアの大学で情報科学&応用数学を学んでいます。主にJavaScriptやPythonについての記事を書いたりしています。

目次