“针对接口而不是对象的代码”的 Python 版本是啥?

Posted

技术标签:

【中文标题】“针对接口而不是对象的代码”的 Python 版本是啥?【英文标题】:What's the Python version for “Code against an interface, not an object”?“针对接口而不是对象的代码”的 Python 版本是什么? 【发布时间】:2011-05-31 17:53:20 【问题描述】:

灵感来自 here. 的一个好问题(和一堆好答案)

“针对接口而不是对象的代码”语句在 Python 中是否有任何意义?

我正在寻找类似于Original Question 中的答案,但需要使用 Python sn-ps 和想法。

【问题讨论】:

只是不要认为 interface 与 Java 或 C# 中的 interface 含义相同。它可以应用于任何面向对象的语言,但可能以不同的方式应用。也许你不能在代码中强制执行它,但这个概念本身仍然适用。 不,python 没有接口。直到最近它还没有抽象类。即便如此,两者都不是必需的。在 python 中为接口编码没有意义,因为您不必在分配或使用变量之前声明它的类型。 @Falmarri:在某种程度上,如果您依赖鸭子类型,您可以针对界面执行代码。因为你假设例如传递给您的方法的对象具有方法 A、B 和 C,您不关心其他任何事情。您只是无法通过代码强制执行此操作。至少,我是这么看的,但也许这是一个过于哲学或意识形态的讨论;) @Felix:是的,这就是语言的作用。但是您没有针对接口进行编码,因为没有接口。在 Java 中针对接口进行编码可确保实现该接口的类型的对象具有这些方法。这在 python 中是不保证的。 @Falmarri:当然不能保证,当然没有 Python 相当于“接口”作为“关于成员的静态检查合同”。但是在类型系统之外,在常规级别上,我们确实使用了可以称为“接口”的东西 - map 期望一个可调用和可迭代的,而不是从假设的 FunctionClassBaseIterable 派生的东西。这只是隐含的。 【参考方案1】:

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

Python 中常见的鸭子类型是文件(实际上,类似文件的对象)、映射(dict-like 对象)、可调用对象(类似函数的对象)、序列(list-like 对象)和iterables(可以迭代的东西,可以是容器或生成器)。

例如,需要文件的 Python 功能通常会很乐意接受实现了它所需的 file 方法的对象;它不需要派生自 file 类。例如,要将对象用作标准输出,它需要的主要是write() 方法(可能还有flush()close(),它们实际上不需要做任何事情)。同样,可调用对象是具有__call__() 方法的任何对象;它不需要从函数类型派生(实际上,你不能从函数类型派生)。

您应该采取类似的方法。检查您要对对象执行的操作所需的方法和属性。更好的是,记录您的期望并假设调用您的代码的人并非完全愚蠢。 (如果他们给你一个你不能使用的对象,他们肯定会从他们得到的错误中足够快地弄清楚这一点。)仅在必要时测试特定类型。 有时是必要的,这就是 Python 为您提供 type()isinstance()issubclass() 的原因,但要小心它们。

Python 的鸭子类型相当于“针对接口而不是对象的代码”,因为建议您不要让代码过于依赖对象的类型,而是查看它是否具有您需要的接口.不同之处在于,在 Python 中,“接口”只是指提供某种行为的对象的属性和方法的非正式捆绑,而不是专门命名为 interface 的语言结构。

您可以使用abc 模块在一定程度上形式化 Python“接口”,它允许您使用您想要的任何标准声明给定类是给定“抽象基类”(接口)的子类,例如因为“它具有属性colortail_lengthquack,并且quack 是可调用的。”但这仍然比具有接口功能的静态语言严格得多。

【讨论】:

+1 请注意,理想情况下,您只需要带有抽象基类作为类型的isinstancetype 真的很邪恶 - 它没有考虑子类型! 公平地说,“针对接口而不是对象的代码”在 Python 中是默认的,除非您明确使用type()isinstance() 等。 @delnan,+1 提到 type() 是邪恶的。 @kindall:为什么我们更喜欢避免isinstance(),而更喜欢引发要处理的异常? 因为没有理由仅仅因为它的血统而拒绝一个可以做你需要它做的事情的对象。【参考方案2】:

