需要特定的退货类型

时间:2017-09-07 09:40:43

标签: python

我有一种情况,我需要来自另一个实现者,需要遵循我的基类设计:

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这样的动态类型控件的语言中,这种情况非常复杂,我不知道如此解决这个问题。

2 个答案:

答案 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,以便教育用户而非惩罚他们。