>>> from env_helper import info; info()
页面更新时间: 2023-12-27 10:44:38
运行环境:
    Linux发行版本: Debian GNU/Linux 12 (bookworm)
    操作系统内核: Linux-6.1.0-16-amd64-x86_64-with-glibc2.36
    Python版本: 3.11.2

6.10. 熟悉Python对象协议

因为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)

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

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

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

_images/img6.png

基本上,只要实现了表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__()则提供对内S函数reversed()的支持。容器类型中最有 特色的是对成员关系的判断符in和not in的支持,这个方法叫__contains__(),只要支持这个 函数就能够使用in和not in运算符了。

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

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

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等语言中的接口,它更像是声明,没有语言上的 约束力,需要大家共同遵守。