Python 中的 Java 抽象/接口设计

发布于 2024-12-16 14:30:56 字数 95 浏览 0 评论 0原文

我有许多类,它们都共享相同的方法,只是实现不同。在 Java 中,让每个类实现一个接口或扩展一个抽象类是有意义的。 Python 是否有与此类似的东西,或者我应该采取替代方法?

I have a number of classes which all share the same methods, only with different implementations. In Java, it would make sense to have each of these classes implement an interface or extend an abstract class. Does Python have anything similar to this, or should I be taking an alternative approach?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

探春 2024-12-23 14:30:56

Python 中的接口背后有一个故事。多年来一直占据主导地位的原始态度是,你不需要它们:Python 遵循 EAFP(请求宽恕比请求许可更容易)原则。也就是说,您不需要指定您接受一个我不知道的 ICloseable 对象,您只需在需要时尝试关闭该对象,并且如果它引发异常,那么它也会引发异常。

因此,本着这种心态,您只需单独编写类,然后按照您的意愿使用它们。如果其中之一不符合要求,你的程序就会抛出异常;相反,如果您使用正确的方法编写另一个类,那么它就会正常工作,而不需要指定它实现您的特定接口。

这工作得很好,但是接口有明确的用例,特别是对于较大的软件项目。 Python 的最终决定是提供 abc 模块,它允许您编写抽象基类,即除非重写其所有方法,否则无法实例化的类。您认为使用它们是否值得由您决定。

介绍 ABC 的 PEP 比我解释得更好:

在面向对象编程领域,以下的使用模式
与对象的交互可以分为两个基本类别,
分别是“调用”和“检查”。

调用意味着通过调用对象的方法来与对象交互。
通常这与多态性相结合,以便调用给定的
方法可能会根据对象的类型运行不同的代码。

检查意味着外部代码的能力(在
对象的方法)来检查该对象的类型或属性,
并据此决定如何处理该物体
信息。

两种使用模式服务于相同的总体目标,即能够
支持处理多样化和潜在新颖的对象
统一的方式,但同时允许处理决策
为每种不同类型的对象定制。

在经典的 OOP 理论中,调用是首选的使用模式,
并且积极劝阻检查,因为被认为是一个遗物
早期的过程式编程风格。然而,在实践中这种观点
简直太教条、死板,导致了一种设计
刚性与动态特性非常不一致
像 Python 这样的语言。

特别是,通常需要以以下方式处理对象:
是对象类的创建者没有预料到的。它不是
始终是构建到每个对象方法中的最佳解决方案
满足该对象的每个可能用户的需求。而且,
有许多强大的调度理念直接影响到
与严格遵守行为的经典 OOP 要求相反
封装在对象内,例如规则或模式匹配
驱动逻辑。

另一方面,经典 OOP 对检查的批评之一
理论家的缺点是缺乏形式主义和特定的性质
正在接受检查。在像 Python 这样的语言中,几乎任何
对象的某个方面可以被外部反映并直接访问
代码中,有许多不同的方法来测试对象是否符合
是否符合特定协议。例如,如果问“这是吗?”
对象可变序列容器?',可以寻找基类
'list',或者可以查找名为 '_getitem_' 的方法。但请注意
尽管这些测试看起来很明显,但它们都不是
正确,因为一个产生假阴性,另一个产生假阴性
积极的一面。

普遍同意的补救措施是标准化测试,并且
将它们分组为正式的安排。这最容易通过
与每个类关联一组标准的可测试属性,
通过继承机制或其他方式。每次测试
带有一系列承诺:它包含关于
班级的一般行为,以及对其他班级的承诺
方法将可用。

本 PEP 提出了组织这些测试的特定策略
称为抽象​​基类,或 ABC。 ABC 就是简单的 Python 类
添加到对象的继承树中以发出某些信号
将该物体的特征提供给外部检查员。测试是使用完成的
isinstance(),并且特定 ABC 的存在意味着测试
已经过去了。

此外,ABC 定义了一组最小的方法来建立
该类型的特征行为。歧视性代码
基于 ABC 类型的对象可以相信这些方法将
永远在场。这些方法中的每一个都伴随着一个
广义抽象语义定义,描述于
ABC 的文档。这些标准语义定义不是
强制执行,但强烈建议。

与 Python 中的所有其他事物一样,这些承诺的本质是
君子协定,在这种情况下意味着,虽然
语言确实执行了 ABC 中做出的一些承诺,它已经完成了
给具体类的实现者,以确保剩余的
保留。

