我有一种情况,我需要来自另一个实现者,需要遵循我的基类设计:
class ExemplaryClass:
pass
class Base(ABC):
@abstractmethod
def process(self):
# should return specific type, for instance ExemplaryClass
和派生类,将由其他程序员创建:
class Derived(Base):
def process(self):
# if return sth different from ExemplaryClass, should throw exception
如何通过代码强制程序员,遵循我的设计? 在C ++和其他具有静态类型控制的语言中,这个问题非常简单:
class ExemplaryClass {}
class Base{
public:
virtual ExemplaryClass proccess() =0;
}
class Derived :public Base {
public:
// ERROR
void proccess() ovveride { }
}
但是在像python这样的动态类型控件的语言中,这种情况非常复杂,我不知道如此解决这个问题。
答案 0 :(得分:2)
正如其他人在评论中指出的那样,做这种类型检查是不好的做法。但是如果你真的想这样做,可以将process
分成两个方法 - 一个抽象方法和另一个调用抽象方法并验证其返回值的方法:
class Base(ABC):
def process(self):
result = self._process()
assert isinstance(result, ExemplaryClass)
return result
@abstractmethod
def _process(self):
raise NotImplementedError
答案 1 :(得分:2)
我终于找到了解决方案。
这个想法是在主类中实现__getattribute__
,所以必须修改子类调用的方法。
考虑两个简单的类:由模块定义的Foo
基类,以及由API的用户定义的Bar
子类。
Bar
类会覆盖method
方法,但您希望它返回int
类型的对象,以便保持与Foo.method
相同的行为。
class Foo:
def method(self):
return 1
class Bar(Foo):
def method(self):
return "a"
当然,这没关系。
但这是使用__getattribute__
方法的一个技巧。
我首先检查所请求的属性是否命名为"method"
。
然后,如果是,而不是返回实际的method
,我创建一个自定义函数,另外断言实际method
的返回值是int
。
def __getattribute__(self, attribute):
if attribute == "method":
def _f(*args, **kwargs):
result = type(self).method(self, *args, **kwargs)
assert isinstance(result, int)
return result
return _f
现在,__getattribute__
方法定义为instance.whatever
将导致对instance.__getattribute__("whatever")
的调用,无论whatever
是否可以作为任何属性找到继承家庭中的阶级。
因此,如果bar = Bar()
,则bar.method
将替换为我在_f
中定义的内部Foo.__getattribute__
。
现在,bar.method()
会产生AssertionError
,但如果我按如下方式对其进行修改:
class Bar(Foo):
def method(self):
return 1
然后执行就没问题。
关于__getattribute__
的定义,它可能有点棘手,因为你无法调用self.something
,因为它会导致对__getattribute__
的递归调用。
解决方案是直接引用type(self).method
,它引用Bar
此处定义的类方法。
在这种情况下,此方法将instance
作为第一个参数,因此必须传递self
,因此行:
result = type(self).method(self, *args, **kwargs)
当然,如果子类本身实现__getattribute__
,所有这些都将失败。
但无论如何,不要忘记Python API很容易破解。
更具体地说,AssertError
可能很容易被抓住。
好吧,你可以用assert
替换sys.exit(1)
,但这可能有点过于强制......
我刚才有个主意。
这或多或少是一种反模式,因为它禁止用户自由地做他们想要的事情。
另一方面,可以仅用assert
替换raise
/ print
,以便教育用户而非惩罚他们。