原文链接
链接:https://www.jianshu.com/p/3f38b777a621
werzeug之LocalProxy源码
LocalProxy用于代理Local对象和LocalStack对象,而所谓代理就是作为中间的代 理人来处理所有针对被代理对象的操作 # 源码注释 """ 充当本地 werkzeug 的代理。 将所有操作转发到 近在咫多的物体。 唯一不支持转发的操作 是右手操作和任何类型的分配。 范例用法: 从 werkzeug.本地导入本地 l = 本地() 这些是代理 请求 = l("请求") 用户 = l("用户") 从 werkzeug.本地导入本地 Stack _response_local = 本地堆栈() 这是一个代理 响应 = _response_local() """
源码如下
@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__'): # "local" is a callable that is not an instance of Local or # LocalManager: mark it as a wrapped function. object.__setattr__(self, '__wrapped__', 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. """ # 由于所有Local或LocalStack对象都有__release_local__ method, 所以如果没有该属性就表明self.__local为callable对象 if not hasattr(self.__local, '__release_local__'): return self.__local() try: # 此处self.__local为Local或LocalStack对象 return getattr(self.__local, self.__name__) except AttributeError: raise RuntimeError('no object bound to %s' % self.__name__) @property def __dict__(self): try: return self._get_current_object().__dict__ except RuntimeError: raise AttributeError('__dict__') def __getattr__(self, name): if name == '__members__': return dir(self._get_current_object()) return getattr(self._get_current_object(), name) def __setitem__(self, key, value): self._get_current_object()[key] = value def __delitem__(self, key): del self._get_current_object()[key] if PY2: __getslice__ = lambda x, i, j: x._get_current_object()[i:j] def __setslice__(self, i, j, seq): self._get_current_object()[i:j] = seq def __delslice__(self, i, j): del self._get_current_object()[i:j] # 截取部分操作符代码 __setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v) __delattr__ = lambda x, n: delattr(x._get_current_object(), n) __str__ = lambda x: str(x._get_current_object()) __lt__ = lambda x, o: x._get_current_object() < o __le__ = lambda x, o: x._get_current_object() <= o __eq__ = lambda x, o: x._get_current_object() == o
浅析
1. 首先在__init__method中传递的local参数会被赋予属性_LocalProxy__local, 该属性可以通过self.__local进行访问 2. LocalProxy通过_get_current_object来获取代理的对象。需要注意的是当初始化参数 为callable对象时,则直接调用以返回Local或LocalStack对象,具体看源代码的注释。 3. 重载了绝大多数操作符,以便在调用LocalProxy的相应操作时, 通过_get_current_object method来获取真正代理的对象,然后再进行相应操作
LocalProxy的使用
#初始化LocalProxy有三种方式: ''' 1. 通过Local或者LocalStack对象的__call__ method from werkzeug.local import Local l = Local() # these are proxies request = l('request') user = l('user') from werkzeug.local import LocalStack _response_local = LocalStack() # this is a proxy response = _response_local() ''' 上述代码直接将对象像函数一样调用,这是因为Local和LocalStack都实现了__call__ 方法,这样其对象就是callable的,因此当我们将对象作为函数调用时, 实际调用的是__call__ 方法,可以看下本文开头部分的Local的源代码, 会发现__call__ 方法,会返回一个LocalProxy对象 2. 通过LocalProxy类进行初始化 ''' l = Local() request = LocalProxy(l, 'request') ''' 实际上这段代码跟第一种方式是等价的,但这种方式是最'原始'的方式, 我们在Local的源代码实现中看到其__call__ 方法就是通过这种方式生成LocalProxy的 3. 使用callable对象作为参数 ''' request = LocalProxy(get_current_request()) ''' 通过传递一个函数,我们可以自定义如何返回Local或LocalStack对象
为什么要使用LocalProxy
可是说了这么多,为什么一定要用proxy,而不能直接调用Local或LocalStack对象呢? 这主要是在有多个可供调用的对象的时候会出现问题,
示例:不用LocalProxy
# use Local object directly from werkzeug.local import LocalStack user_stack = LocalStack() user_stack.push({'name': 'Bob'}) user_stack.push({'name': 'John'}) def get_user(): # do something to get User object and return it return user_stack.pop() # 直接调用函数获取user对象 user = get_user() print user['name'] print user['name'] # 结果 ''' John John '''
示例:使用LocalProxy
# use LocalProxy from werkzeug.local import LocalStack, LocalProxy user_stack = LocalStack() user_stack.push({'name': 'Bob'}) user_stack.push({'name': 'John'}) def get_user(): # do something to get User object and return it return user_stack.pop() # 通过LocalProxy使用user对象 user = LocalProxy(get_user) print user['name'] print user['name'] # 结果 ''' Bob John '''
总结
直接使用LocalStack对象,user一旦赋值就无法再动态更新了,而使用Proxy, 每次调用操作符(这里[]操作符用于获取属性),都会重新获取user, 从而实现了动态更新user的效果。 Flask以及Flask的插件很多时候都需要这种动态更新的效果, 因此LocalProxy就会非常有用了
偏函数
Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function) 偏函数的作用:将所作用的函数作为partial()函数的第一个参数,原函数的各个参 数依次作为partial()函数的后续参数,原函数有关键字参数的一定要带上关键字, 没有的话,按原有参数顺序进行补充。 简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设 置默认值),返回一个新的函数,调用这个新函数会更简单。
flask之LocalProxy应用偏函数
# LocalProxy代理 import functools class LocalProxy(object): def __init__(self, local): # self.__local = local object.__setattr__(self, "_LocalProxy__local", local) def _get_current_object(self): return self.__local() # self._LocalProxy__local() def __setitem__(self, key, value): # 函数() 自动传入session,ctx.session[key] = value self._get_current_object()[key] = value def __getattr__(self, name): # ctx.request.method return getattr(self._get_current_object(), name)
def _lookup_req_object(name):
""" 传入参数:name = request/session/g """ # 取栈顶 ctx = (request, session) / app_ctx = (app, g) top = _request_ctx_stack.top if top is None: raise RuntimeError(_request_ctx_err_msg) # return:ctx.request / ctx.session/ app_ctx.g return getattr(top, name) _request_ctx_stack = LocalStack() _app_ctx_stack = LocalStack() # request.form >>> 触发__getattr__ request = LocalProxy(partial(_lookup_req_object, "request")) # session['k1'] = 123,触发__setitem__ session = LocalProxy(partial(_lookup_req_object, "session")) g = LocalProxy(partial(_lookup_app_object, "g"))