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