Нижнее подчеркивание в Python

Давайте разберемся, зачем в Python используется нижнее подчеркивание.

Многие разработчики Python не знают о функциях символа нижнего подчеркивания. А между тем, его использование помогает писать код более эффективно.

Нижнее подчеркивание (_) — это уникальный символ.

Если вы программист Python, вам, вероятно, встречались подобные строки:

  • for _ in range(100)
  • __init__(self)
  • _ = 2

Значение символа подчеркивания зависит от контекста.

Можно найти пять различных вариантов использования символа нижнего подчеркивания:

  1. Использование в интерпретаторе
  2. Игнорирование значений
  3. Использование в цикле
  4. Разделение цифр в числах
  5. Именование
    • Одинарное подчеркивание в начале
    • Одинарное подчеркивание в конце
    • Двойное подчеркивание в начале
    • Двойное подчеркивание в начале и в конце

Что ж, давайте разберем все эти варианты на примерах.

1. Использование в интерпретаторе

Python автоматически сохраняет значение последнего выражения в интерпретаторе в специальной переменной с именем _. При желании также можно присвоить это значение другой переменной.

Вы можете использовать нижнее подчеркивание как обычную переменную. К примеру, давайте рассмотрим следующий код:

5 + 4
# 9
_     # Сохраняет результат выражения выше
# 9
_ + 6
# 15
_
# 15
a = _  # Присвоение значения _ другой переменной
a
# 15

2. Игнорирование значений

Нижнее подчеркивание (_) также используется для игнорирования значений. Если при распаковке списка или кортежа вы не хотите использовать какие-то значения, просто присвойте их в качестве значений символу нижнего подчеркивания.

Игнорирование означает присвоение значений специальной переменной подчеркивания. Мы присваиваем значения нижнему подчеркиванию (_) и не используем их в дальнейшем коде.

Проигнорировать можно не только одно значение, но и несколько. Делается это с помощью комбинации звездочки и нижнего подчеркивания *_.

Например, давайте рассмотрим следующий пример:

## Игнорирование значения
a, _, b = (1, 2, 3) # a = 1, b = 3
print(a, b)

## Игнорирование нескольких значений
## *(variable) используется для присвоения переменной нескольких значений в виде списка (при распаковке)
## Это "расширенная распаковка", доступна она только в Python 3.x
a, *_, b = (7, 6, 5, 4, 3, 2, 1)
print(a, b)

# Output:
# 1 3
# 7 1
[python_ad_block]

3. Использование в цикле

Также вы можете использовать нижнее подчеркивание (_) в качестве переменной в цикле. Рассмотрим следующий код, чтобы лучше понять, как это работает:

## Цикл делает 5 итераций с использованием _
for _ in range(5):
    print(_)

## Перебор списка с использованием  _
## Символ _ можно использовать так же, как и переменную
languages = ["Python", "JS", "PHP", "Java"]
for _ in languages:
    print(_)

_ = 5
while _ < 10:
    print(_, end = ' ') # Для 'end' значение по умолчанию - '\n'. Мы меняем его на пробел
    _ += 1

Результат:

0
1
2
3
4
Python
JS
PHP
Java
5 6 7 8 9

4. Разделение цифр в числах

Если у вас длинное число, состоящее из большого количества цифр, вы можете разделить цифры на группы по своему усмотрению для лучшего понимания.

К примеру, миллион можно представить как 1_000_000.

Вы также можете использовать символ нижнего подчеркивания (_) для разделения частей двоичных, восьмеричных или шестнадцатеричных чисел.

Например, это может выглядеть так:

binary = 0b_0010, octa = 0o_64, hexa = 0x_23_ab

Давайте выполним все приведенные выше примеры, чтобы увидеть получившиеся результаты:

## Различные системы счисления
## Вы можете проверить правильность написания чисел, конвертировав их в целые числа при помощи метода "int"
million = 1_000_000
binary = 0b_0010
octa = 0o_64
hexa = 0x_23_ab

