有没有办法在继承期间持久化装饰器?
Posted
技术标签:
【中文标题】有没有办法在继承期间持久化装饰器?【英文标题】:Is there a way to persist decorators during inheritance? 【发布时间】:2020-04-14 23:53:08 【问题描述】:我正在尝试使用未实现的方法编写一个抽象类,这将强制继承的子级在重写方法(在装饰器中定义)时返回特定类型的值。
当我使用下面显示的代码时,子方法不会调用装饰器。我认为这是因为该方法被覆盖,这很有意义。我的问题基本上是这样的: 有没有办法通过方法覆盖使装饰器持久化?
我不反对使用装饰器以外的其他东西,但这是一个很快就浮现在脑海中的解决方案,我很想知道是否有任何方法可以让它发挥作用。
如果使用装饰器是正确且可能的选择,它将如下所示:
def decorator(returntype):
def real_decorator(function):
def wrapper(*args, **kwargs):
result = function(*args, **kwargs)
if not type(result) == returntype:
raise TypeError("Method must return 0".format(returntype))
else:
return result
return wrapper
return real_decorator
我需要我的父类看起来与这个相似:
class Parent(ABC):
@decorator(int)
@abstractmethod
def aye(self, a):
raise NotImplementedError
子类会做这样的事情:
class Child(Parent):
def aye(self, a):
return a
如果需要,我非常乐意更好地澄清我的问题,并感谢所有花时间提前阅读此问题的人!
【问题讨论】:
是的。例如,对于一个元类,您可以在其中更改所选方法以应用装饰器。请参阅 here 和 here 和 here 和 here 以获得好的想法和示例。 如果你真的有很好的用例,你也应该metathink。 您还要求做两件不同的事情。正如@mgc 提到的那样,静态类型检查可能就足够了。强制签名对我来说似乎总是反python,但在某些情况下可能需要/有用。没有质疑。 如果返回类型强制是你唯一的目标,你能不使用更简单的东西吗?说def aye(self, a): res = self._aye(a) assert isinstance(res, int) return res
,其中_aye
是子类的实际方法,aye
是它的公共类型检查调用器?
感谢您关注我的问题@progmatico!由于此类消费者的某些限制,我必须确保这些方法以错误的返回类型失败。 @mgc 的解决方案可以工作,因为如果 mypy 没有通过,我可能会使 CI/CD 进程失败,但我绝对必须强制执行返回类型:)
【参考方案1】:
我不确定你是否可以按照你想要的方式保持装饰器的效果,但你仍然可以在 Parent
类中装饰一个包装函数,它不会是一个 abstractmethod
并让子类实现像这样的包装函数:
from abc import ABC, abstractmethod
def decorator(returntype):
def real_decorator(function):
def wrapper(*args, **kwargs):
result = function(*args, **kwargs)
if not type(result) == returntype:
raise TypeError("Method must return 0".format(returntype))
else:
return result
return wrapper
return real_decorator
class Parent(ABC):
@decorator(int)
def aye(self, a):
return self.impl_aye(a)
@abstractmethod
def impl_aye(self, a):
raise NotImplementedError
class Child(Parent):
def impl_aye(self, a):
return a
还有一些解决方案可以保护 aye
方法不受 Parent
类的影响,如果需要,请参阅 this answer 示例。
否则,如果您想使用 type hints 并使用 mypy(Python 的可选静态类型检查器)检查您的代码,如果您尝试使用以下命令实现子类,则会收到错误消息返回类型与其父类不兼容:
from abc import ABC, abstractmethod
class Parent(ABC):
@abstractmethod
def aye(self, a) -> int:
raise NotImplementedError
class Child(Parent):
def aye(self, a) -> str :
return a
mypy
的输出:
a.py:9: error: Return type "str" of "aye" incompatible with return type "int" in supertype "Parent"
Found 1 error in 1 file (checked 1 source file)
【讨论】:
这是一个很好的解决方案,如果我可以使用它使我的 CI/CD 流程失败,我可能会选择这个答案。非常感谢! 不客气!如果我很好地理解了您的问题,我想您可以使您的 CI 过程因我的两个提议而失败(尽管我对我的第一个提议并不感到非常自豪,因为它为开发人员创建了间接性,因为您必须实现 @ 987654332@(或任何其他名称)当呼叫者呼叫aye
,所以不要忘记@progrmatico第一条评论中也有很好的建议!)【参考方案2】:
如果你只是想要强制返回类型,这是我的非装饰器建议(最初没有放入,因为我不喜欢你-不要-想要在 SO 上做这个“答案”)。
class Parent:
def aye(self, a):
res = self._aye(a)
if not isinstance(res, int):
raise TypeError("result should be an int")
return res
def _aye(self, a):
raise NotImplementedError()
class Child(Parent):
def _aye(self, a):
return 1
【讨论】:
引发异常而不是断言会更好,因为断言可以被禁用。 同意并更改。【参考方案3】:以下是使用元类执行此操作的方法。在 Python 3.8 上测试。应该在 3.6 及更高版本上按原样工作。诚然,这有点复杂,使用另一种技术可能会更好。
from abc import ABCMeta, abstractmethod
from functools import wraps
from inspect import isfunction
class InheritedDecoratorMeta(ABCMeta):
def __init__(cls, name, bases, attrs):
for name, attr in attrs.items():
for base in bases:
base_attr = base.__dict__.get(name)
if isfunction(base_attr):
inherited_decorator = getattr(base_attr, 'inherited_decorator', None)
if inherited_decorator:
setattr(cls, name, inherited_decorator()(attr))
break
def inherited_decorator(decorator, result_callback):
def inner_decorator(method):
method.inherited_decorator = lambda: inherited_decorator(decorator, result_callback)
@wraps(method)
def wrapper(*args, **kwargs):
result = method(*args, **kwargs)
return result_callback(method, result, args, kwargs)
return wrapper
return inner_decorator
def returns(type_):
if not isinstance(type_, type) and type_ is not None:
raise TypeError(f'Expected type or None; got type_')
def callback(method, result, args, kwargs):
result_type = type(result)
if type_ is None:
if result is not None:
raise TypeError(f'Expected method method to return None; got result_type')
elif not isinstance(result, type_):
raise TypeError(f'Expected method method to return type_; got result_type')
return result
return inherited_decorator(returns, callback)
class MyBaseClass(metaclass=InheritedDecoratorMeta):
@returns(int)
@abstractmethod
def aye(self, a):
raise NotImplementedError
@returns(None)
@abstractmethod
def bye(self, b):
raise NotImplementedError
class MyClass(MyBaseClass):
def aye(self, a):
return a
def bye(self, b):
return b
@returns(str)
def cye(self, c):
return c
if __name__ == '__main__':
instance = MyClass()
instance.aye(1)
try:
instance.aye('1')
except TypeError as exc:
print(exc)
instance.bye(None)
try:
instance.bye(1)
except TypeError as exc:
print(exc)
instance.cye('string')
try:
instance.cye(1)
except TypeError as exc:
print(exc)
【讨论】:
以上是关于有没有办法在继承期间持久化装饰器?的主要内容,如果未能解决你的问题,请参考以下文章