我有两个基本类,FooBar,以及一个Worker类,它期望对象的行为类似于Foo。然后,我添加了另一个类,该类实现了Foo中的所有相关属性和方法,但是我没有设法通过mypy将其成功传递给静态类型检查。这是一个小例子:

class MyMeta(type):
    pass

class Bar(metaclass=MyMeta):
    def bar(self):
        pass

class Foo:
    def __init__(self, x: int):
        self.x = x

    def foo(self):
        pass

class Worker:
    def __init__(self, obj: Foo):
        self.x = obj.x

这里Worker实际上接受任何Foo -ish对象,即具有x属性和foo方法的对象。因此,如果objFoo一样走动,并且如果像Foo一样嘎嘎作响,那么Worker将很高兴。现在整个项目都使用类型提示,因此目前我指示obj: Foo。到现在为止还挺好。

现在还有另一个类FooBar,它是Bar的子类,其行为类似于Foo,但是它不能成为Foo的子类,因为它通过属性公开其属性(因此__init__参数没有意义):
class FooBar(Bar):
    """Objects of this type are bar and they are foo-ish."""

    @property
    def x(self) -> int:
        return 0

    def foo(self):
        pass

在这一点上,执行Worker(FooBar())显然会导致类型检查器错误:
error: Argument 1 to "Worker" has incompatible type "FooBar"; expected "Foo"

使用抽象基类

为了将Foo -ish的接口(interface)传达给类型检查器,我考虑过为Foo -ish类型创建一个抽象基类:
import abc

class Fooish(abc.ABC):
    x : int

    @abc.abstractmethod
    def foo(self) -> int:
        raise NotImplementedError

但是,我无法使FooBarFooish继承,因为Bar具有自己的元类,因此这会引起元类冲突。所以我考虑过在Fooish.registerFoo上同时使用FooBar,但是mypy并不同意:
@Fooish.register
class Foo:
    ...

@Fooish.register
class FooBar(Bar):
    ...

class Worker:
    def __init__(self, obj: Fooish):
        self.x = obj.x

产生以下错误:
error: Argument 1 to "Worker" has incompatible type "Foo"; expected "Fooish"
error: Argument 1 to "Worker" has incompatible type "FooBar"; expected "Fooish"

使用“普通”类作为接口(interface)

我考虑的下一个选项是创建一个接口(interface),而不以“普通”类的形式从abc.ABC继承,然后让FooFooBar都从该接口(interface)继承:
class Fooish:
    x : int

    def foo(self) -> int:
        raise NotImplementedError

class Foo(Fooish):
    ...

class FooBar(Bar, Fooish):
    ...

class Worker:
    def __init__(self, obj: Fooish):
        self.x = obj.x

现在,mypy并没有抱怨Worker.__init__的参数类型,而是抱怨FooBar.x(这是一个property)与Fooish.x的签名不兼容:
error: Signature of "x" incompatible with supertype "Fooish"
Fooish(抽象)基类现在也可以实例化,并且是Worker(...)的有效参数,尽管它没有意义,因为它不提供x属性,因此它是没有意义的。

问题...

现在,我陷入了一个问题,即如何在不使用继承的情况下将该接口(interface)与类型检查器进行通信(由于元类冲突;即使有可能,mypy仍会抱怨x的签名不兼容)。有办法吗?

最佳答案

  • 要摆脱error: Signature of "x" incompatible with supertype "Fooish",可以注释x: typing.Any
  • 为了使Fooish真正抽象,需要一些技巧来解决元类冲突。我从this answer拿了一个食谱:
  • class MyABCMeta(MyMeta, abc.ABCMeta):
        pass
    

    之后,可以创建Fooish:
    class Fooish(metaclass=MyABCMeta):
    

    整个代码在运行时成功执行,并且没有显示出来自mypy的错误:
    import abc
    import typing
    
    class MyMeta(type):
        pass
    
    class MyABCMeta(abc.ABCMeta, MyMeta):
        pass
    
    class Fooish(metaclass=MyABCMeta):
        x : typing.Any
    
        @abc.abstractmethod
        def foo(self) -> int:
            raise NotImplementedError
    
    class Bar(metaclass=MyMeta):
        def bar(self):
            pass
    
    class Foo(Fooish):
        def __init__(self, x: int):
            self.x = x
    
        def foo(self):
            pass
    
    class Worker:
        def __init__(self, obj: Fooish):
            self.x = obj.x
    
    
    class FooBar(Bar, Fooish):
        """Objects of this type are bar and they are foo-ish."""
    
        @property
        def x(self) -> int:
            return 0
    
        def foo(self):
            pass
    
    print(Worker(FooBar()))
    

    现在是时候考虑您是否真的要抽象Fooish了,因为如果class Fooish(metaclass=MyABCMeta):有很多技巧,那么进行MyMeta可能会有副作用。例如,如果MyMeta定义了__new__,则可能可以在__new__中定义Fooish,它不调用MyMeta.__new__而是调用abc.ABCMeta.__new__。但是事情可能变得复杂起来……因此,拥有非抽象的Fooish可能会更容易。

    07-24 20:46