1. 程式人生 > 程式設計 >Python變數作用域LEGB用法解析

Python變數作用域LEGB用法解析

這篇文章主要介紹了Python變數作用域LEGB用法解析,文中通過示例程式碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下

閉包就是,函式內部巢狀函式. 而 裝飾器只是閉包的特殊場景而已,特殊在如果外函式的引數是指向一個,用來被裝飾的函式地址時(不一定是地址哈,隨意就好),就有了 "@xxx" 這樣的寫法,還是蠻有意思的. 裝飾器的作用是 在不改變原函式的程式碼前提下,額外給原函式填寫新功能. 寫法上來看,還是比較簡潔優雅的.

裝飾器的通俗寫法

# 裝飾器的通用寫法
def out(func):
  def inner(*args,**kwargs):
    print("we are checking...",args[0])
    return func(*args,**kwargs)

  return inner
@out
def check_2019_nCov(name):
  return f"now,{name} is very healthy..."


tmp = check_2019_nCov('youge')
print(tmp)

# output
we are checking... youge
now,youge is very healthy...

給裝飾器傳參

雖然這種 "@" 的寫法,是要求 外函式的引數是一個 func 地址,但要達到可以傳參,只要 再在外面包一層函式 (作用是接受引數),這樣不就相當於擴大作用空間,拿到引數了呀 .

# 最外層的函式作用是,給裝飾器傳遞引數
def get_param(*args,**kwargs):
  def out(func):
    def inner(*args,**kwargs):
      print("get params",args,kwargs)
      return func(*args,**kwargs)

    return inner

  return out


@get_param("youge")
def check_2019_nCov(name):
  return f"now,{name} is very healthy..."



tmp = check_2019_nCov("youge")
print(tmp)

# output
get params ('youge',) {}
now,youge is very healthy...

這種個裝飾器傳遞引數的應用場景,在 Web應用中,以 Flask 為例,就是所有的 路由 url 的概念呀,如 route("/login") 這樣的寫法,其原理就是用各種裝飾器來實現 路由 -> 檢視 的對映關係的.

仔細一看,整個過程忽略了一個重要的話題,即名稱空間,及 變數的作用域,或者說名稱空間如怎樣的.

LEGB 法則

名稱空間

前篇已經詳細闡述過了,Python 變數的本質是指標,是物件的引用,而 Python中 萬物皆物件. 這個物件是真正儲存資料的記憶體地址,是各種類(資料型別,資料結構) 的例項. (變數就是用來引用物件的) 差不多這個意思吧.

最為直觀的解釋:

" A namespace is a mapping from names to objects". (變數名和物件的對映)

"Most namespaces are currently implemented as Python dictionaries." (大部分名稱空間通過字典來實現)

即名稱空間是用來 避免變數命名衝突 的約束. 各個名稱空間是彼此獨立的,一個空間中不能重名,不同空間中是不沒有關係的. 就跟 計算機系統,儲存檔案是樣的邏輯.

for i in range(10):
  print(i)
  
# 這兩句話都用到了 i 但其各自的空間是不一樣的.
  
[i for i in range(100)]
  • 內建空間: (built-in names): Python 內建名稱,如內建函式,異常類...
  • 全域性空間: (global names): 常量,模組中定義的名稱(類,匯入模組)...
  • Enclosed: 可能巢狀在函式內的函式等...
  • 區域性名稱: (local names): 函式中定義的名稱(函式內的變數) ...

Python 查詢變數順序為:Local -> Enclosed -> Global -> Built-in。

其實,從我個人經驗而言,能區分 區域性和全域性 的 相對性. 就好了,基本上. 直觀上,以一個寫程式碼的 py檔案為例. 最外層有,變數,類定義,函式定義,從from .. import .. 的變數或函式名,這些就是 全域性變數,最外面的類或者函式,裡面是各自的名字空間呀.

# var1 是 global
var1 = 666

def foo():
  # var2 是區域性
  var2 = 666
  def foo2():
    # 內嵌的區域性
    var3 = 666
    
    # print(var2)
    
print(var3) # G->L 是找不到的哦
# 在 foo2 中 尋找 var2 是 L->E 是ok的
# 在 foo 中 尋找 var2 是 E->L 是不行的

其實很好理解的. 就上段code來說,根據 L-E-G-B 法則,其實理解一個 相對 就可以了.

