这篇文章主要介绍了Bottle框架中的装饰器类和描述符应用详解,具有一定参考价值,需要的朋友可以了解下。
最近在阅读Python微型Web框架Bottle的源码,发现了Bottle中有一个既是装饰器类又是描述符的有趣实现。刚好这两个点是Python比较的难理解,又混合在一起,让代码有些晦涩难懂。但理解代码之后不由得为Python语言的简洁优美赞叹。所以把相关知识和想法稍微整理,以供分享。
正文
Bottle是Python的一个微型Web框架,所有代码都在一个bottle.py文件中,只依赖标准库实现,兼容Python 2和Python 3,而且最新的稳定版0.12代码也只有3700行左右。虽然小,但它实现了Web框架基本功能。这里就不以过多的笔墨去展示Bottle框架,需要的请访问其网站了解更多前端的相关知识点众多,如果有不清楚之处可以看看《流畅的Python》第20章属性描述符,里面有非常详细的介绍。
简单来说,描述符是对多个属性运用相同存取逻辑的一种方式,如Bottle框架里我们需要对很多属性都进行判断某个键是否在environ中,如果在则返回,如果不在,需要解析一次这样的存取逻辑。而描述符需要实现特定协议,包括__set__, __get__, __delete___方法,分别对应设置,读取和删除属性的方法。他么的参数也比较特殊,如__get__方法的三个参数self, obj, cls分别对应描述符实例的引用,对第三版的代码来说就是Descriptor(‘environ’, ‘bottle.get.query’, query, read_only=True)创建的实例的引用;obj则对应将某个属性托管给描述的实例对象的引用,对应的应该为request对象;而cls则为Request类的引用。在调用request.query时编译器会自动传入这些参数。如果以Request.query的方式调用,那么obj参数的传入值为None,这时候通常的处理是返回描述符实例。
在Descriptor中__get__方法的代码最多,也比较难理解,但如果记住其参数的意义也没那么难。下面以query的实现为例,我添加一些注释来帮助理解
key, storage = self.key, getattr(obj, self.attr) # key='bottle.get.query' # storage = environ 即包含HTTP请求的信息的environ # 判断envrion中是否包含key来决定是否需要解析 if key not in storage: storage[key] = self.getter(obj) # self.getter(obj)就是调用了原来的query方法,不过要传入一个Request实例,也就是obj return storage[key]
而__set__, __delete__代码比较简单,在这里我们把只来源gao($daima.com搞@代@#码(网读属性在赋值和删除时抛出的错误定制为AttributeError(‘Read only property.’),方便调试。
通过使用描述符这个有些难懂的方法,我们可以在Request的方法中专心于编写如何解析的代码,不用担心属性的存取逻辑。和在每个方法中都使用if判断相比高到不知道哪里去。但美中不足的是,这样让我们的方法代码后面拖着一个“小尾巴”,即
query = Descriptor('envrion', 'bottle.get.query', query, read_only=True)
怎么去掉这个这个“小尾巴“呢?回顾之前的代码几乎都是对query之类的方法进行修饰,所以可以尝试使用装饰器,毕竟装饰器就是对某个函数进行修饰的,而且我们应该使用参数化的装饰器,这样才能将envrion等参数传递给装饰器。如果要实现参数化装饰器就需要一个装饰器工厂函数,也就是说装饰器的代码里需要嵌套至少3个函数体,写起来有写绕,代码可阅读性也有差。更大的问题来自如何将描述符与装饰器结合起来,因为Descriptor是一个类而不是方法。
解决办法其实挺简单的。如果知道Python中函数也是对象,实现了__call__方法的对象可以表现得像函数一样。所以我们可以修改Descirptor的代码,实现__call__方法,让它的实例成为callable对象就可以把它用作装饰器;而要传入的参数可以以实例属性存储起来,通过self.attribute的形式访问,而不是像使用工厂函数实现参数化装饰器时通过闭包来实现参数的访问获取。这时候再来看看Bottle里的DictProperty代码
class DictProperty(object): """ Property that maps to a key in a local dict-like attribute. """ def __init__(self, attr, key=None, read_only=False): self.attr, self.key, self.read_only = attr, key, read_only def __call__(self, func): functools.update_wrapper(self, func, updated=[]) self.getter, self.key = func, self.key or func.__name__ return self def __get__(self, obj, cls): if obj is None: return self key, storage = self.key, getattr(obj, self.attr) if key not in storage: storage[key] = self.getter(obj) return storage[key] def __set__(self, obj, value): if self.read_only: raise AttributeError("Read-Only property.") getattr(obj, self.attr)[self.key] = value def __delete__(self, obj): if self.read_only: raise AttributeError("Read-Only property.") del getattr(obj, self.attr)[self.key]
其实就是一个有描述符作用的装饰器类,它的使用方法很简单:
@DictProperty('environ', 'bottle.get.query', read_only=True) def query(self): """ some codes """
拆开会更好理解点:
property = DictProperty('environ', 'bottle.get.query', read_only=True) @property def query(self): """ some codes """
再把@实现的语法糖拆开:
def query(self): """ some codes """ property = DictProperty('environ', 'bottle.get.query', read_only=True) query = property(query) # @实现的语法糖
再修改以下代码形式:
def query(self): """ some codes """ query = DictProperty('environ', 'bottle.get.query', read_only=True)(query)
是不是和第三版的实现方式非常相似。
def query(self): """ some codes """ query = Descriptor('environ', 'bottle.get.query', query, read_only=True)
但我们可以使用装饰器把方法体后面那个不和谐的赋值语句”小尾巴“去掉,将属性存取管理抽象出来,而且只需要使用一行非常简便的装饰器把这个功能添加到某个方法上。这也许就是Python的美之一吧。
写在后面
DictProperty涉及知识远不止文中涉及的那么简单,如果你还是不清楚DictProperty的实现功能,建议阅读《流畅的Python》第7章和第22章,对装饰器和描述符有详细的描述,另外《Python Cookbook》第三版第9章元编程有关于参数化装饰器和装饰器类的叙述和示例。如果你对Bottle为什么要实现这样的功能感到困惑,建议阅读Bottle的文档和WSGI相关的文章。
其实前一阵再阅读Bottle源码时就想写一篇文章,但奈何许久不写东西文笔生疏加上医院实习期间又比较忙,一直推到现在才终于磕磕绊绊地把我阅读的Bottle源码的一些感悟写出来,希望对喜欢Python的各位有些帮助把。
总结
以上就是本文关于Bottle框架中的装饰器类和描述符应用详解的全部内容,希望对大家有所帮助。感谢朋友们对本站的支持!
以上就是Bottle框架中的装饰器类和描述符应用详解的详细内容,更多请关注gaodaima搞代码网其它相关文章!