python3.2
3.2.2
|
00001 """Thread-local objects. 00002 00003 (Note that this module provides a Python version of the threading.local 00004 class. Depending on the version of Python you're using, there may be a 00005 faster one available. You should always import the `local` class from 00006 `threading`.) 00007 00008 Thread-local objects support the management of thread-local data. 00009 If you have data that you want to be local to a thread, simply create 00010 a thread-local object and use its attributes: 00011 00012 >>> mydata = local() 00013 >>> mydata.number = 42 00014 >>> mydata.number 00015 42 00016 00017 You can also access the local-object's dictionary: 00018 00019 >>> mydata.__dict__ 00020 {'number': 42} 00021 >>> mydata.__dict__.setdefault('widgets', []) 00022 [] 00023 >>> mydata.widgets 00024 [] 00025 00026 What's important about thread-local objects is that their data are 00027 local to a thread. If we access the data in a different thread: 00028 00029 >>> log = [] 00030 >>> def f(): 00031 ... items = sorted(mydata.__dict__.items()) 00032 ... log.append(items) 00033 ... mydata.number = 11 00034 ... log.append(mydata.number) 00035 00036 >>> import threading 00037 >>> thread = threading.Thread(target=f) 00038 >>> thread.start() 00039 >>> thread.join() 00040 >>> log 00041 [[], 11] 00042 00043 we get different data. Furthermore, changes made in the other thread 00044 don't affect data seen in this thread: 00045 00046 >>> mydata.number 00047 42 00048 00049 Of course, values you get from a local object, including a __dict__ 00050 attribute, are for whatever thread was current at the time the 00051 attribute was read. For that reason, you generally don't want to save 00052 these values across threads, as they apply only to the thread they 00053 came from. 00054 00055 You can create custom local objects by subclassing the local class: 00056 00057 >>> class MyLocal(local): 00058 ... number = 2 00059 ... initialized = False 00060 ... def __init__(self, **kw): 00061 ... if self.initialized: 00062 ... raise SystemError('__init__ called too many times') 00063 ... self.initialized = True 00064 ... self.__dict__.update(kw) 00065 ... def squared(self): 00066 ... return self.number ** 2 00067 00068 This can be useful to support default values, methods and 00069 initialization. Note that if you define an __init__ method, it will be 00070 called each time the local object is used in a separate thread. This 00071 is necessary to initialize each thread's dictionary. 00072 00073 Now if we create a local object: 00074 00075 >>> mydata = MyLocal(color='red') 00076 00077 Now we have a default number: 00078 00079 >>> mydata.number 00080 2 00081 00082 an initial color: 00083 00084 >>> mydata.color 00085 'red' 00086 >>> del mydata.color 00087 00088 And a method that operates on the data: 00089 00090 >>> mydata.squared() 00091 4 00092 00093 As before, we can access the data in a separate thread: 00094 00095 >>> log = [] 00096 >>> thread = threading.Thread(target=f) 00097 >>> thread.start() 00098 >>> thread.join() 00099 >>> log 00100 [[('color', 'red'), ('initialized', True)], 11] 00101 00102 without affecting this thread's data: 00103 00104 >>> mydata.number 00105 2 00106 >>> mydata.color 00107 Traceback (most recent call last): 00108 ... 00109 AttributeError: 'MyLocal' object has no attribute 'color' 00110 00111 Note that subclasses can define slots, but they are not thread 00112 local. They are shared across threads: 00113 00114 >>> class MyLocal(local): 00115 ... __slots__ = 'number' 00116 00117 >>> mydata = MyLocal() 00118 >>> mydata.number = 42 00119 >>> mydata.color = 'red' 00120 00121 So, the separate thread: 00122 00123 >>> thread = threading.Thread(target=f) 00124 >>> thread.start() 00125 >>> thread.join() 00126 00127 affects what we see: 00128 00129 >>> mydata.number 00130 11 00131 00132 >>> del mydata 00133 """ 00134 00135 from weakref import ref 00136 from contextlib import contextmanager 00137 00138 __all__ = ["local"] 00139 00140 # We need to use objects from the threading module, but the threading 00141 # module may also want to use our `local` class, if support for locals 00142 # isn't compiled in to the `thread` module. This creates potential problems 00143 # with circular imports. For that reason, we don't import `threading` 00144 # until the bottom of this file (a hack sufficient to worm around the 00145 # potential problems). Note that all platforms on CPython do have support 00146 # for locals in the `thread` module, and there is no circular import problem 00147 # then, so problems introduced by fiddling the order of imports here won't 00148 # manifest. 00149 00150 class _localimpl: 00151 """A class managing thread-local dicts""" 00152 __slots__ = 'key', 'dicts', 'localargs', 'locallock', '__weakref__' 00153 00154 def __init__(self): 00155 # The key used in the Thread objects' attribute dicts. 00156 # We keep it a string for speed but make it unlikely to clash with 00157 # a "real" attribute. 00158 self.key = '_threading_local._localimpl.' + str(id(self)) 00159 # { id(Thread) -> (ref(Thread), thread-local dict) } 00160 self.dicts = {} 00161 00162 def get_dict(self): 00163 """Return the dict for the current thread. Raises KeyError if none 00164 defined.""" 00165 thread = current_thread() 00166 return self.dicts[id(thread)][1] 00167 00168 def create_dict(self): 00169 """Create a new dict for the current thread, and return it.""" 00170 localdict = {} 00171 key = self.key 00172 thread = current_thread() 00173 idt = id(thread) 00174 def local_deleted(_, key=key): 00175 # When the localimpl is deleted, remove the thread attribute. 00176 thread = wrthread() 00177 if thread is not None: 00178 del thread.__dict__[key] 00179 def thread_deleted(_, idt=idt): 00180 # When the thread is deleted, remove the local dict. 00181 # Note that this is suboptimal if the thread object gets 00182 # caught in a reference loop. We would like to be called 00183 # as soon as the OS-level thread ends instead. 00184 local = wrlocal() 00185 if local is not None: 00186 dct = local.dicts.pop(idt) 00187 wrlocal = ref(self, local_deleted) 00188 wrthread = ref(thread, thread_deleted) 00189 thread.__dict__[key] = wrlocal 00190 self.dicts[idt] = wrthread, localdict 00191 return localdict 00192 00193 00194 @contextmanager 00195 def _patch(self): 00196 impl = object.__getattribute__(self, '_local__impl') 00197 try: 00198 dct = impl.get_dict() 00199 except KeyError: 00200 dct = impl.create_dict() 00201 args, kw = impl.localargs 00202 self.__init__(*args, **kw) 00203 with impl.locallock: 00204 object.__setattr__(self, '__dict__', dct) 00205 yield 00206 00207 00208 class local: 00209 __slots__ = '_local__impl', '__dict__' 00210 00211 def __new__(cls, *args, **kw): 00212 if (args or kw) and (cls.__init__ is object.__init__): 00213 raise TypeError("Initialization arguments are not supported") 00214 self = object.__new__(cls) 00215 impl = _localimpl() 00216 impl.localargs = (args, kw) 00217 impl.locallock = RLock() 00218 object.__setattr__(self, '_local__impl', impl) 00219 # We need to create the thread dict in anticipation of 00220 # __init__ being called, to make sure we don't call it 00221 # again ourselves. 00222 impl.create_dict() 00223 return self 00224 00225 def __getattribute__(self, name): 00226 with _patch(self): 00227 return object.__getattribute__(self, name) 00228 00229 def __setattr__(self, name, value): 00230 if name == '__dict__': 00231 raise AttributeError( 00232 "%r object attribute '__dict__' is read-only" 00233 % self.__class__.__name__) 00234 with _patch(self): 00235 return object.__setattr__(self, name, value) 00236 00237 def __delattr__(self, name): 00238 if name == '__dict__': 00239 raise AttributeError( 00240 "%r object attribute '__dict__' is read-only" 00241 % self.__class__.__name__) 00242 with _patch(self): 00243 return object.__delattr__(self, name) 00244 00245 00246 from threading import current_thread, RLock