Null в Python: Понимание объекта Python NoneType
NoneType
— объекте None
, выполняющем роль null
в Python. Этот объект представляет собой пустоту, и его можно использовать для обозначения параметров по умолчанию и даже для того, чтобы показать, когда у вас нет результата. None
— инструмент, позволяющий делать все из ничего!Если у вас есть опыт работы с другими языками программирования, например C или Java, то вы, скорее всего, слышали о понятии null
. Во многих языках оно используется для обозначения указателя, который ни на что не указывает, для обозначения пустой переменной или для обозначения параметров по умолчанию, которые еще не заданы. В этих языках null
часто определяется как 0
, но null
в Python отличается.
В Python для определения null
объектов и переменных используется ключевое слово None
. Хотя None
служит для тех же целей, что и null
в других языках, это совершенно другой зверь. Как и null
в Python, None
не определяется как 0
или любое другое значение. В Python None
— это объект и гражданин первого класса!
Понимание Null
в Python
None
— это значение, которое функция возвращает, когда в функции нет оператора return
:
>>> def has_no_return():
... pass
>>> has_no_return()
>>> print(has_no_return())
None
Когда вы вызываете has_no_return()
, вы ничего не видите на выходе. Однако когда вы напечатаете его вызов, вы увидите скрытое значение None
, которое он возвращает.
На самом деле None
так часто появляется в качестве возвращаемого значения, что Python REPL не будет печатать None
, если вы явно не укажите:
>>> None
>>> print(None)
None
None
сам по себе не имеет вывода, но при печати он отображает None
в консоли.
Интересно, что сама функция print()
не имеет возвращаемого значения. Если вы попытаетесь напечатать вызов print()
, вы получите None
:
>>> print(print("Hello, World!"))
Hello, World!
None
Это может показаться странным, но print(print("..."))
показывает None
, который возвращает внутренний print()
.
None
также часто используется в качестве сигнала для отсутствующих параметров или параметров по умолчанию. Например, None
дважды появляется в документации list.sort
:
>>> help(list.sort)
Help on method_descriptor:
sort(...)
L.sort(key=None, reverse=False) -> None -- stable sort *IN PLACE*
Здесь None
— это значение по умолчанию для параметра key
, а также подсказка типа для возвращаемого значения. Точный вывод help
может варьироваться от платформы к платформе. Вы можете получить другой вывод при запуске этой команды в интерпретаторе, но он будет похож.
Использование Null
объекта Python None
Часто вы будете использовать None
как часть сравнения. Одним из примеров является ситуация, когда вам нужно проверить, имеет ли какой-либо результат или параметр значение None
. Возьмите результат, который вы получаете от re.match
. Соответствовало ли регулярное выражение заданной строке? Вы увидите один из двух результатов:
- Возвращает объект
Match
: Ваше регулярное выражение нашло совпадение. - Возвращает объект
None
: Ваше регулярное выражение не нашло соответствия.
В приведённом ниже блоке кода проверяется, соответствует ли шаблон "Goodbye"
строке:
>>> import re
>>> match = re.match(r"Goodbye", "Hello, World!")
>>> if match is None:
... print("It doesn't match.")
It doesn't match.
Здесь используется is None
, чтобы проверить, соответствует ли шаблон строке "Hello, World!"
. Этот блок кода демонстрирует важное правило, о котором следует помнить, когда вы проверяете None
:
- Используйте операторы идентификации
is
иis not
. - Не используйте операторы равенства
==
и!=
.
Операторы равенства можно обмануть, когда вы сравниваете пользовательские объекты, которые переопределяют их:
>>> class BrokenComparison:
... def __eq__(self, other):
... return True
>>> b = BrokenComparison()
>>> b == None # Equality operator
True
>>> b is None # Identity operator
False
Оператор равенства ==
возвращает неверный ответ. Оператор идентификации is
, с другой стороны, нельзя обмануть, потому что вы не можете его переопределить.
None
является ложным, что означает, что not None
это True
. Если всё, что вы хотите знать, это является ли результат ложным, то достаточно теста подобного следующему:
>>> some_result = None
>>> if some_result:
... print("Got a result!")
... else:
... print("No result.")
...
No result.
Вывод не показывает вам, что some_result
точно равен None
, а только, что он ложный. Если вы хотите знать, есть ли у вас объект None
, то используйте is
и is not
.
Следующие объекты также являются ложными:
- Пустые списки.
- Пустые словари.
- Пустые наборы.
- Пустые строки.
0
False
Объявление Null
переменных в Python
В некоторых языках переменные появляются из объявления. Им необязательно присваивать начальное значение. В этих языках начальное значение по умолчанию для некоторых типов переменных может быть null
. Однако в Python переменные оживают благодаря операторам присваивания. Взгляните на следующий блок кода:
>>> print(bar)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'bar' is not defined
>>> bar = None
>>> print(bar)
None
Вы можете увидеть, что переменная со значением None
отличается от неопределённой переменной. Всё переменные в Python создаются путём присваивания. Переменная начнёт жизнь как null
в Python, только если вы присвоите ей значение None
.
Использование None
в качестве параметра по умолчанию
Очень часто вы будете использовать None
в качестве значения по умолчанию для необязательного параметра. Есть очень веская причина для использования None
, а не мутабельный тип, такой как список. Представьте такую функцию:
def bad_function(new_elem, starter_list=[]):
starter_list.append(new_elem)
return starter_list
bad_function()
содержит неприятный сюрприз. Она отлично работает, когда вы вызываете её с существующим списком:
>>> my_list = ['a', 'b', 'c']
>>> bad_function('d', my_list)
['a', 'b', 'c', 'd']
Она без проблем добавляет d
в конец списка.
Но если вызвать эту функцию без параметра starter_list
, вы увидите некорректное поведение:
>>> bad_function('a')
['a']
>>> bad_function('b')
['a', 'b']
>>> bad_function('c')
['a', 'b', 'c']
Значение по умолчанию для starter_list
оценивается только один раз во время определения функции, поэтому код повторно использует его каждый раз, когда вы не передаёте существующий список.
Правильный способ построить эту функцию — использовать None
в качестве значения по умолчанию, затем протестировать его и создать экземпляр нового списка по мере необходимости:
>>> def good_function(new_elem, starter_list=None):
... if starter_list is None:
... starter_list = []
... starter_list.append(new_elem)
... return starter_list
...
>>> good_function('e', my_list)
['a', 'b', 'c', 'd', 'e']
>>> good_function('a')
['a']
>>> good_function('b')
['b']
>>> good_function('c')
['c']
good-function()
ведёт себя так, как вы хотите, создавая новый список при каждом вызове, когда вы не передаёте существующий список. Это работает, потому что ваш код будет выполнять строки 2 и 3 каждый раз, когда вызывается функция с параметром по умолчанию.
Использование None
в качестве значения Null
в Python
Что вы делаете, когда None
является допустимым входным объектом? Например, что, если функция good_function()
может либо добавить элемент в список, либо нет, а None
будет допустимым элементом добавления? В этом случае вы можете определить класс специально для использования по умолчанию, в то же время отличаясь от None
:
>>> class DontAppend: pass
...
>>> def good_function(new_elem=DontAppend, starter_list=None):
... if starter_list is None:
... starter_list = []
... if new_elem is not DontAppend:
... starter_list.append(new_elem)
... return starter_list
...
>>> good_function(starter_list=my_list)
['a', 'b', 'c', 'd', 'e']
>>> good_function(None, my_list)
['a', 'b', 'c', 'd', 'e', None]
Класс DontAppend
служит сигналом не добавлять, поэтому для этого не нужен None
. Это освобождает от добавления None
, когда вы хотите.
Вы можете использовать этот метод, когда None
также может быть возвращаемым значением. Например, dict.get
возвращает None
по умолчанию, если ключ не найден в словаре. Если бы None
был допустимым значением в вашем словаре, вы могли бы вызвать dict.get
следующим образом:
>>> class KeyNotFound: pass
...
>>> my_dict = {'a':3, 'b':None}
>>> for key in ['a', 'b', 'c']:
... value = my_dict.get(key, KeyNotFound)
... if value is not KeyNotFound:
... print(f"{key}->{value}")
...
a->3
b->None
Мы определили собственный класс KeyNotFound
. Теперь вместо того, что бы возвращать None
, когда ключа нет в словаре, можем вернуть KeyNotFound
. Это позволяет проще возвращать None
, если это фактическое значение в словаре.
Расшифровка None
в Трассировке
Когда NoneType
появляется в вашей трассировке, это означает, что то, чего вы не ожидали, что это будет None
, на самом деле было None
, и вы пытались использовать это таким образом, каким нельзя использовать None
. Почти всегда это происходит потому, что вы пытаетесь вызвать для него метод.
Например, вы много раз вызывали append()
для my_list
в примере выше, но если my_list
каким-то образом станет чем-то другим, кроме списка, то append()
завершится ошибкой:
>>> my_list.append('f')
>>> my_list
['a', 'b', 'c', 'd', 'e', None, 'f']
>>> my_list = None
>>> my_list.append('g')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'append'
Код вызывает распространённую ошибку AttributeError
, потому что базовый объект, my_list
, больше не является списком. Он установлен в None
у которого нет атрибута append()
, поэтому код выдаёт исключение.
Когда видите такую трассировку в своём коде, сначала найдите атрибут, вызвавший ошибку. Здесь это append()
. Оттуда вы увидите объект, для которого пытались его вызвать. В данном случае это my_list
, как видно из кода чуть выше трассировки. Наконец, выясните, как этот объект стал None
, и предпримите необходимые шаги для исправления кода.
Проверка на Null
в Python
Есть два случая проверки типов, когда в Python понадобится null
. Первый случай — когда вы возвращаете None
:
>>> def returns_None() -> None:
... pass
Этот случай подобен тому, когда у вас вообще нет оператора return
, который возвращает None
по умолчанию.
Второй случай немного сложнее. Здесь вы принимаете или возвращаете значение, которое может None
, но также может быть и другим (одиночным) типом. Этот случай подобен тому, что мы делали с re.match
выше, когда возвращался либо объект Match
, либо None
.
Процесс аналогичен для параметров:
from typing import Any, List, Optional
def good_function(new_elem:Any, starter_list:Optional[List]=None) -> List:
pass
Мы изменяем good_function()
сверху и импортируем Optional
из typing
, чтобы вернуть Optional[Match]
.
Заглянем под капот
Во многих других языках null
— это просто синоним 0
, но null
в Python — это полноценный объект:
>>> type(None)
<class 'NoneType'>
Эта строка показывает, что None
является объектом и имеет тип NoneType
.
None
сам по себе встроен в язык как null
в Python:
>>> dir(__builtins__)
['ArithmeticError', ..., 'None', ..., 'zip']
Здесь вы можете увидеть None
в списке __builtins__
, который представляет собой словарь, хранимый интерпретатором для модуля builtins
.
None
— это ключевое слово, как True
или False
. Но из-за этого вы не можете получить None
напрямую из __builtins__
, как могли бы, например, ArithmeticError
. Однако вы можете получить его также с помощью трюка getattr()
:
>>> __builtins__.ArithmeticError
<class 'ArithmeticError'>
>>> __builtins__.None
File "<stdin>", line 1
__builtins__.None
^
SyntaxError: invalid syntax
>>> print(getattr(__builtins__, 'None'))
None
Когда используете getattr()
, вы можете получить настоящий None
из __builtins__
, чего нельзя сделать, просто запросив его с помощью __builtins__.None
.
Несмотря на то, что Python печатает слово NoneType
во многих сообщениях об ошибках, NoneType
не является идентификатором в Python. Это не в builtins
. Вы можете добрать до него только с type(None)
.
None
это синглтон. То есть класс NoneType
всегда даёт один и тот же единственный экземпляр None
. В вашей программе Python есть только дин None
:
>>> my_None = type(None)() # Create a new instance
>>> print(my_None)
None
>>> my_None is None
True
Даже если вы попытаетесь создать новый экземпляр, вы всё равно получите существующий None
.
Вы можете доказать, что None
и my_None
являются одним и тем же объектом, с помощью id()
:
>>> id(None)
4465912088
>>> id(my_None)
4465912088
Тот факт, что id
выводит одно и тоже целочисленное значение для None
и my_None
, означает, что они фактически являются одним и тем же объектом.
Если вы попытаетесь присвоить значение объекту None
, вы получите SyntaxError
:
>>> None = 5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
SyntaxError: can't assign to keyword
>>> None.age = 5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'age'
>>> setattr(None, 'age', 5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'age'
>>> setattr(type(None), 'age', 5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't set attributes of built-in/extension type 'NoneType'
Приведённые выше примеры показывают, что нельзя изменить None
или NoneType
. Они настоящие константы.
Также нельзя создать подкласс NoneType
:
>>> class MyNoneType(type(None)):
... pass
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: type 'NoneType' is not an acceptable base type
Эта трассировка показывает, что интерпретатор не позволит создать новый класс, наследуемы от type(None)
.
Заключение
None
— это мощный инструмент в наборе инструментов Python. Подобно True
и False
, None
является иммутабельным ключевым словом. Как null
в Python, вы используете его, чтобы пометить отсутствующие значения и результаты, и даже параметры по умолчанию, где это гораздо лучший выбор, чем мутабельные типы.
Теперь вы можете:
- Проверить на
None
с помощьюis
иis not
. - Выбрать, когда
None
является допустимым значением в коде. - Использовать
None
и его альтернативы в качестве примеров по умолчанию. - Расшифровать
None
иNoneType
в трассировках программ. - Использовать
None
иOptional
в подсказках типов.