Python學習:作用域

  • 2020 年 1 月 13 日
  • 筆記

Python作用域基礎

Python有四個作用域:

  1. L(Local)本地也稱作局部作用域;
  2. E(Enclosing)閉包函數外的函數中;
  3. G(global)全局作用域;
  4. B(Built-in)內建作用域;

變數可以在三個不同的地方分配:

  • 如果一個變數在def內賦值,它被定位在這個函數之內。
  • 如果一個變數在嵌套的def中賦值,對於嵌套的函數來說,它是非本地的。
  • 如果在def之外賦值,它就是整個文件全局的。

值得注意的是,修改一個對象並不是對一個名稱賦值。

變數名解析:LEGB原則

對於一個def語句:

變數名引用分為三個作用域進行查找:首先是本地,之後是函數內(如果有的話),之後全局,最後是內置。L->E->G->B

Python除了def/class/lambda外,其他如:if/elif/else/  try/except  for/while並不能改變作用域。定義在他們之內的變數,外部還是可以訪問。

>>> if True:  ...     a = 'I am A'  ...   >>> a  'I am A'  # 定義在if語言中的變數a,外部還是可以訪問的。  # 但是需要注意如果if被 def/class/lambda 包裹,在內部賦值,就變成了此 函數/類/lambda 的局部作用

在def/class/lambda內進行賦值,就變成了其局部作用域。局部作用域會覆蓋全局作用域,但不會影響全局作用域。

g=1            #全局變數  def func():      g = 2      #局部變數      return g    print func()    #結果為2  print g         #結果為1

值得注意的是,有時候想再函數內調用全局變數,疏忽了會報錯,如下:

#file1  var = 1  def func():      print var      var = 200    func()    #file2  var = 1  def func():      var = var +1      return var    func()    #這兩個函數都會報錯UnboundLocalError: local variable 'var' referenced before assignment

上述兩個函數都會報同樣的錯誤:為賦值之前引用變數!為什麼?在函數內部,解釋器探測到變數var重新被賦值,所以var變成了局部變數,但是在被賦值之前就使用了var,便會出現這個錯誤。解決的方法是在函數內部添加globals var語句,但運行函數後全局的var也會被修改。

#file1  var = 1  def func():      global var      print var      var = 200    func()        #結果為1  print var     #全局變數var變為200    #file2  var = 1  def func():      global var      var = var +1      return var    print func()        #結果為2

閉包Closure

閉包的定義:如果在一個內部函數里,對外部函數內(不是全局變數)進行引用,那麼內部函數就被認為是閉包(closure)。

a = 1  def external():      global a      a = 200      print a      b =100      def internal():          print b          b = 200          return b      internal()      print b    print external()  #一樣會報錯,賦值前引用UnboundLocalError: local variable 'b' referenced before assignment

Python3中有關鍵字nonlocal可以解決這個問題,但在Python2中盡量不要嘗試修改閉包中的變數。

關於閉包,還有一個坑:

from functools import wraps    def wrapper(log):      def external(F):          @wraps(F)          def internal(**kw):              if False:                  log = 'modified'              print log          return internal      return external    @wrapper('first')  def abc():      pass    print abc()

也會出現 引用在賦值之前 的錯誤,原因是解釋器探測到了 if False 中的重新賦值,所以不會去閉包的外部函數(Enclosing)中找變數,但 if Flase 不成立沒有執行,所以便會出現此錯誤。除非你還需要else: log='var' 或者 if True 但這樣添加邏輯語句就沒了意義,所以盡量不要修改閉包中的變數。

好像用閉包無法實現計數器功能,因為在閉包內部count+=1就會出現在賦值前引用的錯誤(Python3用關鍵字nonlocal可以解決)

def counter(start):      count = [start]      def internal():          count[0] += 1          return count[0]      return internal    count = counter(0)  for n in range(10):      print count()  #結果分別為1,2,3,4,5,6,7,8,9,10    count = counter(0)  print count()  #結果為1

global和globals()

global用來在函數內部聲明全局變數,globals() 和 locals() 提供了基於字典的訪問全局和局部變數的方式。

比如:如果函數1內需要定義一個局部變數,名字另一個函數2相同,但又要在函數1內引用這個函數2。

def var():      pass    def f2():      var = 'Just a String'      f1 = globals()['var']      print var      return type(f1)    print f2()  # Just a String  # <type 'function'>