在Python中自定义类似None的占位符

8 投票
4 回答
3249 浏览
提问于 2025-04-16 18:40

我在一个函数中使用了argspec,这个函数可以接收另一个函数或方法作为参数,并返回一个像这样的元组:

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

这意味着传入的函数的第一个参数是arg1,并且它有一个默认值obj1,依此类推。

问题来了:如果没有默认值,我需要一个占位符来表示这一点。我不能用None,因为那样我就无法区分没有默认值默认值是None。同样的道理,False、0、-1等也不行。我可以用一个只有一个元素的元组,但那样检查起来会很麻烦,而且不容易转换成字典。所以我想创建一个类似None的对象,但它又不是None,这就是我想到的:

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

class NoDefault(object):
    __metaclass__ = MetaNoDefault

现在("arg1", NoDefault)表示arg1没有默认值,我可以做一些像if obj1 is NoDefault:这样的检查。这个类的元类让它打印出来时只显示NoDefault,而不是<class '__main__.NoDefault'>

这样做有什么不妥吗?有没有更好的解决方案?

4 个回答

4

其实没有什么理由不使用这种哨兵对象来达到这个目的。除了使用类对象之外,你还可以创建一个动态生成类型的单例实例:

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

我之前也遇到过类似的情况。以下是我想到的解决办法。

# 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,), {})

它还可以作为一个空的可调用对象和序列使用。

4

我最喜欢的哨兵值是 Ellipsis,如果可以的话,我想引用我自己说过的话:

它存在,是一个对象,是一个单例(也就是说只有一个实例),它的名字意思是“缺失”,而不是常用的 None(None 可以作为正常数据流的一部分放入队列中)。你的理解可能会有所不同。

撰写回答