app/django/utils/_threading_local.py
changeset 54 03e267d67478
equal deleted inserted replaced
53:57b4279d8c4e 54:03e267d67478
       
     1 """Thread-local objects
       
     2 
       
     3 (Note that this module provides a Python version of thread
       
     4  threading.local class.  Depending on the version of Python you're
       
     5  using, there may be a faster one available.  You should always import
       
     6  the local class from threading.)
       
     7 
       
     8 Thread-local objects support the management of thread-local data.
       
     9 If you have data that you want to be local to a thread, simply create
       
    10 a thread-local object and use its attributes:
       
    11 
       
    12   >>> mydata = local()
       
    13   >>> mydata.number = 42
       
    14   >>> mydata.number
       
    15   42
       
    16 
       
    17 You can also access the local-object's dictionary:
       
    18 
       
    19   >>> mydata.__dict__
       
    20   {'number': 42}
       
    21   >>> mydata.__dict__.setdefault('widgets', [])
       
    22   []
       
    23   >>> mydata.widgets
       
    24   []
       
    25 
       
    26 What's important about thread-local objects is that their data are
       
    27 local to a thread. If we access the data in a different thread:
       
    28 
       
    29   >>> log = []
       
    30   >>> def f():
       
    31   ...     items = mydata.__dict__.items()
       
    32   ...     items.sort()
       
    33   ...     log.append(items)
       
    34   ...     mydata.number = 11
       
    35   ...     log.append(mydata.number)
       
    36 
       
    37   >>> import threading
       
    38   >>> thread = threading.Thread(target=f)
       
    39   >>> thread.start()
       
    40   >>> thread.join()
       
    41   >>> log
       
    42   [[], 11]
       
    43 
       
    44 we get different data.  Furthermore, changes made in the other thread
       
    45 don't affect data seen in this thread:
       
    46 
       
    47   >>> mydata.number
       
    48   42
       
    49 
       
    50 Of course, values you get from a local object, including a __dict__
       
    51 attribute, are for whatever thread was current at the time the
       
    52 attribute was read.  For that reason, you generally don't want to save
       
    53 these values across threads, as they apply only to the thread they
       
    54 came from.
       
    55 
       
    56 You can create custom local objects by subclassing the local class:
       
    57 
       
    58   >>> class MyLocal(local):
       
    59   ...     number = 2
       
    60   ...     initialized = False
       
    61   ...     def __init__(self, **kw):
       
    62   ...         if self.initialized:
       
    63   ...             raise SystemError('__init__ called too many times')
       
    64   ...         self.initialized = True
       
    65   ...         self.__dict__.update(kw)
       
    66   ...     def squared(self):
       
    67   ...         return self.number ** 2
       
    68 
       
    69 This can be useful to support default values, methods and
       
    70 initialization.  Note that if you define an __init__ method, it will be
       
    71 called each time the local object is used in a separate thread.  This
       
    72 is necessary to initialize each thread's dictionary.
       
    73 
       
    74 Now if we create a local object:
       
    75 
       
    76   >>> mydata = MyLocal(color='red')
       
    77 
       
    78 Now we have a default number:
       
    79 
       
    80   >>> mydata.number
       
    81   2
       
    82 
       
    83 an initial color:
       
    84 
       
    85   >>> mydata.color
       
    86   'red'
       
    87   >>> del mydata.color
       
    88 
       
    89 And a method that operates on the data:
       
    90 
       
    91   >>> mydata.squared()
       
    92   4
       
    93 
       
    94 As before, we can access the data in a separate thread:
       
    95 
       
    96   >>> log = []
       
    97   >>> thread = threading.Thread(target=f)
       
    98   >>> thread.start()
       
    99   >>> thread.join()
       
   100   >>> log
       
   101   [[('color', 'red'), ('initialized', True)], 11]
       
   102 
       
   103 without affecting this thread's data:
       
   104 
       
   105   >>> mydata.number
       
   106   2
       
   107   >>> mydata.color
       
   108   Traceback (most recent call last):
       
   109   ...
       
   110   AttributeError: 'MyLocal' object has no attribute 'color'
       
   111 
       
   112 Note that subclasses can define slots, but they are not thread
       
   113 local. They are shared across threads:
       
   114 
       
   115   >>> class MyLocal(local):
       
   116   ...     __slots__ = 'number'
       
   117 
       
   118   >>> mydata = MyLocal()
       
   119   >>> mydata.number = 42
       
   120   >>> mydata.color = 'red'
       
   121 
       
   122 So, the separate thread:
       
   123 
       
   124   >>> thread = threading.Thread(target=f)
       
   125   >>> thread.start()
       
   126   >>> thread.join()
       
   127 
       
   128 affects what we see:
       
   129 
       
   130   >>> mydata.number
       
   131   11
       
   132 
       
   133 >>> del mydata
       
   134 """
       
   135 
       
   136 # Threading import is at end
       
   137 
       
   138 class _localbase(object):
       
   139     __slots__ = '_local__key', '_local__args', '_local__lock'
       
   140 
       
   141     def __new__(cls, *args, **kw):
       
   142         self = object.__new__(cls)
       
   143         key = '_local__key', 'thread.local.' + str(id(self))
       
   144         object.__setattr__(self, '_local__key', key)
       
   145         object.__setattr__(self, '_local__args', (args, kw))
       
   146         object.__setattr__(self, '_local__lock', RLock())
       
   147 
       
   148         if args or kw and (cls.__init__ is object.__init__):
       
   149             raise TypeError("Initialization arguments are not supported")
       
   150 
       
   151         # We need to create the thread dict in anticipation of
       
   152         # __init__ being called, to make sure we don't call it
       
   153         # again ourselves.
       
   154         dict = object.__getattribute__(self, '__dict__')
       
   155         currentThread().__dict__[key] = dict
       
   156 
       
   157         return self
       
   158 
       
   159 def _patch(self):
       
   160     key = object.__getattribute__(self, '_local__key')
       
   161     d = currentThread().__dict__.get(key)
       
   162     if d is None:
       
   163         d = {}
       
   164         currentThread().__dict__[key] = d
       
   165         object.__setattr__(self, '__dict__', d)
       
   166 
       
   167         # we have a new instance dict, so call out __init__ if we have
       
   168         # one
       
   169         cls = type(self)
       
   170         if cls.__init__ is not object.__init__:
       
   171             args, kw = object.__getattribute__(self, '_local__args')
       
   172             cls.__init__(self, *args, **kw)
       
   173     else:
       
   174         object.__setattr__(self, '__dict__', d)
       
   175 
       
   176 class local(_localbase):
       
   177 
       
   178     def __getattribute__(self, name):
       
   179         lock = object.__getattribute__(self, '_local__lock')
       
   180         lock.acquire()
       
   181         try:
       
   182             _patch(self)
       
   183             return object.__getattribute__(self, name)
       
   184         finally:
       
   185             lock.release()
       
   186 
       
   187     def __setattr__(self, name, value):
       
   188         lock = object.__getattribute__(self, '_local__lock')
       
   189         lock.acquire()
       
   190         try:
       
   191             _patch(self)
       
   192             return object.__setattr__(self, name, value)
       
   193         finally:
       
   194             lock.release()
       
   195 
       
   196     def __delattr__(self, name):
       
   197         lock = object.__getattribute__(self, '_local__lock')
       
   198         lock.acquire()
       
   199         try:
       
   200             _patch(self)
       
   201             return object.__delattr__(self, name)
       
   202         finally:
       
   203             lock.release()
       
   204 
       
   205 
       
   206     def __del__():
       
   207         threading_enumerate = enumerate
       
   208         __getattribute__ = object.__getattribute__
       
   209 
       
   210         def __del__(self):
       
   211             key = __getattribute__(self, '_local__key')
       
   212 
       
   213             try:
       
   214                 threads = list(threading_enumerate())
       
   215             except:
       
   216                 # if enumerate fails, as it seems to do during
       
   217                 # shutdown, we'll skip cleanup under the assumption
       
   218                 # that there is nothing to clean up
       
   219                 return
       
   220 
       
   221             for thread in threads:
       
   222                 try:
       
   223                     __dict__ = thread.__dict__
       
   224                 except AttributeError:
       
   225                     # Thread is dying, rest in peace
       
   226                     continue
       
   227 
       
   228                 if key in __dict__:
       
   229                     try:
       
   230                         del __dict__[key]
       
   231                     except KeyError:
       
   232                         pass # didn't have anything in this thread
       
   233 
       
   234         return __del__
       
   235     __del__ = __del__()
       
   236 
       
   237 try:
       
   238     from threading import currentThread, enumerate, RLock
       
   239 except ImportError:
       
   240     from dummy_threading import currentThread, enumerate, RLock