我正在标记这个"语言不可知的"因为我在询问什么似乎是一般原则,但我使用R和Python作为我的例子。
我按照这个MWE编写了一个R脚本:
## Does not work:
plus_i <- function(x) x + i
times_i <- function(x) x * i
loop_i <- function(x) {
for(i in 1:2) {
x <- plus_i(x)
x <- times_i(x)
}
x
}
loop_i(3)
但由于R的词汇范围,它失败了Error in plus_i(x) : object 'i' not found
。我无法用eval
来解决问题。
Python等价物也失败了:
## Does not work:
def plus_i(x):
return x + i
def times_i(x):
return x * i
def loop_i(x):
# mutable objects beware
for i in [1, 2]:
x = plus_i(x)
x = times_i(x)
return x
loop_i(3)
根据我的理解,这些程序可以使用动态作用域语言,但R和Python都是静态/词法范围的。如果是这样,那么它是否具有某种反范式或其他方面的“坏”&#34;写这样的R和Python代码?这只是一个问题,明确是否比隐含的更好?#或更深入?
编辑:事实上,它似乎更深入了。显然是lexical scoping is an inherent feature of closures。问题仍然适用。
请注意,plus_i
和times_i
未在程序中的loop_i
之外使用。但我 想要做的是在plus_i
内定义times_i
和loop_i
,因为我认为它会严重损害代码的可读性(这不像这个例子那么简单)。我也不想做的是让i
成为一个明确的函数参数,因为有几个这样的i
并且它似乎再次使代码的可读性更低更难调试(通过必须跟踪本地定义的内容和传入的内容)。
另一种方法是创建一个新环境,在其中定义plus_i
和times_i
,然后将其传递给loop_i
。但这仍然感觉就像一个标签外的环境使用。 编辑:或重新分配environment(plus_i) <- environment()
答案 0 :(得分:2)
如果您通读Hadley's guide on functions,您就会明白每项功能都需要牢记四种环境:
在这种情况下,您在循环之外定义plus_i
和times_i
,因此它们的父环境将是全局环境 - 这是他们寻找其他符号的地方(如{{1 }},并且不会找到它们。为了推动这些功能,您可以通过告诉他们在哪里寻找来强制解决问题。
i
请注意,这不会无意中更改任一函数的父环境,因为由于赋值,R隐式创建 plus_i <- function(x) x + i
times_i <- function(x) x * i
loop_i <- function(x) {
environment(plus_i) <- environment() # Look "here", in the local scope
environment(times_i) <- environment() # of loop_i, for i!
for(i in 1:2) {
x <- plus_i(x)
x <- times_i(x)
}
x
}
loop_i(3)
# [1] 12
内每个函数的副本。
但是,一般情况下,不指定loop_i
作为参数是一个坏主意:如果通过引用其参数列表或默认父环境中找不到的符号来提供“不完整”的函数,它将会让别人使用起来要困难得多。想象一下,如果i
是几十行代码,在某处使用plus_i
偷偷摸摸;怎么会有人知道这个功能取决于i
?对于小脚本,它可能没问题,但长期不适合开发卫生。
答案 1 :(得分:1)
我无法与 R 交谈,但对于 Python ,会有几种不同的选择......
关于它是否&#34;坏&#34;的最高级问题定义顶级函数,然后从其他函数调用它们,我会说不 - 在 Python 中经常这样做。
此处使用您编写的代码的问题是您希望顶级函数知道在另一个函数中声明的i
变量。在上面的代码中,i
仅在loop_i
函数中定义。其他函数没有意识到这些函数,并且当您尝试从其中一个顶级函数访问它时,您将获得NameError
例外情况。
使用现有代码最直接的做法可能是在顶级函数之上声明一个名为global
的{{1}}变量,这样顶级函数就会知道它的。
我想到的另一个选项,也就是我可能会使用的选项,是定义一个包含类级变量i
的类,然后定义顶部 - level函数作为 class 的方法。然后,他们可以通过i
访问i
。我通常使用类来编写所有 Python 的代码,所以这不是代码离开的大部分。
如果你想保持它更像 C ,那么ClassName.i
可能是最好的选择,尽管全局变量肯定有它们自己的复杂性。
这是一个可能的global
版本:
global
这是一个可能的#!/usr/bin/env python
def plus_i(x):
return x + i
def times_i(x):
return x * i
def loop_i(x):
global i
# mutable objects beware
for i in [1, 2]:
x = plus_i(x)
print("plus_i x: {0}".format(x))
x = times_i(x)
print("times_i x: {0}".format(x))
return x
loop_i(3)
版本:
class
他们都输出:
#!/usr/bin/env python
class ScopeTest:
i = None
def plus_i(self, x):
return x + ScopeTest.i
def times_i(self, x):
return x * ScopeTest.i
def loop_i(self, x):
# mutable objects beware
for ScopeTest.i in [1, 2]:
x = self.plus_i(x)
print("plus_i x: {0}".format(x))
x = self.times_i(x)
print("times_i x: {0}".format(x))
return x
if __name__ == "__main__":
st = ScopeTest()
st.loop_i(3)
答案 2 :(得分:0)
def plus_i(x, i):
return x + i
def times_i(x, i):
return x * i
def loop_i(x):
# mutable objects beware
for i in [1, 2]:
x = plus_i(x, i)
x = times_i(x, i)
return x
loop_i(3)
通常对于这样的语言,您将i作为函数参数传递,如上所示。
这样做的好处是它使代码引用透明,这使得更容易验证和理解它的行为。答案 3 :(得分:0)
如果您提供&#34; i&#34;他们按预期行事的论证:
plus_i <- function(x,i) x + i
times_i <- function(x,i) x * i
loop_i <- function(x) {
for(i in 1:2) {
x <- plus_i(x,i)
x <- times_i(x,i)
}
x
}
loop_i(3)
#[1] 12
使用环境分配实际上与提供&#34; i&#34;完全相同。作为一个正式的论点。它只是意味着&#34;本地&#34; - &#34; i&#34;将被发现。