簡體   English   中英

派生類的類/元類方法裝飾器

[英]Class/metaclass method decorator for derived class

我有一個元類,它定義了一個類級別屬性,該屬性對於每個子類應該是唯一的,但是在每個子類的實例之間共享。

class MetaValidator(type):
    def __new__(
             cls, name, bases, dct
    ):
        new_cls = super().__new__(cls, name, bases, dct)
        new_cls.valid_funcs = []
        return new_cls

現在我想實現一個裝飾器,它將裝飾的類方法附加到派生類中的valid_funcs。 但是因為派生類仍然被定義,所以我沒有對派生裝飾器的引用,所以我最終附加到基類。 這是我的代碼:

class Validator(object, metaclass=MetaValidator):

    @classmethod
    def add(cls, f):
        cls.valid_funcs.append(f)
        return f

    def _validate(self, **kwargs):
        for f in self.valid_funcs:
            params = inspect.signature(f).parameters.keys()
            f_kwargs = {name: kwargs[name] for name in params}
            f(**f_kwargs)

    def validate(self, **kwargs):
        self._validate(**kwargs)

class A(Validator):

    @staticmethod
    @Validator.add
    def test_func(x):
        return x

class B(Validator):

    @staticmethod
    @Validator.add
    def test_func(x, y):
        return x, y

a = A()
a.validate(x="In A")
b = B()
b.validate(x="In B", y=" Called with arg y")

print(Validator.valid_funcs)
print(a.valid_funcs)
print(b.valid_funcs)

這打印:

[<function A.test_func at 0x7f0189d4fc80>, 
<function B.test_func at 0x7f0189d4fd08>]
[]
[]

我想要:

[]
[<function A.test_func at 0x7f0189d4fc80>]
[<function B.test_func at 0x7f0189d4fd08>]

當執行類體中函數的裝飾器時, 沒有類對象。 首先執行類主體, 然后創建類。

不是讓裝飾器查找要改變的類屬性,而是將屬性添加到修飾的函數對象。 然后,元類_validate()實現查找具有此屬性的任何對象,並在創建類對象后將它們添加到列表中。

我將假設您要保留裝飾器將裝飾項添加到列表中的順序:

from itertools import count

class Validator(metaclass=MetaValidator):
    @classmethod
    def add(cls, f):
        _count = getattr(Validator.add, '_count', None)
        if _count is None:
            _count = Validator.add.__func__._count = count()
        f._validator_function_id = next(_count)
        return f

在元類中:

class MetaValidator(type):
    def __new__(cls, name, bases, dct):
        new_cls = super().__new__(cls, name, bases, dct)
        registered = []
        for v in dct.values():
            id = getattr(v, '_validator_function_id', None)
            if id is None and isinstance(v, (staticmethod, classmethod)):
                # unwrap staticmethod or classmethod decorators
                id = getattr(v.__func__, '_validator_function_id', None)
            if id is not None:
                registered.append((id, v))
        new_cls.valid_funcs = [f for _, f in sorted(registered)]
        return new_cls

請注意,如果您使用的是Python 3.6或更高版本,那么您根本不再需要元類。 您可以將相同的邏輯放入class.__init_subclass__方法中

請注意,這會注冊未綁定的對象 對於staticmethod對象,這意味着調用將失敗:

TypeError: <staticmethod object at 0x10d1b7048> is not a callable object

您可能希望在這種情況下注冊__func__屬性,或者使用.__get__將對象“綁定”到某個東西( staticmethod無論如何都會忽略綁定上下文)`。

如果顯式綁定,在_validate()方法中,則實際上不必使用staticmethod對象:

def _validate(self, **kwargs):
    for f in self.valid_funcs:
        bound = f.__get__(self)
        signature = inspect.signature(bound)
        bound(**{name: kwargs[name] for name in signature.parameters})

現在@validator.add將使用staticmethodclassmethod和常規函數。

如果你有_validate()方法查找方法,那么可以為你完成綁定。 您可以通過使用dir()getattr()來選擇支持繼承:

from operator import itemgetter
from itertools import count


class Validator:
    @classmethod
    def add(cls, f):
        _count = getattr(Validator.add, '_count', None)
        if _count is None:
            _count = Validator.add.__func__._count = count()
        f._validator_function_id = next(_count)
        return f

    def _list_validators(self):
        objects = (getattr(self, name) for name in dir(self))
        return sorted(
            (o for o in objects if hasattr(o, '_validator_function_id')),
            key=attrgetter('_validator_function_id'))

    def _validate(self, **kwargs):
        for f in self._list_validators():
            signature = inspect.signature(f)
            f(**{name: kwargs[name] for name in signature.parameters})

getattr()為您提供綁定對象,無需進一步綁定。

而具有該元類__new__處理添加功能valid_funcs是一個選項,另一個選擇是將注入valid_funcs到類體的命名空間前級甚至存在,使用__prepare__

class MetaValidator(type):
    @classmethod
    def __prepare__(cls, name, bases, **kwds):
        ns = super().__prepare__(name, bases, **kwds)
        ns['valid_funcs'] = []
        return ns

def register(func_list):
    def inner_register(func):
        func_list.append(func)
        return func
    return inner_register

class A(metaclass=MetaValidator):
    @register(valid_funcs)
    def method(self):
        ...

我可能會跳過所有元類的東西,並且要求類自己執行valid_funcs = [] 元類的額外復雜性並不值得為每個類保存一行樣板。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM