用 Python 的 Descriptor 特性解决一个变态的问题

               

Python 中的 Decorator 大家见得多了,但 Descriptor 用过的估计还少,它主要是 Python 自身来实现库的一些特性,比如 staticmethod 之类的,今天有机会学习、试验了 Descriptor,小有所得,跟大家分享。

今天 Jeff 给我们出了一道难题:有个叫 data 的某个 class 的实例,它有一个 item 属性,它可能是一个对象(姑且假设它是个 str object),也可能是一序列对象(比如 list object),在这个前题下,希望做到以下代码能够工作:

# 当 data.item 是一个序列data.item = ['lai', 'yong', 'hao']print(data.item) # output: ['lai', 'yong', 'hao']for i in data.item:  print i# output:laiyonghao# 当 data.item 是单个元素data.item = 'lai'print(data.item) # output: laifor i in data.item:  print i# output: lai 

如果你现在觉得没啥,那肯定是没看仔细。我来提醒一下你,最后一行的 output 居然不是 lai 三个字母分成三行!

也就是说 data.item 要做到当它是单个元素的时候,普通场合要跟单元元素一样,而迭代的场合,要跟包含多个元素的序列一样!这个要求太变态了。这么有挑战的问题,我马上祭出 python documentation,天不负苦心人,我找到了 Descriptor 这个我以前从未使用过的特性,最后解决了这个问题。详情多讲无益,直接上代码:

#!/usr/bin/env python# -*- coding:utf-8 -*-from __future__ import print_functionclass ItemDescriptor(object): def __init__(self):  self._data = [] def __get__(self, instance, type = None):  if len(self._data) == 1:   tmp = self._data[0]   class Wrapper(tmp.__class__): # 注意它的父类    def __iter__(obj):     # 这里使用的是 obj 不是 self,因为 self 已经被用了     return self._data.__iter__()    def next(obj):     return self._data.next()   return Wrapper(tmp)  return self._data def __set__(self, obj, val):  if isinstance(val, list):   self._data = val   return  self._data = [val]class Foo(object): item = ItemDescriptor() # 重要!foo = Foo()# 输出 Jeff,而不是 Jeff 一个字母一行foo.item = 'Jeff' print(foo.item)# 它的行为跟 'Jeff' 一样print('f' in foo.item)# 但迭代的时候,像 ['jeff'] 一样for i in foo.item: print(i)# 输出四行单词,而不是一行 :)foo.item = ['lai', 'pan', 'jeff', 'ken']print(foo.item)# 它的行为像 ['lai', 'pan', 'jeff', 'ken'] 一样print('ken' in foo.item)# 迭代时行为也像 ['lai', 'pan', 'jeff', 'ken'] 一样for i in foo.item: print(i)print('#' * 30)

输出:

lai@sd:~$ python test_descriptor.py JeffTrueJeff['lai', 'pan', 'jeff', 'ken']Truelaipanjeffken##############################

最后,多说一句这些代码在 py2.6 和 py3.1 下测试通过,兼容两大版本。

           
展开阅读全文

没有更多推荐了,返回首页