欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Python學習之名字,作用域,名字空間(下)

 更新時間:2022年05月11日 17:27:44   作者:??編程學習網(wǎng)????  
這篇文章主要介紹了Python學習之名字,作用域,名字空間,緊接上一篇文章內(nèi)容展開全文,需要的小伙伴可以參考一下,希望對你的學習有所幫助

前言:

這里再回顧一下函數(shù)的local空間,首先我們往global空間添加一個鍵值對相當于定義一個全局變量,那么如果往函數(shù)的local空間里面添加一個鍵值對,是不是也等價于創(chuàng)建了一個局部變量呢?

def f1():
    locals()["name "] = "夏色祭"
    try:
        print(name)
    except Exception as e:
        print(e)
f1()  # name 'name' is not defined

對于全局變量來講,變量的創(chuàng)建是通過向字典添加鍵值對的方式實現(xiàn)的。因為全局變量會一直在變,需要使用字典來動態(tài)維護。

但對于函數(shù)來講,內(nèi)部的變量是通過靜態(tài)方式存儲和訪問的,因為局部作用域中存在哪些變量在編譯的時候就已經(jīng)確定了,我們通過PyCodeObject的co_varnames即可獲取內(nèi)部都有哪些變量。

所以,雖然我們說查找是按照LGB的方式查找,但是訪問函數(shù)內(nèi)部的變量其實是靜態(tài)訪問的,不過完全可以按照LGB的方式理解。

因此名字空間是Python的靈魂,它規(guī)定了Python變量的作用域,使得Python對變量的查找變得非常清晰。

LEGB規(guī)則

而從Python2.2開始,由于引入了嵌套函數(shù),所以最好的方式應該是內(nèi)層函數(shù)找不到某個變量時先去外層函數(shù)找,而不是直接就跑到global空間里面找。

那么此時的規(guī)則就是LEGB:

a = 1
def foo():
    a = 2

    def bar():
        print(a)
    return bar
f = foo()
f()
"""
2
"""

調(diào)用f,實際上調(diào)用的是函數(shù)bar,最終輸出的結(jié)果是2。如果按照LGB的規(guī)則來查找的話,由于函數(shù)bar的作用域沒有a、那么應該到全局里面找,打印的結(jié)果是1才對。

但是我們之前說了,作用域僅僅是由文本決定的,函數(shù)bar位于函數(shù)foo之內(nèi),所以函數(shù)bar定義的作用域內(nèi)嵌于函數(shù)foo的作用域之內(nèi)。換句話說,函數(shù)foo的作用域是函數(shù)bar的作用域的直接外圍作用域。

所以應該先從foo的作用域里面找,如果沒有那么再去全局里面找。而作用域和名字空間是對應的,所以最終打印了2。

另外在執(zhí)行f = foo()的時候,會執(zhí)行函數(shù)foo中的def bar():語句,這個時候解釋器會將a=2與函數(shù)bar捆綁在一起,然后返回,這個捆綁起來的整體就叫做閉包。

所以:閉包 = 內(nèi)層函數(shù) + 引用的外層作用域

這里顯示的規(guī)則就是LEGB,其中E表示enclosing,代表直接外圍作用域。

global表達式

有一個很奇怪的問題,最開始學習Python的時候,筆者也為此困惑了一段時間,下面來看一下。

a = 1
def foo():
    print(a)
foo()
"""
1
"""

首先這段代碼打印1,這顯然是沒有問題的,不過下面問題來了。

a = 1
def foo():
    print(a)
    a = 2
foo()
"""
UnboundLocalError: local variable 'a' referenced before assignment
"""

僅僅是在print語句后面新建了一個變量a,結(jié)果就報錯了,提示局部變量a在賦值之前就被引用了,這是怎么一回事,相信肯定有人為此困惑。

而想弄明白這個錯誤的原因,需要深刻理解兩點:

  • 一個賦值語句所定義的變量,在這個賦值語句所在的整個作用域內(nèi)都是可見的;
  • 函數(shù)中的變量是靜態(tài)存儲、靜態(tài)訪問的, 內(nèi)部有哪些變量在編譯的時候就已經(jīng)確定;

在編譯的時候,因為a = 2這條語句,所以知道函數(shù)中存在一個局部變量a,那么查找的時候就會在當前作用域中查找。但是還沒來得及賦值,就print(a)了,所以報錯:局部變量a在賦值之前就被引用了。但如果沒有a = 2這條語句則不會報錯,因為知道局部作用域中不存在a這個變量,所以會找全局變量a,從而打印1

更有趣的東西隱藏在字節(jié)碼當中,我們可以通過反匯編來查看一下:

import dis
a = 1
def g():
    print(a)
dis.dis(g)
"""
  7           0 LOAD_GLOBAL              0 (print)
              2 LOAD_GLOBAL              1 (a)
              4 CALL_FUNCTION            1
              6 POP_TOP
              8 LOAD_CONST               0 (None)
             10 RETURN_VALUE
"""

def f():
    print(a)
    a = 2