“针对接口而不是对象的代码”的 Python 版本是什么?

正确的引用是“针对接口的程序,而不是实现”。该原则在 Python 中的原理与在 Smalltalk 中的原理相同,它起源于它的语言。

是否声明“针对接口而不是对象进行编码”。在 Python 中有什么意义?

是的。它在 Python 中的意义与在此引文所源自的语言 (Smalltalk) 以及其他所有语言中的意义相同。

【讨论】:

我不在乎你居高临下的语气。 @martineau:正如你可能从我的名字中推断出来的,也可能没有,英语不是我的母语。如果有什么我可能错误地以这样的方式表达,以至于你认为它是居高临下的,我深表歉意并谦虚地请求你帮助改进我的答案。 是的,您可以按照任何语言的报价进行操作。但正如其他答案正确指出的那样,在 Python 中做这些事情的正确方法是利用 duck-typing 的力量。您问 Python 版本与 SmallTalk 有何不同是对的,但首选方法可能(是?)不同。【参考方案3】:

接口意味着您期望某些方法在对象之间存在并标准化;这就是接口或抽象基类或您希望考虑的任何实现的重点。

例如(Java),可能有一个对称加密接口,如下所示:

public interface cipher 

    public void encrypt(byte[] block, byte[] key); 
    public void decrypt(byte[] block, byte[] key);    

那么就可以实现了:

public class aes128 implements cipher
 
    public void encrypt(byte[] block, byte[] key)
    
        //...
    
    public void decrypt(byte[] block, byte[] key)
    
        //...
    

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

cipher c;

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

c = new aes128();

您现在可以调用您希望 cipher 拥有的方法。

那是java。现在这是你在 python 中所做的:

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

当你想使用这个,而你不确定你传递的对象是不是时,尝试使用它:

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

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

简而言之,一种编程形式是你必须遵守接口规则,另一种是说你甚至不需要把它们写下来,你只需相信如果它没有出错,它就可以工作.

我希望向您展示两者之间的实际区别。

【讨论】:

这是对这个问题的更多概念性答案的一个很好的补充。 Nitpick:在 Python 中,throw 拼写为 raise。 :) @Kark sssssh 你可以看出我最近写了太多java/c++!!谢谢,已编辑。 非常感谢您的出色解释。我在其他人的代码中看到接口的唯一地方是 zope 和 twisted (IAbstractFactory .. et al)。我自己从未在我的代码中使用过任何这些。想知道我是否做得不对。 不错的答案。这可能意味着在 python 中可以通过针对抽象类进行编程来近似地实现针对接口的编程。 python中有一个抽象类模块。请看这里(***.com/questions/13646245/…)。【参考方案4】:

要了解 Python 中的接口,您必须了解鸭子类型。来自 Python glossary:

duck-typing:一种编程风格,它不查看对象的类型来确定它是否具有正确的接口;相反,方法或属性只是简单地调用或使用(“如果它看起来像鸭子,叫起来像鸭子,那它一定是鸭子。”)通过强调接口而不是特定类型,精心设计的代码通过允许多态替换。 Duck-typing 避免了使用 type() 或 isinstance() 的测试。 (但是请注意,duck-typing 可以用抽象基类来补充。)相反,它通常使用 hasattr() 测试或 EAFP 编程。

Python 鼓励对接口进行编码,只是它们不是强制执行的,而是按照惯例。可迭代对象、可调用对象或文件接口等概念在 Python 中非常普遍——以及依赖于 map、filter 或 reduce 等接口的内置函数。

【讨论】:

EAFP 代表 Easier to ask forgiveness than Permission。 “如果它看起来像鸭子,叫起来像鸭子,我们至少要考虑手上有一只鸭科水生小鸟的可能性。” ——道格拉斯·亚当斯

以上是关于“针对接口而不是对象的代码”的 Python 版本是啥?的主要内容,如果未能解决你的问题,请参考以下文章

如何切换python版本

Anaconda更改python版本

Linux更新Python版本及修改python默认版本的方法

修改系统默认的python版本

python官网上版本那么多要下载哪个?

python各种包的对应版本