Пользовательский заполнитель, такой как 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):
...
...
Подобная реализация имеет следующие преимущества:
- Это позволяет вам использовать
None
как допустимое значение дляoptional_argument
. - Это позволяет выполнять полную проверку типов, в отличие от чего-то вроде
Ellipsis
(...) что бросило быbuiltins.Ellipsis
недействителен как тип. (None не может означать NoneType при использовании в аннотациях типов) - MyPy исключает
Unset
из типа, когда он был исключен с помощью if, как это происходит для None или Enums для PEP484.