Flask源码之偏函数与localproxy

偏函数的功能很简单,以函数名为参数传入functools.partial中对函数进行改造;下面先看下例子:

import functools

def fun(name, age):
    print(name, age)

new_fun = functools.partial(fun, age=19)
new_fun('andy')
# 输出: andy 19
#fun = functools.partial(fun, age=19)
# fun('andy')

可以看到,上面的例子中,偏函数的功能类似于事先传入一个参数,封装在函数内部,所以下次调用时只需要传入一个参数即可。

那如果,函数本身只有一个参数呢?

import functools

def fun(name):
    print(name)

fun = functools.partial(fun, name='andy')
fun()
# 输出 andy

很明显,由于偏函数的使用,已经将参数name传入,再次调用时,便不需要传入参数了。这个在flask中有什么应用呢?

在flask的global模块中:

request = LocalProxy(partial(_lookup_req_object, "request"))
session = LocalProxy(partial(_lookup_req_object, "session"))
g = LocalProxy(partial(_lookup_app_object, "g"))

来看看_lookup_req_object:

def _lookup_req_object(name):
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError(_request_ctx_err_msg)
    return getattr(top, name)

这个函数只有一个name参数,top, LocalStack中的t栈顶,返回的是栈中的name键对应的值。在global模块中,传入了name=request,

也就是说事先将request, session传入了_lookup_re_object函数,经过偏函数的改造,后面再使用_lookup_req_object函数时,便不需要传入函数。

接着我们看看LocalProxy

为了说明这个原理,我们先看一人简单的例子:

class LocalProxy(object):
    def __init__(self, f):
        self.f = f()

    def __setitem__(self, key, value):
        self.f[key] = value

    def __getitem__(self,item):
        return self.f[item]

    def __getattr__(self, item):
        return getattr(self.f, item)


def func():
    return ctx.session


def func2():
    return ctx.request

我们暂时忽略这里的ctx,只是用来演示:

functions = LocalProxy(func)
functions['key1'] = 'andy'
functions['key2']
functions.method

我们把func当作参数传入LocalProxy, LocalProxy.func = func() = ctx.session
functions['key1']调用LocalProxy的setitem方法,LocalProxy.f[key]=ctx.session['key']='andy'
functions['key2'] 调用LocalProxy的getitem方法,即ctx.session['key2']
functions.method 则调用LocalProxy的getattr方法, getattr(ctx.request, method)

下面看看flask中的LocalProxy

class LocalProxy(object):

    def __init__(self, local, name=None):
        object.__setattr__(self, "_LocalProxy__local", local)
        object.__setattr__(self, "__name__", name)

    def _get_current_object(self):
        if not hasattr(self.__local, "__release_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)

    def __setitem__(self, key, value):
        self._get_current_object()[key] = value

    def __delitem__(self, key):
        del self._get_current_object()[key]

下面一点点分析代码:

    def __init__(self, local, name=None):
        object.__setattr__(self, "_LocalProxy__local", local)
        object.__setattr__(self, "__name__", name)

local是:from werkzeug.local import Local, 也就是一个以线程id为键的字典,它的值是也是一个字典。即:__storage__ = {id:{}}

_LocalProxy__local = {id:{}},后面调用的getitem, setitem等都是去这个字典中取值。

可以看到,它与上面的例子是一样的,只是它看起来比较绕,因为这些值是在local对象中,本质上都是去Local中设置值,获取值等操作,但LocalProxy的作用正如它的名字,代理人的角色,而不是直接去取。考虑到这里涉及的源码太多,如果后面有时间再更新。

上一篇:Flask源码分析之请求流程

下一篇:Flask-script简单使用