验证未知的 module/object 必须使用特定接口 (python)

Verify that an unknown module/object is obliged to a specific interface (python)

我想在运行时检查,例如,给定对象是否具有方法 foo()bar()

我的研究系统内置于 python 3.6,高度参数化并且 can/should 接受任何类型的对象作为其内置模块的替代品。此功能非常有用,因为许多使用此系统的不同学生可以轻松研究不同的行为,而无需更改我的系统的源代码。

问题是,如果他们错误地构建了模块,他们可能只有在整个实验结束后(可能是几个小时)才会发现这一点。

我正在寻找一种方法来在运行时的早期阶段检查他们的输入模块是否与特定接口匹配。甚至在它被实例化之前对其进行验证甚至更好(当输入只是类型,而不是实例时)。 例如 some_interface.verify(MyClass).

解决方案

我在网上看到了很多解决方案(比如this),但是有none个是合适的:

  1. 最常见的解决方案(try/catch)只会在运行时失败,不适用于多守护进程系统,因为当只有一个守护进程失败时很难关闭.

  2. 检查 isinstance() 不会验证任何内容。情况可能更糟,因为开发人员可能会忘记实现一个功能并使用基础 class 实现,这可能不适合其当前实现。

  3. 使用ABC(抽象基类)需要开发者继承基class。如果 she/he 不这样做,则在实例化 class 时不会发出警告或错误。另一方面,如果开发者确实实现了接口但没有继承自base,那么issubclass()就会return False。

  4. 使用 zope interfaces 是我的目标,但它有一些缺点:

    • 它要求开发人员明确提及它正在实现接口。不指定这将导致错误,尽管实际实现是正确的。
    • 它无法在实例化之前验证模块。 implementedBy() 方法只会检查声明它的模块是否正在实现接口,但要真正验证它,您应该在实际实例上调用 verifyObject()
    • 它不支持自 python 3.5
    • 以来添加的新输入功能

编辑:显然,zope 还通过调用 verifyObject(YourInterface, obj, tentative=True) 支持隐式实现,这不会强制开发人员显式定义 class 作为接口的实现者。

在我看来,问题不是工具的问题。主要问题是即使支持某些接口,也没有人能确定该模块是否真的有效。我会做的是为模块创建一个测试,并在初始化插件时 运行 它。测试不仅应验证类型和接口(isinstancehasattr 等只是完成任务的工具),而且(如果可能)还应验证模块功能的最小正确性。例如。执行一些不需要太多时间来完成和验证结果的基本任务就可以了。如果插件在此类测试任务期间失败,则该插件无效。

最近的 PEP 终于部分解决了这个问题。 PEP-0544 引入 typing.Protocol 允许定义一个可以在运行时验证的接口。 目前可以通过名为 typing-extensions.

typing 模块的非官方扩展获得

可以这样使用,例如:

from typing_extensions import Protocol, runtime
from typing import Any

@runtime
class IMyProtocol(Protocol):
    member: int

    def foo(self, parameter1: Any, parameter2: Any) -> Any:
        pass

    def bar(self, parameter1: Any) -> Any:
        pass

然后,如果我们定义一个class,我们可以检查它是否遵循协议:

class MyClass:
    def __init__(self):
        self.member = 5

    def foo(self, a, b):
        return a,b

    def bar(self, c):
        return c

isinstance(MyClass(), IMyProtocol)  # Returns True

如果我们定义错了,就会return false:

class MyOtherClass:
    def __init__(self):
        self.member = 5

    def bar(self, c):
        return c

isinstance(MyOtherClass(), IMyProtocol)  # Returns False

这个解决方案的缺点是它不验证方法的参数。并不是说实现具有正确数量的参数而不是参数的类型。