Рекурсия с тернарным оператором "hack" в Python

играя в Python, я обнаружил, что следующий код работает, как я ожидал:

f = lambda S,b : (S if len(S)==b else f(S[1:],b))

из списка S он будет рекурсивно отбрасывать первый элемент до тех пор, пока длина S не будет равна b. Например, f([1,2,3,4,5,6],3) = [4,5,6].

однако, к моему удивлению, следующее решение, которое использует "троичный Хак" [a, b][c] вместо "b, если c еще a" (он же "c?b: a") не работает:

g = lambda S,b : (g(S[1:],b),S)[len(S)==b]

Это превысит максимальную рекурсию глубина.

почему это не работает?

(Я знаю, что ни один из них не является примером отличного стиля кодирования, но это не относится к делу.)

3 ответов


Ok, давайте посмотрим на ast лямбда-функция генерирует:

import ast
tree = ast.parse('lambda S,b : (g(S[1:],b),S)[len(S)==b]')
ast.dump(tree)

после некоторого форматирования в vim это то, что я получил:

Module(
  [Expr(
    Lambda(
      arguments(
        [Name('S', Param()), Name('b', Param())],
        None,
        None,
        []
      ),
      Subscript(
        Tuple(
          [Call(
              Name('g', Load()),
              [Subscript(Name('S', Load()), Slice(Num(1), None, None), Load()), Name('b', Load())],
              [],
              None,
              None
            ),
            Name('S', Load())
          ],
          Load()
        ),
        Index(
          Compare(
            Call(Name('len', Load()), [Name('S', Load())], [], None, None),
            [Eq()],
            [Name('b', Load())]
          )
        ),
        Load()
      )
    )
  )]
)

как вы можете видеть, первое, что этот код выполняет при вызовах лямбда-это создание кортежа, и после этого непосредственно приходит рекурсивный вызов (Call(Name('g'...) к той же лямбде.

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

>>>[1][1:]
[]
>>>[][1:]
[]

это означает, что g(S[1:]) уменьшит ваш список до пустого списка, а затем продолжит бесконечно вызывать g С пустым списком. Это происходит из-за того, как парсер выполняет операторы. Первое, что выполняется, - это вызов рекурсивного метода, поэтому он не остановится.

моя точка зрения: базовый случай не работает над рекурсией.

надеюсь, это принесет больше света к теме.


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

g = lambda S,b : (g(S[1:],b),S)[len(S)==b]

в этом случае g(S[1:],b) получает оценку даже до достижения оператора if.

если у вас есть функция, нет базового случая, который является тем же случаем с g(S[1:],b)

def func(S, b)
    return func(S[1:],)

func(S,b)
#output: error - exceed maximum recursion depth

S[1:] достигнет точки, где он пуст, и если он пуст, он вернет пустой список.

небольшой пример о пустом списке:

S = [0, 1]

S = S[1:]
# [1]

S = S[1:]
# [] # empty

S = S[1:]
# [] # also empty

если у вас A if C else B, потом C выполняется сначала, а потом одним из A или B выполняется (и это результат возвращается), тогда как если вы делаете [B, A][C], и A и B выполнено, а то C. Вы можете легко проверить это, сделав p("A") if p("C") else p("B") и [p("B"), p("A")][p("C")] С функцией p который печатает свой вход, а затем возвращает True или False

Итак, в первом случае S if len(S)==b else f(S[1:],b), выполняется рекурсивный вызов только если условие не применяется. Во втором случае, однако, он выполняется прежде чем условие даже испытано, и то же самое в рекурсивно называемой функции ad infinitum.

(я предполагаю, что вы не собираетесь использовать это на практике, поэтому это может быть не важно, но в любом случае: обратите внимание, что (1) обе функции не имеют защиты для случая len(S) < b, (2) то же самое может быть достигнуто с S[-b:] и (3) Использование if/else, конечно, много более читабельный.)