如何构建优雅的Python API

时间:2022-12-18 09:38:04

优雅的Python APIs

英文原文地址:http://ozkatz.github.com/better-python-apis.html 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并不占用很多的工作,你的用户(包括未来的你)会感谢你的所作所为。