public API surface

When you write modules, packages, and classes, you’re implicitly designing a public API, which consists of names that other code should use and rely on. Everything else is internal implementation details and shouldn’t be exposed to users.

Python doesn’t enforce public and private names. Instead, it relies on a naming convention to signal which names are public and which are non-public:

  • Public names have no leading underscore—for example, counter, convert(), and API_KEY.
  • Non-public names start with a single leading underscore (_)—for example, _items, _remove_spaces(), and _CACHE_DIR.

When designing a module or package’s public API, these best practices help you use public and non-public names effectively:

  • Design a well-thought-out and future-proof public API: Decide which packages, modules, constants, functions, classes, methods, and objects will be part of your project’s public interface. Choose descriptive names and treat them as a contract with your users. This practice will help you avoid breaking your users’ code when they upgrade to a new version.
  • Use non-public names for implementation details: Use non-public names for implementation details that may change in future versions of your code, such as internal helpers, temporary attributes, or other objects you don’t want users to rely on. This helps prevent unintended uses of your code.
  • Don’t use non-public names from other developers’ code: Follow the convention and avoid using non-public names from third-party code. This will prevent your code from breaking if the third-party code changes its internal implementation.
  • Avoid overusing double leading underscores(__): For internal names, a single underscore is the right choice. In class bodies, names like __value can be used to avoid attribute clashes in inheritance hierarchies because they trigger name mangling. Name mangling only applies to identifiers defined in class bodies.
  • Keep the public surface small and stable: Expose only what callers really need. A small, well-documented public API is easier to maintain and version than a large, leaky one.

PEP 8 also recommends using the __all__ attribute in a module to explicitly declare its public interface, while still prefixing internal names with an underscore. It primarily affects from module import * and documentation tools, not access control. Following these conventions makes it clear which names you intend to keep stable.

To see these ideas in action, consider a module that mixes public objects and internal helpers:

🔴 Avoid this:

Python taxes.py
TAX_RATE = 0.20

def calculate_tax(amount):
    """Return the tax for the given amount."""
    return round_amount(amount * TAX_RATE)

def round_amount(amount, decimals=2):
    return round(amount, decimals)

All names in this module seem to be part of its public API. However, your actual intention is for only TAX_RATE and calculate_tax() to be used outside the module. Without visual cues, your users might start importing and using round_amount() directly in their code. If you later decide to rename or remove this function, you risk breaking your users’ code.

Favor this:

Python taxes.py
__all__ = ["TAX_RATE", "calculate_tax"]  # Optional

TAX_RATE = 0.20

def calculate_tax(amount):
    """Return the tax for the given amount."""
    return _round_amount(amount * TAX_RATE)

def _round_amount(amount: float, decimals=2):
    return round(amount, decimals)

In this version, TAX_RATE and calculate_tax() are explicitly part of the public API and listed in __all__. In contrast, _round_amount() is now marked as a non-public helper function. This way, your users know which names they can rely on, and you retain the freedom to change the internal helper without affecting their code.

Tutorial

Single and Double Underscores in Python Names

Learn Python naming conventions with single and double underscores to design APIs, create safe classes, and prevent name clashes.

intermediate best-practices python

For additional information on related topics, take a look at the following resources:


By Leodanis Pozo Ramos • Updated Jan. 12, 2026 • Reviewed by Martin Breuss