CPython如何区分可选参数的默认None与用户传入的None?
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 passesNULLfordeflt. Theif (deflt)check fails, so we throw aKeyError. - When you call
some_dict.pop(some_key, None)(explicitly passingNone), the interpreter passes a pointer to the globalNoneobject fordeflt. Theif (deflt)check passes, so we return thatNone(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:
- If you pass only 1 argument (the key), the interpreter calls
_PyDict_Popwithdeflt = NULL. - If you pass 2 arguments (key + default), the interpreter takes the second argument (even if it's
None) and passes its pointer asdeflt.
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

