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

Python作用域與名字空間源碼學(xué)習(xí)筆記

 更新時(shí)間:2022年05月17日 14:30:41   作者:Blanker  
這篇文章主要為大家介紹了Python作用域與名字空間的源碼學(xué)習(xí)筆記,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪<BR>

作用域與名字空間

問題:

PI = 3.14
def circle_area(r):
    return PI * r ** 2
class Person(object):
    def __init__(self, name):
        self.name = name
    def say(self):
        print('i am', self.name)

以這個(gè)程序?yàn)槔?,代碼中出現(xiàn)的每個(gè)變量的作用域分別是什么?程序中總共涉及多少個(gè)名字空間?Python又以怎樣的順序去查找一個(gè)變量呢?

1. 名字綁定

1.1 賦值

在Python中,變量只是一個(gè)與實(shí)際對(duì)象綁定起來的名字,變量定義本質(zhì)上就是建立名字與對(duì)象的約束關(guān)系。因此,賦值語句本質(zhì)上就是建立這樣的約束關(guān)系,將右邊的對(duì)象與左邊的名字綁定起來:

a = 1

賦值語句是最基本的將名字與對(duì)象綁定的方式,除此之外還有很多其他方式都起到了這樣的作用。

1.2 模塊導(dǎo)入

當(dāng)我們導(dǎo)入一個(gè)模塊時(shí),也會(huì)在當(dāng)前上下文創(chuàng)建一個(gè)名字,并與被導(dǎo)入對(duì)象綁定。

# 在當(dāng)前上下文創(chuàng)建一個(gè)名字test,與被導(dǎo)入的module對(duì)象綁定
import test

1.3 函數(shù)、類定義

# 函數(shù)名circle_area與function對(duì)象綁定
def circle_area(r):
    return PI * r ** 2
# 類名Person與類型對(duì)象綁定
class Person(object):
    def __init__(self):
        pass

1.4 as關(guān)鍵字

# 將名字t與module對(duì)象綁定
import test as t

2. 作用域

問題:當(dāng)我們引入一個(gè)名字之后,它的可見范圍有多大呢?

a = 1
def func1():
    print(a)  # 1
def func2():
    a = 2
    print(a)  # 2
print(a)  # 1

在不同的代碼區(qū)域引入的名字,其影響范圍是不一樣的。第1行定義的a可以影響到func1,而func2中定義的a則不能。此外,一個(gè)名字可能會(huì)在多個(gè)代碼區(qū)域中定義,但最終在某個(gè)代碼區(qū)域中只能使用其中一個(gè)。

2.1 靜態(tài)作用域

一個(gè)名字能夠施加影響的程序正文區(qū)域,便是該名字的作用域。在Python中,一個(gè)名字在程序中某個(gè)區(qū)域能否起作用,是由名字引入的位置決定的,而不是運(yùn)行時(shí)動(dòng)態(tài)決定的。因此,Python具有靜態(tài)作用域,也稱為詞法作用域。那么,作用域具體是如何劃分的呢?

2.2 劃分作用域

  • Python在編譯時(shí),根據(jù)語法規(guī)則將代碼劃分為不同的代碼塊,每個(gè)代碼塊形成一個(gè)作用域。首先,整個(gè).py文件構(gòu)成最頂層的作用域,這就是全局作用域,也成為模塊作用域;其次,當(dāng)代碼遇到函數(shù)定義,函數(shù)體成為當(dāng)前作用域的子作用域;再者,當(dāng)代碼遇到類定義,類定義體成為當(dāng)前作用域的子作用域。
  • 一個(gè)名字在某個(gè)作用域引入后,它的影響范圍就被限制在該作用域內(nèi)。其中,全局作用域?qū)λ兄苯踊蜷g接內(nèi)嵌于其中的子作用域可見;函數(shù)作用域?qū)ζ渲苯幼幼饔糜蚩梢?,并且可以傳遞。
  • 例子中的作用域的嵌套關(guān)系如下:

訪問關(guān)系如下:

2.3 閉包作用域

