Back to Blog
Python

Python Protected Attribute: Conventions and Pitfalls

python protected attribute: Learn how Python's protected attribute convention works, why it's not enforced, and how to use single and double underscores effectively.

protected attributesname manglingaccess controlpython conventionsobject-oriented programminginheritance
Illustration of a Python class with a protected attribute using the underscore convention

python protected attribute requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

The Protected Attribute Convention in Python

Python does not enforce access control the way Java or C++ do. There is no protected keyword, and the interpreter will not stop you from reading or writing any attribute on an object. Instead, Python relies on naming conventions to communicate intent. A single leading underscore, such as _value, is the de facto marker for a protected attribute. It signals that the attribute is internal to the class and its subclasses, and not part of the public API. This convention is widely understood by Python developers and respected by tools like linters, IDEs, and from module import *.

Single Underscore: The Protected Convention

The single underscore prefix is a strong signal. When you see self._connection in a class, you know that the developer intends for that attribute to be used only within the class and its subclasses. It is not a hard barrier, but it is a clear contract. For example:

class Database: def __init__(self): self._connection = None def connect(self, url): self._connection = create_connection(url) def close(self): if self._connection: self._connection.close() self._connection = None

Here, _connection is protected. External code could still access db._connection, but doing so violates the intended design. The underscore also affects from module import *: names starting with an underscore are not imported by default, which helps keep module namespaces clean.

Double Underscore: Name Mangling and Why It Exists

A double leading underscore, such as __value, triggers name mangling. The interpreter rewrites the attribute name to _ClassName__value to prevent accidental overrides in subclasses. This is not about privacy; it is about avoiding name collisions in inheritance hierarchies. Consider:

class Parent: def __init__(self): self.__secret = "parent" class Child(Parent): def __init__(self): super().__init__() self.__secret = "child" # This creates a new attribute, not an override

Because of name mangling, Child actually has two attributes: _Parent__secret and _Child__secret. This can be confusing if you expect __secret to be inherited and overridden. The mangled name can still be accessed directly: obj._Parent__secret, but that is almost always a bad idea.

Accessing Protected Attributes: Should You Do It?

Technically, you can access any attribute in Python. The question is whether you should. Accessing a protected attribute from outside the class breaks encapsulation and ties your code to the internal implementation. If the class author later renames _connection to _conn, your code breaks. The same applies to mangled names, which are even more fragile because they include the class name. In practice, you should treat protected attributes as private unless you are writing a subclass or a close collaborator of the class.

Using Properties to Control Access

A better approach to protecting attributes is to use properties. A property lets you expose a public attribute while keeping the underlying storage private. You can add validation, logging, or computed behavior without changing the public interface. For example:

class Temperature: def __init__(self, celsius): self._celsius = celsius @property def celsius(self): return self._celsius @celsius.setter def celsius(self, value): if value < -273.15: raise ValueError("Temperature below absolute zero") self._celsius = value

Now temp.celsius is a clean public attribute, and _celsius is protected. If you later need to change the internal representation, you can do so without affecting users of the class.

Inheritance and Overriding Protected Members

Protected attributes are intended to be used by subclasses. A subclass can freely read and write self._protected without any special syntax. This is a common pattern for sharing state between a base class and its derived classes. However, you must be careful with name mangling. If you use __ in a base class, subclasses cannot easily override the attribute because the mangled name includes the base class name. For this reason, __ is best reserved for attributes that should never be overridden, such as internal flags or caches. For attributes that subclasses might legitimately need to access or override, use a single underscore.

Practical Implications for API Design and Maintainability

Using underscores consistently makes your codebase more maintainable. When you see _ you know the attribute is not part of the public contract, so you can change it freely. When you see __ you know the attribute is heavily protected and should not be touched. This convention also affects tooling: many linters and type checkers treat _ as a warning if accessed externally. For library authors, this is a way to signal which parts of your code are stable and which are implementation details. It is a lightweight alternative to full access control, and it works well in Python's dynamic environment.

Common Mistakes and Pitfalls

One common mistake is assuming that _ provides any real protection. It does not. Another is overusing __ for attributes that subclasses need to access, which leads to awkward workarounds. A third mistake is accessing protected attributes from external code to "save time" instead of using the public API. This creates hidden dependencies and makes future refactoring painful. Finally, remember that the convention is only as strong as your team's discipline. Document your intent clearly, and use properties when you need actual control over attribute access.

python protected attribute: Practical Usage and Code Example | RYUSLOG DEV