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

Python學(xué)習(xí)之名字,作用域,名字空間

 更新時間:2022年05月11日 09:57:29   作者:??編程學(xué)習(xí)網(wǎng)????  
這篇文章主要介紹了Python學(xué)習(xí)之名字,作用域,名字空間,文章圍繞主題展開詳細(xì)內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考以一下

前言:

我們在PyFrameObject里面看到了3個獨立的名字空間:f_locals、f_globals、f_builtins。名字空間對于Python來說是一個非常重要的概念,Python虛擬機的運行機制和名字空間有著非常緊密的聯(lián)系。并且在Python中,與名字空間這個概念緊密聯(lián)系在一起的還有名字、作用域這些概念,下面就來剖析這些概念是如何體現(xiàn)的。

變量只是一個名字

很早的時候我們就說過,從解釋器的角度來看,變量只是一個泛型指針PyObject *,而從Python的角度來看,變量只是一個名字、或者說符號,用于和對象進(jìn)行綁定的。

變量的定義本質(zhì)上就是建立名字和對象之間的約束關(guān)系,所以a = 1這個賦值語句本質(zhì)上就是將a和1綁定起來,讓我們通過a這個符號可以找到對應(yīng)的PyLongObject。

除了變量賦值,創(chuàng)建函數(shù)、類也相當(dāng)于定義變量,或者說完成名字和對象之間的綁定。

def foo(): pass
class A(): pass

創(chuàng)建一個函數(shù)也相當(dāng)于定義一個變量,會先根據(jù)函數(shù)體創(chuàng)建一個函數(shù)對象,然后將名字foo和函數(shù)對象綁定起來。所以函數(shù)名和函數(shù)體之間是分離的,同理類也是如此。

import os

導(dǎo)入一個模塊,也是在定義一個變量。import os相當(dāng)于將名字os和模塊對象綁定起來,通過os可以找到指定的模塊對象。

import numpy as np當(dāng)中的as語句同樣是在定義變量,將名字np和對應(yīng)的模塊對象綁定起來,以后就可以通過np這個名字去獲取指定的模塊了。

另外,當(dāng)我們導(dǎo)入一個模塊的時候,解釋器是這么做的。比如:import os等價于os=__import__("os"),可以看到本質(zhì)上還是一個賦值語句。

作用域和名字空間

我們說賦值語句、函數(shù)定義、類定義、模塊導(dǎo)入,本質(zhì)上只是完成了名字和對象之間的綁定。而從概念上講,我們實際上得到了一個name和obj之間的映射關(guān)系,通過name可以獲取對應(yīng)的obj,而它們的容身之所就是名字空間。

所以名字空間是通過PyDictObject對象實現(xiàn)的,這對于映射來說簡直再適合不過了。而在前面介紹字典的時候,我們說過字典是被高度優(yōu)化的,原因就是虛擬機本身也重度依賴字典,從這里的名字空間即可得到體現(xiàn)。

但是一個模塊內(nèi)部,名字還存在可見性的問題,比如:

a = 1
def foo():
    a = 2
    print(a)  # 2

foo()
print(a)  # 1

我們看到同一個變量名,打印的確是不同的值,說明指向了不同的對象,換句話說這兩個變量是在不同的名字空間中被創(chuàng)建的。

然后我們知道名字空間本質(zhì)上是一個字典,如果兩者是在同一個名字空間,那么由于字典的key的不重復(fù)性,當(dāng)執(zhí)行a=2的時候,會把字典里面key為a的value給更新成2。但是在外面還是打印1,這說明兩者所在的不是同一個名字空間,打印的也就自然不是同一個a。

因此對于一個模塊而言,內(nèi)部是可能存在多個名字空間的,每一個名字空間都與一個作用域相對應(yīng)。作用域就可以理解為一段程序的正文區(qū)域,在這個區(qū)域里面定義的變量是有意義的,然而一旦出了這個區(qū)域,就無效了。

對于作用域這個概念,至關(guān)重要的是要記?。核鼉H僅是由源程序的文本所決定的。在Python中,一個變量在某個位置是否起作用,是由它的文本位置決定的。

