Подтвердить что ты не робот

Альтернатива `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 объекта), хотя общий случай более полезен. Я также пытаюсь оптимизировать для удобства чтения, а не для производительности.

Есть ли какая-то лучшая конструкция, чем эта?

4b9b3361

Ответ 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 даже не является достаточно полезным для существования."