見出し画像

継承したクラスのメソッドにまとめて排他制御を反映する魔法(Python)

最終更新日(2024年7月6日)
4においてオーバーライドしたときsuperを使用しての親クラスのメソッドを呼び出せない問題を修正。


非同期処理するときに必要になる排他制御を反映するのが面倒なときに役立つかも?
(Discordのtasks.loopとかで共通の変数を扱うときとか)

1.使用するモジュール

import asyncio, inspect
from functools import wraps, partial
from asyncio import Lock
from abc import ABCMeta

2.非同期関数にするためのwrapper

def to_async(method):
    if inspect.iscoroutinefunction(method):
        return method
    @wraps(method)
    async def async_wrapper(*args, **kwargs):
        loop = asyncio.get_event_loop()
        bound_method = partial(method, *args, **kwargs)
        return await loop.run_in_executor(None, bound_method)
    return async_wrapper

やってあることは単純で非同期関数じゃない関数を非同期化しているだけ。

3.排他制御の処理を追加するためのwrapper

def to_lock(method):
    @wraps(method)
    async def wrapper(self, *args, **kwargs):
        if not hasattr(self, "_lock"):
            self._lock = Lock()
        async with self._lock:
            return await method(self, *args, **kwargs)
    return wrapper

本命、これを適応したいがための前述の非同期化。
非同期処理では必要なのに参照先一つにつき一つ書いておかないといけない手間のかかる子。

4.親クラスのメソッドに排他制御を反映するためのメタクラス

class LookMeta(ABCMeta):
    def __new__(cls, name, bases, namespace, **kwargs):
        new_namespace = {}
        # ▼所持しているメソッドの親子関係を連結▼
        def set_inheritance(attr_name, attr_value):
            def wrapper(self, *args, **kwargs):
                return attr_value(self, *args, **kwargs)
            return wrapper if callable(attr_value) else attr_value
        for base in bases:
            for attr_name, attr_value in base.__dict__.items():
                new_namespace[attr_name] = set_inheritance(attr_name, attr_value)
        for attr_name, attr_value in namespace.items():
            new_namespace[attr_name] = set_inheritance(attr_name, attr_value)

        # ▼ラッパーをメソッドへ反映▼
        for attr_name, attr_value in new_namespace.items():
            if callable(attr_value) and attr_name.startswith("set"):
                new_namespace[attr_name] = to_lock(to_async(attr_value))
        return super().__new__(cls, name, bases, new_namespace, **kwargs)

今回のメタクラスではsetメソッドを対象に反映しています。
必要があればそれぞれ反映したいメソッドに合わせて下から4行目のif文を書き直してください。

継承例

class LockValues(Values, metaclass=LookMeta):
    pass

あとは上記のように親クラスとこのメタクラスを継承することで排他制御が一括で反映されます。

さいごに

ここまで読んでくださりありがとうございます。
AIVtuberの愛音ゆにと天音うみを開発しています。
秋頃デビュー予定なのでぜひチャンネル登録お願いします。


この記事が気に入ったらサポートをしてみませんか?