因此Python具有靜態(tài)作用域(詞法作用域),而名字空間則是作用域的動態(tài)體現(xiàn),一個由程序文本定義的作用域在Python運行時會轉(zhuǎn)化為一個名字空間、即一個PyDictObject對象。而進(jìn)入一個函數(shù),顯然進(jìn)入了一個新的作用域,因此函數(shù)在執(zhí)行時,會創(chuàng)建一個名字空間。

我們之前說,在對Python源代碼進(jìn)行編譯的時候,對于代碼中的每一個block,都會創(chuàng)建一個PyCodeObject與之對應(yīng)。而當(dāng)進(jìn)入一個新的名字空間、或者說作用域時,我們就算是進(jìn)入一個新的block了。

而根據(jù)我們使用Python的經(jīng)驗,顯然函數(shù)、類都是一個新的block,當(dāng)Python運行的時候會為它們創(chuàng)建各自的名字空間。

所以名字空間是名字、或者變量的上下文環(huán)境,名字的含義取決于名字空間。更具體的說,一個變量綁定的對象是不確定的,需要由名字空間來決定。

位于同一個作用域的代碼可以直接訪問作用域中出現(xiàn)的名字,即所謂的直接訪問;但不同作用域,則需要通過訪問修飾符 . 進(jìn)行屬性訪問。

class A:
    a = 1


class B:
    b = 2
    print(A.a)  # 1
    print(b)  # 2

如果想在B里面訪問A里面的內(nèi)容,要通過A.屬性的方式,表示通過A來獲取A里面的屬性。但是訪問B的內(nèi)容就不需要了,因為都是在同一個作用域,所以直接訪問即可。

訪問名字這樣的行為被稱為名字引用,名字引用的規(guī)則決定了Python程序的行為。

a = 1
def foo():
    a = 2
    print(a)  # 2

foo()
print(a)  # 1

還是上面的代碼,如果我們把函數(shù)里面的a=2給刪掉,意味著函數(shù)的作用域里面已經(jīng)沒有a這個變量了,那么再執(zhí)行程序會有什么后果呢?從Python層面來看,顯然是會尋找外部的a。

因此我們可以得到如下結(jié)論:

  • 作用域是層層嵌套的;
  • 內(nèi)層作用域可以訪問外層作用域;
  • 外層作用域無法訪問內(nèi)層作用域,盡管我們沒有試,但是想都不用想。如果是把外層的a=1給去掉,那么最后面的print(a)鐵定報錯;
  • 查找元素會依次從當(dāng)前作用域向外查找,也就是查找元素時,對應(yīng)的作用域是按照從小往大、從里往外的方向前進(jìn)的;

LGB規(guī)則

我們說函數(shù)、類有自己的作用域,但是模塊對應(yīng)的源文件本身也有相應(yīng)的作用域。比如:

name = "編程學(xué)習(xí)網(wǎng)"
age = 16

def foo():
    return 123

class A:
    pass

由于這個文件本身也有自己的作用域,顯然是global作用域,所以解釋器在運行這個文件的時候,也會為其創(chuàng)建一個名字空間,而這個名字空間就是global名字空間。它里面的變量是全局的,或者說是模塊級別的,在當(dāng)前文件的任意位置都可以直接訪問。

而函數(shù)也有作用域,這個作用域稱為local作用域,對應(yīng)local名字空間;同時Python自身還定義了一個最頂層的作用域,也就是builtin作用域,像內(nèi)置函數(shù)、內(nèi)建對象都在builtin里面。

這三個作用域在Python2.2之前就存在了,所以那時候Python的作用域規(guī)則被稱之為LGB規(guī)則:名字引用動作沿著local作用域(local名字空間)、global作用域(global名字空間)、builtin作用域(builtin名字空間)來查找對應(yīng)的變量。

而獲取名字空間,Python也提供了相應(yīng)的內(nèi)置函數(shù):

  • locals函數(shù):獲取當(dāng)前作用域的local名字空間,local名字空間也稱為局部名字空間;
  • globals函數(shù):獲取當(dāng)前作用域的global名字空間,global名字空間也稱為全局名字空間;
  • __builtins__函數(shù):或者import builtins,獲取當(dāng)前作用域的builtin名字空間,builtint名字空間也稱為內(nèi)置名字空間;

