為什麼 Python 不是 lexical scoping?

這似乎背離了現代程序設計語言的一般設計思路。

比如

def foo():
x = 1
def inner():
return x + 1
x = 3
print inner()

會列印出 4 而不是 2。

---------------------------- edit --------------------------

def foo():
def inner():
return x + 1
x = 3
print inner()

仍然會列印出來 4。


這段代碼是lexical scoping,靜態作用域是指我們可以根據代碼文本的位置,確定變數的存在區域。

按照python的LEGB(Local,Enclosing,Global,Built-in)規則,當調用inner()時,x實際上是在foo的scope中找到的。inner之所能夠訪問foo中的x,是因為inner is inside the text of foo,這正是lexical的含義

Bash是Dynamic Scoping的

x=1
function g () { echo $x ; x=2 ; }
function f () { local x=3 ; g ; }
f #f中的g執行時列印出的x是3而不是1
echo $x #這時列印出的x是1

g並沒有在f中定義,而只是在f中調用,但是卻可以訪問f中local variable的值,這是由於dynamic scoping。

王垠也在一篇文章中給出過emacs lisp的dynamic scoping的例子

我們定義一個函數f:

(setq f
(let ((x 1))
(lambda (y) (* x y))))

當這樣調用f時,返回的不是1×2二是2×2

(let ((x 2))
(funcall f 2))

我猜你可能混淆了LEGB Rule和Dynamic Scoping,他們相同的地方都是由內向外逐層檢查變數的定義是否存,但是有閉包的lexical scoping是逐層向外檢查Closure中變數是否存在,而dynamic scoping則是根據函數調用鏈逐層向外檢查變數是否存在,哪怕函數的定義不是嵌套的。

參考文獻

王垠:Lisp 已死,Lisp 萬歲!

Wikipedia:Scope (computer science)

Learning Python, Scoping Basics:https://www.inkling.com/read/learning-python-mark-lutz-4th/chapter-17/python-scope-basics


你以為Python是

let foo () =
let x = 1 in
let inner () = x + 1 in
let x = 3 in
print (inner ())

實際上Python是

let foo () =
let x = ref 1 in
let inner () = !x + 1 in
x := 3;
print (inner ())

這和lexical scoping一點關係都沒有啊

這是個mutable vs immutable的問題

python的scope是這樣的

+--------------------------+
| +---------------+ |
| def foo|(): | |
| +----+ | |
| | x = 1 | |
| | +-------+ | |
| | def inner|(): | | |
| | +------+ | | |
| | | return x + 1 | | |
| | +--------------+ | |
| | x = 3 | |
| | print inner() | |
| +--------------------+ |
+--------------------------+

你認為的scope是這樣的

let foo () =
+-------------------------------+
| let x = 1 in |
| +--------------------------+ |
| | let inner () = x + 1 in | |
| | +---------------------+ | |
| | | let x = 3 in | | |
| | | print (inner ()) | | |
| | +---------------------+ | |
| +--------------------------+ |
+-------------------------------+

因為Python的scope是mutable的,所以x = 3隻是把x的binding修改成指向3

就是這樣


python的閉包里的自由變數是按引用傳遞的,而不是按值傳遞,所以會有這個結果。

這和scoping沒有關係。

C++的lambda就可以選擇capture by copy或者capture by reference.


根據之前閱讀Python源碼的經驗(如果記錯請指正),在題主的例子裡面,這個inner是一個閉包。閉包在Python裡面的實現方式是保存一個通往外部namespace的指針(可以理解成一個dictionary)。

樓主可以參看這個例子

def foo():
def inner():
return x + 1
x = 1
print inner() # output 2
x = 2
print inner() # output 3

所以實際上每次inner都會直接引用foo裡面這個x的值。題主這個例子也就沒那麼難以理解了。這和lexical scoping沒有關係(相反是一個說明lexical scoping的好例子),和Python具體的閉包實現有關係。

發揮一下,如果樓主想要實現在第一個例子裡面輸出2,其實也是可能的,只需要做一點兒點兒修改。代碼如下:

def foo():
x = 1
def inner(x=x):
return x + 1
print inner() # output 2
x = 2
print inner() # output 2

至於為什麼么~就留給題主自己思考啦(Hint: Python的參數默認值的生成機制)。


這分明就是lexical scoping嘛,譬如說等價的C#代碼

void Foo()
{
int x=1;
Func& inner = ()=&>x+1;
x=3;
Console.WriteLine(inner());
}


def foo():
x = 1
def inner():
return x + 1
x = 3
print inner()
foo()
# 輸出4

為什麼輸出4而不是2?

有這個感覺,是因為對詞法作用域(靜態作用域)的理解不夠充分。

有人可能會怎麼想:靜態作用域嘛,所以是靜態決定的,inner定義時「捕獲」到引用環境x=1。所以調用時應該返回2。關鍵是:捕獲的是x的引用,而非值。

