Null в Python: Понимание нетипового объекта Python
Оглавление
- Понимание Null в Python
- Использование нулевого объекта Python None
- Объявление нулевых переменных в Python
- Использование None в качестве параметра по умолчанию
- Использование None в качестве нулевого значения в Python
- Расшифровка отсутствует в обратных следах
- Проверка наличия Null в 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
. Соответствует ли ваше регулярное выражение заданной строке? Вы увидите один из двух результатов:
- Возвращает
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
.
Все следующие объекты также являются ложными:
Подробнее о сравнениях, истинных и ложных значениях вы можете прочитать в статье о том, как использовать оператор 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? Оставьте комментарий в разделе комментариев ниже!