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

Лучше "return if not None" в Python

Есть ли лучший способ написать этот код в python?

result = slow_function()
if result:
    return result
[...]

Функция slow_function может возвращать значение или None, и она медленная, поэтому это невозможно:

if slow_function():
    return slow_function()

Нет ничего плохого в первом способе, но использование временной переменной кажется излишним для python.

Этот код очень полезен, когда вы решаете проблему с использованием рекурсивных вызовов по f и с локальным допущением, например, вы выбираете элемент из списка, а затем проверяете, есть ли приемлемое решение, иначе вам нужно выбрать Еще один. Что-то вроде:

def f(n):
    for x in xrange(n):
        result = slow_function(x):
        if result:
            return result
        [...]

Не лучше ли было бы более идиоматично:

def f(n):
    for x in xrange(n):
        return slow_function(x) if is not None

Это может быть расширено для проверки любого значения. Это было бы легко читаемое выражение return if.


Дополнительный пример для любителей кода

Представьте, что у вас есть список списков чисел:

lists = [[1,2,3],[4,5],[6,7,8],[9,10],...]

и вы хотите выбрать один элемент для каждого списка, чтобы в выборе было не более одного четного числа. Там может быть много списков, поэтому попытка каждой комбинации была бы расточительной, так как вы уже можете сказать, что если вы начнете выбирать [1,2,4,...], то не может быть возможных решений.

def check(selected):
    even_numbers = filter(lambda n: (n % 2) == 0, selected)
    return len(even_numbers) < 2

def f(lists, selected=[]):
    if not lists:
        return selected

    for n in lists[0]:
        if check(selected + [n]):
            result = f(lists[1:], selected + [n])
            if result:
                return result

Не лучше ли синтаксис:

def f(lists, selected=[]):
    return selected if not lists
    for n in lists[0]:
        if check(selected + [n]):
            return f(lists[1:], selected + [n]) if is not None

Самое лучшее, что я сделал до сих пор, это превратить функцию в генератор допустимых решений:

def f(lists, selected=[]):
    if not lists:
        yield selected
    else:
        for n in lists[0]:
            if check(selected + [n]):
                for solution in f(lists[1:], selected + [n]):
                    yield solution
4b9b3361

Ответ 1

Ваш последний комментарий может помочь вам понять, что вы хотите сделать:

Представьте, что вы передаете список и выбираете элемент, а затем называет себя передачей списка без элемента и так далее, пока у вас больше не будет элементов. Вы проверяете, возможно ли решение, если это возможно, вы вернете решение, и это должно пройти весь путь через стек вызовов, иначе вы вернете None. Таким образом, вы изучите все проблемы в топологическом порядке, но вы также можете пропустить проверки, когда знаете, что предыдущие выбранные элементы не смогут создать приемлемое решение.

Возможно, вы можете попробовать использовать yield вместо return. То есть, ваша рекурсивная функция не будет генерировать одно решение, но даст все возможные решения. Без конкретного примера я не могу быть уверен, что вы делаете в точности, но скажите, прежде чем это было так:

