Julia

只能在其他全局作用域块中嵌套的作用域块:

  • 全局作用域

    • 模块,裸模块

    • 在交互式提示行(REPL)

  • 局部作用域(不允许嵌套)

    • 可变的)结构,宏

可以在任何地方嵌套的作用域块(在全局或者局部作用域中):

  • 局部作用域

    • for,while,try-catch-finally,let

    • 函数(语法,匿名或者do语法块)

    • 推导式,broadcast-fusing

值得注意的是,这个表内没有的是 begin 块和 if 块,这两个块不会引进新的作用域块。这两种作用域遵循的规则有点不一样,会在下面解释。

Julia同R一样使用词法作用域(Lexical scoping),也就是说一个函数的作用域不会从其调用者的作用域继承,而从函数定义处的作用域继承。举个例子,在下列的代码中foo中的x指向的是模块Bar的全局作用域中的x。

In [2]:
module Bar
    x = 1
    foo() = x
end
Out[2]:
Main.Bar

在foo被使用的地方x并不在作用域中:

In [3]:
import .Bar
x = -1
Bar.foo()
Out[3]:
1

所以词法作用域表明变量作用域只能通过源码推断

全局作用域

每个模块会引进一个新的全局作用域,与其他所有模块的全局作用域分开;无所不包的全局作用域不存在。模块可以把其他模块的变量引入到它的作用域中,通过using 或者 import语句或者通过点符号这种有资格的通路,也就是说每个模块都是所谓的命名空间。值得注意的是变量绑定只能在它们的全局作用域中改变,在外部模块中不行。

In [4]:
module A
    a = 1 # a global in A's scope
end
Out[4]:
Main.A
In [5]:
module B
    module C
        c = 2
    end
    b = C.c    # can access the namespace of a nested global scope throuth a qulified access
    import ..A # makes the module A available
    d = A.a
end
Out[5]:
Main.B
In [6]:
module D
    b = a # errors as D's global scope is separate from A's
end
UndefVarError: a not defined

Stacktrace:
 [1] top-level scope at none:0
In [7]:
module E
    import ..A  # make module A available
    A.a = 2     # throw blow error
end
cannot assign variables in other modules

Stacktrace:
 [1] setproperty!(::Module, ::Symbol, ::Int64) at ./sysimg.jl:14
 [2] top-level scope at none:0

注意交互式提示行(即REPL)是在模块Main的全局作用域中。

局部作用域

局部作用域会从父作用域中继承所有的变量,读和写都一样。另外,局部作用域还会继承赋值给其父全局作用域块的所有全局变量(如果由全局if或者begin作用域包围)。不像全局作用域,局部作用域并不是命名空间,所以在其内部作用域中的变量无法通过一些合格的通路在其父作用域中得到。

接下来的规则和例子都适用于局部作用域。 在局部作用域中新引进的变量不会反向传播到其父作用域。 例如,这里z并没有引入到顶层作用域:

In [8]:
for i  = 1:10
    z = i
end
In [9]:
z
UndefVarError: z not defined

Stacktrace:
 [1] top-level scope at In[9]:1

在局部作用域中可以使用local关键字来使一个变量强制为新的局部变量。

In [1]:
x = 0
for i = 1:10
    x = i+1
end
In [2]:
x
Out[2]:
11
In [10]:
x = 0
for i = 1:10
    local x # this is also the default
    x = i+1
end
In [11]:
x
Out[11]:
0

在局部作用域内部,可以使用global关键字赋值给一个全局变量:

In [12]:
for i  = 1:10
    global z
    z = i
end
In [13]:
z
Out[13]:
10

大多数块关键字都会引入局部作用域,而begin和if是例外。

在一个局部作用域中,所有的变量都会从其父作用域块中继承,除非:

  • 赋值会导致全局变量改变,或者
  • 变量专门使用local关键字标记

所以全局变量只能通过读来继承,而不能通过写来继承。

In [14]:
x, y = 1,2;
function foo()
    x = 2 # assignment introduce a new local
    return x+y # y refers to the global
end
Out[14]:
foo (generic function with 1 method)
In [15]:
foo()
Out[15]:
4
In [16]:
x
Out[16]:
1

为一个全局变量赋值需要显式的global

In [17]:
x = 1;
function foobar()
    global x = 2
end;
In [18]:
foobar()
Out[18]:
2
In [19]:
x
Out[19]:
2

注意嵌套函数会改变其父作用域的局部变量:

In [22]:
x, y = 1,2
function baz()
    x = 2 # introduce a new local
    function bar()
        x = 10 # modifies the parent's x
        return x + y
    end
    return bar() + x # 12 + 10 (x is modified in call for the bar())
end
Out[22]:
baz (generic function with 1 method)
In [23]:
baz()
Out[23]:
22
In [24]:
x, y # verify that global x and y are unchanged
Out[24]:
(1, 2)

Python

Python没有像Julia一样,其嵌套函数不会改变其父作用域的局部变量:

In [1]:
x, y = 1,2
def baz():
    x = 2
    def bar():
        x = 10
        return x+y
    return bar() + x

baz()
Out[1]:
14
In [ ]:
x

for循环中,Python同Julia不同,在局部作用域中新引进的变量会反向传播到其父作用域,例如,这里n被引入到顶层作用域:

In [6]:
for i in range(10):
    n = i
In [7]:
n
Out[7]:
9

R

R同Python,都没有像Julia一样,其嵌套函数不会改变其父作用域的局部变量:

In [2]:
x <- 1
y <- 2
baz <- function() {
    x <- 2
    bar <- function() {
        x <- 10
        return(x + y)
    }
    return(bar() + x)
}
In [3]:
baz()
14
In [4]:
x
1

for循环中,R同Julia不同,在局部作用域中新引进的变量会反向传播到其父作用域,例如,这里z被引入到顶层作用域:

In [7]:
for(i  in 1:10) {
    z = i
}
In [8]:
z
10