SoFunction
Updated on 2024-11-18

A brief look at the meaning of the 5 types of underscores in Python

1. Single leading underscore: _var

The single underscore prefix has an agreed meaning when it comes to variable and method names. It is a hint to programmers - meaning that the Python community agrees on what it should mean, but the behavior of the program is unaffected.

The meaning of the underscore prefix is to inform other programmers that variables or methods beginning with a single underscore are for internal use only. This convention is defined in PEP 8.

This is not mandated by Python. Python doesn't have a strong distinction between "private" and "public" variables like Java does. It's like someone raised a little underlined warning flag and said:

"Hey, it's not really going to be part of the public interface of the class. Just leave it alone. "

2. Single end underscore var_

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

>>> def make_object(name, class):
SyntaxError: "invalid syntax"

>>> def make_object(name, class_):
...    pass

In short, the single final underscore (suffix) is a convention used to avoid naming conflicts with Python keywords. PEP 8 explains this convention.

3. Double leading underscore __var

The meanings of all the naming patterns we've covered so far come from conventions that have been agreed upon. The situation is a little different for properties (including variables and methods) of Python classes that begin with a double underscore.

The double underscore prefix causes the Python interpreter to rewrite property names to avoid naming conflicts in subclasses.

This is also called name mangling - the interpreter changes the name of a variable so that it is less likely to conflict when the class is extended.

I know this sounds abstract. Therefore, I have assembled a small code example to illustrate it:

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

Let's take a look at the properties of this object using the built-in dir() function:

>>> t = Test()
>>> dir(t)
['_Test__baz', '__class__', '__delattr__', '__dict__', '__dir__',
'__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
'__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__',
'__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__setattr__', '__sizeof__', '__str__', '__subclasshook__',
'__weakref__', '_bar', 'foo']

Above is a list of the properties of this object. Let's take a look at this list and look for our original variable namesfoo,_barrespond in singing__baz - I promise you'll notice some interesting changes.

variable is shown as unmodified to foo in the attribute list. self._bar behaves in the same way - it starts with_barform is displayed on the class. Like I said before, in this case the leading underscore is simply a convention. It's just a hint to the programmer. However, for theself.__bazFor that matter, things look a little different. When you search that list for__bazWhen you do, you will not see variables with this name.

What's going on with ____baz?

If you look closely, you'll see that this object has an object named_Test__bazattributes. This is what the Python interpreter does with name modification. It does this to prevent variables from being overridden in subclasses.

Let's create another class that extends the Test class and try to override the existing properties added in the constructor:

class ExtendedTest(Test):
   def __init__(self):
       super().__init__()
        = 'overridden'
       self._bar = 'overridden'
       self.__baz = 'overridden'

Now, you think foo._barcap (a poem)__bazvalue will appear on this instance of the ExtendedTest class? Let's take a look:

>>> t2 = ExtendedTest()
>>> 
'overridden'
>>> t2._bar
'overridden'
>>> t2.__baz
AttributeError: "'ExtendedTest' object has no attribute '__baz'"

Wait a minute, when we try to view thet2 .__ bazWhy do we get AttributeError when the value of the Name modifier is triggered again? It turns out that the object doesn't even__bazProperties:

>>> dir(t2)
['_ExtendedTest__baz', '_Test__baz', '__class__', '__delattr__',
'__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',
'__getattribute__', '__gt__', '__hash__', '__init__', '__le__',
'__lt__', '__module__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', '__weakref__', '_bar', 'foo', 'get_vars']

As you can see.__bazchange into_ExtendedTest__bazto prevent accidental modifications:

>>> t2._ExtendedTest__baz
'overridden'

former_Test__bazStill in:

>>> t2._Test__baz
42

The double underscore name modifier is completely transparent to the programmer. The following example confirms this:

class ManglingTest:
   def __init__(self):
       self.__mangled = 'hello'

   def get_mangled(self):
       return self.__mangled

>>> ManglingTest().get_mangled()
'hello'
>>> ManglingTest().__mangled
AttributeError: "'ManglingTest' object has no attribute '__mangled'"

