返回介绍

建议63:熟悉 Python 对象协议

发布于 2024-01-30 22:19:09 字数 3157 浏览 0 评论 0 收藏 0

因为Python是一门动态语言,Duck Typing的概念遍布其中,所以其中的Concept并不以类型的约束为载体,而另外使用称为协议的概念。所谓协议,类似你讲英语,我也讲英语,我们就可以交流;在Python中就是我需要调用你某个方法,你正好就有这个方法。比如在字符串格式化中,如果有占位符%s,那么按照字符串转换的协议,Python会去自动地调用相应对象的__str__()方法。

>>> class Object(object):
...  def __str__(self):
...      print 'called __str__'
...      return super(Object, self).__str__()
... 
>>> o = Object()
>>> print "%s" % o
called __str__
<__main__.Object object at 0x10277a5d0>

这倒数第二行就是明证。除了__str__()外,还有其他的方法,比如__repr__()、__int__()、__long__()、__float__()、__nonzero__()等,统称类型转换协议。除了类型转换协议之外,还有许多其他协议。

1)用以比较大小的协议,这个协议依赖于__cmp__()方法,与C语言库函数cmp类似,当两者相等时,返回0,当self<other时返回负值,反之返回正值。因为它的这种复杂性,所以Python又有__eq__()、__ne__()、__lt__()、__gt__()等方法来实现相等、不等、小于和大于的判定。这也就是Python对==、!=、<和>等操作符的进行重载的支撑机制。

2)数值类型相关的协议,这一类的函数比较多,如表6-2所示。

表6-2 Python的协议与函数对应关系表

基本上,只要实现了表6-2中的几个方法,基本上就能够模拟数值类型了。不过还需要提到一个Python中特有的概念:反运算。别被吓着,其实非常简单。以加法为例,something+other,调用的是something的__add__()方法,如果something没有__add__()方法怎么办呢?调用other.__add__()是不对的,这时候Python有一个反运算的协议,它会去查看other有没有__radd__()方法,如果有,则以something为参数调用之。类似__radd__()的方法,所有的数值运算符和位运算符都是支持的,规则也是一律在前面加上前缀r即可,在此不再细表。

3)容器类型协议。容器的协议是非常浅显的,既然为容器,那么必然要有协议查询内含多少对象,在Python中,就是要支持内置函数len(),通过__len__()来完成,一目了然。而__getitem__()、__setitem__()、__delitem__()则对应读、写和删除,也很好理解。__iter__()实现了迭代器协议,而__reversed__()则提供对内置函数reversed()的支持。容器类型中最有特色的是对成员关系的判断符in和not in的支持,这个方法叫__contains__(),只要支持这个函数就能够使用in和not in运算符了。

4)可调用对象协议。所谓可调用对象,即类似函数对象,能够让类实例表现得像函数一样,这样就可以让每一个函数调用都有所不同。

>>> class Functor(object):
...  def __init__(self, context):
...      self._context = context
...  def __call__(self):
...      print 'do something with %s' % self._context
... 
>>> lai_functor = Functor('lai')
>>> yong_functor = Functor('yong')
>>> lai_functor()
do something with lai
>>> yong_functor()
do something with yong

5)与可调用对象差不多的,还有一个可哈希对象,它是通过__hash__()方法来支持hash()这个内置函数的,这在创建自己的类型时非常有用,因为只有支持可哈希协议的类型才能作为dict的键类型(不过只要继承自object的新式类默认就支持了)。

6)前面的文档谈对描述符协议和属性交互协议(__getattr__()、__setattr__()、__delattr()),那么剩下来还值得一谈的就是上下文管理器协议了,也就是对with语句的支持,这个协议通过__enter__()和__exit__()两个方法来实现对资源的清理,确保资源无论在什么情况下都会正常清理。

class Closer:
  '''
通过with
语句和一个close
方法来关闭一个对象'''
  def __init__(self, obj):
    self.obj = obj
  def __enter__(self):
    return self.obj # bound to target
  def __exit__(self, exception_type, exception_val, trace):
    try:
      self.obj.close()
    except AttributeError: # obj isn't closable
      print 'Not closable.'
      return True # exception handled successfully

对于实现了这两个方法的Closer类,我们可以如下使用它:

>>> from ftplib import FTP
>>> with Closer(FTP('ftp.somesite.com')) as conn:
...  conn.dir()
...
>>> conn.dir()
>>>

可以看到第二次调用conn.dir()已经没有输出,这是因为这个FTP连接会话已被关闭的缘故。与这里Closer类似的类在标准库中已经存在,就是contextlib里的closing。

至此,常用的对象协议就讲完了,只要活学活用这些协议,就能够写出更为Pythonic的代码。不过也要注意,协议不像C++、Java等语言中的接口,它更像是声明,没有语言上的约束力,需要大家共同遵守。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文