Python 检查类是否实现了不相关的接口

mic*_*cah 8 python

我知道可以检查一个类是否实现了另一个类(如果它们确实相关)。

class A(ABC):

  @abstractmethod
  @property
  def name(self):
    raise NotImplementedException()
    

class B(A):
  
  @property
  def name(self):
    return "B"

implements(B(), A) # Not a real method, what i'd like to exist
Run Code Online (Sandbox Code Playgroud)

但我还想检查一个不相关的类是否根据类的内容实现了另一个类。

Class C:
  
  @property
  def name(self):
    return "C"

implements(C(), A) # Not a real method, what i'd like to exist
Run Code Online (Sandbox Code Playgroud)

有没有一种简单的方法可以做到这一点,或者我是否需要编写一个自定义函数来比较所有方法及其方法签名?

Pyn*_*hia 9

为什么不使用协议

并让runtime_checkable装饰器启用扩展检查

from typing import Protocol, runtime_checkable

@runtime_checkable
class A(Protocol):
    @property
    def name(self):
        ...

def implements(proto: Type):
    """ Creates a decorator for classes that checks that the decorated class implements the runtime protocol `proto`
    """
    def _deco(cls_def):
        try:
            assert issubclass(cls_def, proto)
        except AssertionError as e:
            e.args = (f"{cls_def} does not implement protocol {proto}",)
            raise
        return cls_def
    return _deco

@implements(A)
class C:
  def name(self):
    return "C"
Run Code Online (Sandbox Code Playgroud)

根据需要添加所有额外的类型提示,以确保满足参数和方法返回值的约束。

注意:我还没有尝试过属性


Eme*_*kin 1

有一种方法可以使用issubclass()orisinstance()而不是 来完成类似的事情implements()。这是通过实现一个新BaseInterface类来实现的,您可以使用它来派生适当的接口(即定义需要实现的方法列表的类型,但不能提供默认实现或实例化)。

实施BaseInterface

首先,我们需要一个用于类接口类型的新元类。issubclass()这将提供和的所需行为isinstance(),并防止Interfaces 被错误地实例化。在代码片段的底部有一个空BaseInterface类,可以从中派生自定义接口。

class InterfaceMeta(type):
    """A metaclass for defining interfaces.

    An interface defines a set of public methods and attributes needed
    to interact with an object in a particular way, but is never
    instantiated directly.

    """

    def __new__(meta, name, bases, attrs):
        """Prevent interfaces from overriding __new__, __init__, or __call__."""
        banned_methods = ('__new__', '__init__', '__call__')
        if any([banned in attrs for banned in banned_methods]):
            # Someone has tried to make it possible to instantiate an Interface
            raise AttributeError(
                'It is illegal to instantiate an Interface. '
                'Therefore, it is illegal to implement any of the '
                'following methods: {}'.format(', '.join(banned_methods))
            )
        else:
            return super().__new__(meta, name, bases, attrs)

    def __call__(cls, *args, **kwargs):
        """Prevent interfaces from being instantiated."""
        raise NotImplementedError("Interface cannot be instantiated.")

    def __instancecheck__(cls, instance):
        """Control the behaviour of `isinstance`.
        
        An object is considered to be an instance of `cls` if it has
        all of `cls`'s public attributes and methods.

        """
        for cls_attr in dir(cls):
            if cls_attr.startswith('_'):
                # Private and protected attributes are not considered
                # to be part of the interface.
                continue

            if not hasattr(instance, cls_attr):
                # If `instance` is missing a public attribute of `cls`,
                # it is not an instance of `cls`.
                return False
            elif (
                callable(getattr(cls, cls_attr))
                and not callable(getattr(instance, cls_attr))
            ):
                # If `instance` is missing a public method of `cls`,
                # it is not an instance of `cls`.
                return False

        return True
        

class BaseInterface(metaclass=InterfaceMeta):
    """Base class for all interfaces."""
    pass
Run Code Online (Sandbox Code Playgroud)

用法示例

现在我们可以通过继承来定义自定义接口BaseInterface

class SomeInterface(BaseInterface):
    """A class defining an interface.

    Other objects that have the same public methods and attributes 
    as `SomeInterface` are considered to be instances of `SomeInterface`.

    """

    def name(self):
        pass

    def age(self):
        pass


class Foo:
    def name(self):
        # Some implementation...
        pass

    def age(self):
        # Some implementation...
        pass
Run Code Online (Sandbox Code Playgroud)

由于Foo实现了 的所有公共方法SomeInterface,我们得到以下行为。

In [2]: issubclass(Foo(), SomeInterface)
Out[2]: True

In [3]: isinstance(Foo(), SomeInterface)
Out[3]: True
Run Code Online (Sandbox Code Playgroud)

更多详细信息请参见此处