关于oop:”面向接口的代码,而不是对象的代码”的python版本是什么?

What's the Python version for “Code against an interface, not an object”?

灵感来自一个伟大的问题(和一堆伟大的答案)。

语句"针对接口而不是对象编码"在Python中是否有任何意义?

我在寻找与原始问题类似的答案,但是用python代码片段和想法。


"针对接口的代码,而不是对象"在Python中没有字面意义,因为该语言没有接口特性。粗糙的python等价物是"使用duck类型"。如果您想查看一个对象是否是duck,换句话说,您应该检查它是否有quack()方法,或者更好的方法是尝试quack()并提供适当的错误处理,而不是测试它是否是Duck的实例。

python中常见的duck类型有:文件(实际上是类文件的对象)、映射(dict类对象)、可调用(类函数的对象)、序列(list类对象)和iterables(可以迭代的对象,可以是容器或生成器)。

例如,需要一个文件的python特性通常乐于接受一个实现它需要的file方法的对象;它不需要从file类派生。例如,要使用一个对象作为标准输出,它需要的主要内容是一个write()方法(可能是flush()close(),它们实际上不需要做任何事情)。类似地,可调用是任何具有__call__()方法的对象;它不需要从函数类型派生(实际上,您不能从函数类型派生)。

你应该采取类似的方法。检查您将要对对象执行的操作所需的方法和属性。更好的是,记录您所期望的,并假设调用您的代码的人不是一个完全的涂鸦者。(如果他们给你一个你不能使用的对象,他们肯定会很快从错误中找到答案。)只有在必要的时候才测试特定的类型。有时是必要的,这就是为什么python会给你type()isinstance()issubclass(),但要小心。

python的duck类型相当于"针对接口的代码,而不是对象的代码",从这个意义上讲,我们建议您不要让代码过于依赖于对象的类型,而是看它是否具有所需的接口。不同之处在于,在Python中,"接口"只是指提供某种行为的对象的属性和方法的非正式捆绑包,而不是专门命名为interface的语言构造。

在某种程度上,可以使用abc模块将python的"interfaces"形式化,该模块允许您使用所需的任何条件(例如"它具有colortail_lengthquack属性和quack可调用)声明给定类是给定"抽象基类"(interface)的子类。但这是st比具有接口特性的静态语言要严格得多。


要理解Python中的接口,您必须理解duck类型。从python术语表中:

duck-typing: A programming style which does not look at an object’s type to determine if it has the right interface; instead, the method or attribute is simply called or used ("If it looks like a duck and quacks like a duck, it must be a duck.") By emphasizing interfaces rather than specific types, well-designed code improves its flexibility by allowing polymorphic substitution. Duck-typing avoids tests using type() or isinstance(). (Note, however, that duck-typing can be complemented with abstract base classes.) Instead, it typically employs hasattr() tests or EAFP programming.

Python鼓励对接口进行编码,只是它们不是强制的,而是通过约定来实现的。诸如iterables、callables或file接口之类的概念在python中非常普遍——以及依赖于map、filter或reduce等接口的内置组件。


接口意味着您希望某些方法存在并跨对象标准化;这是接口或抽象基类的要点,或者您想考虑的任何实现。

例如,Java可能有一个对称加密的接口,如:

1
2
3
4
5
public interface cipher
{
    public void encrypt(byte[] block, byte[] key);
    public void decrypt(byte[] block, byte[] key);    
}

然后您可以实现它:

1
2
3
4
5
6
7
8
9
10
11
public class aes128 implements cipher
{
    public void encrypt(byte[] block, byte[] key)
    {
        //...
    }
    public void decrypt(byte[] block, byte[] key)
    {
        //...
    }
}

然后可以这样声明一个对象:

1
cipher c;

我们在这里做了什么?我们已经创建了这个对象c,它的类型必须与接口的类型匹配。c可以引用任何与此接口匹配的内容,因此下一阶段将是:

1
c = new aes128();

现在可以调用预期cipher所具有的方法。

那是Java。下面是您在python中的操作:

1
2
3
4
5
6
7
8
9
10
11
class aes128(Object):

    def __init__(self):
        pass

    def encrypt(self, block, key):
        # here I am going to pass, but you really
        # should check what you were passed, it could be
        # anything. Don't forget, if you're a frog not a duck
        # not to quack!
        pass

如果您想使用此项,但不确定所传递的对象是否为,请尝试使用它:

1
2
3
4
5
c = aes128()
try:
    c.encrypt(someinput, someoutput)
except:
    print"eh? No encryption method?!"

在这里,如果C.Encrypt不能处理已经传递的内容(如果方法存在),那么您就依赖于C.Encrypt对raise的实现。当然,如果c是字符串类型,因此不是您需要的正确类型,那么它也将自动抛出,并且您将捕获(希望如此)。

简言之,一种编程形式的类型是这样的,你必须遵守接口规则,另一种是说你甚至不需要写下来,你只是相信如果它没有错误,它是有效的。

我希望这能告诉你这两者的实际区别。


What's the Python version for"Code against an interface, not an object."?

正确的引用是"针对接口的程序,而不是实现"。这一原则在Python中与它在Smalltalk(它的起源语言)中的做法相同。

Does the statement"Code against an interface, not an object." have any significance in Python?

对。它在python中的意义与它在引用的语言(smalltalk)以及其他所有语言中的意义相同。