閉包的概念:在計(jì)算機(jī)科學(xué)中,閉包,又稱詞法閉包或函數(shù)閉包,是引用了自由變量的函數(shù)。這個(gè)被引用的自由變量將和這個(gè)函數(shù)一同存在,即使已經(jīng)離開了創(chuàng)造它的環(huán)境也不例外。所以,有另一種說法認(rèn)為閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實(shí)體。閉包在運(yùn)行時(shí)可以有多個(gè)實(shí)例,不同的引用環(huán)境和相同的函數(shù)組合可以產(chǎn)生不同的實(shí)例

代碼示例:

>>> pi = 3.14
>>> def closure_print(name: str):
    	def circle_area(r: int):
        	print(name, pi * r * r)
	    return circle_area
>>> circle_area1 = closure_print("circle1: ")
>>> circle_area2 = closure_print("circle2: ")
>>> circle_area1(1)
circle1:  3.14
>>> circle_area2(2)
circle2:  12.56

劃分作用域:

思考:circle_area1和circle_area2函數(shù)對(duì)象是怎么拿到name的?

2.4 類作用域

代碼示例:

>>> language = 'chinese'
>>> class Male:
        gender: str = 'male'
        def __init__(self, name: str):
            self.name = name
        def Speak(self):
            print('i speak', language)
        def Gender(self):
            print('i am', gender)
>>> male = Male('zhangsan')
>>> male.Gender()
Traceback (most recent call last):
  File "<pyshell#11>", line 1, in <module>
    male.Gender()
  File "<pyshell#9>", line 8, in Gender
    print('i am', gender)
NameError: name 'gender' is not defined
>>> male.Speak()
i speak chinese

作用域分析:

全局作用域?qū)ζ渌袃?nèi)嵌其中的作用域均可見,所以在函數(shù)Speak()中可以訪問到language

類作用域和函數(shù)作用域不一樣,它對(duì)其子作用域是不可見的,所以在函數(shù)Gende()中g(shù)ender是不可見的

思考:

>>> male.gender
'male'
>>> Male.gender
'male'
>>> male.gender = 'male2'
>>> male.gender
'male2'
>>> Male.gender
'male'

2.5 復(fù)雜嵌套

2.5.1 函數(shù)嵌套類

在Python中,類可以動(dòng)態(tài)創(chuàng)建,甚至在函數(shù)中返回。通過在函數(shù)中創(chuàng)建并返回類,可以按函數(shù)參數(shù)對(duì)類進(jìn)行動(dòng)態(tài)定制

代碼示例:

>>> language = 'chinese'
>>> def MakeMale(sSortName: str):
        class Male:
            sortName = sSortName
            def __init__(self, name: str):
                self.name = name
            def Speak(self):
                print('i speak', language)
			def Sort(self):
				print(sSortName)
        return Male
>>> ChineseMale: type = MakeMale('Chinese Men')
>>> chineseMale = ChineseMale('zhangsan')
>>> chineseMale.Speak()
i speak chinese
>>> chineseMale.sortName
Chinese Men
>>> chineseMale.Sort()
Chinese Men

2.5.2 類嵌套類

代碼示例:

>>> class OutClass:
        inName = 'in'
        class InClass:
            name = inName
Traceback (most recent call last):
  File "<pyshell#26>", line 1, in <module>
    class OutClass:
  File "<pyshell#26>", line 3, in OutClass
    class InClass:
  File "<pyshell#26>", line 4, in InClass
    name = inName
NameError: name 'inName' is not defined

3. 名字空間

作用域是語法層面的概念,是靜態(tài)的。當(dāng)程序開始執(zhí)行后,作用域中的名字綁定關(guān)系需要存儲(chǔ)起來,存儲(chǔ)的地方就是名字空間。由于名字綁定關(guān)系是由名字和對(duì)象組成的鍵值對(duì),因此用dict是理想的存儲(chǔ)容器(之前在介紹dict的相關(guān)內(nèi)容時(shí)也有提到)

以計(jì)算圓面積的例子來認(rèn)識(shí)作用域背后的運(yùn)行時(shí)實(shí)體——名字空間。代碼示例如下:

>>> PI = 3.14
>>> def closure_print(name: str):
    	def circle_area(r: int):
        	print(name, PI * r * r)
	    return circle_area

3.1 Globals

在Python中,每個(gè)模塊都有一個(gè)dict對(duì)象,用于存儲(chǔ)全局作用域中的名字,這就是全局名字空間Globals。在上述的例子中,根據(jù)我們之前對(duì)作用域的劃分,可以肯定全局名字空間中一定包含兩個(gè)名字:PI和closure_print。

如果其他模塊也需要使用PI或closure_print函數(shù),就需要通過import語句將模塊導(dǎo)入,導(dǎo)入后我們就可以獲得一個(gè)模塊對(duì)象:

# 假設(shè)我們?cè)趖est.py中導(dǎo)入上述模塊testglobal.py
>>> import testglobal
>>> testglobal
<module 'testglobal' from 'D:\\myspace\\code\\pythonCode\\mix\\namespace\\testglobal.py'>
>>> type(testglobal)
<class 'module'>

通過內(nèi)置函數(shù)dir()我們可以知道模塊對(duì)象下有哪些屬性可以訪問:

>>> dir(testglobal)
['PI', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'closure_print']
>>> testglobal.closure_print
<function closure_print at 0x000002F33B14A050>

在Python中,一個(gè)對(duì)象可以訪問哪些屬性,成為對(duì)象的屬性空間。因此,模塊的屬性空間和全局名字空間本質(zhì)上就是同一個(gè)東西,都通過一個(gè)dict對(duì)象進(jìn)行存儲(chǔ)。那么如何找到這個(gè)dict對(duì)象呢——通過__dict__屬性:

>>> testglobal.__dict__

此外,我們也可以通過內(nèi)置函數(shù)globals()來獲取當(dāng)前模塊的全局名字空間:

>>> globals()

我們分別打印它們的id,本質(zhì)上就是同一個(gè)對(duì)象:

>>> id(testglobal.__dict__)
2219833831040
>>> id(globals())
2219833831040

3.2 Locals

Python執(zhí)行一個(gè)作用域內(nèi)的代碼時(shí),需要一個(gè)容器來訪問當(dāng)前作用域的名字,這就是局部名字空間Locals

當(dāng)Python執(zhí)行closure_print()函數(shù)時(shí),將分配一個(gè)棧幀對(duì)象PyFrameObject來保存上下文信息以及執(zhí)行狀態(tài)。作為代碼執(zhí)行時(shí)必不可少的上下文信息,全局名字空間和局部名字空間也會(huì)在PyFrameObject上記錄:

struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    PyObject *f_trace;          /* Trace function */
    int f_stackdepth;           /* Depth of value stack */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */
    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;
    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number. Only valid if non-zero */
    int f_iblock;               /* index in f_blockstack */
    PyFrameState f_state;       /* What state the frame is in */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};

3.3 Enclosings

在作用域存在嵌套的情況下,Python將內(nèi)層代碼塊依賴的所有外層名字存儲(chǔ)在一個(gè)容器內(nèi),這就是閉包名字空間Enclosings

對(duì)于示例:

>>> pi = 3.14
>>> def closure_print(name: str):
    	def circle_area(r: int):
            name = 1
        	print(name, pi * r * r)
	    return circle_area

當(dāng)Python執(zhí)行到print(name, pi * r * r)語句時(shí),按照Locals、Enclosings、Globals這樣的順序查找語句中涉及的名字:名字name在Enclosings中找到,名字pi在Globals中找到,名字r在Locals中找到。那么還有一個(gè)名字print是如何找到的呢?

3.4 Builtin

Python在builtin模塊中提供了很多內(nèi)建函數(shù)和類型,構(gòu)成運(yùn)行時(shí)的另一個(gè)名字空間:內(nèi)建名字空間Builtin

全局名字空間中有一個(gè)名字指向內(nèi)建名字空間:

>>> import builtins
>>> id(testglobal.__builtins__)
3065787874688
>>> id(builtins.__dict__)
3065787874688