Does the name modifier also apply to method names? Yes, it also applies. The name modifier affects methods that, in the context of a class, begin with the two underscore characters("dunders")All names beginning with:

class MangledMethod:
   def __method(self):
       return 42

   def call_it(self):
       return self.__method()

>>> MangledMethod().__method()
AttributeError: "'MangledMethod' object has no attribute '__method'"
>>> MangledMethod().call_it()
42

This is another, perhaps surprising, example of the use of name modification:

_MangledGlobal__mangled = 23

class MangledGlobal:
   def test(self):
       return __mangled

>>> MangledGlobal().test()
23

In this example, I declared a file named_MangledGlobal__mangledof the global variable. I then access the variable within the context of the class named MangledGlobal. Because of the name modifier, I was able to access the variable from within the class's test() method with a__mangledto cite_MangledGlobal__mangledGlobal variables.

The Python interpreter automatically sets the name__mangledExpand to_MangledGlobal__mangled, because it begins with two underscore characters. This indicates that the name modifier is not specifically associated with class attributes. It applies to any name that begins with two underscore characters used in the class context.

There's a lot to absorb, I guess.

Honestly, these examples and explanations didn't just pop into my head. I did some research and processing to get them out. I've been using Python for years, but rules and special cases like these don't always come to mind.

Sometimes the most important skill a programmer can have is "pattern recognition" and knowing where to look for information. If you feel a little overwhelmed at this point, don't worry. Take your time and try some of the examples in this article.

Let these concepts sink in completely so you can understand the general idea of name modification, as well as some of the other behaviors I've shown you. If you ever cross paths with them, you'll know what to look up by in the documentation.

4. Double leading and double trailing underscores_var_

Perhaps surprisingly, if a name begins and ends with both double underscores, no name modification is applied. Variables surrounded by a double underscore prefix and suffix are not modified by the Python interpreter:

class PrefixPostfixTest:
   def __init__(self):
       self.__bam__ = 42

>>> PrefixPostfixTest().__bam__
42

However, Python reserves names with double leading and double ending underscores for special purposes. Examples of such names are.__init__object constructor, or__call__ - It makes an object callable.

These dunder methods are often referred to as magic methods - but many in the Python community (myself included) don't like them.

It is best to avoid using names that begin and end with double underscores ("dunders") in your own programs to avoid conflicts with future changes to the Python language.

5. Single underline _

By convention, sometimes a single independent underscore is used as a name to indicate that a variable is temporary or irrelevant.

For example, in the following loop, we don't need to access the running index, we can use "_" to indicate that it is just a temporary value:

>>> for _ in range(32):
...    print('Hello, World.')

You can also use a single underscore as a "don't care" variable in an unpacking expression to ignore specific values. Again, this means "by convention" and does not trigger special behavior in the Python interpreter. The single underscore is simply a valid variable name that will be used for this purpose.

In the code example below, I'm splitting the car tuple into separate variables, but I'm only interested in the color and mileage values. However, in order for the split expression to run successfully, I need to assign all the values contained in the tuple to variables. In this case, "_" can come in handy as a placeholder variable:

>>> car = ('red', 'auto', 12, 3812.4)
>>> color, _, _, mileage = car

>>> color
'red'
>>> mileage
3812.4
>>> _
12

In addition to being used as a temporary variable, "_" is a special variable in most Python REPLs that indicates the result of the most recent expression evaluated by the interpreter.

This is convenient because, for example, you can access the results of a previous computation in a single interpreter session or, alternatively, you are dynamically constructing multiple objects and interacting with them without having to assign names to these objects beforehand:

>>> 20 + 3
23
>>> _
23
>>> print(_)
23

>>> list()
[]
>>> _.append(1)
>>> _.append(2)
>>> _.append(3)
>>> _
[1, 2, 3]

to this article on the meaning of the 5 kinds of Python underscore to this article, more related Python 5 kinds of underscore content please search for my previous posts or continue to browse the following related articles I hope you will support me more in the future!