“对接口编程,而非对对象编程”的Python版本是什么?
4 个回答
接口的意思是你希望某些方法在不同的对象中都是存在的,并且是标准化的;这就是接口或抽象基类的意义所在,不管你想用什么样的实现方式。
举个例子(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
是一个字符串类型,而不是你需要的正确类型,它也会自动抛出错误,你会捕获到这个错误(希望如此)。
简单来说,一种编程方式是有类型的,你必须遵循接口规则,另一种方式则是说你甚至不需要把这些规则写下来,你只需相信如果没有错误,那就说明它工作正常。
我希望这能让你明白两者之间的实际区别。
要理解Python中的接口,首先得明白“鸭子类型”这个概念。根据Python的官方解释:
鸭子类型:这是一种编程风格,它不关注一个对象的具体类型来判断它是否有合适的接口;相反,它只是直接调用或使用这个对象的方法或属性(“如果它看起来像鸭子,叫声也像鸭子,那它就一定是鸭子。”)通过强调接口而不是具体类型,设计良好的代码可以提高灵活性,允许多态替换。鸭子类型避免使用type()或isinstance()这样的类型检查。不过,鸭子类型可以和抽象基类一起使用。通常,它会使用hasattr()测试或者EAFP(“先尝试,后处理”)的编程风格。
Python鼓励根据接口来编写代码,但这些接口并不是强制的,而是通过约定来实现的。像可迭代对象、可调用对象或者文件接口这样的概念在Python中非常普遍,还有一些内置函数,比如map、filter和reduce,它们也依赖于这些接口。
“针对接口编写代码,而不是对象”在Python中并不完全成立,因为Python没有接口这个特性。Python中类似的概念是“使用鸭子类型”。换句话说,如果你想判断一个对象是不是鸭子,你应该检查它是否有一个quack()
的方法,或者更好的是,直接尝试调用quack()
并处理可能出现的错误,而不是去测试它是否是Duck
类的实例。
在Python中,常见的“鸭子类型”包括文件(其实是类似文件的对象)、映射(像dict
的对象)、可调用对象(像函数的对象)、序列(像list
的对象)和可迭代对象(可以遍历的东西,可能是容器或生成器)。
举个例子,Python中的一些功能如果需要文件,通常会接受任何实现了所需file
方法的对象;这个对象不一定要是file
类的子类。例如,要把一个对象用作标准输出,最重要的是它需要有一个write()
方法(可能还需要flush()
和close()
,但这些方法不一定要真的执行什么操作)。同样,一个可调用对象是指任何有__call__()
方法的对象;它不需要是函数类型的子类(实际上,你不能从函数类型派生)。
你也应该采取类似的方法。检查你需要的对象的方法和属性。更好的是,记录下你期望的内容,并假设调用你代码的人不是完全的傻瓜。(如果他们给你一个你无法使用的对象,他们很快就会从错误中发现问题。)只有在必要时才测试特定类型。确实有时候是必要的,这就是为什么Python提供了type()
、isinstance()
和issubclass()
,但使用这些时要小心。
Python的鸭子类型在某种意义上等同于“针对接口编写代码,而不是对象”,因为它建议你不要过于依赖对象的类型,而是看看它是否具备你需要的接口。不同的是,在Python中,“接口”只是指一个对象的属性和方法的非正式集合,这些属性和方法提供了某种行为,而不是一个专门命名为interface
的语言构造。
你可以在一定程度上使用abc
模块来规范化Python的“接口”,这个模块允许你声明某个类是某个“抽象基类”(接口)的子类,使用你想要的任何标准,比如“它有属性color
、tail_length
和quack
,并且quack
是可调用的。”但这仍然比静态语言中的接口特性要宽松得多。