每個函數(shù)都有自己local名字空間,因為不同的函數(shù)對應(yīng)不同的作用域,但是global名字空間則是全局唯一。

name = "編程學(xué)習(xí)網(wǎng)"

def foo():
    pass

print(globals())  
# {..., 'name': '編程學(xué)習(xí)網(wǎng)', 'foo': <function foo at 0x000002977EDF61F0>}

里面的...表示省略了一部分輸出,我們看到創(chuàng)建的全局變量就在里面。而且foo也是一個變量,它指向一個函數(shù)對象。

但是注意,我們說foo也是一個獨立的block,因此它會對應(yīng)一個PyCodeObject。但是在解釋到def foo的時候,會根據(jù)這個PyCodeObject對象創(chuàng)建一個PyFunctionObject對象,然后將foo和這個函數(shù)對象綁定起來。

當(dāng)我們調(diào)用foo的時候,再根據(jù)PyFunctionObject對象創(chuàng)建PyFrameObject對象、然后執(zhí)行,這些留在介紹函數(shù)的時候再細(xì)說。總之,我們看到foo也是一個全局變量,全局變量都在global名字空間中。

總之,global名字空間全局唯一,它是程序運行時的全局變量和與之綁定的對象的容身之所,你在任何一個地方都可以訪問到global名字空間。正如,你在任何一個地方都可以訪問相應(yīng)的全局變量一樣。

此外,我們說名字空間是一個字典,變量和對象會以鍵值對的形式存在里面。那么換句話說,如果我手動地往這個global名字空間里面添加一個鍵值對,是不是也等價于定義一個全局變量呢?

globals()["name"] = "編程學(xué)習(xí)網(wǎng)"
print(name)  # 編程學(xué)習(xí)網(wǎng)


def f1():
    def f2():
        def f3():
            globals()["age"] = 16
        return f3
    return f2


f1()()()
print(age)  # 16

我們看到確實如此,通過往global名字空間里面插入一個鍵值對完全等價于定義一個全局變量。并且global名字空間是唯一的,你在任何地方調(diào)用globals()得到的都是global名字空間,正如你在任何地方都可以訪問到全局變量一樣。

所以即使是在函數(shù)中向global名字空間中插入一個鍵值對,也等價于定義一個全局變量、并和對象綁定起來。

  • name="xxx" 等價于 globals["name"]="xxx";
  • print(name) 等價于 print(globals["name"]);

對于local名字空間來說,它也對應(yīng)一個字典,顯然這個字典就不是全局唯一的了,每一個局部作用域都會對應(yīng)自身的local名字空間。

def f():
    name = "夏色祭"
    age = 16
    return locals()


def g():
    name = "神樂mea"
    age = 38
    return locals()


print(locals() == globals())  # True
print(f())  # {'name': '夏色祭', 'age': 16}
print(g())  # {'name': '神樂mea', 'age': 38}

顯然對于模塊來講,它的local名字空間和global名字空間是一樣的,也就是說,模塊對應(yīng)的PyFrameObject對象里面的f_locals和f_globals指向的是同一個PyDictObject對象。

但是對于函數(shù)而言,局部名字空間和全局名字空間就不一樣了。調(diào)用locals是獲取自身的局部名字空間,而不同函數(shù)的local名字空間是不同的。但是globals函數(shù)的調(diào)用結(jié)果是一樣的,獲取的都是global名字空間,這也符合函數(shù)內(nèi)找不到某個變量的時候會去找全局變量這一結(jié)論。

所以我們說在函數(shù)里面查找一個變量,查找不到的話會找全局變量,全局變量再沒有會查找內(nèi)置變量。本質(zhì)上就是按照自身的local空間、外層的global空間、內(nèi)置的builtin空間的順序進(jìn)行查找。

因此local空間會有很多個,因為每一個函數(shù)或者類都有自己的局部作用域,這個局部作用域就可以稱之為該函數(shù)的local空間;但是global空間則全局唯一,因為該字典存儲的是全局變量。無論你在什么地方,通過調(diào)用globals函數(shù)拿到的永遠(yuǎn)是全局名字空間,向該空間中添加鍵值對,等價于創(chuàng)建全局變量。