全域性 vs 區域性

total = 0 # 全域性

def sum(a,b):
  """重寫內建sum"""
  total = a + b 
  print("區域性total:",total)
  
sum(1,1)
print("全域性total:",total)

# output
區域性total: 2
全域性total: 0

可以看到,區域性是不會改變全域性的哦,而在區域性內是可以拿到全域性變數的. 不然閉包,外函式接收的引數,內函式怎麼可以拿到呢? 就是外函式,"擴充了" 內函式的作用域呀,根據 L->E->G->B 法則去搜索到.

global 和 nonlocal

name = "youge"

def change_name():
  name = "youyou"
  

# 希望將 "youge" 改為 "youyou"
change_name()
print(name)

# output
youge

發現沒有能改掉,這是自然的. 因為,在呼叫函式時,裡面的 name 是一個 Local 變數,是不會影響到全域性的 name的,如果想實現在 在函式內部來改變 全域性變數,則將 該變數用 global 關鍵字宣告即可.

name = "youge"

def change_name():
  
  global name
  name = "youyou"

# 希望將 "youge" 改為 "youyou"
change_name()
print(name)

# output
youyou

很簡單,在函式內部,用 global 將其宣告為全域性變數即可. 同樣,針對於** 函式巢狀,即向閉包,裝飾器等,通過 關鍵字 nonlocal 實現將 函式內的變數,宣告為 函式外的 Enclose 層**

name = "jack"

def outer():
  name = "youge"

  # 函式內有一個local函式空間
  def inner():
    name = "youyou"
    print("local:",name)

  inner() # 嘗試改掉 巢狀層的 name
  print("encolse:",name)


print("global:",name)

outer()

# output
global: jack
local: youyou
encolse: youge

現在想在,inner函式 (L層) 中來修改 E 層的 name,即在inner中將 name 宣告為 nonlocal 即可.

name = "jack"

def outer():
  name = "youge"

  # 函式內有一個local函式空間
  def inner():
    nonlocal name
    name = "youyou"
    print("local:",name)

outer()


# output 
global: jack
local: youyou 
encolse: youyou

函式巢狀場景中,通過 在 local 層,宣告 name 為 nonlocal 則將 enclosed 層的name改掉了. 但如果在 local 層 宣告 global 則是沒有其效果的,為啥,嗯... 暫時還不清楚,也是實驗的,暫時.

哦,突然想貼一個,我還是菜鳥時常,犯的小錯誤:

name = 'youge'
def change_name():
  name = name + "youyou"

change_name()  
print(name)

# output
UnboundLocalError: local variable 'name' referenced before assignment

原因就在於,在函式內部的空間中,對 name 是沒有定義的. 在 Python中,對於函式過程的儲存,是通過 遞迴棧 實現的. 利用棧的 FILO,(先進後出) 的特點,當遇到一個函式,就用棧將其參與的成員,依次入棧,如有 return 則將置為棧元素.

變數要先定義,後使用嘛,Python中的定義是指,該變數指向某個例項物件即可,而非 其它語言中的 型別宣告 哦,這裡最容易混淆.

修改 name 為全域性變數,通過函式引數傳遞即可:

# 方式1: 定義個單獨的函式來處理
name = 'youge'

def change_name(s):
  name = s + "youyou"
  print(name)

# 全域性變數來傳遞給 函式空間,即"先定義,後執行")

change_name(name)  

# output
yougeyouyou
# 方式2: 宣告為全域性即可,不推薦
name = 'youge'

def change_name():
  global name
  name = name + "youyou"

change_name()
print(name)

# output
yougeyouyou

小結

  • 閉包,裝飾器的本質是函式的巢狀,引數及函式能被傳遞的原因是,Pyhton變數的本質是之指標
  • Python中用 名稱空間 來 解決 變數名衝突,原理跟 計算機系統(如 Linux) 儲存檔案是一樣的邏輯
  • 變數名尋找的規則為 Local -> Enclosed -> Global -> Built-in
  • 個人覺得能理解,全域性與區域性的"相對性" 即可,另外,可用 global 與 nonlocal (E層) 改變變數作用等級.
  • 變數作用域,一直在用,但卻經常忽略它,這裡做個總結,沒事常翻翻,作用域,就到這吧.

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。