dis.dis(f)
"""
 12           0 LOAD_GLOBAL              0 (print)
              2 LOAD_FAST                0 (a)
              4 CALL_FUNCTION            1
              6 POP_TOP

 13           8 LOAD_CONST               1 (2)
             10 STORE_FAST               0 (a)
             12 LOAD_CONST               0 (None)
             14 RETURN_VALUE
"""

中間的序號代表字節(jié)碼的偏移量,我們先看第二條,g的字節(jié)碼是LOAD_GLOBAL,意思是在global名字空間中查找;而f的字節(jié)碼是LOAD_FAST,表示在local名字空間中查找。因此結(jié)果說明Python采用了靜態(tài)作用域策略,在編譯的時候就已經(jīng)知道了名字藏身于何處。

而且上面的例子也表明,一旦函數(shù)內(nèi)有了對某個名字的賦值操作,這個名字就會在作用域內(nèi)可見,就會出現(xiàn)在local名字空間中。換句話說,會遮蔽外層作用域中相同的名字。

當然Python也為我們精心準備了global關鍵字,讓我們在函數(shù)內(nèi)部修改全局變量。比如函數(shù)內(nèi)部出現(xiàn)了global a,就表示我后面的a是全局的,直接到global名字空間里面去找,不要在local空間里面找了。

a = 1
def bar():
    def foo():
        global a
        a = 2
    return foo

bar()()
print(a)  # 2
# 當然,也可以通過globals函數(shù)拿到名字空間
# 然后直接修改里面的鍵值對

但如果外層函數(shù)里面也出現(xiàn)了變量a,而我們想修改的也是外層函數(shù)的a、不是全局的a,這時該怎么辦呢?Python同樣為我們準備了關鍵字: nonlocal,但是使用nonlocal的時候,必須是在內(nèi)層函數(shù)里面。

a = 1
def bar():
    a = 2
    def foo():
        nonlocal a
        a = "xxx"
    return foo

bar()()
print(a)  # 1
# 外界依舊是1,但是bar里面的a已經(jīng)被修改了

屬性引用與名字引用

屬性引用實質(zhì)上也是一種名字引用,其本質(zhì)都是到名字空間中去查找一個名字所引用的對象。這個就比較簡單了,比如a.xxx,就是到a里面去找屬性xxx,這個規(guī)則是不受LEGB作用域限制的,就是到a里面查找,有就是有、沒有就是沒有。

但是有一點需要注意,我們說查找會按照LEGB規(guī)則,但這必須限制在自身所在的模塊內(nèi),如果是多個模塊就不行了。舉個栗子:

# a.py
print(name)
# b.py
name = "夏色祭"
import a

關于模塊的導入我們后續(xù)會詳細說,總之目前在b.py里面執(zhí)行的import a,你可以簡單認為就是把a.py里面的內(nèi)容拿過來執(zhí)行一遍即可,所以這里相當于print(name)。

但是執(zhí)行b.py的時候會提示變量name沒有被定義,可把a導進來的話,就相當于print(name),而我們上面也定義name這個變量了呀。

顯然,即使我們把a導入了進來,但是a.py里面的內(nèi)容依舊是處于一個模塊里面。而我們也說了,名稱引用雖然是LEGB規(guī)則,但是無論如何都無法越過自身所在的模塊。print(name)在a.py里面,而變量name被定義在b.py里面,所以不可能跨過模塊a的作用域去訪問模塊b里面的name,因此在執(zhí)行 import a 的時候會拋出 NameError。

所以我們發(fā)現(xiàn),雖然每個模塊內(nèi)部的作用域規(guī)則有點復雜,因為要遵循LEGB;但模塊與模塊之間的作用域還是劃分的很清晰的,就是相互獨立。

關于模塊,我們后續(xù)會詳細說??傊ㄟ^ . 的方式,本質(zhì)上都是去指定的名字空間中查找對應的屬性。

屬性空間

我們知道,自定義的類里面如果沒有__slots__,那么這個類的實例對象都會有一個屬性字典。

class Girl:
    def __init__(self):
        self.name = "古明地覺"
        self.age = 16
g = Girl()
print(g.__dict__)  # {'name': '古明地覺', 'age': 16}

# 對于查找屬性而言, 也是去屬性字典中查找
print(g.name, g.__dict__["name"])  # 古明地覺 古明地覺

# 同理設置屬性, 也是更改對應的屬性字典
g.__dict__["gender"] = "female"
print(g.gender)  # female

當然模塊也有屬性字典,本質(zhì)上和類的實例對象是一致的。

import builtins
print(builtins.str)  # <class 'str'>
print(builtins.__dict__["str"])  # <class 'str'>
# 另外,有一個內(nèi)置的變量 __builtins__,和導入的 builtins 等價
print(__builtins__ is builtins)  # True

另外這個__builtins__位于 global名字空間里面,然后獲取global名字空間的globals又是一個內(nèi)置函數(shù),于是一個神奇的事情就出現(xiàn)了。

