A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© Mr吴 初级黑马   /  2019-1-15 09:46  /  801 人查看  /  0 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

预备知识:本地线程我们要在同一个进程中隔离不同线程的数据,那么我们会优先选择threading.local,来实现数据的彼此隔离.但是当基于协程(Greenlet/Eventlet)实现时,Local很难满足我们的需求.
在Flask中基于Werkzeug实现了可以满足协程的Local,werkzeug.local.Local
werkzeug.local.Local源码如下

[Python] 纯文本查看 复制代码
class Local(object):
    #__slots__是为了限制此类的可绑定属性,对于其派生类不起作用.
    __slots__ = ('__storage__', '__ident_func__')

    # 当该类实例化时调用object的方法给Local对象绑定上__storage__和__ident_func__属性或方法.
    # 为啥使用自己的__setattr__方法?因为这样会进入一个死循环,自己写一下代码试试吧
    def __init__(self):
        object.__setattr__(self, '__storage__', {})
        object.__setattr__(self, '__ident_func__', get_ident)

    def __iter__(self):
        return iter(self.__storage__.items())

    def __call__(self, proxy):
        """Create a proxy for a name."""
        return LocalProxy(self, proxy)

    def __release_local__(self):
        self.__storage__.pop(self.__ident_func__(), None)

    def __getattr__(self, name):
        try:
            return self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        ident = self.__ident_func__()
        storage = self.__storage__
        try:
            storage[ident][name] = value
        except KeyError:
            storage[ident] = {name: value}

    def __delattr__(self, name):
        try:
            del self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

其中通过get_ident这个方法来拿到线程或者协程的唯一标识
[Python] 纯文本查看 复制代码
try:
    from greenlet import getcurrent as get_ident
except ImportError:
    try:
        from thread import get_ident
    except ImportError:
        from _thread import get_ident

通过上面的引用代码来看.Werkzeug实现的Local与threading.local最大的不同点是:

[Python] 纯文本查看 复制代码
werkzeug会在Greenlet可用的情况下优先使Greenlet的id而不是本地线程

LocalStack和LocalProxy
LocalStack是基于Local实现的一个栈结构.栈的特性就是后入先出。当我们进入一个 Context 时,将当前的的对象推入栈中。然后我们也可以获取到栈顶元素。从而获取到当前的上下文信息。
LocalStack的代码如下:
[Python] 纯文本查看 复制代码
class LocalStack(object):

    """This class works similar to a :class:`Local` but keeps a stack
    of objects instead.  This is best explained with an example::

        >>> ls = LocalStack()
        >>> ls.push(42)
        >>> ls.top
        42
        >>> ls.push(23)
        >>> ls.top
        23
        >>> ls.pop()
        23
        >>> ls.top
        42

    They can be force released by using a :class:`LocalManager` or with
    the :func:`release_local` function but the correct way is to pop the
    item from the stack after using.  When the stack is empty it will
    no longer be bound to the current context (and as such released).

    By calling the stack without arguments it returns a proxy that resolves to
    the topmost item on the stack.

    .. versionadded:: 0.6.1
    """

    def __init__(self):
        self._local = Local()

    def __release_local__(self):
        self._local.__release_local__()

    def _get__ident_func__(self):
        return self._local.__ident_func__

    def _set__ident_func__(self, value):
        object.__setattr__(self._local, '__ident_func__', value)
    __ident_func__ = property(_get__ident_func__, _set__ident_func__)
    del _get__ident_func__, _set__ident_func__
def __call__(self):
        def _lookup():
            rv = self.top
            if rv is None:
                raise RuntimeError('object unbound')
            return rv
        return LocalProxy(_lookup)

    def push(self, obj):
        """Pushes a new item to the stack"""
        rv = getattr(self._local, 'stack', None)
        if rv is None:
            self._local.stack = rv = []
        rv.append(obj)
        return rv

    def pop(self):
        """Removes the topmost item from the stack, will return the
        old value or `None` if the stack was already empty.
        """
        stack = getattr(self._local, 'stack', None)
        if stack is None:
            return None
        elif len(stack) == 1:
            release_local(self._local)
            return stack[-1]
        else:
            return stack.pop()

    @property
    def top(self):
        """The topmost item on the stack.  If the stack is empty,
        `None` is returned.
        """
        try:
            return self._local.stack[-1]
        except (AttributeError, IndexError):
            return None
LocalProxy 是代理模式的一种实现。在实例化的时候,传入一个 callable 的参数。然后这个参数被调用后将会返回一个 Local 对象。我们后续的所有操作,比如属性调用,数值计算等,都会转发到这个参数返回的 Local 对象上。
现在大家可能不太清楚,我们为什么要用 LocalProxy 来进行操作,我们来给大家看一个例子
[Python] 纯文本查看 复制代码
from werkzeug.local import LocalStack
test_stack = LocalStack()
test_stack.push({'abc': '123'})
test_stack.push({'abc': '1234'})

