geekdoc-python-zh/docs/realpython/null-in-python.md

17 KiB

Python 中的 Null:理解 Python 的 NoneType 对象

原文:https://realpython.com/null-in-python/

*立即观看**本教程有真实 Python 团队创建的相关视频课程。和文字教程一起看,加深理解: Python 的 None: Null in Python

如果你有使用其他编程语言的经验,如 CJava ,那么你可能听说过 null 的概念。许多语言用这个来表示不指向任何东西的指针,表示变量何时为空,或者标记尚未提供的默认参数。在那些语言中,null通常被定义为0,但是在 Python 中的null是不同的。

Python 使用关键字 None来定义null对象和变量。虽然在其他语言中,None确实服务于与null相同的一些目的,但它完全是另一种野兽。与 Python 中的null一样,None没有被定义为0或其他任何值。在 Python 中,None是对象,是一等公民!

在本教程中,您将学习:

  • 什么是 None 以及如何测试
  • 何时以及为何使用None作为默认参数
  • 在你的回溯NoneNoneType是什么意思
  • 如何在型式检验中使用None
  • Python 中的 null是如何工作的

免费奖励: 并学习 Python 3 的基础知识,如使用数据类型、字典、列表和 Python 函数。

理解 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("..."))向你展示了内在print()返回的None

None也常用作缺失或默认参数的信号。例如,Nonelist.sort 的文档中出现两次:

>>> help(list.sort)
Help on method_descriptor:

sort(...)
 L.sort(key=None, reverse=False) -> None -- stable sort *IN PLACE*

这里,Nonekey参数的默认值,也是返回值的类型提示help的确切产量可能因平台而异。当您在您的解释器中运行这个命令时,您可能会得到不同的输出,但是它将是相似的。

Remove ads

使用 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时要记住:

  • 使用了身份运算符isis not吗?
  • 不要使用等式运算符==!=

当您比较用户定义的对象时,等式操作符可能会被愚弄,这些对象被覆盖:

>>> class BrokenComparison:
...     def __eq__(self, other):
...         return True
>>> b = BrokenComparison()
>>> b == None  # Equality operator
True
>>> b is None  # Identity operator
False

这里,等式运算符==返回错误的答案。另一方面,身份操作符is 不会被愚弄,因为你不能覆盖它。

**注意:**关于如何与None进行比较的更多信息,请查看该做的和不该做的:Python 编程建议

None福尔西,意思是not NoneTrue。如果您只想知道结果是否为假,那么如下测试就足够了:

>>> some_result = None
>>> if some_result:
...     print("Got a result!")
... else:
...     print("No result.")
...
No result.

输出没有告诉你some_result就是None,只告诉你它是假的。如果你必须知道你是否有一个None对象,那么使用isis not

以下对象也是假的:

关于比较、真值和假值的更多信息,你可以阅读如何使用 Python or操作符,如何使用 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 中的所有变量都是通过赋值产生的。如果你给一个变量赋值None,它在 Python 中只会以null开始。

Remove ads

使用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 行。

在 Python 中使用None作为空值

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。现在,当一个键不在字典中时,你可以返回KeyNotFound,而不是返回None。这使得您可以返回None,而这是字典中的实际值。

回溯中的解密None

NoneType出现在你的回溯中,说明你没想到会是None的东西实际上是None,你试图用一种你不能用None的方式使用它。几乎总是,这是因为你试图在它上面调用一个方法。

例如,您在上面的my_list中多次调用了 append() ,但是如果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的,并采取必要的步骤来修改代码。

Remove ads

检查 Python 中的空值

在 Python 中,有两种类型检查情况需要关注null。第一种情况是你在返回NoneT5 的时候:

>>> 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(),从typing导入Optional,返回一个Optional[Match]

在引擎盖下看一看

在许多其他语言中,null只是0的同义词,但 Python 中的null是一个成熟的对象:

>>> type(None)
<class 'NoneType'>

这一行显示None是一个对象,它的类型是NoneType

None本身作为 Python 中的null内置于语言中:

>>> dir(__builtins__)
['ArithmeticError', ..., 'None', ..., 'zip']

在这里,你可以看到__builtins__列表中的None,它是解释器为 builtins 模块保留的字典。

None是一个关键词,就像TrueFalse一样。但是正因为如此,你不能像你可以直接从__builtins__到达None,比如说ArithmeticError。不过,你可以用一个 getattr() 的招数得到它:

>>> __builtins__.ArithmeticError
<class 'ArithmeticError'>
>>> __builtins__.None
  File "<stdin>", line 1
    __builtins__.None
                    ^
SyntaxError: invalid syntax
>>> print(getattr(__builtins__, 'None'))
None

当你使用getattr()时,你可以从__builtins__中获取实际的None,这是你简单地用__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

你可以用 id() 证明Nonemy_None是同一个对象:

>>> id(None)
4465912088
>>> id(my_None)
4465912088

这里,idNonemy_None输出相同的整数值意味着它们实际上是同一个对象。

**注意:**由id产生的实际值会因系统而异,甚至会因程序执行而异。在最流行的 Python 运行时 CPython 下,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)的新类。

Remove ads

结论

None是 Python 工具箱中一个强大的工具。与TrueFalse一样,None是一个不可变的关键字。作为 Python 中的null,您可以用它来标记缺失的值和结果,甚至是默认参数,这是比可变类型更好的选择。

现在你可以:

  • isis not测试None
  • 选择None何时是代码中的有效值
  • 使用None及其替代参数作为默认参数
  • 破译回溯中的NoneNoneType
  • 在类型提示中使用NoneOptional

Python 中的null怎么用?在下面的评论区留下你的评论吧!

立即观看本教程有真实 Python 团队创建的相关视频课程。和文字教程一起看,加深理解: Python 的 None: Null in Python***