NoneType 在 Python 中是一个非常特殊的类型,其唯一的值是 None。这个值经常用来表示“无”或“没有值”,在 Python 的许多方面扮演着重要角色。以下是对 NoneType 的实现、原因和细节的详细解释。

NoneType 的实现,原因和细节_Python

1、问题背景

我最近在某个地方读到,Python 中的特殊值 None 是其自己的类(具体地说是 NoneType)的一个单例对象。这解释了很多问题,因为涉及 Python 中的 None 的大多数错误都会产生 AttributeError,而不是产生某种特殊的“NoneError”或类似错误。由于所有这些 AttributeError 都反映了 NoneType 缺少的属性,因此我开始对 NoneType 感到好奇,想知道它有哪些属性,如果有的话。

我决定研究一下这个 NoneType 并了解更多关于它的信息。我一直认为学习新语言特性的最好方法就是使用它,所以我尝试在 IDLE 中实例化 NoneType:

>>> n = NoneType()

这产生了一个错误:

Traceback (most recent call last):
File "<pyshell#0>", line 1, in <module>
n = NoneType()
NameError: name 'NoneType' is not defined

我感到困惑,于是检查了 None 以确认我是否正确地获得了类型名称。果然,

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

现在我更加困惑了,于是我快速地谷歌搜索了一下。搜索结果显示,由于某种原因,NoneType 在 Python 3 中被删除了。

好啊,我想,我可以通过将 None 的类型存储在一个变量中来解决这个问题,因为在 Python 中类是对象。这似乎可行:

>>> NoneType = type(None)
>>> n = NoneType()

当我打印 n 时,我得到了我预期的结果:

>>> print(n)
None

但是然后发生了以下情况:

>>> n is None
True

还有:

>>> id(n)
506768776
>>> id(None)
506768776

我的变量 n 等于 None。它不仅仅是与 None 类型相同。它是 None 本身。这并不是我预期的结果。

我尝试使用 dis 来获取有关 NoneType 的更多信息,但是当我调用以下代码时:

>>> dis.dis(type(None))

它没有产生任何输出。

然后,我尝试调查 new 方法,几位用户在评论中提到了这个方法:

dis.dis(type(None).__new__)

这次,我遇到了另一个错误:

Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    dis.dis(type(None).__new__)
  File "C:\Python33\lib\dis.py", line 59, in dis
    type(x).__name__)
TypeError: don't know how to disassemble builtin_function_or_method objects

更多的错误。

以下是我想问的问题:

  • 为什么 n 与 None 是完全相同的对象?
  • 为什么语言被设计成 n 与 None 是完全相同的对象?
  • 如何用 Python 实现这种行为?

2、解决方案

方法 1:

其他答案描述了如何使用 new 来实现单例,但这并不是 None 的实际实现方式(至少在 cPython 中是这样,我没有研究过其他实现)。

尝试通过 type(None)() 创建 None 的实例是一个特殊情况,最终会调用以下 C 函数:

static PyObject *
none_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
{
    if (PyTuple_GET_SIZE(args) || (kwargs && PyDict_Size(kwargs))) {
        PyErr_SetString(PyExc_TypeError, "NoneType takes no arguments");
        return NULL;
    }
    Py_RETURN_NONE;
}

而 Py_RETURN_NONE 在此处定义:

/*
_Py_NoneStruct is an object of undefined type which can be used in contexts
where NULL (nil) is not suitable (since NULL often means 'error').

Don't forget to apply Py_INCREF() when returning this value!!!
*/
PyAPI_DATA(PyObject) _Py_NoneStruct; /* Don't use this directly */
#define Py_None (&_Py_NoneStruct)

/* Macro for returning Py_None from a function */
#define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None

将其与创建普通 Python 对象的函数进行对比:

PyObject *
_PyObject_New(PyTypeObject *tp)
{
    PyObject *op;
    op = (PyObject *) PyObject_MALLOC(_PyObject_SIZE(tp));
    if (op == NULL)
        return PyErr_NoMemory();
    return PyObject_INIT(op, tp);
}

当你创建一个普通对象时,会为该对象分配内存并对其进行初始化。当你尝试创建一个新的 None 实例时,你得到的只是一个指向已存在的 _Py_NoneStruct 的引用。这就是为什么无论你做什么,对 None 的每个引用都是完全相同的原因。

方法 2:

  • 为什么 n 与 None 是完全相同的对象?

C 实现保留了一个单例实例。NoneType.new 返回单例实例。

  • 为什么语言被设计成 n 与 None 是完全相同的对象?

如果没有单例实例,那么你就不能依靠 x is None 进行检查,因为 is 操作符是基于标识的。虽然 None == None 也为 True,但有可能当 x 不等于 None 时,x == None 也为 True。查看此答案以了解一个示例。

  • 如何用 Python 实现这种行为?

你可以通过覆盖 new 来实现此模式。这里有一个简单的示例:

class Singleton(object):
  _instance = None
  def __new__(cls, *args, **kwargs):
    if Singleton._instance is None:
      Singleton._instance = object.__new__(cls, *args, **kwargs)
    return Singleton._instance

if __name__ == '__main__':
  s1 = Singleton()
  s2 = Singleton()
  print 's1 is s2:', s1 is s2
  print 'id(s1):', id(s1)
  print 'id(s2):', id(s2)

输出:

s1 is s2: True 
id(s1): 4506243152 
id(s2): 4506243152

方法 3:

  • 为什么 n 与 None 是完全相同的对象?

Python 中的许多不可变对象都被归为一类,包括 None、较小的整数和许多字符串。

示例:

>>> s1='abc'
>>> s2='def'
>>> s3='abc'
>>> id(s1)
4540177408
>>> id(s3)
4540177408    # 注意:与 s1 相同
>>> x=1
>>> y=2
>>> z=1
>>> id(x)
4538711696
>>> id(z)
4538711696    # 注意:与 x 相同
  • 为什么语言被设计成 n 与 None 是完全相同的对象?

请参阅上面给出的答案——速度、效率、消除歧义和内存使用情况是将不可变对象归为一类的原因之一。

  • 如何用 Python 实现这种行为?

除其他方法外,你可以覆盖 new 以返回相同对象:

class Singleton(object):
    _instance = None
    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(Singleton, cls).__new__(
                                cls, *args, **kwargs)
        return cls._instance

对于字符串,你可以在 Python 2 中调用 intern,或在 Python 3 中调用 sys.intern。

在设计自己的 API 或函数时,使用 None 作为默认值或返回值需要慎重考虑,确保它在上下文中的意义明确,避免混淆。例如,如果函数通常返回集合类型的值,那么在异常情况下返回 None 可能导致调用者在未进行空值检查的情况下尝试操作结果,从而引发错误。在这种情况下,返回一个空的集合可能是更安全的做法。

总的来说,NoneNoneType 是 Python 语言中用来表示空值和缺省状态的重要部分,理解它们的工作方式有助于编写更清晰、更健壯的代码。