SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

EuroPythonレポート

EuroPython 2018参加レポート(2)~Pythonによるサウンド生成、C APIのエミュレート、Decoratorの分類

  • X ポスト
  • このエントリーをはてなブックマークに追加

Decoratorの分類 A-E

A Taxonomy of Decorators: A-E
A Taxonomy of Decorators: A-E

 このセッションでは、PythonにおけるDecoratorの使い方について、発表者がケース別にA-Eで分類したものについて発表しました。

 Decoratorは汎用性が高く、応用範囲も広いためさまざまな場面で活用できます。

 このセッションでは経験に基づく実装が多いDecoratorの数々を、ケース別に体系的に分類しており、とてもわかりやすかったため紹介したいと思います。

A - Argument Changing Decorators

 Argument Changing Decoratorsは関数呼び出し時に、引数の追加や削除、変更をするDecoratorです。また、引数の変更だけでなく返り値を変更するパターンもこれに該当します。

 このパターンでは、共通して使用する値を明示的に注入するためにDecoratorとして付与したり、1つの関数で複数ケースの値を与えるような時に用いられます。

 例えば、関数呼び出し時に引数を追加するDecoratorは以下のようなものです。

関数実行時に関数名を渡すDecoratorの例

def fn_with_name(func):
    def wrapper(*args, **kwargs):
        func(*args, func.__name__, **kwargs)
    return wrapper

@fn_with_name
def do_something(arg1, func_name):
    print('{name}: {arg1}'.format(name=func_name, arg1=arg1))

do_something('here') # do_something: here

