Custom placeholder like None in python

2019-01-26 10:11发布

问题:

I'm using argspec in a function that takes another function or method as the argument, and returns a tuple like this:

(("arg1", obj1), ("arg2", obj2), ...)

This means that the first argument to the passed function is arg1 and it has a default value of obj1, and so on.

Here's the rub: if it has no default value, I need a placeholder value to signify this. I can't use None, because then I can't distinguish between no default value and default value is None. Same for False, 0, -1, etc. I could make it a tuple with a single element, but then the code for checking it would be ugly, and I can't easily turn it into a dict. So I thought I'd create a None-like object that isn't None, and this is what I've come up with:

class MetaNoDefault(type):
    def __repr__(cls):
        return cls.__name__
    __str__ = __repr__

class NoDefault(object):
    __metaclass__ = MetaNoDefault

Now ("arg1", NoDefault) indicates arg1 has no default value, and I can do things like if obj1 is NoDefault: etc. The metaclass makes it print as just NoDefault instead of <class '__main__.NoDefault'>.

Is there any reason not to do it like this? Is there a better solution?

回答1:

My favourite sentinel is Ellipsis, and if I may quote myself:

it's there, it's an object, it's a singleton, and its name means "lack of", and it's not the overused None (which could be put in a queue as part of normal data flow). YMMV.



回答2:

I had a similar situation some time ago. Here's what I came up with.

# Works like None, but is also a no-op callable and empty iterable.
class NULLType(type):
    def __new__(cls, name, bases, dct):
        return type.__new__(cls, name, bases, dct)
    def __init__(cls, name, bases, dct):
        super(NULLType, cls).__init__(name, bases, dct)
    def __str__(self):
        return ""
    def __repr__(self):
        return "NULL"
    def __nonzero__(self):
        return False
    def __len__(self):
        return 0
    def __call__(self, *args, **kwargs):
        return None
    def __contains__(self, item):
        return False
    def __iter__(self):
        return self
    def next(*args):
        raise StopIteration
NULL = NULLType("NULL", (type,), {})

It can also act as a null callable and sequence.



回答3:

There isn't any reason to not use such sentinel objects for such purposes. As an alternative to a class object, you could also create a singleton instance of a dynamically created type:

NoDefault = type('NoDefault', (object,), {
    '__str__': lambda s: 'NoDefault', '__repr__': lambda s: 'NoDefault'})()