这就是魔法发生的地方:
return (s[0] == s[-1]) and isPal(s[1:-1])
(我添加了括号,因此您对运算符的优先级非常清楚)。重要的是要知道 Python AND 语句将评估第一个参数,并且只有当它是True
时,它才会评估第二个参数。如果第一个是False
,那么它将False
立即返回而不调用isPal()
。这称为短路评估。Blender在他的帖子中做了一个很好的例子。
s[0]
是字符串中的第一个字母,是字符串s[-1]
中的最后一个字母。首先我们检查第一个和最后一个字母是否相同。如果它们不同,则字符串不可能是回文。如果它们相同,我们可以忽略它们并移动到字符串的内部。
s[1:-1]
修剪掉第一个和最后一个字母,然后将其传递给isPal()
函数,这恰好是我们当前所在的函数——这称为递归。当我们再次“递归”到函数中时,会创建一个新堆栈,创建新的局部变量,并且该函数的输入参数将与上一次调用中的不同。
我们继续检查第一个和最后一个字母,并一直递归到单词的中心。在这一点上,剩下 1 个或 0 个字符,如果我们已经走到那一步,我们就知道我们找到了一个回文。
当我们从这个最终的内部调用返回时isPal()
,返回值(True
如果已找到回文)被传递给调用函数,然后调用函数将其返回给它的调用函数,等等,直到最终的“外部”isPal()
函数返回结果到初始调用者。我们将此过程称为“展开堆栈”。
这是一个例子:
s = 'racecar'
s[0] == s[-1] # True - both letters are 'r'
s = s[1:-1] # s becomes 'aceca'
s[0] == s[-1] # True - both letters are 'a'
s = s[1:-1] # s becomes 'cec'
s[0] == s[-1] # True - both letters are 'c'
s = s[1:-1] # s becomes 'e'
len(s) <= 1 # This statement is now True, so the stack will start to unwind
从视觉上看,调用将如下所示:
'racecar' ---------
|
V 'aceca'
True <--- isPal( ) ----
^ |
True | V 'cec'
---- isPal( ) ----
^ |
True | V 'e'
---- isPal( ) ----
^ |
True | V
---- isPal( ) # We get all the way down here,
# and then we start to return
在我们没有回文的情况下,会发生这种情况:
'race2car' --------
|
V 'ace2ca'
False <--- isPal( ) ----
^ |
False | V 'ce2c'
---- isPal( ) ----
^ |
False | V 'e2'
---- isPal( ) ----
^ |
False | V
---- isPal( ) # Here the outer two letters
# do not match, so we start to
# return False