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

Оценка короткого замыкания, такая как Python "и" при сохранении результатов проверок

У меня есть несколько дорогих функций, которые возвращают результаты. Я хочу вернуть кортеж результатов всех проверок, если все проверки будут успешными. Однако, если одна проверка завершилась неудачно, я не хочу вызывать более поздние проверки, например, поведение короткого замыкания and. Я мог бы вложить выражения if, но это будет из-под контроля, если будет много проверок. Как я могу получить поведение короткого замыкания and, а также сохранить результаты для последующего использования?

def check_a():
    # do something and return the result,
    # for simplicity, just make it "A"
    return "A"

def check_b():
    # do something and return the result,
    # for simplicity, just make it "B"
    return "B"

...

Это не короткое замыкание:

a = check_a()
b = check_b()
c = check_c()

if a and b and c:
    return a, b, c

Это грязно, если есть много проверок:

if a:
   b = check_b()

   if b:
      c = check_c()

      if c:
          return a, b, c

Есть ли более короткий способ сделать это?

4b9b3361

Ответ 1

Просто используйте простой старый цикл:

results = {}
for function in [check_a, check_b, ...]:
    results[function.__name__] = result = function()
    if not result:
        break

Результатом будет сопоставление имени функции с их возвращаемыми значениями, и вы можете делать то, что хотите, со значениями после разрыва цикла.

Используйте предложение else в цикле for, если вам нужна специальная обработка для случая, когда все функции вернули правдивые результаты.

Ответ 2

Напишите функцию, которая выполняет итерацию выполняемых функций. Вызовите их и добавьте результат в список или верните None, если результат False. Либо эта функция перестанет вызывать дополнительные проверки после того, как произойдет сбой, либо вернет результаты всех проверок.

def all_or_none(checks, *args, **kwargs):
    out = []

    for check in checks:
        rv = check(*args, **kwargs)

        if not rv:
            return None

        out.append(rv)

    return out
rv = all_or_none((check_a, check_b, check_c))

# rv is a list if all checks passed, otherwise None
if rv is not None:
    return rv
def check_a(obj):
    ...

def check_b(obj):
    ...

# pass arguments to each check, useful for writing reusable checks
rv = all_or_none((check_a, check_b), obj=my_object)

Ответ 3

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

if (a = check_a()) and (b = check_b()) and (c = check_c()):

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

result = []
def put(value):
    result.append(value)
    return value

if put(check_a()) and put(check_b()) and put(check_c()):
    # if you need them as variables, you could do
    # (a, b, c) = result
    # but you just want
    return tuple(result)

Это может немного ослабить связь между переменными и вызовами функций, поэтому, если вы хотите делать много разных вещей с переменными, вместо использования элементов result в том порядке, в котором они были помещены в список, Я бы предпочел избежать такого подхода. Тем не менее, это может быть быстрее и короче, чем некоторый цикл.

Ответ 4

Вы можете использовать либо список, либо OrderedDict, используя цикл for, который будет служить для эмуляции короткого замыкания.

from collections import OrderedDict


def check_a():
    return "A"


def check_b():
    return "B"


def check_c():
    return "C"


def check_d():
    return False


def method1(*args):
    results = []
    for i, f in enumerate(args):
        value = f()
        results.append(value)
        if not value:
            return None

    return results


def method2(*args):
    results = OrderedDict()

    for f in args:
        results[f.__name__] = result = f()
        if not result:
            return None

    return results

# Case 1, it should return check_a, check_b, check_c
for m in [method1, method2]:
    print(m(check_a, check_b, check_c))

# Case 1, it should return None
for m in [method1, method2]:
    print(m(check_a, check_b, check_d, check_c))

Ответ 5

Есть много способов сделать это! Здесь другой.

Вы можете использовать выражение генератора для отсрочки выполнения функций. Затем вы можете использовать itertools.takewhile для реализации логики короткого замыкания, потребляя элементы из генератора, пока один из них не станет ложным.

from itertools import takewhile
functions = (check_a, check_b, check_c)
generator = (f() for f in functions)
results = tuple(takewhile(bool, generator))
if len(results) == len(functions):
    return results

Ответ 6

Другой способ справиться с этим - использовать генератор, поскольку генераторы используют ленивую оценку. Сначала поместите все проверки в генератор:

def checks():
    yield check_a()
    yield check_b()
    yield check_c()

Теперь вы можете принудительно оценить все, переведя его в список:

list(checks())

Но стандартная все функции выполняет правильную оценку коротких сокращений на итераторе, возвращаемом с помощью проверок(), и возвращает, являются ли все элементы правдивыми:

all(checks())

Наконец, если вы хотите, чтобы результаты последующих проверок до отказа, вы можете использовать itertools.takewhile, чтобы выполнить только первый запуск правдивых значений. Поскольку результат tenewil является ленивым, вам нужно будет преобразовать его в список, чтобы увидеть результат в REPL:

from itertools import takewhile
takewhile(lambda x: x, checks())
list(takewhile(lambda x: x, checks()))

Ответ 7

Основная логика:

results = list(takewhile(lambda x: x, map(lambda x: x(), function_list)))
if len(results) == len(function_list):
  return results

вы можете много узнать о преобразованиях коллекции, если вы посмотрите на все методы api, например http://www.scala-lang.org/api/2.11.7/#scala.collection.immutable.List и выполните поиск/реализацию эквивалентов python

