This rule raises an issue when private attributes in data validation or modeling frameworks use a double leading underscore prefix (like
__attr).
In Python, this specifically applies to Pydantic models.
In data validation frameworks, private attributes are typically defined using a specific naming convention (e.g., prefixing the attribute name with a single underscore). These attributes are not validated during object instantiation and are excluded from the schema or type definition.
When an attribute is declared with a double leading underscore (e.g., __attr), Python applies name mangling at the class body level,
renaming the attribute to _ClassName__attr before the framework even sees it. The class will define and instantiate without any error,
but this creates a silent behavioral trap:
obj.__attr) raises an AttributeError,
because the stored name is _ClassName__attrself.__attr mangles the name to _SubClass__attr, not _ParentClass__attr,
causing an AttributeError at runtimeThis behavior causes subtle bugs that are difficult to detect, because the class appears to be defined correctly and instantiation succeeds without any warning.
In Pydantic, private attributes must be declared with a single leading underscore (e.g., _private_attr). When an attribute is declared
with a double leading underscore (e.g., __private_attr), Python’s name mangling silently renames it to
_ClassName__private_attr before Pydantic processes the class. This causes two concrete problems:
model.__private_attr) raises an AttributeError. Only the mangled name model._ClassName__private_attr works,
which is an implementation detail callers are not supposed to use.self.__private_attr will mangle the name to
_SubClass__private_attr, not _ParentClass__private_attr, resulting in an AttributeError at runtime.Using a double leading underscore for private attributes in data modeling frameworks leads to:
AttributeErrorReplace the double leading underscores with a single underscore prefix. Pydantic recognizes single-underscore attributes as private and initializes them correctly.
from pydantic import BaseModel, PrivateAttr
class Model(BaseModel):
__counter: int = PrivateAttr(default=0) # Noncompliant: name mangling prevents expected access
m = Model()
print(m.__counter) # Raises AttributeError: 'Model' object has no attribute '__counter'
from pydantic import BaseModel, PrivateAttr
class Model(BaseModel):
_counter: int = PrivateAttr(default=0) # Compliant: single underscore prefix
m = Model()
print(m._counter) # Works correctly: outputs 0