Конечно, метаклассы - это самый питонический путь, когда вы хотите изменить способ, которым питон создает объекты. Что можно сделать, переопределив метод __new__
вашего класса. Но есть некоторые моменты вокруг этой проблемы (особенно для python 3.X), о которых я хотел бы упомянуть:
types.FunctionType
не защищает специальные методы от декорирования, так как они являются типами функций. В качестве более общего способа вы можете просто украсить объекты, имена которых не начинаются с двойного подчеркивания (__
). Еще одно преимущество этого метода состоит в том, что он также охватывает те объекты, которые существуют в пространстве имен и начинаются с __
, но не функционируют как __qualname__
, __module__
и т. Д.
Аргумент namespace
в заголовке __new__
не содержит атрибутов класса в __init__
. Причина в том, что __new__
выполняется до __init__
(инициализация).
Нет необходимости использовать classmethod
в качестве декоратора, так как в большинстве случаев вы импортируете свой декоратор из другого модуля.
- Если ваш класс содержит глобальный элемент (вне
__init__
) для отказа от оформления вместе с проверкой, не начинается ли имя с __
, вы можете проверить тип с помощью types.FunctionType
, чтобы убедиться, что Вы не украшаете нефункциональный объект.
Вот пример метакассы, который вы можете использовать:
class TheMeta(type):
def __new__(cls, name, bases, namespace, **kwds):
# if your decorator is a class method of the metaclass use
# `my_decorator = cls.my_decorator` in order to invoke the decorator.
namespace = {k: v if k.startswith('__') else my_decorator(v) for k, v in namespace.items()}
return type.__new__(cls, name, bases, namespace)
Демо-версия:
def my_decorator(func):
def wrapper(self, arg):
# You can also use *args instead of (self, arg) and pass the *args
# to the function in following call.
return "the value {} gets modified!!".format(func(self, arg))
return wrapper
class TheMeta(type):
def __new__(cls, name, bases, namespace, **kwds):
# my_decorator = cls.my_decorator (if the decorator is a classmethod)
namespace = {k: v if k.startswith('__') else my_decorator(v) for k, v in namespace.items()}
return type.__new__(cls, name, bases, namespace)
class MyClass(metaclass=TheMeta):
# a = 10
def __init__(self, *args, **kwargs):
self.item = args[0]
self.value = kwargs['value']
def __getattr__(self, attr):
return "This class hasn't provide the attribute {}.".format(attr)
def myfunction_1(self, arg):
return arg ** 2
def myfunction_2(self, arg):
return arg ** 3
myinstance = MyClass(1, 2, value=100)
print(myinstance.myfunction_1(5))
print(myinstance.myfunction_2(2))
print(myinstance.item)
print(myinstance.p)
Выход:
the value 25 gets modified!!
the value 8 gets modified!!
1
This class hasn't provide the attribute p. # special method is not decorated.
Для проверки 3-го пункта из вышеупомянутых примечаний вы можете раскомментировать строку a = 10
и сделать print(myinstance.a)
и увидеть результат, затем изменить понимание словаря в __new__
следующим образом, затем снова увидеть результат:
namespace = {k: v if k.startswith('__') and not isinstance(v, types.FunctionType)\
else my_decorator(v) for k, v in namespace.items()}