print(globals()["__builtins__"].globals()["__builtins__"].
      globals()["__builtins__"].globals()["__builtins__"].
      globals()["__builtins__"].globals()["__builtins__"]
      )  # <module 'builtins' (built-in)>
print(globals()["__builtins__"].globals()["__builtins__"].
      globals()["__builtins__"].globals()["__builtins__"].
      globals()["__builtins__"].globals()["__builtins__"].list("abc")
      )  # ['a', 'b', 'c']

所以global名字空間和builtin名字空間,都保存了指向彼此的指針,不管套娃多少次,都是可以的。

小結(jié)

在 Python 中,一個名字(變量)的可見范圍由作用域決定,而作用域由語法靜態(tài)劃分,劃分規(guī)則提煉如下:

  • .py文件(模塊)最外層為全局作用域;
  • 遇到函數(shù)定義,函數(shù)體形成子作用域;
  • 遇到類定義,類定義體形成子作用域;
  • 名字僅在其作用域以內(nèi)可見;
  • 全局作用域?qū)ζ渌凶饔糜蚩梢姡?/li>
  • 函數(shù)作用域?qū)ζ渲苯幼幼饔糜蚩梢?,并且可以傳遞(閉包);

與作用域相對應, Python在運行時借助PyDictObject對象保存作用域中的名字,構(gòu)成動態(tài)的名字空間 。

這樣的名字空間總共有 4 個:

  • 局部名字空間(local):不同的函數(shù),局部名字空間不同,可以通過調(diào)用 locals 獲?。?/li>
  • 全局名字空間(global):全局唯一,可以通過調(diào)用 globals 獲取;
  • 閉包名字空間(enclosing);
  • 內(nèi)置名字空間(builtin):可以通過調(diào)用 builtins__.__dict 獲取;

查找名字時會按照LEGB規(guī)則查找,但是注意:無法跨越文件本身,也就是按照自身文件的LEGB。如果屬性查找都找到builtin空間了,那么證明這已經(jīng)是最后的倔強。如果builtin空間再找不到,那么就只能報錯了,不可能跑到其它文件中找。

到此這篇關于Python學習之名字,作用域,名字空間(下)的文章就介紹到這了,更多相關Python名字空間內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Python機器學習NLP自然語言處理基本操作之命名實例提取

    Python機器學習NLP自然語言處理基本操作之命名實例提取

    自然語言處理(?Natural?Language?Processing,?NLP)是計算機科學領域與人工智能領域中的一個重要方向。它研究能實現(xiàn)人與計算機之間用自然語言進行有效通信的各種理論和方法
    2021-11-11
  • 詳解python3中socket套接字的編碼問題解決

    詳解python3中socket套接字的編碼問題解決

    本篇文章主要介紹了詳解python3中socket套接字的編碼問題解決,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-07-07
  • Python圖像處理庫PIL的ImageEnhance模塊使用介紹

    Python圖像處理庫PIL的ImageEnhance模塊使用介紹

    這篇文章主要介紹了Python圖像處理庫PIL的ImageEnhance模塊使用介紹,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-02-02
  • Python Charles抓包配置實現(xiàn)流程圖解

    Python Charles抓包配置實現(xiàn)流程圖解

    這篇文章主要介紹了Python Charles抓包實現(xiàn)流程圖解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-09-09
  • 在 Pycharm 安裝使用black的方法詳解

    在 Pycharm 安裝使用black的方法詳解

    這篇文章主要介紹了如何在 Pycharm 安裝使用black的相關知識,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-04-04
  • Python3基礎之list列表實例解析

    Python3基礎之list列表實例解析

    這篇文章主要介紹了Python3的list列表用法,這是Python3數(shù)據(jù)類型中非常常見的應用,需要的朋友可以參考下
    2014-08-08
  • python中的變量命名規(guī)則詳情

    python中的變量命名規(guī)則詳情

    這篇文章主要介紹了python中的變量命名規(guī)則詳情,變量名可以包括字母、數(shù)字、下劃線,但是數(shù)字不能做為開頭,變量用的好或不好,和代碼質(zhì)量有著非常重要的聯(lián)系,合理的使用變量,可以讓你的代碼可讀性更高并且更加簡潔,下面相關內(nèi)容吧需要的小伙伴可以參考一下
    2022-03-03
  • Sphinx生成python文檔示例圖文解析

    Sphinx生成python文檔示例圖文解析

    這篇文章主要介為大家紹了Sphinx生成python文檔示例圖文解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪
    2022-04-04
  • Python中的shape()詳解

    Python中的shape()詳解

    這篇文章主要介紹了Python中的shape()詳解,在debug深度學習相關代碼的時候,很容易出現(xiàn)shape()這樣形式的東西,用來告知輸出數(shù)據(jù)的形式,需要的朋友可以參考下
    2023-08-08
  • python 中的列表生成式、生成器表達式、模塊導入

    python 中的列表生成式、生成器表達式、模塊導入

    這篇文章主要介紹了python中的列表生成式、生成器表達式、模塊導入 ,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-06-06

最新評論