def get_item():
    return test_stack.pop()

item = get_item()

print(item['abc'])
print(item['abc'])

你看我们这里的输出的的值,都是统一的 1234 ,但是我们这里想做到的是每次获取的值都是栈顶的最新的元素,那么我们这个时候就应该用 proxy 模式了
[Python] 纯文本查看 复制代码
rom werkzeug.local import LocalStack, LocalProxy
test_stack = LocalStack()
test_stack.push({'abc': '123'})
test_stack.push({'abc': '1234'})

def get_item():
    return test_stack.pop()

item = LocalProxy(get_item)
print(item['abc'])
print(item['abc'])

你看我们这里就是 Proxy 的妙用。
在Flask中常用全局变量如下,他们良好的利用了LocalStack和LocalProxy
[Python] 纯文本查看 复制代码
#global.py文件
# context locals
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
g = LocalProxy(partial(_lookup_app_object, 'g'))

Contex由于 Flask 基于 Werkzeug 实现,因此 App Context 以及 Request Context 是基于前文中所说的 LocalStack 实现。
从命名上,大家应该可以看出,App Context 是代表应用上下文,可能包含各种配置信息,比如日志配置,数据库配置等。而 Request Context 代表一个请求上下文,我们可以获取到当前请求中的各种信息。比如 body 携带的信息。
这两个上下文的定义是在 flask.ctx 文件中,分别是 AppContext 以及 RequestContext 。而构建上下文的操作则是将其推入在 flask.globals 文件中定义的 _app_ctx_stack 以及 _request_ctx_stack 中。前面说了 LocalStack 是“线程”(这里可能是传统意义上的线程,也有可能是 Greenlet 这种)隔离的。同时 Flask 每个线程只处理一个请求,因此可以做到请求隔离。
目前还有一个小疑问:
1.为什么要区分APP Context 和Request Context
2.为什么要用栈结构来实现Contex
这篇文章主要参考知乎大佬Manjusaka这俩问题的情况,请移步那里吧.
简单来说就是:Werkzeug 内置的 Middleware 将两个 Flask App 组合成一个一个 WSGI Application。这种情况下两个 App 都同时在运行,只是根据 URL 的不同而将请求分发到不同的 App 上处理。
为什么不用Blueprint?
[Python] 纯文本查看 复制代码
Blueprint 是在同一个 App 下运行。其挂在 App Context 上的相关信息都是一致的。但是如果要隔离彼此的信息的话,那么用 App Context 进行隔离,会比我们用变量名什么的隔离更为方便

Middleware 模式是 WSGI 中允许的特性,换句话来讲,我们将 Flask 和另外一个遵循 WSGI 协议的 web Framework (比如 Django)那么也是可行的。

g
数据库配置和其余的重要配置信息,就挂载 App 对象上。但是如果是一些用户代码,比如你不想一层层函数传数据的话,然后有一些变量需要传递,那么可以挂在 g 上。
下面来讲解一下下列代码的意思:




[Python] 纯文本查看 复制代码
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
g = LocalProxy(partial(_lookup_app_object, 'g'))

[Python] 纯文本查看 复制代码
def _lookup_app_object(name):
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
    return getattr(top, name)

partial偏函数,是将参数传递给偏函数的第一个参数(某函数对象),得到一个新的函数对象
比如
[Python] 纯文本查看 复制代码
request = LocalProxy(partial(_lookup_req_object,'request'))
等价于
将'request'传递给_lookup_app_objec函数,生成一个无参数的函数对象,然后传递给LocalProxy类,进行实例化,执行LocalProxy的__init__方法
Proxy的代码如下:
[Python] 纯文本查看 复制代码
class LocalProxy(object):

    __slots__ = ('__local', '__dict__', '__name__', '__wrapped__')
    #LocalProxy实例化,需要传递一个可调用对象(一般是函数如上面由偏函数生成的函数),然后可以给这个对象起一个名字name
    def __init__(self, local, name=None):
        # 注意下面一行代码是个坑,很大很大的坑,就是给self设定属性,_LocalProxy__local,等价于self.__local = ***
        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.
        """
        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):
        # 当要获取代理的某个属性时,通过_get_current_object()拿到代理的对象,相当于__local属性加括号,执行那个可调用对象,拿到代理的东西
        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]
# 下面省略了好多代码,详情请见源码
    __setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
    __delattr__ = lambda x, n: delattr(x._get_current_object(), n)
    __call__ = lambda x, *a, **kw: x._get_current_object()(*a, **kw)
    __getitem__ = lambda x, i: x._get_current_object()[i]

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马