python 中带有条件 returns 的函数式样式
Functional style with conditional returns in python
假设我们有一个具有以下结构的函数:
def f():
# ...
# some computations
# ...
if something_is_wrong_with_previous_computations:
return None
# ...
# some other computations
# ...
if something_is_wrong_with_previous_computations2:
return some_variable
#...
return result
在我看来,在函数中间使用 return
语句根本不起作用。如果我们使用某种 lispy 语言,我们会有 let
(然后可以用 let*
编写计算)语句,这将帮助我们轻松处理这些情况。不幸的是,我们这里没有。我们该怎么办?
- 通过创建大量嵌套函数并就地调用它们来模拟
let
?
- 使用像
Maybe
monad 或其他类似的复杂东西?
- 不要浪费时间写命令式了?
- 还有别的吗?
函数中的 return
语句 anywhere 不起作用。
在Python,你别无选择。
Lisp 代码
(defun sgnum (x)
(cond
((< x 0) -1)
((zerop x) 0)
(t 1)))
变成Python为
def sgnum(x):
if x < 0:
return -1
elif x == 0:
return 0
else:
return 1
在 Lisp 中,当我们使用变量赋值或“程序特性”时,我们知道我们已经偏离了函数式编码:显式 progn
构造,或隐式等价物,或任何类似 prog
或 prog1
。 Lisp 中的函数式函数总是有一个由单个表达式(或者可能根本没有表达式)组成的函数体。
您可以在 Python 中重新定义“函数式编码”的含义。这些怎么样
规则:
“功能函数”中的每条语句都必须是单个语句;它后面不能跟另一个声明。因此,一个函数的整个主体是一条语句,并且其中嵌入了一条语句。
函数中的任何语句都不允许控制通过它。每条语句必须return。因此 return
不仅被认为是“功能性的”,而且对于实现这一目标至关重要。
可以定义变量,但不能重新定义。并行、互斥的控制流可以为同一个变量分配不同的值,但在同一个控制流中不能为变量分配多次。
使用这些规则,您可以使程序拥有类似于纯 Lisp 风格程序的控制流图:基本上是树的控制图具有嵌入式计算和变量绑定的决策,其叶子是要 returned 的值。
说到变量绑定,应该还有第四条规则:
- 语句之前可以有一系列不包含副作用的新变量赋值。这样的序列连同它后面的语句一起算作一个语句。
可以说也是第五个:
- 不得使用对任何包含的表达式或语句进行多次计算的语句。
否则我们允许循环,但循环不起作用。这很棘手,因为一些循环结构表现得相对较好,比如隐式地在列表元素上步进一个虚拟变量。您可以判断它不起作用的唯一方法是在循环中捕获的词法闭包很容易揭示只有一个变量被改变,而不是为每次迭代绑定一个新变量。
根据这些规则,sgnum
是“功能性的”:它只包含一个 if/elif/else
语句,不允许控制通过它:每个分支 returns:
此版本的 sgnum
不再“有效”:
def sgnum(x):
if x < 0:
return -1
if x == 0:
return 0
return 1
它依次包含三个语句。而以下是“功能性”的,即使它也包含三个语句:
def distance(x0, y0, x1, y1):
xd = x1 - x0
yd = y1 - y0
return math.sqrt(xd * xd + yd * yd)
这些符合规则。前两个语句绑定新变量,满足规则 3,因此 4 允许在语句之前。 return 语句符合规则 1 和 2。这非常类似于:
(defun distance (x0 y0 x1 y1)
(let ((xd (- x1 x0))
(yd (- y1 y0)))
(sqrt (+ (* xd xd) (* yd yd)))))
最后,请注意我们的规则与“函数中只有一个出口点”的古老编程建议有何不同。您可能会在某些编码约定中发现的那个小花絮是非常反功能的。要在非平凡函数中实现 return 的单点,需要命令式风格的控制流通过多个语句 and/or 变量赋值。从功能的角度来看,这是一个短视的、愚蠢的规则;但它在推荐的上下文中是有意义的,因为它可以帮助改进结构非常糟糕的命令式代码。
假设我们有一个具有以下结构的函数:
def f():
# ...
# some computations
# ...
if something_is_wrong_with_previous_computations:
return None
# ...
# some other computations
# ...
if something_is_wrong_with_previous_computations2:
return some_variable
#...
return result
在我看来,在函数中间使用 return
语句根本不起作用。如果我们使用某种 lispy 语言,我们会有 let
(然后可以用 let*
编写计算)语句,这将帮助我们轻松处理这些情况。不幸的是,我们这里没有。我们该怎么办?
- 通过创建大量嵌套函数并就地调用它们来模拟
let
? - 使用像
Maybe
monad 或其他类似的复杂东西? - 不要浪费时间写命令式了?
- 还有别的吗?
函数中的 return
语句 anywhere 不起作用。
在Python,你别无选择。
Lisp 代码
(defun sgnum (x)
(cond
((< x 0) -1)
((zerop x) 0)
(t 1)))
变成Python为
def sgnum(x):
if x < 0:
return -1
elif x == 0:
return 0
else:
return 1
在 Lisp 中,当我们使用变量赋值或“程序特性”时,我们知道我们已经偏离了函数式编码:显式 progn
构造,或隐式等价物,或任何类似 prog
或 prog1
。 Lisp 中的函数式函数总是有一个由单个表达式(或者可能根本没有表达式)组成的函数体。
您可以在 Python 中重新定义“函数式编码”的含义。这些怎么样 规则:
“功能函数”中的每条语句都必须是单个语句;它后面不能跟另一个声明。因此,一个函数的整个主体是一条语句,并且其中嵌入了一条语句。
函数中的任何语句都不允许控制通过它。每条语句必须return。因此
return
不仅被认为是“功能性的”,而且对于实现这一目标至关重要。可以定义变量,但不能重新定义。并行、互斥的控制流可以为同一个变量分配不同的值,但在同一个控制流中不能为变量分配多次。
使用这些规则,您可以使程序拥有类似于纯 Lisp 风格程序的控制流图:基本上是树的控制图具有嵌入式计算和变量绑定的决策,其叶子是要 returned 的值。
说到变量绑定,应该还有第四条规则:
- 语句之前可以有一系列不包含副作用的新变量赋值。这样的序列连同它后面的语句一起算作一个语句。
可以说也是第五个:
- 不得使用对任何包含的表达式或语句进行多次计算的语句。
否则我们允许循环,但循环不起作用。这很棘手,因为一些循环结构表现得相对较好,比如隐式地在列表元素上步进一个虚拟变量。您可以判断它不起作用的唯一方法是在循环中捕获的词法闭包很容易揭示只有一个变量被改变,而不是为每次迭代绑定一个新变量。
根据这些规则,sgnum
是“功能性的”:它只包含一个 if/elif/else
语句,不允许控制通过它:每个分支 returns:
此版本的 sgnum
不再“有效”:
def sgnum(x):
if x < 0:
return -1
if x == 0:
return 0
return 1
它依次包含三个语句。而以下是“功能性”的,即使它也包含三个语句:
def distance(x0, y0, x1, y1):
xd = x1 - x0
yd = y1 - y0
return math.sqrt(xd * xd + yd * yd)
这些符合规则。前两个语句绑定新变量,满足规则 3,因此 4 允许在语句之前。 return 语句符合规则 1 和 2。这非常类似于:
(defun distance (x0 y0 x1 y1)
(let ((xd (- x1 x0))
(yd (- y1 y0)))
(sqrt (+ (* xd xd) (* yd yd)))))
最后,请注意我们的规则与“函数中只有一个出口点”的古老编程建议有何不同。您可能会在某些编码约定中发现的那个小花絮是非常反功能的。要在非平凡函数中实现 return 的单点,需要命令式风格的控制流通过多个语句 and/or 变量赋值。从功能的角度来看,这是一个短视的、愚蠢的规则;但它在推荐的上下文中是有意义的,因为它可以帮助改进结构非常糟糕的命令式代码。