4. 問題與總結(jié)

函數(shù)作用域?qū)?nèi)部所有的作用域均可見,包括內(nèi)部嵌套的類作用域和函數(shù)作用域(例如閉包);類作用域?qū)?nèi)部所有的作用域均不可見,包括內(nèi)部嵌套的類作用域和函數(shù)作用域。

“只要在當(dāng)前Locals命名空間中無同名變量且沒有g(shù)lobal,nonlocal等關(guān)鍵字的聲明的話,就一定創(chuàng)建一個(gè)該名字的新局部變量”,以nonlocal的使用為例:

示例1:

>>> def closure_print(name: str):
        def circle_area(r: int):
            print(locals())
            print(name, PI * r * r)
        return circle_area
>>> c = closure_print('circle1')
>>> c(1)
{'r': 1, 'name': 'circle1'}
circle1 3.14

示例2:

>>> PI = 3.14
>>> def closure_print(name: str):
        def circle_area(r: int):
            print(locals())
            name += '1'
            print(name, PI * r * r)
        return circle_area
>>> c = closure_print('circle1')
>>> c(1)
{'r': 1}
Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    c(1)
  File "<pyshell#2>", line 4, in circle_area
    name += '1'
UnboundLocalError: local variable 'name' referenced before assignment

示例3:

>>> PI = 3.14
>>> def closure_print(name: str):
        def circle_area(r: int):
            print(locals())
            name = 'circle2'
            print(locals())
            print(name, PI * r * r)
        return circle_area
>>> c = closure_print('circle1')
>>> c(1)
{'r': 1}
{'r': 1, 'name': 'circle2'}
circle2 3.14

示例4:

>>> PI = 3.14
>>> def closure_print(name: str):
        def circle_area(r: int):
            print(locals())
            nonlocal name
            name += '1'
            print(locals())
            print(name, PI * r * r)
        return circle_area
>>> c = closure_print('circle1')
>>> c(1)
{'r': 1, 'name': 'circle1'}
{'r': 1, 'name': 'circle11'}
circle11 3.14

locals()輸出的到底是什么?C源碼如下:

int
PyFrame_FastToLocalsWithError(PyFrameObject *f)
{
    /* Merge fast locals into f->f_locals */
    PyObject *locals, *map;
    PyObject **fast;
    PyCodeObject *co;
    Py_ssize_t j;
    Py_ssize_t ncells, nfreevars;
    if (f == NULL) {
        PyErr_BadInternalCall();
        return -1;
    }
    // 初始賦值locals為f->f_locals
    locals = f->f_locals;
    if (locals == NULL) {
        locals = f->f_locals = PyDict_New();
        if (locals == NULL)
            return -1;
    }
    // 獲取對(duì)應(yīng)的PyCodeObject
    co = f->f_code;
    // 獲取co_varnames字段
    map = co->co_varnames;
    if (!PyTuple_Check(map)) {
        PyErr_Format(PyExc_SystemError,
                     "co_varnames must be a tuple, not %s",
                     Py_TYPE(map)->tp_name);
        return -1;
    }
    fast = f->f_localsplus;
    j = PyTuple_GET_SIZE(map);
    if (j > co->co_nlocals)
        j = co->co_nlocals;
    if (co->co_nlocals) {
        // 將co_varnames加入到locals中
        if (map_to_dict(map, j, locals, fast, 0) < 0)
            return -1;
    }
    // 閉包相關(guān)
    ncells = PyTuple_GET_SIZE(co->co_cellvars);
    nfreevars = PyTuple_GET_SIZE(co->co_freevars);
    if (ncells || nfreevars) {
        // 將co_cellvars加入到locals
        if (map_to_dict(co->co_cellvars, ncells,
                        locals, fast + co->co_nlocals, 1))
            return -1;
        /* If the namespace is unoptimized, then one of the
           following cases applies:
           1. It does not contain free variables, because it
              uses import * or is a top-level namespace.
           2. It is a class namespace.
           We don't want to accidentally copy free variables
           into the locals dict used by the class.
        */
        if (co->co_flags & CO_OPTIMIZED) {
            // 將co_freevars加入到locals
            if (map_to_dict(co->co_freevars, nfreevars,
                            locals, fast + co->co_nlocals + ncells, 1) < 0)
                return -1;
        }
    }
    return 0;
}