print(million)
print(binary)
print(octa)
print(hexa)

# Output:
# 1000000
# 2
# 52
# 9131

5. Именование с использованием подчеркивания (_)

Нижнее подчеркивание может применяться для именования переменных, функций и классов и т. д. При этом оно может использоваться четырьмя способами:

  • Единичное подчеркивание в начале: _variable
  • Единичное подчеркивание в конце: variable_
  • Двойное подчеркивание в начале: __variable
  • Двойное подчеркивание начале и в конце: __variable__

5.1. Единичное подчеркивание в начале

_name

Единичное подчеркивание в начале применяется в именах переменных для внутреннего использования. Поэтому им нечасто пользуются.

Рассмотрим следующий пример:

class Test:

    def __init__(self):
        self.name = "pythonist"
        self._num = 7

obj = Test()
print(obj.name)
print(obj._num)

# Output:
# pythonist
# 7

Заметим, что одинарное подчеркивание в начале имени не мешает нам получить доступ к переменной.

Однако такое одинарное подчеркивание в начале влияет на имена, которые импортируются из модуля.

Давайте напишем следующий код в файле my_funtions:

## filename:- my_functions.py

def func():
    return "pythonist"

def _private_func():
    return 7

Если вы импортируете все методы и имена из my_functions.py, Python не импортирует имена, начинающиеся с единичного подчеркивания в начале. Мы получим следующий результат:

>>> from my_functions import *
>>> func()
'pythonist'
>>> _private_func()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name '_private_func' is not defined

Однако вы можете избежать вышеуказанной ошибки, импортируя модуль в обычном режиме следующим образом:

>>> import my_functions
>>> my_functions.func()
'pythonist'
>>> my_functions._private_func()
7

Ещё раз оговорим, что одинарное нижнее подчеркивание в начале имени предназначено только для внутреннего использования.

5.2. Одинарное подчеркивание в конце

name_

При необходимости вы можете использовать ключевые слова Python в качестве имен переменных, функций или классов. Чтобы при этом избежать конфликтов с ключевыми словами Python, добавляется нижнее подчеркивание в конце имени, которое вы хотите использовать.

Давайте посмотрим, как это может выглядеть:

>>> def function(class):
  File "<stdin>", line 1
    def function(class):
                 ^
SyntaxError: invalid syntax
>>> def function(class_):
...     pass
...
>>>

Одинарное нижнее подчеркивание в конце используется, когда вы хотите дать переменной имя, совпадающее с ключевым словом Python. В таких случаях, во избежание конфликтов, нужно добавить подчеркивание в конце имени вашей переменной.

5.3. Двойное подчеркивание в начале

__name

Двойное нижнее подчеркивание в начале используется для искажения имени.

Такое подчеркивание указывает интерпретатору Python переписать имя атрибута подклассов, чтобы избежать конфликтов имен.

Искажение имени означает, что интерпретатор Python изменяет имя переменной таким образом, что при наследовании классов не происходит конфликтов.

Давайте посмотрим на следующий пример:

class Sample():

    def __init__(self):
        self.a = 1
        self._b = 2
        self.__c = 3
obj1 = Sample()
dir(obj1)
['_Sample__c',
 '__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__',
 '_b',
 'a']

Приведенный выше код возвращает все атрибуты объекта класса. Посмотрим на наши переменные в списке атрибутов.

Переменная self.a появляется в списке без каких-либо изменений.

self._b также появляется в списке без каких-либо изменений. Однако, как мы уже говорили выше, это только для внутреннего использования.

Есть ли в списке переменная self.__c? Нет. Но если вы внимательно посмотрите на список атрибутов, вы найдете атрибут с именем _Sample__c. Это и есть искажение имени. Это делается для того, чтобы избежать переопределения переменной в подклассах.

Давайте создадим еще один класс, который наследуется от класса Sample, чтобы увидеть, как работает переопределение:

