クラスデコレータ使ってバリデーションしてみた。(前回の続き)

前回のやり方だと関数一つ一つにデコレータいちいち付けてらんねぇよってなるので多少楽になるようにしてみます。
と言ってもクラスのメソッドのみです。

まず前回用意したtest.pyのvalidate関数に追記します。

        argSpec = getfullargspec(func)
+       if "self" in argSpec.args:
+           argSpec.args.remove("self")
        for name, value in zip(argSpec.args, args):

おわかりのとおりメソッドの第一引数selfをスルー!

次にtest2.py

# -*- coding: utf-8 -*-
from inspect import ismethod
from test import validate


class Deco:
    def __init__(self, cls):
        self.cls = cls
        self.instance = cls()

    def __call__(self):
        return self

    def __getattr__(self, name):
        attr = getattr(self.instance, name)
        if ismethod(attr):
            return validate(attr)
        return getattr(self.instance, name)


@Deco
class A:
    def output_num(self, num: int = 0) -> str:
        return "渡された数値は{}です".format(num)

クラスでデコレートしてみました。

で、実行してみます。

>>> from test2 import A
>>> a = A()
>>> a.output_num(3)
OK
'渡された数値は3です'
>>> a.output_num("3")
引数 num の 3 は <class 'int'> と型が違います
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "test.py", line 28, in _validate
    raise ArgumentError(name, value, obj_type)
test.ArgumentError

それっぽくなりました。

クラスデコレータ使うとデコったクラスのインスタンスを返しちゃうのでisinstance関数とかわかりにくくなるのが難点です。

何が言いたいかっていうと

>>> a.__class__
<class 'test2.Deco'>
>>> a.instance
<test2.A object at 0x10061e810>
>>> a.instance.__class__
<class 'test2.A'>
>>> A
<test2.Deco object at 0x10061e990>
>>> isinstance(a, A)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: isinstance() arg 2 must be a type or tuple of types
>>> isinstance(a, a.instance.__class__)
False
>>> a
<test2.Deco object at 0x10061e990>

継承とかしてるわけじゃないし別のインスタンスなんだから当たり前なんですけど何か良い方法無いですかね。こういうのってあまり意識しないで使いたいですから。

@Decoを付けるだけでチェックしてくれるようになるけど上の問題で違うインスタンスになっちゃってるもんだからまともに動かないよぉ。