以上就是Python作用域與名字空間源碼學(xué)習(xí)筆記的詳細(xì)內(nèi)容,更多關(guān)于Python作用域名字空間的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Python列表元素刪除和remove()方法詳解

    Python列表元素刪除和remove()方法詳解

    這篇文章主要給大家介紹了關(guān)于Python列表元素刪除和remove()方法的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • pandas溫差查詢案例的實(shí)現(xiàn)

    pandas溫差查詢案例的實(shí)現(xiàn)

    本文主要介紹了pandas溫差查詢案例的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • 詳解python使用金山詞霸的翻譯功能(調(diào)試工具斷點(diǎn)的使用)

    詳解python使用金山詞霸的翻譯功能(調(diào)試工具斷點(diǎn)的使用)

    這篇文章主要介紹了詳解python使用金山詞霸的翻譯功能(調(diào)試工具斷點(diǎn)的使用),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-01-01
  • Keras - GPU ID 和顯存占用設(shè)定步驟

    Keras - GPU ID 和顯存占用設(shè)定步驟

    這篇文章主要介紹了Keras - GPU ID 和顯存占用設(shè)定步驟,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-06-06
  • Python切片操作實(shí)例分析

    Python切片操作實(shí)例分析

    這篇文章主要介紹了Python切片操作,結(jié)合實(shí)例形式較為詳細(xì)的分析了Python切片相關(guān)的正向、反向、步長(zhǎng)等使用技巧與相關(guān)注意事項(xiàng),需要的朋友可以參考下
    2018-03-03
  • Python協(xié)程實(shí)踐分享

    Python協(xié)程實(shí)踐分享

    這篇文章主要分享的是Python協(xié)程實(shí)踐,協(xié)程簡(jiǎn)單來說就是一個(gè)更加輕量級(jí)的線程,并且不由操作系統(tǒng)內(nèi)核管理,完全由程序所控制,下文相關(guān)介紹需要的朋友可以參考一下
    2022-05-05
  • 基于Python+Matplotlib實(shí)現(xiàn)直方圖的繪制

    基于Python+Matplotlib實(shí)現(xiàn)直方圖的繪制

    Matplotlib是Python的繪圖庫,它能讓使用者很輕松地將數(shù)據(jù)圖形化,并且提供多樣化的輸出格式。本文將為大家介紹如何用matplotlib繪制直方圖,感興趣的朋友可以學(xué)習(xí)一下
    2022-04-04
  • tensorflow+k-means聚類簡(jiǎn)單實(shí)現(xiàn)貓狗圖像分類的方法

    tensorflow+k-means聚類簡(jiǎn)單實(shí)現(xiàn)貓狗圖像分類的方法

    這篇文章主要介紹了tensorflow+k-means聚類簡(jiǎn)單實(shí)現(xiàn)貓狗圖像分類,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-04-04
  • 使用PyInstaller將Python程序文件轉(zhuǎn)換為可執(zhí)行程序文件

    使用PyInstaller將Python程序文件轉(zhuǎn)換為可執(zhí)行程序文件

    與py2exe一樣,PyInstaller程序也可以將Python的.py程序文件轉(zhuǎn)換為.exe,并且還有Linux的版本,下面我們就來詳細(xì)看一下如何使用PyInstaller將Python程序文件轉(zhuǎn)換為可執(zhí)行程序文件
    2016-07-07
  • python wav模塊獲取采樣率 采樣點(diǎn)聲道量化位數(shù)(實(shí)例代碼)

    python wav模塊獲取采樣率 采樣點(diǎn)聲道量化位數(shù)(實(shí)例代碼)

    這篇文章主要介紹了python wav模塊獲取采樣率 采樣點(diǎn)聲道量化位數(shù),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-01-01

最新評(píng)論