ยปCore Development>Code coverage>Lib/_threading_local.py

Python code coverage for Lib/_threading_local.py

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