Yes, this is intentional, the object is being used as a unique sentinel object. It is never itself mutated, nor are the list object contents ever inspected. It's only purpose is to have a unique object that can be used in an is
test.
It is more common to use None
as a sentinel to test if something has not been set, but you sometimes need to allow for None
to be a valid set value. In the implementation of deepcopy()
the _nil
object is used to replace None
:
y = memo.get(d, _nil)
if y is not _nil:
return y
This allows memo[d] = None
to be set, as memo.get(d)
would return None
both if memo[d]
exists or is missing:
>>> memo = {}; d = 'foo'
>>> _nil = []
>>> memo.get(d) is None
True
>>> memo.get(d, _nil) is _nil
True
>>> memo[d] = None
>>> memo.get(d) is None
True
>>> memo.get(d, _nil) is _nil
False
If you are thinking of using your own unique not-None
sentinel object, these days you'd probably want to use object()
. But object()
didn't exist yet in the Python language when the deepcopy()
function was written.
_nil
is an argument to make it a local variable. This makes looking up the _nil
reference much cheaper than if it were a global in the module, see Why does Python code run faster in a function? This is important in a function that can easily be called a lot when used to copy a deep and wide structure of custom objects. So, as the initial underscore to the name suggests, _nil
is an implementation detail.
Also see Is there a way to set a default parameter equal to another parameter value?