You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

CPython如何区分可选参数的默认None与用户传入的None?

How CPython's dict.pop() Distinguishes Default None vs Explicitly Passed None

Great question! This is a perfect example of how CPython's C implementation can leverage lower-level mechanics that aren't available in pure Python. Let's break this down step by step.

The Core Difference: NULL vs Python's None

In pure Python, if you write:

def pop(self, key, default=None):
    # ...

you can't tell if default is the implicit None (user didn't pass it) or an explicitly passed None. But in CPython's C code, we have a trick: we use a NULL pointer to represent "no argument passed", while a pointer to the actual None object represents an explicitly passed None.

Looking at the _PyDict_Pop Code

Let's walk through the code you shared:

PyObject * _PyDict_Pop(PyObject *dict, PyObject *key, PyObject *deflt) {
    Py_hash_t hash;
    if (((PyDictObject *)dict)->ma_used == 0) {
        if (deflt) {  // Here's the key check!
            Py_INCREF(deflt);
            return deflt;
        }
        _PyErr_SetKeyError(key);
        return NULL;
    }
    // ... rest of the code ...
}

The critical line is if (deflt). In C, a NULL pointer evaluates to false, and any non-NULL pointer (even one pointing to the None object) evaluates to true. So:

  • When you call some_dict.pop(some_key) (no default argument), the interpreter passes NULL for deflt. The if (deflt) check fails, so we throw a KeyError.
  • When you call some_dict.pop(some_key, None) (explicitly passing None), the interpreter passes a pointer to the global None object for deflt. The if (deflt) check passes, so we return that None (after incrementing its reference count, per CPython's memory rules).

How the Interpreter Handles Argument Passing

You might wonder: how does the interpreter know whether to pass NULL or the None object? It all comes down to how the dict.pop() method is registered with the interpreter.

In CPython, methods like dict.pop() are defined using a PyMethodDef structure. For pop, the definition specifies that it accepts 1 or 2 arguments. When you call the method:

  1. If you pass only 1 argument (the key), the interpreter calls _PyDict_Pop with deflt = NULL.
  2. If you pass 2 arguments (key + default), the interpreter takes the second argument (even if it's None) and passes its pointer as deflt.

Pure Python Equivalent

If you wanted to replicate this behavior in pure Python, you'd need to use a unique sentinel object instead of None, like this:

_sentinel = object()  # Unique object that no user would ever pass

def pop(self, key, default=_sentinel):
    if default is _sentinel:
        # User didn't pass a default; throw KeyError if key not found
        if key not in self:
            raise KeyError(key)
        value = self[key]
        del self[key]
        return value
    else:
        # User passed a default (even if it's None); return it if key not found
        return self.pop(key, default)

But CPython doesn't need this workaround because it can use NULL pointers directly at the C level.

内容的提问来源于stack exchange,提问作者roganjosh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:48:56