skip to content

Which class name does Python's `__attr` mangling use, and when is it applied?

level: middleimportance: should knowfreq 45%

answer

  1. Ask which class, and asked when
  2. Decided while compiling the body
  3. The enclosing class body, not the instance
  4. Leading underscores of the class name are stripped
  5. Base and subclass end up with two keys

basics

~20 s

It uses the name of the class whose body lexically encloses the code, with that name's leading underscores stripped, and applies it while compiling the class body. The runtime type of the object is irrelevant.

solid answer

~50 s

The compiler rewrites any identifier appearing textually inside a class body that starts with two or more underscores and does not end in two or more, turning `__attr` into `_ClassName__attr`. `ClassName` is the lexically enclosing class, not the object's runtime type, and its own leading underscores are stripped — inside `class _Cache`, `__hits` becomes `_Cache__hits`. If the class name is nothing but underscores, no mangling happens. The consequence people miss is inheritance. A method defined in `Base` that reads `self.__state` compiled to `_Base__state`, so it keeps reading the base's value even when `self` is an instance of a subclass that also assigned `self.__state` — that assignment created `_Sub__state`. The two coexist in the instance `__dict__`. This is exactly the collision avoidance the feature is for, and it is what makes a mangled attribute safe inside a mixin.

code

python · 17 lines
python
class Base:
    def __init__(self):
        self.__state = "base"

    def show(self):
        return self.__state


class Sub(Base):
    def __init__(self):
        super().__init__()
        self.__state = "sub"


s = Sub()
print(s.show())   # base
print(vars(s))    # {'_Base__state': 'base', '_Sub__state': 'sub'}

go deeper

for a junior

Remember that the prefix comes from the class you are typing inside, and that it is decided when the file is compiled. Seeing _Base__state in vars(obj) should immediately tell you which class body wrote it.

for a middle

Explain the rule precisely — two or more leading underscores, fewer than two trailing, class name with leading underscores stripped — and walk through a base/subclass pair showing two keys living side by side in one instance dictionary.

for a senior

Demonstrate that you know when the lexical binding helps and when it hurts: safe bookkeeping in a mixin versus a template-method attribute a subclass can never replace. Diagnose the silent version of that bug from vars(instance).

for a principal

Frame it as an API contract for a widely subclassed base: which internals you namespace against downstream collisions, which you leave single-underscore as designated extension points, and how that choice is documented so subclass authors are not guessing.

Private name mangling is a **compile-time, lexical** transform. Both words carry weight, and most confusion about the feature dissolves once you accept them. ## The exact rule An identifier that occurs textually inside a class definition, begins with two or more underscore characters, and does not end in two or more underscores, is replaced by `_ClassName__identifier`, where `ClassName` is the enclosing class's name with any leading underscores removed. Three corollaries fall straight out: * Inside `class _Cache`, the name `__hits` becomes `_Cache__hits` — one leading underscore, from the prefix the rule adds, not from the class name. * Inside a class whose name is only underscores, stripping leaves nothing and no mangling is performed at all. * `__init__` and every other dunder is exempt, because it ends in two underscores. ## "Textually inside a class definition" is broader than attributes The rewrite is applied to identifiers, wherever they appear in the class body — including inside method bodies, which are compiled as part of it. That covers attribute names after a dot, but also *bare names*: ```python __g = "module level" class H: def get(self): return __g # compiles to a lookup of _H__g H().get() # NameError: name '_H__g' is not defined ``` A method named `__helper` becomes `_H__helper` in the class `__dict__`; a keyword argument spelled `__x=` in a call inside the body is mangled too. Nothing outside a class body is touched: a double-underscore name at module level or inside a plain function is left exactly as written. ## Lexical, not dynamic Because the class name is baked in when the body compiles, the *runtime* class of `self` never enters into it: ```python class Base: def __init__(self): self.__state = "base" def show(self): return self.__state # always _Base__state class Sub(Base): def __init__(self): super().__init__() self.__state = "sub" # writes _Sub__state s = Sub() s.show() # 'base' vars(s) # {'_Base__state': 'base', '_Sub__state': 'sub'} ``` This is the whole design in miniature. `Base.show` cannot be broken by a subclass that reuses the spelling, because the two names were never the same name after compilation. That is *collision avoidance*, and it is a different goal from access control. ## Why this is the mixin-safe idiom A mixin is combined with classes its author never saw. If the mixin stores bookkeeping in `self._retries`, any class in the final MRO that also uses `_retries` silently shares — and corrupts — it. If the mixin stores `self.__retries`, the compiler namespaced it to `_MyMixin__retries`, and the collision is structurally impossible. This is the single strongest argument for spending a double underscore, and it is the answer an interviewer is fishing for when they ask "when *would* you use one?". ## The flip side: you cannot override it on purpose The same property blocks a legitimate use. A template-method base that reads `self.__policy` gives a subclass no way to supply a different policy by assigning `self.__policy`; the subclass's write lands elsewhere. If a base class wants an attribute a subclass can replace, it must use a single underscore or a plain name — or expose a hook method. Choosing `__` for such an attribute converts a designed extension point into a dead end, and the failure is silent: no exception, just a value that never takes effect. ## Practical detection When an inherited method "ignores" the value you assigned, `vars(instance)` settles it in one line: two mangled keys with different class prefixes mean you assigned to a different attribute than the one being read. The same check explains a `NameError` mentioning a name you never wrote — the `_ClassName__` prefix in the message is the compiler telling you exactly which class body performed the rewrite. ## Which class body counts "Enclosing class" means the lexically nearest one, and nesting makes that concrete. In a class defined inside another class, code in the inner body mangles with the *inner* class's name: ```python class Outer: class Inner: def __init__(self): self.__v = 1 vars(Outer.Inner()) # {'_Inner__v': 1} ``` Functions nested inside a method count as part of the same class body, so a closure or a lambda written inside a method mangles exactly like the method around it. A function defined at module level and later attached to a class as an attribute does not: it was compiled outside any class body, so a `self.__x` inside it stays a literal `__x` and will not find the mangled attribute. That last case is the one that surprises people writing monkey-patching or plugin code.

  • A method reads a module-level name written `__config` and you get a NameError naming `_Loader__config`. What happened?
    The rewrite applies to every identifier in the class body, not only attribute names, so the bare global reference `__config` inside `class Loader` compiled to `_Loader__config` and no such global exists. Rename the module-level variable to `_config`, or read it through the module object. The mangled name in the error message is the tell.
  • When should a base class deliberately avoid a mangled attribute?
    Whenever a subclass is meant to replace the value. A template-method base that reads `self.__policy` can never see a subclass's `self.__policy`, because the two compile to different keys, and the failure is silent. Use a single underscore for anything an extension point relies on, and reserve the double underscore for bookkeeping subclasses must never touch.

saying these in an interview costs you the question

  • Says the mangled prefix comes from the object's runtime class
  • Thinks a subclass assignment overwrites the base's mangled attribute
  • Believes mangling only affects names written after `self.`
  • Assumes `class _Cache` produces `__Cache__hits`
  • Claims the lookup is resolved dynamically at attribute-access time

context