阅读实际使用的开源项目如flask,对于提高编程能力有巨大好处。flask是实现网站功能,使用数据库的一个编程框架,已有中文出版有关书籍介绍。本系列讲座涉及的是非常精彩的The Flask Mega-Tutorial,开始于2017年12月6日开始,结束于2018年5月结束,每周一课。
对于一个不太大,但足够复杂的app的代理current_app的实现方法,本系列讲座进行彻底分析,分析过程像美食家需要慢慢品味精美大餐,以体会高明厨师的精巧设计和制作。主餐啦:
系列四
主餐(1)
get_ident线程标识函数
本地容器Local
本地工作栈LocalStrak
1、 get_ident线程标识函数
Import 语句与其它语句一样,也可作为try,except格式中的语句,寻找最新的可用get_ident函数。
# since each threadhas its own greenlet we can just use those as identifiers
# for the context. If greenlets are notavailable we fall back to the
# current thread ident depending on where it is.
try:
from greenletimport getcurrent as get_ident
except ImportError:
try:
from threadimport get_ident
except ImportError:
from _threadimport get_ident
get_ident(或greenlet 模块的getcurrent)可以作为线程的标识,用于线程管理,其机理如下:
当某一线程调用get_ident将得到与此线程相关的标识。如果函数来自于thread模块,此标识就是一个整数;如果使用的是greenlet 模块中的getcurrent函数,此标识可以为greenlet的对象,例如<greenlet.greenlet object at 0x01373E90>。同一线程无论调用get_ident多少次,标识不会改变,但不同线程的标识不会相同。有关greenlet信息,可查阅python之greenlet学习。
2、 工作容器Local的测试方法
下面release_local函数的注释部分给出了工作容器Local的测试方法。测试方法具体见文中中文注释。
def release_local(local):
"""Releases the contents of the local forthe current context.
This makes it possible to use localswithout a manager.
Example::
>>> loc = Local() #产生一个Local的实例loc
>>> loc.foo = 42 #增加属性foo,并赋值42
>>>release_local(loc) #释放包括属性的内容
>>> hasattr(loc, 'foo') #测试属性foo是否还存在
False #测试结果:属性foo已不存在
With this function one can release:class:`Local` objects as well
as :class:`LocalStack` objects. However it is not possible to
release data held by proxies thatway, one always has to retain
a reference to the underlying localobject in order to be able
to release it.
.. versionadded:: 0.6.1
"""
local.__release_local__()
最后一句表明,函数release_local的作用是调用参数local的release_local方法。
3、 工作容器Local类
Local类的开始部分如下:
class Local(object): __slots__ = ('__storage__', '__ident_func__') 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)
__slots__语句(见系列三)申明Local类有且仅有__storage__和__ident_func__两个属性。所以要关注的就是两个属性。__storage__将作为Local类的属性存放的字典,代替__dict__字典;__ident_func__是识别函数变量。
在__init__中,此两个属性被初始化:字典__storage__被初始化为空字典,识别函数__ident_func__初始化为前面的get_ident函数。
使用object.__setattr__的方法,而不使用一般初始化属性采用的格式:
self.__storage__= {}
self.__ident_func__= get_ident
的原因是在后面__setattr__被重载,参见系列一。
后面是此类的迭代方法__iter__。
最后是__call__方法,使得Local的实例可以像函数一样调用。例如
>>>local = Local()
>>>local(proxy)
__call__在下一系列中进行测试。
下面是类的后半部分,涉及与属性操作相关的三个方法,使用字典storage。由于重载了__setattr__,对属性写入操作调用此方法。而对于属性读出操作,对于槽口__ident_func__和storage,会调用__get__,对于其它属性,属性的常规操作getattr将失败,就会调用__getattr__。(参见系列一__getattr__、系列三__slots__)。
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)
先看__setattr__方法:
在前面的初始化中,storage被初始化成空字典。
首先变量ident得到函数get_ident返回代表线程的标识;
然后试图进行操作:
storage[ident][name] = value
如果storage为空字典,将发生exceptKeyError,使得执行后面的语句
storage[ident] ={name: value}
用此ident作为键,设置{name:value}作为值。其中name是“属性”的名称,value是“属性”的值。例如假设loc是Local的实例,ident为整数的简单情形如7916,则属性赋值:
loc.foo = 42
后,loc.__storage__为变成
{7916: {'foo': 42}}。
当同一个线程第二次设置“属性”的值,变量ident仍旧得到同一标识,则调用__setattr__进行的操作为:
storage[7916][name] = value;
否则仍将发生except KeyError,而执行其后的语句。
__getattr__方法:按线程__ident_func__标识函数的结果,返回此线程的属性的值。
__delattr__方法:按线程__ident_func__标识函数的结果,删除线程的此属性。
__release_local__方法:
删除特定线程的内容。例如上面的标识为7916的线程的内容,此线程的所有“属性”也将不存在了。
4、 本地工作栈LocalStrak
定义一个实例变量_local,它是上面Local的一个实例。从Local可知,实例变量_local的属性和方法有:
1) 实例属性变量__ident_func__,用于线程标识;
2) _local可以进行属性操作;
3) 删除特定线程的内容。
利用上面的1),定义一个property属性,名称为__ident_func__,对此属性的访问,就是对_local.__ident_func__的访问,省去了_local。
利用上面的3),定义__release_local__,调用Local的__release_local__,删除特定线程的内容。
LocalStack的前半部分如下,其中的__call__方法以后分析:
from my_app2.flask4_1 import Local
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` functionbut 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 currentcontext (and as such released).
By calling the stack withoutarguments 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)
利用2)的属性操作,建立一个stack属性。
stack属性是一个工作栈,用列表list构成。栈的常规操作为push、pop。
先看push:
def push(self, obj):
"""Pushes a new item to thestack"""
rv = getattr(self._local, 'stack', None)
if rv is None:
self._local.stack= rv = []
rv.append(obj)
return rv
如果_local有属性stack,则rv会得到此stack,否则rv将得到None,接下来程序把stack初始化为空列表,由此创建了stack属性。
列表的append方法向列表添加对象了,就是使对象入栈。
下面是方法pop:
def pop(self):
"""Removes the topmost item from thestack, will return the
old value or `None` if the stackwas already empty.
"""
stack = getattr(self._local, 'stack', None)
if stackis None:
return None
elif len(stack)== 1:
release_local(self._local)
return stack[-1]
else:
return stack.pop()
从工作栈弹出对象:
先把工作栈赋值给stack变量,然后判stack的元素个数,如只有一个,程序删除此线程的内容,stack属性也会丢失,随后返回此元素;否则程序从stack的尾部,即栈顶弹出一个对象,返回此对象。
注意像list这样的mutable变量,在下面的语句
stack = getattr(self._local, 'stack', None)
执行后,对于stack的操作,仍会影响_local.stack,因为在stack赋值中得到是一个id号码,此id号码对应一个list的对象,_local.stack的值也是此id号码。对于python来说,就是同一对象,所以对于stack的内容更改,也就是对_local.stack的更改。
下面是property的属性top,返回栈顶对象。
@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
小结:LocalStack是一个栈,有push、pop、top方法。
测试如下:
>>> ls= LocalStack()
>>> ls.push(42)
>>> ls.top
42
>>> ls.push(23)
>>> ls.top
23
>>> ls.pop()
23
>>> ls.top
42