Python has no private methods. There is no private keyword, no access control, and no enforcement - anything that looks like privacy is a convention or a name transformation you can trivially defeat. A single leading underscore (_helper) means “this is internal, do not touch it”, and that is the answer you want in almost every case. A double leading underscore (__helper) triggers name mangling, which people reach for thinking it means “more private” - it does not. It means something quite specific, and using it for privacy will cause you problems later.
Table of contents
- The single underscore is the whole convention
- What the double underscore actually does
- What name mangling is actually for
- Why the double underscore hurts you later
- The trailing underscore, and the dunder you should not invent
- How this fits the rest of the stack
- FAQ
The single underscore is the whole convention
class OrderService:
def place_order(self, items):
total = self._calculate_total(items)
self._notify_warehouse(total)
return total
def _calculate_total(self, items):
return sum(i.price for i in items)
def _notify_warehouse(self, total):
...
_calculate_total is internal. Nothing stops you calling service._calculate_total([]) from outside, and Python will not complain. What the underscore communicates is: this is not part of the public API, I may change or delete it without warning, and if you depend on it that is your problem.
That is a real contract, enforced socially and by tooling rather than by the interpreter. Linters flag external access to underscore-prefixed members. IDEs de-emphasise them in autocomplete. from module import * skips them. Documentation generators exclude them.
This is the Pythonic position, and it is a deliberate design choice rather than an oversight: we are all consenting adults here. The language gives you the information you need to use the class correctly, and trusts you to act on it. If you need to reach into the internals to debug something at 3am, Python does not stand in your way - which anyone who has fought a private field in Java at 3am will recognise as a kindness.
What the double underscore actually does
This is the part that is consistently misunderstood.
class Base:
def __init__(self):
self.__value = 42
def show(self):
print(self.__value)
b = Base()
b.show() # 42
b.__value # AttributeError!
It looks like privacy. It is not. Python has renamed the attribute:
print(b._Base__value) # 42
This is name mangling: inside a class body, any identifier of the form __name (two leading underscores, at most one trailing) is rewritten to _ClassName__name. It is a purely mechanical text transformation, applied at compile time.
So the attribute is not hidden. It is renamed to something predictable that you can reach in one line. Anybody who tells you __ means private in Python is describing the side effect, not the feature.
And the reason that matters: the purpose of name mangling is not privacy, it is collision avoidance in inheritance.
What name mangling is actually for
The real use case:
class Base:
def __init__(self):
self.__cache = {} # becomes _Base__cache
class Child(Base):
def __init__(self):
super().__init__()
self.__cache = {} # becomes _Child__cache - a DIFFERENT attribute
Two separate attributes. Base can rely on self.__cache being its cache, and a subclass cannot accidentally clobber it by choosing the same name. Without mangling, Child setting self._cache would silently overwrite the parent’s internal state and break it in a way that is extremely hard to trace.
That is the feature. It is for library authors writing base classes that will be subclassed by people they have never met, who might reasonably pick the same attribute name.
Which means the rule is:
- Writing a base class in a library that others will subclass, and you have internal state that must not be trampled? Use
__name. This is what it is for. - Everything else? Use
_name.
If you are writing application code and using __ because it feels more private, you are using a collision-avoidance mechanism as a security blanket, and you will discover the cost the first time you try to override something in a subclass or reach for the attribute in a test.
Why the double underscore hurts you later
Three concrete costs.
Subclassing becomes painful. A subclass cannot access or override the parent’s __attribute - it gets its own separately-mangled one. This is the entire point when it is intentional, and a maddening obstacle when it is not.
Testing gets uglier. Tests frequently need to poke at internals. service._calculate_total(items) is straightforward. service._OrderService__calculate_total(items) is a sentence nobody wants to write, and it will break if you rename the class.
Debugging gets noisier. vars(obj) shows you _OrderService__cache, not __cache. Not fatal, just consistently annoying.
And the payoff for all of this is: nothing. It does not prevent access - _ClassName__attr is a one-line workaround that any determined caller will find in seconds. You have added friction for your colleagues and yourself, and none whatsoever for anybody with bad intentions.
The honest summary: __ for privacy is cargo-culted from languages that have real access modifiers. Python’s answer to encapsulation is documentation and convention, and it works better than it has any right to.
The trailing underscore, and the dunder you should not invent
Two conventions that complete the picture.
Trailing underscore avoids a clash with a built-in or keyword:
def filter_items(list_, class_=None, id_=None):
...
list, class, and id are taken; list_, class_, and id_ are the conventional escape. class_ in particular shows up constantly in HTML-generating libraries.
Dunder names - __name__, with underscores on both sides - are reserved by Python. These are the special methods: __init__, __repr__, __len__, __enter__. The language defines what they mean and when they are called.
Do not invent your own. def __process__(self) is not more official-looking, it is a name in a namespace Python has claimed, and a future version of the language could give it a meaning. Note also that dunder names are not mangled - the trailing underscores exempt them - so it does not even do the thing you might have been hoping for.
The complete rule set, which is genuinely all of it:
name- public._name- internal. Use this.__name- name-mangled, for collision avoidance in base classes. Use rarely, deliberately.name_- avoiding a keyword clash.__name__- reserved by Python. Implement the existing ones; never invent new ones.
How this fits the rest of the stack
Whatever you decide here, the cost of the decision only shows up as a bill. The RunxBuild hosting calculator is the right place to model that before committing: the compute, the database, the storage, the bandwidth, the worker - each one is a separate line item, and the real cost of a platform is the sum, not the headline number. The RunxBuild dashboard is where the team sees the actual usage once it is running.
Useful related references:
- Build a Python VPN Client: When It Makes Sense and When It Doesn’t
- Python Secrets: How to Stop Hardcoding Them, Where to Put Them, and the Pattern That Scales
- Private Domain: Internal DNS, Public DNS, and When a Name Should Not Be on the Internet
- Deploying Python services on RunxBuild
FAQ
Does Python have private methods?
No. There is no access control and no private keyword. A single leading underscore is a convention meaning internal, and a double leading underscore triggers name mangling - which renames the attribute rather than hiding it. Both are trivially bypassed by anyone who wants to.
What is the difference between _method and __method in Python?
A single underscore is a convention: this is internal, do not depend on it. A double underscore triggers name mangling, rewriting __x inside class Foo to _Foo__x. The double underscore exists for collision avoidance in inheritance, not for privacy.
Should I use double underscores for private methods?
Usually not. Name mangling makes subclassing and testing awkward, and it provides no actual protection - _ClassName__method is one line away. Use it only when writing a base class whose internal state must not be accidentally overwritten by a subclass.
How do I access a name-mangled attribute?
Prefix it with an underscore and the class name: an attribute __value in class Base is accessible as _Base__value. The fact that this is a one-liner is exactly why name mangling should not be thought of as a privacy mechanism.
Can I create my own dunder methods?
You should not. Names with double underscores on both sides are reserved by Python for special methods like init and repr, and a future version could assign meaning to a name you invented. They are also exempt from name mangling, so they do not even provide that behaviour.