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,_bar
respond 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_bar
form 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.__baz
For that matter, things look a little different. When you search that list for__baz
When 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__baz
attributes. 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._bar
cap (a poem)__baz
value 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 .__ baz
Why do we get AttributeError when the value of the Name modifier is triggered again? It turns out that the object doesn't even__baz
Properties:
>>> 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.__baz
change into_ExtendedTest__baz
to prevent accidental modifications:
>>> t2._ExtendedTest__baz 'overridden'
former_Test__baz
Still 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__mangled
of 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__mangled
to cite_MangledGlobal__mangled
Global variables.
The Python interpreter automatically sets the name__mangled
Expand 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!