Python tricks:Underscores, Dunders, and More


Single and double underscores have a meaning in Python variable and method names. Some of that meaning is merely by convention and intended as a hint to the programmer–and someof it is enforced by the Python interpreter.

If you’re wondering, “What’s the meaning of single and double underscores in Python variable and method names?” I’ll do my best to get you the answer here. Now, we’ll discuss the following five underscore patterns and naming conventions, and how they affect the behavior of your Python programs:

  • Single Leading Underscore: _var
  • Single Trailing Underscore: var_
  • Double Leading Underscore:__var
  • Double ledaing and Trailing Underscore:__var__
  • Single Underscore:_

1. Single Leading Underscore:“_var”

When it comes to variable and method names, the single underscore prefix has a meaning by convention only.It’s a hint to the programmer–it means what the Python community agrees it should mean, but it does not affect the behavior of your programs.

The underscore prefix is meant as a hint to tell another programmer that a variable or method starting with a single underscore is intended for internal use. This convention is defined in PEP 8, the most commonly used Python code style guide.

However, this convention isn’t enforced by the Python interpreter. Python does not have strong distinctions between “private” and “public” variable like Java does. Adding a single underscore in front of a variable name is more like someone putting up a tiny underscore warning sign that says:" Hey, this isn’t really meant to be a part of the public interface of this class. Best to leave it alone."

Take a look at the following example:

class Test:
  def __init__(self):
    self.foo = 11
    self._bar = 23

What’s going to happen if you instantiate this class and try to access the foo and _bar attributes defined in its __init__constructor?

Let’s find out:

>>> t = Test()
>>> t.foo
11
>>> t._bar
23

As you can see, the leading single underscore in _bar did not prevent us from “reaching into” the class and accessing the value of that variable.

That’s because the single underscore prefix in Python is merely an agreed-upon convention–at least when it comes to variable and method names.

However, leading underscore do impact how names get imported from modules. Imagine you had the following code in a module called my_module:

# my_moudle.py
def external_func():
    return 23

def _internal_func():
    return 42

Now, if you use a wildcard import to import all the names from the module, Python will not import names with a leading underscore(unless the module defines an __all__list that overrides this behavior):

from my_module import *
external_func()
_internal_func()

  File "/Users/liuxiaowei/PycharmProjects/Python_trick/ll.py", line 3, in <module>
    _internal_func()
NameError: name '_internal_func' is not defined

By the way, wildcard imports should be voided as they make it unclear which names are present in the namespace. It’s better to stick to regular imports for the sake of clarity. Unlike wildcard imports, regular imports are not affected by the leading single underscore naming convention:

import my_module
print(my_module.external_func())
print(my_module._internal_func())

/Users/liuxiaowei/PycharmProjects/venv/bin/python /Users/liuxiaowei/PycharmProjects/Python_trick/ll.py
23
42

I know this might be a little confusing at this point. If you stick to the PEP 8 recommendation that wildcard imports should be avoided, then all you really need to remember is this:

Single undersocres are a Python naming convention that indicates a name is meant for internal use. It is generally not enforced by the Python interpreter and is only meant as a hint to the programmer.

2. Single Trailing Underscore:“var_”

Sometimes the most fitting name for a variable is already taken by a keyword in the Python language. Therefore, names like class or def cannot be used as variable names in Python. In this case, you can append a single underscore to break the naming conflict:

>>> def make_object(name, class):
  SyntaxError: invalid syntax
    
>>> def make_object(name, class_):
...     pass

In summary, a single trailing underscore(postfix) is used by convention to avoid naming conflicts with Python keywords. This convention is defined and explained in PEP 8.

To be continued…