<jupyter_start><jupyter_text>In this module, we learn the multifaceted use of underscore "\_" in Python. Many of the Python Developers don't know about the functionalities of underscore in Python. It helps users to write Python code productively. We now go over some tricks involving the use of underscores. To start with, underscore itself can be used as a variable or name:<jupyter_code>_=5+4
_
a,_,c=(1,5,6)
print(_)<jupyter_output>5<jupyter_text>Underscores can be used in loops:<jupyter_code>for _ in range(3):
    print(_, "Done!")
for _ in range(4):
    _*=2.1
    print(_)
a,*_,b=(1,'string',3,4)
print(a)
print(_)
print(b)<jupyter_output>1
['string', 3]
4<jupyter_text>You can also use underscores to separate digits of numbers so that it's more readable:<jupyter_code>c=1_000_000_000
print(c)<jupyter_output>1000000000<jupyter_text>You can put the underscore at the beginning for a function name, such as \_my_func(). However, We discourage the use of this because if you define a function outside the Jupyter notebook (say you define a function called \_private_function() in a ".py" file named "outfilefunc.py"), and if you import all the methods and names from that ".py" file through the following syntax, Python doesn't import the names which starts with a single pre underscore:    from outfilefunc import *    Instead, you need to do this:    import outfilefunc     The key here is that leading (single) underscores do impact how names get imported from modules. So try to avoid using it if you can. You can put the underscore after a normal name. Here is a scenario: say the most fitting 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 a single underscore to break the naming conflict:<jupyter_code>def def_(class_):
    print(class_, 'Boom!')
def_('Boom')<jupyter_output>Boom Boom!<jupyter_text>Now let's talk about leading double underscores. The naming patterns we covered so far received their meaning from agreed upon conventions only. With Python class attributes (variables and methods) that start with double underscores, things are a little different. A double underscore prefix alone causes the Python interpreter to rewrite the attribute name in order to avoid naming conflicts in subclasses. This is also called **name mangling**—the interpreter changes the name of the variable in a way that makes it harder to create collisions when the class is extended later.This is a bit abstract, let's use an example. Here we create a class called 'Test' that has 3 user-define attributes. Let’s take a look at all of the attributes on this object using the built-in dir() function:<jupyter_code>class Test:
    def __init__(self):
        self.foo = 11
        self._bar = 23
        self.__baz = 23

t1 = Test()
print(dir(t1))<jupyter_output>['_Test__baz', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_bar', 'foo']<jupyter_text>This gives us a list with the object’s attributes. Let’s take this list and look for our original variable names foo, \_bar, and __baz.   1. The self.foo variable appears unmodified as foo in the attribute list.   2. The self.\_bar behaves the same way—it shows up on the class as \_bar. The leading underscore is just a convention in this case, hint for the programmer.   3. However with self.__baz, things look a little different. When we search for __baz in that list we’ll see that there is no variable with that name.So what happened to __baz?If you look closely you’ll see there’s an attribute called \_Test__baz on this object. This is the name mangling that the Python interpreter applies. It does this to protect the variable from getting overridden in subclasses.Does name mangling also apply to method names? It sure does—name mangling affects all names that start with two underscore characters (“dunders”) in a class context. The important conclusion here is that name mangling isn’t tied to class attributes specifically. It applies to any name starting with two underscore characters used in a class context.On a side note, double underscores are often referred to as “dunders” in the Python community. The reason is that double underscores appear quite often in Python code and to avoid fatiguing their jaw muscles Pythonistas often shorten “double underscore” to “dunder.”<jupyter_code>class MangledMethod:
    def __methods(self):
        return 42

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

try:
    MangledMethod().__methods()
except AttributeError:
    print( "AttributeError:'MangledMethod' object has no attribute '__method'")<jupyter_output>AttributeError:'MangledMethod' object has no attribute '__method'<jupyter_text>Lastly, perhaps surprisingly, name mangling is not applied if a name starts and ends with double underscores. Variables surrounded by a double underscore prefix and postfix are left unscathed by the Python interpeter:<jupyter_code>class Test2:
    def __init__(self):
        self.__bam__ = 42

t2=Test2()
t2.__bam__<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.07321162444113263, 'fraction_numerical': 0.010432190760059613, 'mean_word_length': 4.972191323692992, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 1, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '7', 'dir': 'jupyter-structured-clean-dedup', 'id': '102929', 'license': 'MIT', 'n_tokens_mistral': 1573, 'n_tokens_neox': 1488, 'n_words': 720, 'path': 'Lecture 19 The Use of Underscores in Python.ipynb', 'repo_name': 'pgaods/Introduction-to-Python-Programming', 'size': '5368'}