Альтернатива `any`, которая возвращает последний оцениваемый объект?
Я просто написал немного кода, где я хотел:
def foo(container)
return any((some_obj.attr <= 0 for some_obj in container))
где foo
вернет первый some_obj
, где some_obj.attr
равен нулю или меньше. Альтернатива, я полагаю, была бы
def foo(container):
return next((some_obj for some_obj in container if some_obj.attr <= 0), False)
но это очень хрипло.
Я закончил писать, но мне не нравится, насколько глубоко он вложен.
def foo(container):
for some_obj in container:
if some_obj.attr <= 0:
return some_obj
return False
Чтобы уточнить: container
в этом случае, скорее всего, будет не более 6 объектов (и часто 2 объекта), хотя общий случай более полезен. Я также пытаюсь оптимизировать для удобства чтения, а не для производительности.
Есть ли какая-то лучшая конструкция, чем эта?
Ответы
Ответ 1
Документы для any
объясняют, что это эквивалентно:
def any(iterable):
for element in iterable:
if element:
return True
return False
Итак, я не думаю, что ваш код слишком глубоко вложен, если он имеет ту же структуру, что и код, который использовался для иллюстрации функций самого any
.
Тем не менее, я бы, вероятно, обернул это функцией:
def first(iterable, predicate):
for element in iterable:
if predicate(element):
return element
return False
Итак, теперь вы можете сделать это:
def foo(container):
return first(container, lambda obj: obj.attr <= 0)
Или, наоборот, просто используйте выражение генератора и передайте его в двух аргумент next
, как вы уже делаете:
def foo(container):
return next((obj for obj in container if obj.attr <= 0), False)
Это имеет ту же самую "глубину", это всего лишь горизонтальная, а не вертикальная.
Или, может быть, вытащите ген xpr и назовите его:
def foo(container):
nonpositives = (obj for obj in container if obj.attr <= 0)
return next(nonpositives, False)
Как бы вы выбрали между ними? Я думаю, что если предикат слишком сложный для чтения как lambda
, но не настолько сложный, чтобы его можно было абстрагироваться от внелинейной функции, я бы пошел с genxpr. В противном случае - функция обертки. Но это действительно вопрос вкуса.
Ответ 2
next(filter
должен сделать это, и вот забавный способ протестировать <= 0
:
>>> next(filter((0).__ge__, [3,2,1,-1,-2]), False)
-1
Ha, даже tricker:
>>> next(filter(0..__ge__, [3,2,1,-1,-2]), False)
-1
Или, как заметил Абарнерт:
>>> next(filter(0 .__ge__, [3,2,1,-1,-2]), False)
-1
Ответ 3
Просто для удовольствия, чтобы продлить ответ Stefan Pochmann для обработки obj.attr <= 0
, все еще без необходимости лямбда:
from operator import attrgetter
from functional import compose
next(filter(compose(0..__ge__, attrgetter('attr')), [3, 2, 1, -1, -2]), False)
Если у вас нет модуля functional
(которого вы, вероятно, нет, потому что версия PyPI не работает с Python 2.4 или так...) и не хочет искать современную замену, вы можете написать compose
самостоятельно (и немного лучше):
def compose(f, g):
@functools.wraps(f):
def wrapper(x):
return f(g(x))
return wrapper
Примерно один раз в год есть предложение добавить compose
в stdlib и, возможно, даже дать ему инфиксный оператор. При добавлении @
для матричного умножения вы можете угадать последнее предложение. * Итак, если это произойдет (что, вероятно, не будет), вы можете сделать это:
from operator import attrgetter
next(filter(0..__ge__ @ attrgetter('attr'), [3, 2, 1, -1, -2]), False)
Теперь единственное, что нам нужно, это секция в стиле Haskell, чтобы мы могли избавиться от связанного метода, ..
hack, и необходимость в функции attrgetter
(предполагая, что вы считаете dot-атрибуцию оператором, которого это действительно не так, но пусть притворяется...). Тогда:
next(filter((<= 0) @ (.attr), [3, 2, 1, -1, -2]), False)
* На самом деле, это было предложено дважды в ходе первоначального обсуждения PEP 465, поэтому ППС упоминает: "Во время обсуждений этого PEP было сделано аналогичное предположение, чтобы определить @
как общий и это страдает от одной и той же проблемы; functools.compose
даже не является достаточно полезным для существования."