с настройками и альтернативами:

import sys
if sys.version_info.major == 2:
  from collections import imap
  map = imap

def test(bool):
  def inner():
    print(bool)
    return bool
  return inner

def function_for_return():
  function_list = [test(True),test(True),test(False),test(True)]

  from itertools import takewhile

  print("results:")

  results = list(takewhile(lambda x:x,map(lambda x:x(),function_list)))
  if len(results) == len(function_list):
    return results

  print(results)
  #personally i prefer another syntax:
  class Iterator(object):
    def __init__(self,iterable):
      self.iterator = iter(iterable)

    def __next__(self):
      return next(self.iterator)

    def __iter__(self):
      return self

    def map(self,f):
      return Iterator(map(f,self.iterator))

    def takewhile(self,f):
      return Iterator(takewhile(f,self.iterator))

  print("results2:")
  results2 = list(
    Iterator(function_list)
      .map(lambda x:x())
      .takewhile(lambda x:x)    
  )

  print(results2)

  print("with additional information")
  function_list2 = [(test(True),"a"),(test(True),"b"),(test(False),"c"),(test(True),"d")]
  results3 = list(
    Iterator(function_list2)
      .map(lambda x:(x[0](),x[1]))
      .takewhile(lambda x:x[0])    
  )
  print(results3)

function_for_return()

Ответ 8

Гибкое короткое замыкание действительно лучше всего выполняется с помощью исключений. Для очень простого прототипа вы можете даже просто утверждать каждый результат проверки:

try:
    a = check_a()
    assert a
    b = check_b()
    assert b
    c = check_c()
    assert c
    return  a, b, c
except AssertionException as e:
    return None

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

Короче говоря, обработка исключений очень гибкая и именно то, что вы ищете, не бойтесь ее использовать. Если вы узнали, что обработка исключений не будет использоваться для вашего собственного управления потоком, это не относится к python. Либеральное использование обработки исключений считается pythonic, как в EAFP.

Ответ 9

Если вам не нужно брать произвольное количество выражений во время выполнения (возможно, завернутое в lambdas), вы можете развернуть свой код прямо в этот шаблон:

def f ():
    try:
        return (<a> or jump(),
                <b> or jump(),
                <c> or jump())
    except NonLocalExit:
        return None

Если применяются эти определения:

class NonLocalExit(Exception):
    pass

def jump():
    raise NonLocalExit()

Ответ 10

Так как я не могу прокомментировать "wim": s ответ в качестве гостя, я просто добавлю дополнительный ответ. Так как вам нужен кортеж, вам нужно собрать результаты в списке, а затем добавить в кортеж.

def short_eval(*checks):
    result = []
    for check in checks:
        checked = check()
        if not checked:
            break
        result.append(checked)
    return tuple(result)

# Example
wished = short_eval(check_a, check_b, check_c)

Ответ 11

Вы можете использовать декоратор @lazy_function от lazy_python пакет. Пример использования:

from lazy import lazy_function, strict

@lazy_function
def check(a, b):
    strict(print('Call: {} {}'.format(a, b)))
    if a + b > a * b:
        return '{}, {}'.format(a, b)

a = check(-1, -2)
b = check(1, 2)
c = check(-1, 2)

print('First condition')
if c and a and b: print('Ok: {}'.format((a, b)))

print('Second condition')
if c and b: print('Ok: {}'.format((c, b)))
# Output:
# First condition
# Call: -1 2
# Call: -1 -2
# Second condition
# Call: 1 2
# Ok: ('-1, 2', '1, 2')

Ответ 12

Это похоже на ответ Берги, но я думаю, что ответ пропускает точку, требующую отдельных функций (check_a, check_b, check_c):

list1 = []

def check_a():
    condition = True
    a = 1
    if (condition):
        list1.append(a)
        print ("checking a")
        return True
    else:
        return False

def check_b():
    condition = False
    b = 2
    if (condition):
        list1.append(b)
        print ("checking b")
        return True
    else:
        return False

def check_c():
    condition = True
    c = 3
    if (condition):
        list1.append(c)
        print ("checking c")
        return True
    else:
        return False


if check_a() and check_b() and check_c():
    # won't get here

tuple1 = tuple(list1)    
print (tuple1)    

# output is:
# checking a
# (1,)

Или, если вы не хотите использовать глобальный список, передайте ссылку локального списка на каждую из функций.

Ответ 13

Если основное возражение

Это грязно, если есть много проверок:

if a:
   b = check_b()

   if b:
      c = check_c()

      if c:
          return a, b, c

Довольно хороший шаблон - это отменить условие и вернуть раннее

if not a:
    return  # None, or some value, or however you want to handle this
b = check_b()
if not b:
    return
c = check_c()
if not c:
    return

# ok, they were all truthy
return a, b, c

Ответ 14

Попробуйте следующее:

mapping = {'a': assign_a(), 'b': assign_b()}
if None not in mapping.values():
    return mapping

Где assign_a и assign_b имеют вид:

def assign_<variable>():
    if condition:
        return value
    else:
        return None

Ответ 15

Если я правильно понимаю, вам не нужно пропускать последние функции, потому что если первое условие не выполняется, другие не будут оцениваться.

Для меня ваш код:

a = check_a()
b = check_b()
c = check_c()
....
if a and b and c and ...
    return (a, b, c, ...)

является правильным. Если сбой, b и c не будут оцениваться (избыточно, но необходимо).