def solve(args, result_so_far):
    if not slow_check_is_feasible(result_so_far):
        #dead-end
        return None

    if not args:
        #valid and done
        return result_so_far

    for i, item in enumerate(args):
        #pass list without args - slow
        new_args = args[:]
        del new_args[i]
        result = solve(new_args, accumulate_result(result_so_far, item)
        if result is not None:
            #found it, we are done
            return result
        #otherwise keep going

Теперь это выглядит так:

def solve_all(args, result_so_far):
    if not slow_check_is_feasible(result_so_far):
        #dead-end
        return

    if not args:
        #yield since result was good
        yield result_so_far
        return

    for i, item in enumerate(args):
        #pass list without args - slow
        new_args = args[:]
        del new_args[i]
        for result in solve(new_args, accumulate_result(result_so_far, item):
            yield result

Преимущества:

  • Вы генерируете все ответы вместо первого, но если вам по-прежнему нужен только один ответ, вы можете просто получить первый результат.
  • Прежде чем использовать возвращаемые значения как для ложных проверок, так и для ответов. Теперь вы только уступаете, когда у вас есть ответ.

Ответ 2

Не зная, что еще вы можете вернуть, есть несколько вариантов.

  • Вы можете просто вернуть результат функции None или нет:

    return slow_function()
    

    В этом вы полагаетесь на вызывающего, зная, что делать со значением None, и на самом деле просто сдвигайте, где будет ваша логика.

  • Если у вас есть значение по умолчанию для возврата вместо None, вы можете сделать это:

    return slow_function() or default
    

    В этом случае, если slow_function - None (что является "ложным" ), оно вернет последнее значение, в противном случае, если slow_function возвращает "правдивое" значение, оно вернет это. Остерегайтесь, если slow_function может возвращать другие "ложные" значения, такие как False, [] или 0, они будут игнорироваться.

  • Кроме того, иногда у вас есть действительно действующий код. Вы хотите сравнить со значением, а если это значение, верните его. Код, который у вас есть, очевиден в том, что он делает, а иногда это важнее, чем "умность" кода.

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

  • Вычислить значение и сохранить его в качестве результата
  • Если есть допустимый результат, верните его.
  • В противном случае продолжайте делать что-то, чтобы получить лучший результат.

Лучше, как правило, очень субъективно, и я не вижу каких-либо очевидных способов улучшить это с точки зрения вычислений, и, как написано, он очень читается человеком, что является явным преимуществом. Другие решения могут быть короче или умнее, но удобство для чтения часто превосходит преимущество кода.

Ответ 3

По существу, вы хотите оценить выражение, а затем использовать его дважды, не привязывая его к локальной переменной. Единственный способ сделать это, поскольку у нас нет анонимных переменных, - это передать его в функцию. К счастью, поток управления для того, возвращает ли текущая функция, не контролируется функциями, которые он вызывает... однако, исключения распространяют стек вызовов.

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

def slow_function(x):
    if x % 5 == 0:
        return x * 200

@if_returner
def foobme(l):
    for i in l:
        print "Checking %s..." % (i,)
        return_if(slow_function(i))

print foobme([2, 3, 4, 5, 6])

Выход:

Checking 2...
Checking 3...
Checking 4...
Checking 5...
1000

Трюк заключается в том, чтобы справляться с обработкой исключений, поскольку они распространяются через вызовы функций. Если вам это нравится, здесь реализация:

class ReturnExc(Exception):
    def __init__(self, val):
        self.val = val

def return_if(val):
    if val is not None:
        raise ReturnExc(val)

def if_returner(f):
    def wrapped(*args, **kwargs):
        try:
            return f(*args, **kwargs)
        except ReturnExc, e:
            return e.val
    return wrapped

Ответ 4

Для проблемы, в которой slow_function работает над циклом, выражение генератора будет выглядеть как способ. В Python 3 все здесь лениво, поэтому вы бесплатно получаете свой фильтр:

f = filter(slow_function(x) for x in range(...))

В Python 2 вам просто нужны itertools:

from itertools import ifilter

f = ifilter(slow_function(x) for x in xrange(...))

Каждая итерация будет выполняться только тогда, когда вы ее попросите. Если вам нужно продолжить операцию, если функция возвращает значение false, вам понадобится False ness как контролер, поэтому ваше решение будет прекрасным:

def f():
  for x in xrange(...):
    sentinel = slow_function(x)
    if sentinel:
      return sentinel
    # continue processing

или вы можете сэкономить переменную, используя здесь генератор:

from itertools import imap

def f():
  for x in imap(slow_function, xrange(...)):
    if x:
      return x
    # continue processing

Ответ 5

Не рекомендация, но вы можете злоупотреблять пониманием списка и делать что-то в этом направлении:

# Note: Doesn't work in python 3.
def func():
    if [value for value in (slow_function(),) if value is not None]:
        return value
    # continue processing...

Ответ 6

То, что вы написали, выглядит отлично, но если вы хотите избежать нескольких операторов возврата, вы можете сделать что-то вроде этого:

def f():
    result = slow_function()
    if result is None:
        [...]
        result = [...]
    return result