In an effort to get back on the blogging saddle, here’s a little note on default values in Python.
In Python there are often default values. The most typical default value is None — None is a object of vague meaning that almost screams "I’m a default". But sometimes None is a valid value, and sometimes you want to detect the case of "no value given" and None can hardly be called no value.
Here’s an example:
def getuser(username, default=None):
if not user_exists(username):
return default
...
In this case there is always a default, and so anytime you call getuser() you have to check for a None result. But maybe you have code where you’d really just like to get an exception if the user isn’t found. To get this you can use a sentinel. A sentinel is an object that has no particular meaning except to signal the end (like a NULL byte in a C string), or a special condition (like no default user).
Sometimes people do it like this:
_no_default = ()
def getuser(username, default=_no_default):
if not user_exists(username):
if default is _no_default:
raise LookupError("No user with the username %r" % username)
return default
...
This works because that zero-item tuple () is a unique object, and since we are using the comparison default is _no_default only that exact object will trigger that LookupError.
Once you understand the pattern, this is easy enough to read. But when you use help() or other automatic generation it is a little confusing, because the default value just appears as (). You could also use object() or [] or anything else, but the automatically generated documentation still won’t look that nice. So for a bit more polish I suggest:
class _NoDefault(object):
def __repr__(self):
return '(no default)'
NoDefault = _NoDefault()
del _NoDefault
def getuser(username, default=NoDefault):
...
You might then think "hey, why isn’t there one NoDefault that everyone can share?" If you do share that sentinel you run the risk of accidentally passing in that value even though you didn’t intend to. The value "NoDefault" will become overloaded with meaning, just as None is. By having a more private sentinel object you avoid that. A single nice sentinal factory (like _NoDefault in this example) would be nice, though. Though really PEP 3102 will probably make sentinals like this unnecessary for Python 3.0.
Note that you can also implement arguments with no default via *args and **kwargs, e.g.:
def getuser(username, *args):
if not user_exists(username):
if not args:
raise LookupError(...)
else:
return args[0]
But to do this right you should test that len(args)<=1, raise appropriate errors, maybe consider keyword arguments, and so one. It’s a pain in the butt, and when you’re finished the signature displayed by help() will be wrong anyway.
Automatically generated list of related posts:
- Defaults & Inheritance I thought I’d note a way I try to make...
Using an empty tuple as a sentinel seems like a bad idea for similar reasons as using None. It’s a valid value and it’s not necessarily unique:
>>> a = ()
>>> b = ()
>>> a is b
True
Though it’s not a singleton like None, its immutability means that any variable set to the same value may point to the same object in memory. All the more reason to use your custom sentinel, or at least [] or object().
What is “del _NoDefault” for?
Something to keep in mind with regards to sentinel values is whether or not you want to be able to pickle them.
Schevo uses a metaclass to provide repr and str for the class itself, as well as some metaclass subclasses that also provide nonzero, cmp, len for Schevo’s special UNASSIGNED constant.
See http://github.com/gldnspud/schevo/tree/master/schevo/constant.py
When pickling an instance of a sentinel class, you have a situation where the in-memory singleton instance of a sentinel class is not the same object as an unpickled instance of the same class, therefore “is” comparisons won’t work.
By pickling the class itself, when the sentinel is unpickled it becomes a reference to the class itself, so “is” comparisons work as intended.
Just create an instance of object for your sentinel
What Matthew said, but simpler, just create the sentinel class and use that, use a naming convention like ‘NoDefaultSentinel’ and that’s it, but I find it difficult to find a sane use case for that, if you have control over the call why don’t you use the signature def(user, defualt=None,nodefault=False): …?
There have been a handful of times I’ve come across similar patterns, but I’ve skipped your step of making NoDefault an object. This works just as well:
On a totally off-topic note, if I don’t fill in my email and I hit “post”, I’m asked for the “admin” login via http authentication.
Mostly I don’t use a class when I want to define
__repr__
. I used to just use a NoDefault class, but in generated documentation it looks bad. I don’t pickle things very often so that aspect hasn’t come up. Also these objects should really only be used locally in a module, they aren’t something you should hold onto… but of course if you did for some reason, you’d end up with a very hard to debug problem. I suspect by doingdel NoDefault
that you’d at least stop pickle from unpickling the object.Alec and Ian:
I tend to use (1,) as my sentinel.
>>> MARKER1 = (1,)
>>> MARKER2 = (1,)
>>> MARKER1 is MARKER2
False
Thus you can use it to give different sentinels distinct instances. I feel Ian’s solution is a bit .. much .. for such a simple thing.
As pointed out, the trouble with Ian’s example is that different zero-item tuples are not guaranteed to be unique:
I dislike it for that reason, but also because it’s not clear that the zero-item tuple might not mean something else. This also rules out, for me, other objects that seem to have a potential meaning in themselves.
So my preference is for a bald instance of the ‘object’ type:
The statement creating the object is pretty clearly not creating an object that is useful for anything other than being unique. This makes it distinct in a way that a tuple, of whatever length, is not; and so that makes it uniquely suitable for defining a sentinel value.