优雅的Python APIs
Python提供了大量的内置函数,运算符和关键字。利用Python自身提供的数据结构和内置的类型使开发工作变得容易,但通常当我们定义自己的数据类型(类)时,我们更倾向于提出我们自己的一套方式来操作和使用我们的数据。
Python中的好东西之一是:我们可以用“下划线方法”(魔术方法),以使我们自己建的类与内置函数和运算符兼容一致,这往往是我们缺少的。
利用这些魔术方法使得我们的代码更容易使用,而且讲讨厌的,复杂的实现隐藏封装,从而使用户有一个更好的工作环境。更重要的是,它使我们的代码更加直观。这意味着,在许多情况下,我们的API按照用户的期望做它做的事。我把这种代码UX,我们应该努力不断地改进使用这些魔术方法。
下面提出一些实际的例子:
让你的代码友好的REPL(表现)
Python有一个很酷的的REPL
shell,我经常用很多的时间来反省代码,并发挥它的功能来实现反省,最后才将它们添加到我的代码库。我用 BPython
或 IPython
,而不是常规的Python shell,因为他们提供了许多细微的功能如自动完成,语法高亮,历史,和在线文档。我知道每天有许多Python开发人员使用这些工具。
那么,如何才能使我们的代码更容易在这样的环境中使用呢?
看下面这个类的例子:
class Container(object): def__init__(self,*args): self.objects= args
如果在我们的REPL环境创建一个Container 对象,我们将看到
>>>from myprogram import Container >>> Container(1,2,'Hello',False) <myprogram.Containerobject at 0x107f74f90> >>>
不是很有可读性,看似很混乱。让我们添加一个__repr__
方法:
class Container(object): def__init__(self,*args): self.objects= args def__repr__(self): return'Container(%s)'% (', '.join(map(repr,self.objects)))
现在在Python shell里做同样的事情
>>>from myprogram import Container >>> Container(1,2,'Hello',False) Container(1,2,'Hello',False) >>>
这下看起来好多了。我们现在得到了一个可以实际使用的表现对象的方式。我们没有跟踪类的创建,我们也没有猜测对象含有什么。
同时在做日志时,这也是非常有用的可复用的方式。
创建含有较复杂数据列表的迭代器
我们可以使用__
iter__方法使我们的类具有迭代器的方式来遍历我们的目标。如果我们正与一些外部资源的列表或创建列表工作,我们可以暴露一个迭代器方法,并对用户隐藏我们的潜在的复杂性,并让他们简单地用一个for
循环就可以遍历所有数据。
下面是一个例子:一个 YouTube的搜索API的包装,可以看出使用了迭代器(为了简化说明,这是一个简化版本,和实际的代码相比,我已经将错误处理,缓存或验证等删除)
import requests class YoutubeSearch(object): def __init__(self, term): self.query_url='https://gdata.youtube.com/feeds/api/videos' self.query_params= { 'q': term, 'alt':'json', 'orderby':'relevance', 'v':'2' } def _do_request(self): return requests.get(self.query_url, params=self.query_params).json def __iter__(self): for video inself._do_request().get('feed').get('entry'): result = {} result['title']= video.get('title').get('$t') result['url']= video.get('link')[0].get('href') yield result
要运行这个例子,你需要安装Requests。
现在,我们可以简单的实例化一个对象来搜索YouTube,并将结果遍历
>>>from myprogram import YoutubeSearch >>> yt_search = YoutubeSearch('DjangoCon 2012') >>>for video in yt_search: ... print(video) ... {'url':u'https://www.youtube.com/watch?v=0FD510Oz2e4&feature=youtube_gdata','title':u'DjangoCon 2012 - Alex Gaynor "Take Two: If I got to do it all over again"'} {'url':u'https://www.youtube.com/watch?v=IKQzXu43hzY&feature=youtube_gdata','title':u'DjangoCon 2012 - Daniel Lindsley "API Design Tips"'} ...
这使得我们建立了一个非常简单和可读性的API,我们也可以使用这种技术来加载资源和生成实例,而不是在需要的时候提前获取一切,返回一个大列表。因此,我们也可以利用它来提高效率和性能。
算术运算符重载,
如果你是处理你自己的特殊数据类型,你可以覆盖算术运算符并在被处理的实例之间使用。
例如,Python的datetime
模块可以让你用一个日期对象减去另一个日期对象,在处理timedelta
对象,只需使用 -
(减号),即可处理您的数字。
当然要做到这一点,你的类需要定义一个__
sub__ 方法
class ExpressiveList(list): def__sub__(self, other): new_list = ExpressiveList(self) ifisinstance(other,list): for item in other: new_list.remove(item) else: new_list.remove(other) return new_list
现在,我们可以使用-
运算符从列表中删除项目
>>> l = ExpressiveList([1,2,3,4,5]) >>> l [1,2,3,4,5] >>> l -2 [1,3,4,5] >>> l - [2,4,1] [3,5] >>>
我们可以同样覆盖__add__,__mul__,__
div__等基本上所有其他的算术运算魔法函数。
给你的对象添加布尔值意义
有时我们想给我们的对象一个布尔值意义。例如,Python的列表如果为空将返回false,如果它的长度> 0返回true。我们可以在我们自己的类重载 __nonzero__(或__
bool__ 在Python 3)方法:
class Container(object): def __init__(self,*args): self.objects= args def__nonzero__(self): return bool(self.objects) def __bool__(self): # Python 3 compatibility return self.__nonzero__()
现在我们可以测试布尔值
>>>if Container(1,2,'Hello',True): ... print('true!') ...else: ... print('false') ... true! >>># would print "false" for an empty Container()
但是,这还不是全部
我们还可以用一些其他的方法来使我们的代码更容易使用。这里有几个常用的方法:
-
__len__ -确定LEN(my_object)的
返回值 -
__contains__ -确定x
in my_object是真或假 - __getattr__ -创建动态属性或返回一些特殊的找不到对象的属性值。非常的REPL友好(因为我们不能在对象上设置自动完成属性)
-
__cmp__ -使用这个类的对象之间的比较。一旦定义,我们可以使用内建的sorted()
函数包含我们的类的对象列表进行排序。
而且有很多
有用的方法。在Python中创建具有表现力和直观的API并不占用很多的工作,你的用户(包括未来的你)会感谢你的所作所为。