_threading_local.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. """Thread-local objects.
  2. (Note that this module provides a Python version of the threading.local
  3. class. Depending on the version of Python you're using, there may be a
  4. faster one available. You should always import the `local` class from
  5. `threading`.)
  6. Thread-local objects support the management of thread-local data.
  7. If you have data that you want to be local to a thread, simply create
  8. a thread-local object and use its attributes:
  9. >>> mydata = local()
  10. >>> mydata.number = 42
  11. >>> mydata.number
  12. 42
  13. You can also access the local-object's dictionary:
  14. >>> mydata.__dict__
  15. {'number': 42}
  16. >>> mydata.__dict__.setdefault('widgets', [])
  17. []
  18. >>> mydata.widgets
  19. []
  20. What's important about thread-local objects is that their data are
  21. local to a thread. If we access the data in a different thread:
  22. >>> log = []
  23. >>> def f():
  24. ... items = sorted(mydata.__dict__.items())
  25. ... log.append(items)
  26. ... mydata.number = 11
  27. ... log.append(mydata.number)
  28. >>> import threading
  29. >>> thread = threading.Thread(target=f)
  30. >>> thread.start()
  31. >>> thread.join()
  32. >>> log
  33. [[], 11]
  34. we get different data. Furthermore, changes made in the other thread
  35. don't affect data seen in this thread:
  36. >>> mydata.number
  37. 42
  38. Of course, values you get from a local object, including a __dict__
  39. attribute, are for whatever thread was current at the time the
  40. attribute was read. For that reason, you generally don't want to save
  41. these values across threads, as they apply only to the thread they
  42. came from.
  43. You can create custom local objects by subclassing the local class:
  44. >>> class MyLocal(local):
  45. ... number = 2
  46. ... def __init__(self, /, **kw):
  47. ... self.__dict__.update(kw)
  48. ... def squared(self):
  49. ... return self.number ** 2
  50. This can be useful to support default values, methods and
  51. initialization. Note that if you define an __init__ method, it will be
  52. called each time the local object is used in a separate thread. This
  53. is necessary to initialize each thread's dictionary.
  54. Now if we create a local object:
  55. >>> mydata = MyLocal(color='red')
  56. Now we have a default number:
  57. >>> mydata.number
  58. 2
  59. an initial color:
  60. >>> mydata.color
  61. 'red'
  62. >>> del mydata.color
  63. And a method that operates on the data:
  64. >>> mydata.squared()
  65. 4
  66. As before, we can access the data in a separate thread:
  67. >>> log = []
  68. >>> thread = threading.Thread(target=f)
  69. >>> thread.start()
  70. >>> thread.join()
  71. >>> log
  72. [[('color', 'red')], 11]
  73. without affecting this thread's data:
  74. >>> mydata.number
  75. 2
  76. >>> mydata.color
  77. Traceback (most recent call last):
  78. ...
  79. AttributeError: 'MyLocal' object has no attribute 'color'
  80. Note that subclasses can define slots, but they are not thread
  81. local. They are shared across threads:
  82. >>> class MyLocal(local):
  83. ... __slots__ = 'number'
  84. >>> mydata = MyLocal()
  85. >>> mydata.number = 42
  86. >>> mydata.color = 'red'
  87. So, the separate thread:
  88. >>> thread = threading.Thread(target=f)
  89. >>> thread.start()
  90. >>> thread.join()
  91. affects what we see:
  92. >>> mydata.number
  93. 11
  94. >>> del mydata
  95. """
  96. from weakref import ref
  97. from contextlib import contextmanager
  98. __all__ = ["local"]
  99. # We need to use objects from the threading module, but the threading
  100. # module may also want to use our `local` class, if support for locals
  101. # isn't compiled in to the `thread` module. This creates potential problems
  102. # with circular imports. For that reason, we don't import `threading`
  103. # until the bottom of this file (a hack sufficient to worm around the
  104. # potential problems). Note that all platforms on CPython do have support
  105. # for locals in the `thread` module, and there is no circular import problem
  106. # then, so problems introduced by fiddling the order of imports here won't
  107. # manifest.
  108. class _localimpl:
  109. """A class managing thread-local dicts"""
  110. __slots__ = 'key', 'dicts', 'localargs', 'locallock', '__weakref__'
  111. def __init__(self):
  112. # The key used in the Thread objects' attribute dicts.
  113. # We keep it a string for speed but make it unlikely to clash with
  114. # a "real" attribute.
  115. self.key = '_threading_local._localimpl.' + str(id(self))
  116. # { id(Thread) -> (ref(Thread), thread-local dict) }
  117. self.dicts = {}
  118. def get_dict(self):
  119. """Return the dict for the current thread. Raises KeyError if none
  120. defined."""
  121. thread = current_thread()
  122. return self.dicts[id(thread)][1]
  123. def create_dict(self):
  124. """Create a new dict for the current thread, and return it."""
  125. localdict = {}
  126. key = self.key
  127. thread = current_thread()
  128. idt = id(thread)
  129. def local_deleted(_, key=key):
  130. # When the localimpl is deleted, remove the thread attribute.
  131. thread = wrthread()
  132. if thread is not None:
  133. del thread.__dict__[key]
  134. def thread_deleted(_, idt=idt):
  135. # When the thread is deleted, remove the local dict.
  136. # Note that this is suboptimal if the thread object gets
  137. # caught in a reference loop. We would like to be called
  138. # as soon as the OS-level thread ends instead.
  139. local = wrlocal()
  140. if local is not None:
  141. dct = local.dicts.pop(idt)
  142. wrlocal = ref(self, local_deleted)
  143. wrthread = ref(thread, thread_deleted)
  144. thread.__dict__[key] = wrlocal
  145. self.dicts[idt] = wrthread, localdict
  146. return localdict
  147. @contextmanager
  148. def _patch(self):
  149. impl = object.__getattribute__(self, '_local__impl')
  150. try:
  151. dct = impl.get_dict()
  152. except KeyError:
  153. dct = impl.create_dict()
  154. args, kw = impl.localargs
  155. self.__init__(*args, **kw)
  156. with impl.locallock:
  157. object.__setattr__(self, '__dict__', dct)
  158. yield
  159. class local:
  160. __slots__ = '_local__impl', '__dict__'
  161. def __new__(cls, /, *args, **kw):
  162. if (args or kw) and (cls.__init__ is object.__init__):
  163. raise TypeError("Initialization arguments are not supported")
  164. self = object.__new__(cls)
  165. impl = _localimpl()
  166. impl.localargs = (args, kw)
  167. impl.locallock = RLock()
  168. object.__setattr__(self, '_local__impl', impl)
  169. # We need to create the thread dict in anticipation of
  170. # __init__ being called, to make sure we don't call it
  171. # again ourselves.
  172. impl.create_dict()
  173. return self
  174. def __getattribute__(self, name):
  175. with _patch(self):
  176. return object.__getattribute__(self, name)
  177. def __setattr__(self, name, value):
  178. if name == '__dict__':
  179. raise AttributeError(
  180. "%r object attribute '__dict__' is read-only"
  181. % self.__class__.__name__)
  182. with _patch(self):
  183. return object.__setattr__(self, name, value)
  184. def __delattr__(self, name):
  185. if name == '__dict__':
  186. raise AttributeError(
  187. "%r object attribute '__dict__' is read-only"
  188. % self.__class__.__name__)
  189. with _patch(self):
  190. return object.__delattr__(self, name)
  191. from threading import current_thread, RLock