When you subclass threading.local and override __init__, how often does __init__ actually run?
answer
- A constructor that is not really a constructor
- The trigger is first use, not thread creation
- Counted per thread, not per object
- The arguments are captured once and reused
- It is the hook for a per-thread resource
basics
~10 sOnce per thread that uses the instance, not once per instance. threading.local keeps the constructor's arguments and re-runs the subclass init with them the first time each new thread touches the object.
solid answer
~50 sA plain `threading.local()` runs no per-thread code — the first assignment in a thread creates that thread's entry. A subclass that defines `__init__` is different: the interpreter re-invokes it the first time each new thread accesses the instance, passing the same arguments that were given to the constructor. So one object touched by four threads runs `__init__` five times. This is deliberate — it is the documented hook for lazily building a per-thread resource such as a connection, a parser or a buffer, since empty per-thread storage is usually not what a subclass wants. The traps follow from code that assumes a constructor runs once: side effects like logging, file opening or registry appends multiply by thread count, and a mutable argument is the *same* object in every thread, so it is shared rather than isolated.
code
python · 12 linesimport threading
class Connection(threading.local):
def __init__(self, dsn):
print("__init__ on", threading.current_thread().name)
self.dsn = dsn
conn = Connection("sensors.db")
for name in ("collector-1", "collector-2"):
t = threading.Thread(target=lambda: conn.dsn, name=name)
t.start()
t.join()go deeper
Recall that a plain threading.local() instance needs no subclass at all, and that subclassing changes when your constructor runs. Knowing that much prevents the common surprise.
Explain the rule exactly: once per thread on that thread's first access, with the constructor's original argument objects, and be able to say why that is the intended hook for per-thread resources.
Show the consequences you would look for in review: multiplied side effects, handles held per worker for the life of a pool, and a shared mutable argument silently defeating the isolation the class was chosen for.
Own the judgement of when per-thread resource caching is the right shape at all, versus an explicit pool of resources with a lifecycle you can observe, size and shut down deliberately.
A bare `threading.local()` needs no initialization: the first assignment in a thread creates that thread's entry, and that is that. The surprise starts when you *subclass* it. ## The documented behaviour If a subclass of `threading.local` defines `__init__`, the interpreter calls it again the first time each new thread accesses the instance — with the same arguments that were passed to the constructor. Construct one object, touch it from four threads, and `__init__` has run five times: once at construction on the creating thread, then once per new thread on first use. The trigger is *first use in that thread*, not the thread's creation. A thread that never touches the object never runs its `__init__`. That makes it a lazy hook: the per-thread setup happens exactly when the thread needs the object, and not before. ## Why the interpreter does this The whole point of `threading.local` is that each thread gets its own storage — but storage that starts empty is often useless. The per-thread state a subclass wants usually has to be *built*: a database connection, a parser, a buffer, a random-number generator, a scratch file. Re-running `__init__` per thread is the hook that lets a subclass populate its slots without every caller remembering to. It is a feature, not an accident, and the subclass form is how you express "each thread gets one of these, constructed on demand." ## Where it bites Three failure modes account for nearly all the bugs: * **Side effects multiply.** If `__init__` logs, bumps a counter, opens a file, registers a callback or appends to a global registry, it does so once per thread rather than once per object. On a pool of workers that quietly becomes one open handle per worker, held for the life of the pool. * **Arguments are shared, not rebuilt.** The arguments are captured once, at construction, and the very same objects are handed to every per-thread `__init__`. If one of them is a mutable list or dict, every thread's instance is initialized from — and may mutate — the one shared object. The per-thread isolation you were relying on stops at the slots, not at what you put in them. * **Expensive setup runs N times.** If constructing the resource is costly, the cost scales with the number of threads that touch the object. Usually that is what you asked for; occasionally it is a surprise on a pool sized for I/O concurrency rather than for connection count. ## Testing and reasoning about it The cheap way to see it is to print the current thread's name from `__init__` and touch the instance from two threads: three lines appear. It is also worth remembering that a construction-time exception can now surface *inside an unrelated worker thread* on first use, rather than at the point where the object was built — the traceback points at a line of application code that merely read an attribute. ## What to do instead when you do not want it If the per-thread state does not need building, do not subclass at all: a plain `threading.local()` instance plus a helper that assigns the slot is simpler and runs no code per thread. If the state is genuinely per unit of work rather than per thread, neither form is right — that belongs in `contextvars.ContextVar`, because thread-keyed storage is stale on a reused worker and shared across coroutines on one event-loop thread. ## The interview answer "Once per thread that uses it, not once per object, with the constructor's original arguments — and that is the documented way to build a per-thread resource lazily." Then name the trap: side effects and mutable arguments, both of which assume a constructor runs once.
- Is that re-initialization a defect to work around, or the intended design?Intended. Per-thread storage that starts empty is rarely useful, so re-running the subclass `__init__` is the hook that lets each thread build its own resource on first use. It becomes a defect only when the constructor has side effects — logging, opening handles, appending to a registry — that were written assuming they happen once.
- What arguments does each per-thread __init__ receive?The ones passed to the constructor, captured once and reused — the very same objects, not fresh ones. So a mutable argument such as a list or dict is shared by every thread's initialization, which quietly defeats the isolation people expect from a thread-local. Pass immutable values, or a factory the __init__ calls.
- Does __init__ run when a thread starts, or at some other moment?At that thread's first access to the instance. A thread that never touches the object never runs it, which makes the setup lazy and demand-driven. One consequence worth knowing: an exception raised during that setup surfaces inside whichever thread first used the object, with a traceback pointing at an ordinary attribute read.
saying these in an interview costs you the question
- Says __init__ runs exactly once, at construction time
- Expects each thread to get freshly built arguments
- Thinks subclassing is required for per-thread attributes
- Assumes __init__ fires when the thread is started
- Puts one-time side effects in the subclass __init__