class SecondClass(Sample):

    def __init__(self):
        super().__init__()
        self.a = "overridden"
        self._b = "overridden"
        self.__c = "overridden"
obj2 = SecondClass()
print(obj2.a)
print(obj2._b)
print(obj2.__c)
overridden
overridden



---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-2-4bf6884fbd34> in <module>()
      9 print(obj2.a)
     10 print(obj2._b)
---> 11 print(obj2.__c)


AttributeError: 'SecondClass' object has no attribute '__c'

Здесь снова работает искажение имени. Программа изменяет obj2.__c на _SecondClass__c. Например, попробуем напечатать этот элемент, используя измененный атрибут:

print(obj2._SecondClass__c)
# overridden

Видите, это сработало. Вы также можете получить доступ к ранее созданной переменной, используя _Sample__c. К примеру, посмотрим на следующий вывод:

print(obj1._Sample__c)
# 3

Кроме того, вы можете получить доступ к переменным с двойным подчеркиванием в начале, используя методы в классе. Давайте посмотрим на следующий пример:

class SimpleClass:

    def __init__(self):
        self.__datacamp = "Excellent"

    def get_datacamp(self):
        return self.__datacamp

obj = SimpleClass()
print(obj.get_datacamp()) ## it prints the "Excellent" which is a __var
print(obj.__datacamp)     ## here, we get an error as mentioned before. It changes the name of the variable
Excellent



---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-5-8006c0a9b061> in <module>()
      9 obj = SimpleClass()
     10 print(obj.get_datacamp()) ## it prints the "Excellent" which is a __var
---> 11 print(obj.__datacamp)     ## here, we get an error as mentioned before. It changes the name of the variable


AttributeError: 'SimpleClass' object has no attribute '__datacamp'

Вы также можете использовать двойные подчеркивания для имен методов. Рассмотрим пример:

class SimpleClass:

    def __datacamp(self):
        return "datacamp"

    def call_datacamp(self):
        return self.__datacamp()

obj = SimpleClass()
print(obj.call_datacamp()) ## same as above it returns the Dobule pre underscore method
print(obj.__datacamp())    ## we get an error here
datacamp



---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-1-cd8ce2e83589> in <module>()
      9 obj = SimpleClass()
     10 print(obj.call_datacamp()) ## same as above it returns the Dobule pre underscore method
---> 11 print(obj.__datacamp())    ## we get an error here


AttributeError: 'SimpleClass' object has no attribute '__datacamp'

Давайте взглянем на искажение имен с другой стороны. Сначала мы создадим переменную с именем _SimpleClass__name. После этого попытаемся получить доступ к ней, используя двойное подчеркивание в начале.

Обратимся к следующему примеру:

_SimpleClass__name = "datacamp"

class SimpleClass:

    def return_name(self):
        return __name

obj = SimpleClass()
print(obj.return_name()) ## it prints the __name variable

# Output:
# datacamp

Вы поняли концепцию? Если нет, попробуйте перечитать ещё раз.

5.4. Двойное подчеркивание в начале и в конце

__name__

В Python вы найдете разные имена, которые начинаются и заканчиваются двойным подчеркиванием. Они называются магическими методами или дандер-методами (англ. dunder — сокращение от Double Under (Underscores) — букв. “двойное подчеркивание”).

class Sample():

    def __init__(self):
        self.__num__ = 7

obj = Sample()
obj.__num__

# Output:
# 7

Использование данных методов в качестве имен переменных приведет к конфликтам. Поэтому с ними нужно быть аккуратнее.

Заключение

Итак, мы разобрали, зачем нужны символы нижнего подчеркивания в Python. Мы на примерах рассмотрели пять способов их использования и подробно обсудили, как они применяются в именах переменных.

Согласимся, что данную концепцию не все поймут с первого прочтения. Так что не теряйте терпения, перечитайте, если не получилось с первого раза.

Надеемся, что данная статья была вам полезна! Успехов в написании кода!

Перевод статьи «Role of Underscore(_) in Python».