1つの関数で複数ケースの値を渡すDecoratorの例(pytestの@pytest.mark.parametrize

import pytest

from datetime import datetime, timedelta

testdata = [
    (datetime(2001, 12, 12), datetime(2001, 12, 11), timedelta(1)),
    (datetime(2001, 12, 11), datetime(2001, 12, 12), timedelta(-1)),
]

@pytest.mark.parametrize("a,b,expected", testdata)
def test_timedistance_v0(a, b, expected):
    diff = a - b
    assert diff == expected

B - Binding Decorators

 Binding Decoratorsは関数の振る舞いがどのようなものかをDescriptor[7]を用いて定義したDecoratorです。

 普段よく目にする標準ライブラリの@staticmethod@classmethod@property等のDecoratorもこれに該当します。

インスタンスメソッドであることを明示するDecoratorの例

class instance_method:
    def __init__(self, func):
        self.func = func

    def __get__(self, inst, cls):
        if inst is None:
            raise TypeError(f'{self.func.__name__} is only valid on instances.')

class GoodClass:
    @instance_method
    def simple_method(self):
        print("simple_method")

    def normal_method(self):
        print("normal_method")

GoodClass().simple_method() # simple_method
GoodClass().normal_method() # normal_method

GoodClass.normal_method # <function __main__.GoodClass.normal_method>
GoodClass.simple_method # TypeError: simple_method is only valid on instances.

C - Control Flow Decorators

 Control Flow Decoratorsは関数が呼ばれるかどうか、または、何回呼び出されるかを制御するためのDecoratorです。

 よくあるパターンとしては、あるAPI呼び出し関数の発火条件として認証済みであることが必須の場合に、認証済みであれば実行し、認証済みでなければ関数を実行せずエラーを返したり、Exceptionを送出するパターンがあります。また、APIのリトライなどを明示する場合にも用いられます。

成功するまでリトライするDecoratorの例

def infinite_retry(func):
    def wrapper(*args, **kwargs):
        while True:
            try:
                return func(*args, **kwargs)
            except RuntimeError as e:
                print(e)

@infinite_retry
def random_fail(max_value):
    ret = random.randint(-100, max_value)
    if ret < 0:
        raise RuntimeError("Invalid negative number {ret}.format(ret=ret)")
    return ret

random_fail(10)

# Invalid negative number -79
# Invalid negative number -67
# 6

D - Descriptive Decorators

 Descriptive Decoratorsは装飾されたオブジェクト(関数の戻り値など)をコレクションに追加するDecoratorです。

 これにより作成されたコレクションはドキュメント化やディスパッチング、プラグインなどを提供するために使用されます。

 少し難しい言い回しかもしれませんが、FlaskのルーティングDecoratorなどがこれに該当します。

 FlaskのルーティングではDecoratorの引数として与えられたパスに対して対象の関数を追加する操作をしています[8]

FlaskにおけるルーティングDecoratorの例

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

E - Execution Decorators

 Execution DecoratorsはDecoratorに与えられたコードを解釈して関数を実行するDecoratorです。

 Python以外のコードを再解釈して実行する際に広く利用されます。

 実行時にはDecoratorによって変換・生成されたコードが呼び出されるため、動作を追うのが難しく注意が必要です。しかし、使い方によってはPythonへ任意のコードを橋渡しすることが可能になります。

コード置換するDecoratorの例

def replacer(old, new):
    def wrapper(func):
        source = inspect.getsource(func)
        lines = source.split('\n')
        new_source = lines[1] + '\n' + ('\n'.join(lines[2:]).replace(old, new))
        exec(new_source, globals())
        return globals()[func.__name__]
    return wrapper

@replacer('b', 'b*3')
def calc(a, b):
    x = a + b
    y = x * 2
    print('Result: ' + str(y))

calc(1, 4) # 26

Cythonの型へ割り当てを行うDecoratorの例

cython = MagicMock()

@cython.locals(a=cython.double, b=cython.doule, n=cython.p_double)
def foo(a, b, x, y):
    n = a * b
    ...

 以上がセッションで発表されたDecoratorの5分類です。

 セッション動画の最後では、既存のライブラリで使用されているDecoratorが、どれに分類されるかについてconclusionとしてまとめていますので、興味がある方は動画のリンクからご覧ください。

 DecoratorはPythonの表現を豊かにしている言語仕様の1つだと思います。なので、既存のユースケースやセッションで紹介された分類を頭の中で整理しておくことで、よりPythonらしいコードを書けるようになると思います。

 今後Decoratorを見かけた時には、どの分類のDecoratorなのかを考えてみるのも楽しいかもしれませんね。

[7] Descriptorとは振る舞いを束縛したオブジェクト属性のことを指し、__get____set____delete__メソッドのいずれかをオーバーロードしたオブジェクトを意味します。

[8] flask.app.routeの実装(Github)

おわりに

 第2回にわたってEuroPython 2018のレポートをお届けしました。

 第1回EuroPython 2018レポートでは、会場の雰囲気や基調講演、Python3.7の新機能を中心に紹介し、第2回では筆者らが興味を持ったセッションを中心に紹介してきました。

 EuroPythonはその規模もさることながら、セッション数がとても多く、かつ、並行して進むため筆者らが聴けなかったセッションもあります。そのため今回紹介しきれなかったセッションも数多くあり、紹介できたセッションは全体のごく一部と言えます。

 EuroPython 2018の公式チャンネルで動画が公開されているので、興味のある方は是非ご覧ください。

 今回カンファレンスに参加された日本の方は筆者ら含め合計4人でしたが、カンファレンスを通してその方たちや現地の方などとの出会いもあり、とても有意義な時間を過ごせました。

会場で仲良くなった日本の方
会場で仲良くなった日本の方

 EuroPython 2019も開催が決定しているので、機会があればまた是非参加したいです。そのときには、本記事をご覧いただいた皆さんとお会いできることも楽しみにしています!

 最後までご覧いただき、ありがとうございました。

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
EuroPythonレポート連載記事一覧
この記事の著者

芝田 将(株式会社サイバーエージェント)(シバタ マサシ)

 株式会社サイバーエージェント AbemaTV 配信チーム所属。go-prompt 開発者。 共訳書『エキスパートPythonプログラミング改訂2版』。 Webサイト: https://c-bata.link

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

石川 諒(株式会社サイバーエージェント)(イシカワ リョウ)

 株式会社サイバーエージェント AbemaTV iOSエンジニア。主にコード品質や視聴周りの改善を行う本質改善チームに在籍。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/11097 2018/09/28 14:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング