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

Jef*_*ose 43 python oop interface inversion-of-control

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

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

我正在寻找像原始问题中的答案,但有Python片段和想法.

kin*_*all 60

"对接口而不是对象的代码"在Python中没有字面意义,因为该语言没有接口功能.该粗糙的Python相当于是"使用鸭打字." 如果你想看一个对象是否是一个鸭子,换句话说,你应该检查它是否有一个quack()方法,或者更好地尝试quack()并提供适当的错误处理,而不是测试它是否是一个实例Duck.

Python中常见的duck类型是文件(嗯,实际上是类文件对象),映射(类似对象),dictcallables(类似函数的对象),sequence(类似对象)list和iterables(你可以迭代的东西,可以是容器或发电机).

例如,想要文件的Python功能通常会乐于接受实现file其所需方法的对象; 它不需要从file类中派生出来.例如,要将对象用作标准输出,它需要的主要write()方法是一种方法(也许flush()并且close(),实际上不需要做任何事情).类似地,callable是具有__call__()方法的任何对象; 它不需要从函数类型派生(事实上,你不能从函数类型派生).

你应该采取类似的方法.检查您要对对象执行的操作所需的方法和属性.更好的是,记录您的期望并假设任何调用您的代码的人都不是完全的doofus.(如果他们给你一个你不能使用的对象,他们肯定会从他们得到的错误中快速地找出它.)仅在必要时测试特定类型.这必要的时间,这就是为什么Python的给你type(),isinstance()issubclass(),但要小心他们.

Python的鸭子类型等同于"对接口而不是对象的代码",因为建议您不要让代码过于依赖对象的类型,而是要看它是否具有您需要的接口.不同之处在于,在Python中,"接口"只是指提供特定行为的对象的非正式属性和方法捆绑,而不是特定命名的语言构造interface.

您可以使用该abc模块在某种程度上形式化Python"接口" ,这允许您使用您期望的任何条件声明给定类是给定"抽象基类"(接口)的子类,例如"它具有属性color,tail_length,和quack,并且quack可以赎回." 但这仍然比具有接口功能的静态语言严格得多.

  • 可以公平地说,除非你*明确*使用`type()`,`isinstance()`等,否则默认情况下"Python中的代码而不是对象"是默认的. (4认同)
  • +1注意虽然理想情况下,你只需要`isinstance`和抽象基类作为类型.`type`真是邪恶 - 它不考虑子类型! (3认同)

Pau*_*ine 29

要理解Python中的接口,您必须了解duck-typing.从Python 词汇表中:

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

Python鼓励对接口进行编码,只是它们不是按照惯例强制执行的.像iterables,callables或文件接口这样的概念在Python中非常普遍 - 以及依赖于map,filter或reduce等接口的内置函数.

  • EAFP代表比宽容更容易请求宽恕. (7认同)
  • "如果它看起来像一只鸭子,像鸭子一样嘎嘎叫,我们至少要考虑到我们手上有一只家庭雁鸭科小型水鸟的可能性." - 道格拉斯·亚当斯 (7认同)

小智 18

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

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

public interface cipher 
{
    public void encrypt(byte[] block, byte[] key); 
    public void decrypt(byte[] block, byte[] key);    
}
Run Code Online (Sandbox Code Playgroud)

然后你可以实现它:

public class aes128 implements cipher
{ 
    public void encrypt(byte[] block, byte[] key)
    {
        //...
    }
    public void decrypt(byte[] block, byte[] key)
    {
        //...
    }
}
Run Code Online (Sandbox Code Playgroud)

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

cipher c;
Run Code Online (Sandbox Code Playgroud)

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

c = new aes128();
Run Code Online (Sandbox Code Playgroud)

您现在可以调用您希望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
Run Code Online (Sandbox Code Playgroud)

如果你想使用它,并且你不确定你传递的对象是什么,只是尝试使用它:

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

在这里,raise如果方法存在,你依赖于c.encrypt的实现,如果它无法处理传递的内容.当然,如果c是一个字符串类型,因此不是你需要的正确类型,它也会自动抛出,你会抓住(希望).

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

我希望能告诉你两者之间的实际区别.

  • Nitpick:在Python中,`throw`拼写为"raise".:) (2认同)