What is Local?
Whether you are exposed to threading.Local or werkzeug.Local, they represent a variable - each thread's own global variable.
Global variables, usually on the process heap. All threads of a process can access the same global variable, because they share the address space of the process, so each thread can access it, which also brings problems. If multiple threads access the same variable at the same time, it will cause unpredictable results for the reading and writing of the variable, so we usually use locks or other synchronization mechanisms to control the thread Access to shared variables. Of course, this is not the focus of this article.
Back to Local, we mentioned at the beginning that Local is the global variable of the thread itself. The so-called thread's own means that the "global variable" can only be accessed by the thread itself, which is invisible to other threads. How to understand this definition? Let's first look at A scenario: function A processes parameter A, function B processes parameter A processed by function A, then function A passes parameter A to function B. What if function C has to deal with this parameter, and function D has to? Then parameter A must be passed between these functions continuously, and parameters must be declared in advance when these functions are alive. It can be imagined that if there are parameters to be passed between functions, then the function will become very complex, and the call function will also be very complex. Is there an easy way?
In fact, we transfer parameters between functions to make this parameter visible to all the functions we need. Then we can turn it into a global variable? However, if it becomes a global variable, other threads will access my global variable and may change its value. This is not the intention of this thread. I just want to have it by myself. At this time, we need a variable. For this thread, it should be a global variable. For other threads of the process, it is also like a Local variable. This is a scenario of using Local, which is such a variable.
If a local is defined in the global domain, the local is not actually a global variable. When each thread accesses this variable, it actually gets the local corresponding to the thread. How to achieve this effect? In fact, local itself is not a variable, it also contains some operations. You can understand that each process has a global dictionary, each thread has its own thread ID, and all threads of the process can access the global dictionary. Then they regard their thread ID as the key of the dictionary, and what they need to store as value. Each thread can only access the dictionary through its own key, so value itself is equivalent to A thread exclusive global variable! Right? Each thread strangely takes its own thing, a global thing, which is equivalent to a global variable inside the thread. The specific code implementation is different, but in general, it is this idea.
class Local(object): __slots__ = ('__storage__', '__ident_func__') def __init__(self): object.__setattr__(self, '__storage__', {}) # A global dictionary for storing things object.__setattr__(self, '__ident_func__', get_ident) # key of each thread def __iter__(self): return iter(self.__storage__.items()) def __call__(self, proxy): """Create a proxy for a name.""" return LocalProxy(self, proxy) # Here, a LocalProxy object is returned. LocalProxy is a proxy, representing the Local object. 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)
How to use Local?
The pseudo code is as follows
local = Local() local.request = "i am a request" local.response = "i am a response" def work(): local.request = xxxx # Each thread will only access its own request and response local.response = xxxx # Even if the response is changed, only the value of the thread is changed if __name__ == "__main__": for i in range(10): Thread(target=work).start()
By declaring a global Local object and then accessing the values you want to keep just like accessing the properties of the object. You can understand that Local is equivalent to a dictionary. I want to access the value I need through the key defined by myself, that is, call local.key to get the value. In fact, it's awkward to use this method. Obviously, I define a value, but it becomes like accessing an object's attribute. It's strange to write, and sometimes it's hard to understand. Can we directly use a Local variable like defining a global variable?
# I want this effect request = "i am a request" response = "i am a response"
The call method of Local does this. Using the call method, we can make a Local look like a global variable.
# You only need to call the Local object's call method local = Local() local.request = "i am a request" my_request = local("request") # Note that the string passed in here needs to be consistent with the one saved above
my_request # "i am a request"
My request is now equivalent to local.request. Does my request look more like a global variable than local.request? But remember, it's a "thread specific global variable.".
What is LocalProxy?
Local is equivalent to a dictionary, and x of local.x is equivalent to a key, while local proxy manages the key and local. We only need to visit LocaProxy itself, and it automatically uses the key to look up the value in the local dictionary and return it to us, which is the proxy
My request is actually a LocalProxy, which directly accesses my request. It is an "i am a request" string. We mentioned earlier that the local object can store the local global variables I need through local.xxx=value. Such a local object looks like a dictionary and can store any value. But it's too troublesome to get the value we want through local.xxx every time. We need an object to help us complete this repetitive action. Give it the key and the dictionary. As long as I access it, it will look up the value in the dictionary through the key and return the value to me. So it's like the stored value itself to me. This is the agent.
This is the principle of LocalProxy. It helps us find values in local. So we need to tell the "XXX" when storing local.xxx, the key to open local, to the proxy, and then tell the local itself to the proxy. In this way, LocalProxy has the key and the door to be opened. Naturally, he can return the contents of the door to us. From this point of view, local itself can also be regarded as an agent. It represents the global variables of the thread, and the key it holds is the id of the thread. It will find the global variables of the thread in the global dict through the id, and then return them to us.
class LocalProxy(object): __slots__ = ('__local', '__dict__', '__name__', '__wrapped__') def __init__(self, local, name=None): object.__setattr__(self, '_LocalProxy__local', local) # Door to open object.__setattr__(self, '__name__', name) # Key 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__) # Get value from key (name) to Dictionary (local) 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 __repr__(self): try: obj = self._get_current_object() except RuntimeError: return '<%s unbound>' % self.__class__.__name__ return repr(obj) def __bool__(self): try: return bool(self._get_current_object()) except RuntimeError: return False def __unicode__(self): try: return unicode(self._get_current_object()) # noqa except RuntimeError: return repr(self) def __dir__(self): try: return dir(self._get_current_object()) except RuntimeError: return [] def __getattr__(self, name): if name == '__members__': return dir(self._get_current_object()) return getattr(self._get_current_object(), name) # Find the real value in the dictionary (local) through key (name), and return 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)
There are many methods in LocalProxy, which are general methods implemented by LocalProxy itself. These methods are not calls to itself, but calls to proxy values.
We can also construct a LocalProxy directly through the constructor of LocalProxy without calling the Local call method, which is essentially the same.
local = Local() local.request = "request" my_request = LocalProxy(local, "request") # The second parameter should be the same as XXX of local.xxx
What is LocalStack?
LocalStack is similar to Local, but Local is like a dictionary. LocalStack is a stack, which stores data in different ways. It can be considered as a global stack unique to a thread. Use it without worrying about being disturbed by other threads of the process.
class LocalStack(object): 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
The difference between Local and thread safety
Local does not mean thread safety. Thread safety focuses more on the synchronization mechanism when multiple threads access the same global variable. The global variable represented by local is exclusive to threads, which is invisible to other threads, so there is no thread safety problem at all. Local will only be operated by this thread forever, so if it is hard to make a definition, it is thread safe.