Как безопасно распаковать произвольную однозначную структуру данных (list, tuple, set, ...)? - PullRequest
2 голосов
/ 17 апреля 2020

Иногда я хочу убедиться, что последовательность или подобная множеству структура данных (например, список, кортеж или набор, или любой совместимый определенный пользователем тип структуры данных) содержит ровно один элемент, а затем использует этот элемент, но имеет код Ошибка появляется, если элементов меньше или больше, чем точно.

Наивный подход

def unpack_single(elements):
    assert len(elements) == 1, f"expected exactly 1 element, found {len(elements)} elements"
    return elements[0]

Это работает для списков и кортежей:

>>> unpack_single([5])
5

>>> unpack_single((42,))
42

>>> unpack_single([])  # fails as expected
AssertionError: expected exactly 1 element, found 0 elements

>>> unpack_single(tuple())  # fails as expected
AssertionError: expected exactly 1 element, found 0 elements

>>> unpack_single([23, "foo"])  # fails as expected
AssertionError: expected exactly 1 element, found 2 elements

>>> unpack_single(tuple("foobar"))  # fails as expected
AssertionError: expected exactly 1 element, found 6 elements

Но не работает для наборов , которые не являются подписными:

>>> unpack_single({"wat"})  # fails, but I want this to work, too!
TypeError: 'set' object is not subscriptable

Альтернатива

def unpack_single(elements):
    assert len(elements) == 1, f"expected exactly 1 element, found {len(elements)} elements"
    return elements.pop()

работает для наборов, но не работает для кортежей и frozensets , так как они не являются изменяемыми и не реализуют метод pop().

Элегантный, но, возможно, более неясный способ получить эту работу - использовать распаковку (присвоение структуры) списку переменных длины-1:

def unpack_single(elements):
    only_element, = elements
    return only_element

Это прекрасно работает для списков, кортежей, наборов, frozensets и даже для неконтейнерных итераций, таких как range или itertools.takewhile. (И даже для неконечных, таких как itertools.count, так как правильно сообщает "слишком много значений для распаковки" для них.)

Хотя я боюсь, что последний подход трудно понять для читателей кода. Существуют ли более подходящие подходы, которые не требуют внешних сторонних зависимостей? Может быть, даже то, что предлагает стандартная библиотека?

1 Ответ

1 голос
/ 17 апреля 2020

Попробуйте next(iter(elements)) возможно?

def unpack_single(elements):
    assert len(elements) == 1, f"expected exactly 1 element, found {len(elements)} elements"
    return next(iter(elements))
...