Null в Python: Понимание нетипового объекта Python

Оглавление

Если у вас есть опыт работы с другими языками программирования, такими как 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 может варьироваться от платформы к платформе. При запуске этой команды в вашем интерпретаторе вы можете получить другой результат, но он будет похожим.

Использование нулевого объекта Python None

Часто для сравнения используется None. Например, вам нужно проверить, соответствует ли какой-либо результат или параметр None. Возьмем результат, полученный из re.match. Соответствует ли ваше регулярное выражение заданной строке? Вы увидите один из двух результатов:

  1. Возвращает Match объект: В вашем регулярном выражении найдено совпадение.
  2. Возвращает объект 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.

Все следующие объекты также являются ложными:

Подробнее о сравнениях, истинных и ложных значениях вы можете прочитать в статье о том, как использовать оператор or Python, как использовать Оператор Python and и как использовать оператор Python not.

Объявление нулевых переменных в 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, а не изменяемого типа, такого как list. Представьте себе функцию, подобную этой:

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 в качестве значения по умолчанию, затем протестировать его и создать новый список по мере необходимости:

 1>>> def good_function(new_elem, starter_list=None):
 2...     if starter_list is None:
 3...         starter_list = []
 4...     starter_list.append(new_elem)
 5...     return starter_list
 6...
 7>>> good_function('e', my_list)
 8['a', 'b', 'c', 'd', 'e']
 9>>> good_function('a')
10['a']
11>>> good_function('b')
12['b']
13>>> good_function('c')
14['c']

good_function() ведет себя так, как вы хотите, создавая новый список при каждом вызове, в котором вы не передаете существующий список. Это работает, потому что ваш код будет выполнять строки 2 и 3 каждый раз, когда он вызывает функцию с параметром по умолчанию.

Использование None в качестве нулевого значения в 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, будет варьироваться в зависимости от системы и даже от выполнения программы. В CPython, самой популярной среде выполнения Python, 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 в подсказках по вводу

Как вы используете null в Python? Оставьте комментарий в разделе комментариев ниже!

Back to Top