[英]Python: mark method as implementing/overriding
给定我想要实现的各种“合同”,我希望代码
例如,在 C++ 中,您可以
class X: public Somethingable {
int get_something() const override
{ return 10; }
};
现在,当我重命名Somethingable::get_something
(例如,简单的something
)时,编译器将在我的X::get_something
上出错,因为它不再是覆盖(不再)。
在 C# 中,读者可以获得更多信息:
class X : Somethingable {
int GetSomething() implements Somethingable.GetSomething { return 10; }
}
在 Python 中,我们可以使用abc.ABC
和@abstractmethod
来注释子类必须定义 this 和 that 成员,但是在实现站点上是否有标准化的方法来注释这种关系?
class X(Somethingable):
@typing.implements(Somethingable.get_something) # does not exist
def get_something(self):
return 10
我高估了这种解决方案的复杂性,它更短:
import warnings
def override(func):
if hasattr(func, 'fget'): # We see a property, go to actual callable
func.fget.__overrides__ = True
else:
func.__overrides__ = True
return func
class InterfaceMeta(type):
def __new__(mcs, name, bases, attrs):
for name, a in attrs.items():
f = getattr(a, 'fget', a)
if not getattr(f, '__overrides__', None): continue
f = getattr(f, '__wrapped__', f)
try:
base_class = next(b for b in bases if hasattr(b, name))
ref = getattr(base_class, name)
if type(ref) is not type(a):
warnings.warn(f'Overriding method {name} messes with class/static methods or properties')
continue
if _check_lsp(f, ref):
warnings.warn(f'LSP violation for method {name}')
continue
except StopIteration:
warnings.warn(f'Overriding method {name} does not have parent implementation')
return super().__new__(mcs, name, bases, attrs)
override
装饰器可以标记覆盖方法,并且InterfaceMeta
确认这些方法确实存在于超类中。 _check_lsp
是其中最复杂的部分,我会在下面解释。
究竟发生了什么? 首先,我们从装饰器中获取一个可调用对象并为其添加一个属性。 然后元类查找具有此标记的方法,并且:
property
仍然是属性, classmethod
仍然是classmethod
,而staticmethod
仍然是staticmethod
def stupid_decorator(func):
"""Stupid, because doesn't use `wrapt` or `functools.wraps`."""
def inner(*args, **kwargs):
return func(*args, **kwargs)
return inner
class IFoo(metaclass=InterfaceMeta):
def foo(self): return 'foo'
@property
def bar(self): return 'bar'
@classmethod
def cmethod(cls): return 'classmethod'
@staticmethod
def smethod(): return 'staticmethod'
def some_1(self): return 1
def some_2(self): return 2
def single_arg(self, arg): return arg
def two_args_default(self, arg1, arg2): return arg1
def pos_only(self, arg1, /, arg2, arg3=1): return arg1
def kwonly(self, *, arg1=1): return arg1
class Foo(IFoo):
@override
@stupid_decorator # Wrong signature now: "self" not mentioned. With "self" in decorator won't fail.
def foo(self): return 'foo2'
@override
@property
def baz(self): return 'baz'
@override
def quak(self): return 'quak'
@override
@staticmethod
def cmethod(): return 'Dead'
@override
@classmethod
def some_1(cls): return None
@override
def single_arg(self, another_arg): return 1
@override
def pos_only(self, another_arg, / , arg2, arg3=1): return 1
@override
def two_args_default(self, arg1, arg2=1): return 1
@override
def kwonly(self, *, arg2=1): return 1
这警告:
LSP violation for method foo
Overriding method baz does not have parent implementation
Overriding method quak does not have parent implementation
Overriding method cmethod messes with class/static methods or properties
Overriding method some_1 messes with class/static methods or properties
LSP violation for method single_arg
LSP violation for method kwonly
您也可以在Foo
上设置元类,结果相同。
LSP(Liskov 替换原则)是一个非常重要的概念,特别是它假设任何父类都可以被任何子类替换而没有接口不兼容。 _check_lsp
只执行非常简单的检查,忽略类型注释(这是mypy
区域,我不会碰它!)。 它证实了
*args
和**kwargs
不会消失实施如下:
from inspect import signature, Parameter
from itertools import zip_longest, chain
def _check_lsp(child, parent):
child = signature(child).parameters
parent = signature(parent).parameters
def rearrange(params):
return {
'posonly': sum(p.kind == Parameter.POSITIONAL_ONLY for p in params.values()),
'regular': [(name, p.default is Parameter.empty)
for name, p in params.items()
if p.kind == Parameter.POSITIONAL_OR_KEYWORD],
'args': next((p for p in params.values()
if p.kind == Parameter.VAR_POSITIONAL),
None) is not None,
'kwonly': [(name, p.default is Parameter.empty)
for name, p in params.items()
if p.kind == Parameter.KEYWORD_ONLY],
'kwargs': next((p for p in params.values()
if p.kind == Parameter.VAR_KEYWORD),
None) is not None,
}
child, parent = rearrange(child), rearrange(parent)
if (
child['posonly'] != parent['posonly']
or not child['args'] and parent['args']
or not child['kwargs'] and parent['kwargs']
):
return True
for new, orig in chain(zip_longest(child['regular'], parent['regular']),
zip_longest(child['kwonly'], parent['kwonly'])):
if new is None and orig is not None:
return True
elif orig is None and new[1]:
return True
elif orig[0] != new[0] or not orig[1] and new[1]:
return True
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.