Пользовательский заполнитель, такой как None в Python

Я использую 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 ответа

Решение

Мой любимый страж Ellipsisи если я могу процитировать себя:

это там, это объект, это одиночный объект, и его имя означает "отсутствие", и это не чрезмерное None (которое может быть помещено в очередь как часть обычного потока данных). YMMV.

У меня была похожая ситуация некоторое время назад. Вот что я придумал.

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

Он также может выступать в качестве нулевого вызова и последовательности.

Нет никаких причин не использовать такие дозорные объекты для таких целей. В качестве альтернативы объекту класса вы также можете создать одноэлементный экземпляр динамически создаваемого типа:

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

def my_method(optional_argument: str | None | Unset = Unset()):
  if not isinstance(optional_argument, Unset):
    ...
  ...

Подобная реализация имеет следующие преимущества:

  1. Это позволяет вам использоватьNoneкак допустимое значение дляoptional_argument.
  2. Это позволяет выполнять полную проверку типов, в отличие от чего-то вродеEllipsis(...) что бросило быbuiltins.Ellipsisнедействителен как тип. (None не может означать NoneType при использовании в аннотациях типов)
  3. MyPy исключаетUnsetиз типа, когда он был исключен с помощью if, как это происходит для None или Enums для PEP484.
Другие вопросы по тегам