现在的位置: 首页 > 综合 > 正文

如何构建优雅的Python API

2014年09月05日 ⁄ 综合 ⁄ 共 3590字 ⁄ 字号 评论关闭

优雅的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并不占用很多的工作,你的用户(包括未来的你)会感谢你的所作所为。

抱歉!评论已关闭.