對于builtin名字空間,它也是一個字典。當(dāng)local空間、global空間都沒有的時候,會去builtin空間查找。問題來了,builtin名字空間如何獲取呢?答案是使用builtins模塊,通過builtins.__dict__即可拿到builtin名字空間。

# 等價于__builtins__
import builtins

#我們調(diào)用list顯然是從內(nèi)置作用域、也就是builtin名字空間中查找的
#但我們只寫list也是可以的
#因為local空間、global空間沒有的話,最終會從builtin空間中查找
#但如果是builtins.list,那么就不兜圈子了
#表示: "builtin空間,就從你這獲取了"
print(builtins.list is list)  # True

builtins.dict = 123
#將builtin空間的dict改成123
#那么此時獲取的dict就是123
#因為是從內(nèi)置作用域中獲取的
print(dict + 456)  # 579

str = 123
#如果是str = 123,等價于創(chuàng)建全局變量str = 123
#顯然影響的是global空間
print(str)  # 123
# 但是此時不影響builtin空間
print(builtins.str)  # <class 'str'>

這里提一下Python2當(dāng)中,while 1比while True要快,為什么?

因為True在Python2中不是關(guān)鍵字,所以它是可以作為變量名的。那么Python在執(zhí)行的時候就要先看local空間和global空間里有沒有True這個變量,有的話使用我們定義的,沒有的話再使用內(nèi)置的True。

而1是一個常量,直接加載就可以,所以while True多了符號查找這一過程。但是在Python3中兩者就等價了,因為True在Python3中是一個關(guān)鍵字,也會直接作為一個常量來加載。

eval和exec

記得之前介紹 eval 和 exec 的時候,我們說這兩個函數(shù)里面還可以接收第二個參數(shù)和第三個參數(shù),它們分別表示global名字空間、local名字空間。

# 如果不指定,默認(rèn)當(dāng)前所在的名字空間
# 顯然此時是全局名字空間
exec("name = '古明地覺'")
print(name)  # 古明地覺

# 但是我們也可以指定某個名字空間
dct = {}
# 將 dct 作為全局名字空間
# 這里我們沒有指定第三個參數(shù),也就是局部名字空間
# 如果指定了全局名字空間、但沒有指定局部名字空間
# 那么局部名字空間默認(rèn)和全局名字空間保持一致
exec("name = 'satori'", dct)
print(dct["name"])  # satori

至于 eval 也是同理:

dct = {"seq": [1, 2, 3, 4, 5]}
try:
    print(eval("sum(seq)"))
except NameError as e:
    print(e)  # name 'seq' is not defined

# 告訴我們 seq 沒有被定義
# 因為我們需要將 dct 作為名字空間
print(eval("sum(seq)", dct))  # 15

所以名字空間本質(zhì)上就是一個字典,所謂的變量不過是字典里面的一個 key。為了進(jìn)一步加深印象,

再舉個模塊的例子:

# 我們自定義一個模塊吧
# 首先模塊也是一個對象,類型為 <class 'module'>
# 但是底層沒有將這個類暴露給我們,所以需要換一種方式獲取
import sys
module = type(sys)
# 以上就拿到了模塊的類型對象,調(diào)用即可得到模塊對象
my_module = module("自己定義的")
print(sys)  # <module 'sys' (built-in)>
print(my_module)  # <module '自己定義的'>

# 此時的 my_module 啥也沒有,我們?yōu)槠涮泶u加瓦
my_module.__dict__["name"] = "古明地覺"
print(my_module.name)  # 古明地覺
# 給模塊設(shè)置屬性,本質(zhì)上也是操作相應(yīng)的屬性字典
# 當(dāng)然獲取屬性也是如此。如果再和exec結(jié)合的話
code = """
age = 16
def foo():
    return "我是函數(shù)foo"
    
from functools import reduce     
"""
# 此時屬性就設(shè)置在了模塊的屬性字典里面
exec(code, my_module.__dict__)
print(my_module.age)  # 16
print(my_module.foo())  # 我是函數(shù)foo
print(my_module.reduce(int.__add__, [1, 2, 3, 4, 5]))  # 15

怎么樣,是不是很有趣呢?以上就是本次分享的所有內(nèi)容,想要了解更多歡迎前往公眾號:Python編程學(xué)習(xí)圈,每日干貨分享

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

相關(guān)文章

最新評論