調用foo,最後print的時候inner才執行。x已經由1變為了3。如果把print inner()放在x = 3這行前面,自然會輸出2

---題外話---

關於python不是lexical scoping,是有這個說法的,或者說是:不支持「完整的」詞法作用域。比如不能在inner中對外部的變數賦值(Python3增加了nonlocal關鍵字解決了這個問題)


import inspect
def foo():
x = 1
print id(inspect.currentframe()), inspect.currentframe().f_locals.keys()
def inner():
f_c = inspect.currentframe()
print id(f_c), f_c.f_locals.keys()
print id(f_c.f_back), f_c.f_back.f_locals.keys()
return x + 1

print id(inspect.currentframe()), inspect.currentframe().f_locals.keys()
x = 3
print inner()

foo()

### 輸出:
30846736 ["x"]
30846736 ["x", "inner"]
30847096 ["x", "f_c"]
30846736 ["x", "inner"]
4

把 frame 的地址打出來就清楚了.


我Python2.x和3.x混用了

def inner():
x = 1
return x+1
def foo():
x = 3
print(inner())

如果py是動態作用域,這樣就是4了。。。。

結果是2。上面大神已經說的很明白了

補充:這樣也能2了

def foo():
x = 1
def inner():
return x + 1
print inner()
x = 3


我覺得完全沒有背離啊。。

輸出感覺當前 x 走的


把樓主的代碼改寫成 lua 可以看看 Python 和 Lua 在處理上的不同:

第一個例子結果都是一樣的,因為變數綁定的是引用而不是值。第二個例子:

function foo()
function inner()
return x + 1
end
local x = 3
print(inner())
end

foo()

Lua 會報錯:

lua: test.lua:3: attempt to perform arithmetic on global "x" (a nil value)

因為在定義 inner() 的時候,還沒有出現 local x ,所以 inner 裡面的 x 綁定到一個全局變數上了。然後這個全局變數沒有值,所以就掛了。

Python 的做法可以簡單地理解成:任何在函數內部定義的變數,都相當於在函數的最開頭定義了。跟 js 是一樣的,可以參考我的這篇 blog: 理解 lua 的 for 中的閉包,及其與 js 的閉包的比較

所以結論就是:Lua 大法好!如果樓主覺得 Lua 的做法更符合直覺,歡迎學習 Lua 。


這個問題很好回答:x是inner函數的環境變量,所以在inner的定義中出現的x其實就是對定義外面的,也就是inner的環境變量的一個引用而已。

函數只有在被調用的時候才會執行,你前面將置為1,後面又改為3,不過是改變了x的引用值而已,相當於給x重新賦值。然後執行inner函數,使用x所對應的值為3,因此答案就是4了。


題主所說的 現代化的編程語言指的是什麼? js經過這麼多代的更新迭代,現在也是這樣~

(function foo() {
function inner() {
return x+1;
}
x = 3;
console.log(inner());
})();

正常的閉包特性吧~


首先,按照LEGB原則, 閉包inner封裝的確實是foo里所定義的局部變數x。這同時符合lexical scoping定義。

再者(這很關鍵),閉包只有再被調用的那一刻才從外層scope里查找變數,這時候x已經被修改成為3.

那麼問題來了,我就是要封裝x=1那一刻的值,如何做呢?可以如下

def foo():
x = 1
def inner(x=x):
return x+1
x = 3
inner(x)

這樣使得定義inner函數的時候,我們就給它的一個局部變數選取了一個預設值,即為外圍函數的局部變數的當前值,並將其封存在自己的局部變數里。


沒什麼、我就試試 Javascript 什麼結果:

var foo = function(){
var x = 1;
var inner = function(){
return x + 1;
};
var x = 3;
console.log(inner());
};
foo();


建議讀下這篇,會有清晰的理解。

Dynamic scoping | World eBook Fair - eBooks | Read eBooks online

「 Importantly, in lexical scoping a variable with function scope moves out of scope when another function is called within the function, and moves back into scope when the function returns – called functions have no access to the local variables of calling functions, and local variables are only in scope within the body of the function in which they are declared. By contrast, in dynamic scoping, local variables stay in scope when another function is called, only moving out of scope when the defining function ends, and thus local variables are in scope of the function is which they are defined and all called functions. In languages with lexical scoping and nested functions, local variables are in scope for nested functions, since these are within the same lexical scope, but not for other functions that are not lexically nested. A local variable of an enclosing function is known as a non-local variable for the nested function. Function scope is also applicable to anonymous functions. 」

這個鏈接下同時提供其它明確概念的地方。


推薦閱讀:

Python 在 for 或者 if 語句後的冒號是冗餘嗎?
Python 會不會替代 MATLAB?
Python 中 「is」 和 「==」 的問題?
anaconda中如何安裝keras?
wxPython什麼時候可以支持Python3.5?

TAG:編程語言 | Python | 函數式編程 |