There's a bit of a story behind interfaces in Python. The original attitude, which held sway for many years, is that you don't need them: Python works on the EAFP (easier to ask forgiveness than permission) principle. That is, instead of specifying that you accept an, I don't know, ICloseable object, you simply try to close the object when you need to, and if it raises an exception then it raises an exception.

So in this mentality you would just write your classes separately, and use them as you will. If one of them doesn't conform to the requirements, your program will raise an exception; conversely, if you write another class with the right methods then it will just work, without your needing to specify that it implements your particular interface.

This works pretty well, but there are definite use cases for interfaces, especially with larger software projects. The final decision in Python was to provide the abc module, which allows you to write abstract base classes i.e. classes that you can't instantiate unless you override all their methods. It's your decision as to whether you think using them is worth it.

The PEP introducing ABCs explain much better than I can:

In the domain of object-oriented programming, the usage patterns for
interacting with an object can be divided into two basic categories,
which are 'invocation' and 'inspection'.

Invocation means interacting with an object by invoking its methods.
Usually this is combined with polymorphism, so that invoking a given
method may run different code depending on the type of an object.

Inspection means the ability for external code (outside of the
object's methods) to examine the type or properties of that object,
and make decisions on how to treat that object based on that
information.

Both usage patterns serve the same general end, which is to be able to
support the processing of diverse and potentially novel objects in a
uniform way, but at the same time allowing processing decisions to be
customized for each different type of object.

In classical OOP theory, invocation is the preferred usage pattern,
and inspection is actively discouraged, being considered a relic of an
earlier, procedural programming style. However, in practice this view
is simply too dogmatic and inflexible, and leads to a kind of design
rigidity that is very much at odds with the dynamic nature of a
language like Python.

In particular, there is often a need to process objects in a way that
wasn't anticipated by the creator of the object class. It is not
always the best solution to build in to every object methods that
satisfy the needs of every possible user of that object. Moreover,
there are many powerful dispatch philosophies that are in direct
contrast to the classic OOP requirement of behavior being strictly
encapsulated within an object, examples being rule or pattern-match
driven logic.

On the other hand, one of the criticisms of inspection by classic OOP
theorists is the lack of formalisms and the ad hoc nature of what is
being inspected. In a language such as Python, in which almost any
aspect of an object can be reflected and directly accessed by external
code, there are many different ways to test whether an object conforms
to a particular protocol or not. For example, if asking 'is this
object a mutable sequence container?', one can look for a base class
of 'list', or one can look for a method named '_getitem_'. But note
that although these tests may seem obvious, neither of them are
correct, as one generates false negatives, and the other false
positives.

The generally agreed-upon remedy is to standardize the tests, and
group them into a formal arrangement. This is most easily done by
associating with each class a set of standard testable properties,
either via the inheritance mechanism or some other means. Each test
carries with it a set of promises: it contains a promise about the
general behavior of the class, and a promise as to what other class
methods will be available.

This PEP proposes a particular strategy for organizing these tests
known as Abstract Base Classes, or ABC. ABCs are simply Python classes
that are added into an object's inheritance tree to signal certain
features of that object to an external inspector. Tests are done using
isinstance(), and the presence of a particular ABC means that the test
has passed.

In addition, the ABCs define a minimal set of methods that establish
the characteristic behavior of the type. Code that discriminates
objects based on their ABC type can trust that those methods will
always be present. Each of these methods are accompanied by an
generalized abstract semantic definition that is described in the
documentation for the ABC. These standard semantic definitions are not
enforced, but are strongly recommended.

Like all other things in Python, these promises are in the nature of a
gentlemen's agreement, which in this case means that while the
language does enforce some of the promises made in the ABC, it is up
to the implementer of the concrete class to insure that the remaining
ones are kept.

夜雨飘雪 2024-12-23 14:30:56

我对 Python 不太熟悉,但我大胆猜测它并不熟悉。

Java 中存在接口的原因是它们指定了一个契约。例如,实现 java.util.List 的东西保证有一个 add() 方法来符合接口上定义的一般行为。您可以在不知道其特定类的情况下放入 List 的任何(正常)实现,调用接口上定义的一系列方法并获得相同的一般行为。

此外,开发人员和编译器都可以知道这样的方法存在并且可以在相关对象上调用,即使他们不知道其确切的类。这是静态类型所需的一种多态性形式,以允许不同的实现类,但仍然知道它们都是合法的。

这在 Python 中实际上没有意义,因为它不是静态类型的。您不需要声明对象的类,也不需要让编译器相信您调用的方法确实存在。鸭子类型世界中的“接口”就像调用方法并相信对象可以正确处理该消息一样简单。

注意 - 欢迎更有知识的 Pythonista 进行编辑。

I'm not that familiar with Python, but I would hazard a guess that it doesn't.

The reason why interfaces exist in Java is that they specify a contract. Something that implements java.util.List, for example, is guaranteed to have an add() method to conforms to the general behaviour as defined on the interface. You could drop in any (sane) implementation of List without knowing its specific class, call a sequence of methods defined on the interface and get the same general behaviour.

Moreover, both the developer and compiler can know that such a method exists and is callable on the object in question, even if they don't know its exact class. It's a form of polymorphism that's needed with static typing to allow different implementation classes yet still know that they're all legal.

This doesn't really make sense in Python, because it's not statically typed. You don't need to declare the class of an object, nor convince the compiler that methods you're calling on it definitely exist. "Interfaces" in a duck-typing world are as simple as invoking the method and trusting that the object can handle that message appropriately.

Note - edits from more knowledgeable Pythonistas are welcome.

梦里泪两行 2024-12-23 14:30:56

也许你可以使用这样的东西。这将充当抽象类。因此每个子类都被迫实现 func1()

class Abstract:

    def func1(self):
        raise NotImplementedError("The method not implemented")

May be you can use something like this. This will act as an abstract class. Every subclass is thus forced to implement func1()

class Abstract:

    def func1(self):
        raise NotImplementedError("The method not implemented")
感情旳空白 2024-12-23 14:30:56

我在 3.5+ 中编写了一个 ,允许用 Python 编写接口。

要点是在 inspect 的帮助下编写一个类装饰器。

import inspect


def implements(interface_cls):
    def _decorator(cls):
        verify_methods(interface_cls, cls)
        verify_properties(interface_cls, cls)
        verify_attributes(interface_cls, cls)
        return cls

    return _decorator


def verify_methods(interface_cls, cls):
    methods_predicate = lambda m: inspect.isfunction(m) or inspect.ismethod(m)
    for name, method in inspect.getmembers(interface_cls, methods_predicate):
        signature = inspect.signature(method)
        cls_method = getattr(cls, name, None)
        cls_signature = inspect.signature(cls_method) if cls_method else None
        if cls_signature != signature:
            raise NotImplementedError(
                "'{}' must implement method '{}({})' defined in interface '{}'"
                .format(cls.__name__, name, signature, interface_cls.__name__)
            )


def verify_properties(interface_cls, cls):
    prop_attrs = dict(fget='getter', fset='setter', fdel='deleter')
    for name, prop in inspect.getmembers(interface_cls, inspect.isdatadescriptor):
        cls_prop = getattr(cls, name, None)
        for attr in prop_attrs:
            # instanceof doesn't work for class function comparison
            if type(getattr(prop, attr, None)) != type(getattr(cls_prop, attr, None)):
                raise NotImplementedError(
                    "'{}' must implement a {} for property '{}' defined in interface '{}'"  # flake8: noqa
                    .format(cls.__name__, prop_attrs[attr], name, interface_cls.__name__)
                )


def verify_attributes(interface_cls, cls):
    interface_attributes = get_attributes(interface_cls)
    cls_attributes = get_attributes(cls)
    for missing_attr in (interface_attributes - cls_attributes):
        raise NotImplementedError(
            "'{}' must have class attribute '{}' defined in interface '{}'"
            .format(cls.__name__, missing_attr, interface_cls.__name__)
        )


def get_attributes(cls):
    boring = dir(type('dummy', (object,), {}))
    return set(item[0] for item in inspect.getmembers(cls)
               if item[0] not in boring and not callable(item[1]))

然后你可以像这样编写类:

class Quackable:
    def quack(self) -> bool:
        pass


@implements(Quackable)
class MallardDuck:    
    def quack(self) -> bool:
        pass

但是下面会给你一个错误:

@implements(Quackable)
class RubberDuck:    
    def quack(self) -> str:
        pass

NotImplementedError: 'RubberdDuck' must implement method 'quack((self) -> bool)' defined in interface 'Quackable'

I wrote a library in 3.5+ the allows for writing interfaces in Python.

The gist is to write a class decorator with the help of inspect.

import inspect


def implements(interface_cls):
    def _decorator(cls):
        verify_methods(interface_cls, cls)
        verify_properties(interface_cls, cls)
        verify_attributes(interface_cls, cls)
        return cls

    return _decorator


def verify_methods(interface_cls, cls):
    methods_predicate = lambda m: inspect.isfunction(m) or inspect.ismethod(m)
    for name, method in inspect.getmembers(interface_cls, methods_predicate):
        signature = inspect.signature(method)
        cls_method = getattr(cls, name, None)
        cls_signature = inspect.signature(cls_method) if cls_method else None
        if cls_signature != signature:
            raise NotImplementedError(
                "'{}' must implement method '{}({})' defined in interface '{}'"
                .format(cls.__name__, name, signature, interface_cls.__name__)
            )


def verify_properties(interface_cls, cls):
    prop_attrs = dict(fget='getter', fset='setter', fdel='deleter')
    for name, prop in inspect.getmembers(interface_cls, inspect.isdatadescriptor):
        cls_prop = getattr(cls, name, None)
        for attr in prop_attrs:
            # instanceof doesn't work for class function comparison
            if type(getattr(prop, attr, None)) != type(getattr(cls_prop, attr, None)):
                raise NotImplementedError(
                    "'{}' must implement a {} for property '{}' defined in interface '{}'"  # flake8: noqa
                    .format(cls.__name__, prop_attrs[attr], name, interface_cls.__name__)
                )


def verify_attributes(interface_cls, cls):
    interface_attributes = get_attributes(interface_cls)
    cls_attributes = get_attributes(cls)
    for missing_attr in (interface_attributes - cls_attributes):
        raise NotImplementedError(
            "'{}' must have class attribute '{}' defined in interface '{}'"
            .format(cls.__name__, missing_attr, interface_cls.__name__)
        )


def get_attributes(cls):
    boring = dir(type('dummy', (object,), {}))
    return set(item[0] for item in inspect.getmembers(cls)
               if item[0] not in boring and not callable(item[1]))

You can then write classes like this:

class Quackable:
    def quack(self) -> bool:
        pass


@implements(Quackable)
class MallardDuck:    
    def quack(self) -> bool:
        pass

Below would give you an error though:

@implements(Quackable)
class RubberDuck:    
    def quack(self) -> str:
        pass

NotImplementedError: 'RubberdDuck' must implement method 'quack((self) -> bool)' defined in interface 'Quackable'
幻梦 2024-12-23 14:30:56

根据这篇文章 https://realpython.com/python-interface/ ,这就是你可以do formal interface

import abc

class CRUDInterface(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def create(self, *args, **kwargs):
        """Load in the data set"""
        raise NotImplementedError
    
    @abc.abstractmethod
    def read(self, *args, **kwargs):
        """Load in the data set"""
        raise NotImplementedError
    
    @abc.abstractmethod
    def update(self, *args, **kwargs):
        """Load in the data set"""
        raise NotImplementedError
    
    @abc.abstractmethod
    def delete(self, *args, **kwargs):
        """Load in the data set"""
        raise NotImplementedError



class ModelTest1(CRUDInterface):
    def create(self, *args, **kwargs):
        pass
    def read(self, *args, **kwargs):
        pass
    def update(self, *args, **kwargs):
        pass
    def delete(self, *args, **kwargs):
        pass
    
class ModelTest2(CRUDInterface):
        pass
    

当抽象方法未被重写时会引发错误的正式接口。

>>> t1 = ModelTest1()
>>> t2 = ModelTest2()
Traceback (most recent call last):
  File "../abstractmodel.py", line 46, in <module>
    t2 = ModelTest2()
TypeError: Can't instantiate abstract class ModelTest2 with abstract methods create, delete, read, update

according this articule https://realpython.com/python-interface/ , this is how you can do formal interface:

import abc

class CRUDInterface(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def create(self, *args, **kwargs):
        """Load in the data set"""
        raise NotImplementedError
    
    @abc.abstractmethod
    def read(self, *args, **kwargs):
        """Load in the data set"""
        raise NotImplementedError
    
    @abc.abstractmethod
    def update(self, *args, **kwargs):
        """Load in the data set"""
        raise NotImplementedError
    
    @abc.abstractmethod
    def delete(self, *args, **kwargs):
        """Load in the data set"""
        raise NotImplementedError



class ModelTest1(CRUDInterface):
    def create(self, *args, **kwargs):
        pass
    def read(self, *args, **kwargs):
        pass
    def update(self, *args, **kwargs):
        pass
    def delete(self, *args, **kwargs):
        pass
    
class ModelTest2(CRUDInterface):
        pass
    

formal interface that will raise errors when the abstract methods aren’t overridden.

>>> t1 = ModelTest1()
>>> t2 = ModelTest2()
Traceback (most recent call last):
  File "../abstractmodel.py", line 46, in <module>
    t2 = ModelTest2()
TypeError: Can't instantiate abstract class ModelTest2 with abstract methods create, delete, read, update
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文