阅读实际使用的开源项目如flask,对于提高编程能力有巨大好处。flask是实现网站功能,使用数据库的一个编程框架,已有中文出版有关书籍介绍。本系列讲座涉及的是非常精彩的The Flask Mega-Tutorial,开始于2017年12月6日开始,结束于2018年5月结束,每周一课。
对于一个不太大,但足够复杂的app的代理current_app的实现方法,本系列讲座进行彻底分析,分析过程像美食家需要慢慢品味精美大餐,以体会高明厨师的精巧设计和制作。
系列五
主餐(2):发现暗器
本地代理LocalProxy
测试Local.__call__
测试LocalStack.__call__
partial函数
1、 本地代理LocalProxy
LocalProxy代理对象,可以通过代理访问对象的属性。
LocalProxy代理的对象分为两类,一种对象可以通过类似于属性的格式获得,称为A类对象;另一种对象可以通过调用函数的方法获得,称为B类对象。
A类对象。对象的属性也是一个对象,例如c一个可以由属性的对象(例如c是类C的实例),有属性b。此c.b也是一个可以有属性的对象,例如有属性a。如果用LocalProxy的实例localProxy代理c.b,则访问localProxy.a与访问c.b.a等价。所以创建LocalProxy的实例时,必须提供对象名与属性名(本例的c,b)作为第一第二参数。对于本例以LocalProxy(c, a)创建实例。
B类对象,例如某一函数f的返回值是对象,并且此对象有属性a,则访问localProxy.a与访问f().a等价。所以创建LocalProxy的实例时,必须提供函数名作为第一参数。对于本例以LocalProxy(f)创建实例。
特别地,第一参数有__release_local__属性时,总是按A类处理。注意Local与LocalStack均有__release_local__的方法。
下面是程序,为方便,注释了装饰类函数implements_bool。
本地代理有四个槽口:'__local'、 '__dict__'、 '__name__'、'__wrapped__'。由于有'__dict__',可以使用常规的属性操作。
#@implements_bool class LocalProxy(object): __slots__ = ('__local', '__dict__', '__name__', '__wrapped__') def __init__(self, local, name=None): object.__setattr__(self, '_LocalProxy__local', local) object.__setattr__(self, '__name__', name) if callable(local) and not hasattr(local, '__release_local__'): object.__setattr__(self, '__wrapped__', local) #print(self._LocalProxy__local is self.__local) def _get_current_object(self): """Return the current object. This is useful if you want the real object behind the proxy at a time for performance reasons or because you want to pass the object into a different context. """ if not hasattr(self.__local, '__release_local__'): print('------self=%s, self.__local=%s' %(self, str(self.__local))) return self.__local() try: return getattr(self.__local, self.__name__) except AttributeError: raise RuntimeError('no object bound to %s' % self.__name__) def __getattr__(self, name): if name == '__members__': return dir(self._get_current_object()) return getattr(self._get_current_object(), name)
对于A类对象,需要提供第一第二参数,对应__init__方法的参数local, name。
对于B类对象,需要提供第一参数,对应参数local。
在初始化时使用暗器对槽口__local进行初始化成参数local的值。
说使用暗器是指,对于属性_LocalProxy__local的设置,等价于对属性__local进行设置。下面有个注释掉的print语句,可以表明两者是一样的。
然后初始化__name__成name的值。
如果函数是可调用的,并且不能有属性__release_local__,加上__wrapped__属性。这样虽然Local与LocalStack由于__call__的存在,都是可调用的,但如果直接作为第一参数local创建,不能加上__wrapped__属性。
当一个函数有__wrapped__属性,表明此函数是被包装后的函数,例如被装饰语句包装。使用__wrapped__属性可以得到被包装前的函数。
对于方法_get_current_object:
如果没有属性__release_local__,执行调用__local()。注意对于B类,__local是函数名。
否则执行getattr(self.__local,self.__name__)得到对象。注意对于A类,__local是对象名, __name__是属性名。
对于方法__getattr__:
由于存在槽口__dict__,对于LocalProxy的属性操作使用__dict__。但是当读操作时,如果在__dict__中找不到,在父类也找不到等,将调用此方法。参见系列一中对__getattr__的说明。
如果name为'__members__'则返回调用方法_get_current_object得到对象的所有属性名(dir的结果)。
否则,调用方法_get_current_object得到对象,然后得到对象的属性。
由于只有__getattr__没有__setattr__,所以使用代理只能读取参数。
2、 测试
在Local.__call__与LocalStack.__call__的测试恰可以包括A类与B类。
1)测试Local.__call__
对于类Local,__call__如下
def __call__(self, proxy): """Create a proxy for a name.""" return LocalProxy(self, proxy)
调用LocalProxy的参数分别是Local的实例名与Local的属性名。下面,Local的实例名是loc,与loc的属性名‘req’,即对象loc.req将被代理。
测试如下:
>>>from my_app2.flask4_1 import Local >>>from my_app2.flask4_2 import LocalStack >>>from my_app2.flask5_1 import LocalProxy >>>loc = Local() >>>request = loc('req') #request代理loc.req >>>class C: ... pass
... >>>c = C() >>>loc.req = c #request将代理c >>>c.a = 6 #c设置属性 >>>request.a #使用代理可以读出 6
2) 测试LocalStack.__call__
对于类LocalStack,__call__复杂一些:
def __call__(self): def _lookup(): rv = self.top if rv is None: raise RuntimeError('object unbound') return rv return LocalProxy(_lookup)
调用LocalProxy时的参数是_lookup函数,当被执行时将返回LocalStack实例的栈顶元素。测试如下:
>>>from my_app2.flask4_1 import Local >>>from my_app2.flask4_2 import LocalStack >>>from my_app2.flask5_1 import LocalProxy >>>_response_local = LocalStack() >>>response = _response_local() #代理_response_local.top >>>class C: ... pass ... >>>c = C() >>>_response_local.push(c) #c将出现在_response_local.top [<C object at 0x0260F870>] >>>c.foo2 = 8 >>>print(response.foo2) 8
3、 partial函数
partial函数是functools模块中的函数。格式:
partial(func,*args, **keywords)
看例子容易理解。
类int(x,base=10)返回整数对象,例如对于字符串,以基为2进行解读:
>>>int('10010', base=2) 18
使用partial,可以利用类int形成一个新的类,把base固定为2:
>>>from functools import partial >>>basetwo = partial(int, base=2)
这样就可以使用此函数了:
>>>basetwo.__doc__ = 'Convert base 2 string to an int.' >>>basetwo('10010') 18