As a WSGI Tool Library, Werkzeug does not directly use the ThreadLocal class built in python due to some considerations. Instead, it implements a series of Local classes. Including simple Local, and LocalStack, LocalManager, and LocalProxy implemented on this basis. Next, let's take a look at the usage of these classes, the original design intention, and specific implementation skills. As a WSGI Tool Library, Werkzeug does not directly use the ThreadLocal class built in python due to some considerations. Instead, it implements a series of Local classes. Including simple Local, and LocalStack, LocalManager, and LocalProxy implemented on this basis. Next, let's take a look at the usage of these classes, the original design intention, and specific implementation skills.
Local class design
The Werkzeug designer believes that the ThreadLocal of python cannot meet the requirements, mainly for the following two reasons:
Werkzeug mainly uses "ThreadLocal" to meet the concurrency requirements. the ThreadLocal provided by python can only implement thread-based concurrency. In python, there are many other concurrency methods, such as the common greenlet, so you need to implement a Local object that supports the coroutine.
WSGI does not guarantee that a new thread is generated each time to process requests. that is to say, threads can be reused (a thread pool can be maintained to process requests ). In this way, if werkzeug uses the ThreadLocal provided by python, a "not clean (containing data related to previously processed requests)" thread will be used to process new requests.
To solve these two problems, werkzeug implements the Local class. The Local object can isolate data between threads and threads. In addition, it also supports clearing data in a thread or coroutine (in this way, corresponding data can be cleared after a request is processed, and then wait for the next request to arrive ).
How can we implement it? the idea is actually very simple. we have mentioned the ThreadLocal variable (I) in Python in depth, that is, creating a global Dictionary, then, the thread (or coroutine) identifier is used as the key, and the local data of the corresponding thread (or coroutine) is used as the value. Here, werkzeug is implemented according to the above ideas. However, it uses some dark magic of python and finally provides users with a clear and simple interface.
Implementation
The implementation of the Local class is analyzed in werkzeug. local with code of 8a84b62. Through the first two articles on ThreadLocal, we have learned the features and usage of the Local object. So here we will not provide examples of using the Local object. let's look at the code directly.
class Local(object): __slots__ = ('__storage__', '__ident_func__') def __init__(self): object.__setattr__(self, '__storage__', {}) object.__setattr__(self, '__ident_func__', get_ident) ...
Because there may be a large number of Local objects, in order to save space occupied by the Local object, use _ slots _ to write the attributes that can be owned by the Local object:
_ Storage __: The value is a dictionary used to save the actual data. the initialization is empty;
_ Ident_func __: The value is a function used to find the identifier of the current thread or coroutine.
Because the actual data of the Local object is saved in _ storage _, the operation on the Local attribute is actually an operation on _ storage. Here, we use the magic method _ getattr _ to intercept _ storage _ and _ ident_func _ to retrieve attributes, direct it to the data of the current thread or coroutine stored by _ storage. For the set or del attribute values, use _ setattr _ and _ setattr _ respectively (for details about these magic methods, see attribute control ). The key code is as follows:
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)
Suppose we have N threads or coroutines with ID 1, 2,..., N. each thread stores some Local data with a Local object. the content of the Local object is shown in:
In addition, the Local class provides the _ release_local _ method to release the data stored by the current thread or coroutine.
Local extension interface
Werkzeug implements LocalStack and LocalManager based on Local to provide more friendly interface support.
LocalStack
LocalStack encapsulates Local to implement an independent stack structure of a thread (or coroutine). there are specific usage methods in the annotations. A simple example is as follows:
ls = LocalStack() ls.push(12) print ls.top # 12 print ls._local.__storage__ # {140735190843392: {'stack': [12]}}
The implementation of LocalStack is interesting. it uses a Local object as its own attribute _ local, and then defines the interface push, pop and top methods to perform corresponding stack operations. Here, the list of _ local. _ storage _. _ local. _ ident_func _ () is used to simulate the stack structure. In the push, pop, and top interfaces, you can simulate stack operations by operating on this list. Note that when obtaining this list within the interface function, you do not need to be as complex as the list above, you can directly use the getattr () method of _ local. Taking the push function as an example, the implementation is as follows:
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
The implementation of pop and top is similar to that of general stacks. they all perform operations on the list of stack = getattr (self. _ local, 'stack', None. In addition, LocalStack also allows us to customize _ ident_func __. here, the built-in function property is used to generate the descriptor and encapsulate the get and set operations of _ ident_func, A property value _ ident_func _ is provided as an interface. the specific code is as follows:
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__
LocalManager
Both Local and LocalStack are single objects independent of threads or coroutines. in many cases, we need a separate thread or coroutine container, to organize multiple Local or LocalStack objects (just as we use a list to organize multiple int or string types ).
Werkzeug implements LocalManager. it stores the managed Local or LocalStack object through a list type attribute locals, and provides the cleanup method to release all Local objects. In Werkzeug, the main interface of LocalManager is the method make_middleware of the decorator. the code is as follows:
def make_middleware(self, app): """Wrap a WSGI application so that cleaning up happens after request end. """ def application(environ, start_response): return ClosingIterator(app(environ, start_response), self.cleanup) return application
This modifier registers the callback function cleanup. after a thread (or coroutine) processes the request, cleanup is called to clean up the Local or LocalStack objects managed by it (the implementation of ClosingIterator is in werkzeug. in wsgi ). The following is a simple example of using LocalManager:
From werkzeug. local import Local, LocalManager local = Local () local_2 = Local () local_manager = LocalManager ([local, local2]) def application (environ, start_response): local. request = request (environ )... # After the application is processed, the content of local_manager is automatically cleared.
Through make_middleware of LocalManager, we can clear all the Local or LocalStack objects after a thread (coroutine) processes a request, so that this thread can process another request. So far, the second problem mentioned at the beginning of the article can be solved. Werkzeug. local also implements a LocalProxy to act as the proxy of the Local object, which is also worth learning.