Python: Понимание объекта NoneType
Если у вас есть опты работы с другими языками программирования, такими как C или Java, вероятно, вы слышали о концепции null
. Многие языки используют её для представления указателя, который ни на что не указывает. Для обозначения того, что переменная пуста, или пометки параметров по умолчанию, которые вы ещё не предоставили. null
часто определяется как 0
в этих языках, но null
в Python отличается.
Python использует ключевое слово None
для определения null
объектов и переменных. Хотя None
служит некоторым из тех же целей, что и null
в других языках, это совершенно другой зверь. Как null
в Python, None
не определяется как 0
или любое другое значение. В Python None
является объектом и гражданином первого класса!
В этом руководстве вы узнаете:
- Что такое
None
и как его проверить. - Когда и зачем использовать
None
в качестве параметра по умолчанию. - Что означают
None
иNoneType
в вашей трассировке. - Как использовать
None
в проверке типов. - Как
null
в Python работает под капотом.
Понимание 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
, означает, что они фактически являются одним и тем же объектом.
Примечание: Актуальное значение, создаваемое id
, будет отличаться в разных системах и даже между запусками программы. Два объекта живущие по одному и тому же адресу памяти, являются одним и тем же объектом.
Если вы попытаетесь присвоить значение объекту 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
в подсказках типов.