<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
問題:
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)
以這個程式為例,程式碼中出現的每個變數的作用域分別是什麼?程式中總共涉及多少個名稱空間?Python又以怎樣的順序去查詢一個變數呢?
在Python中,變數只是一個與實際物件繫結起來的名字,變數定義本質上就是建立名字與物件的約束關係。因此,賦值語句本質上就是建立這樣的約束關係,將右邊的物件與左邊的名字繫結起來:
a = 1
賦值語句是最基本的將名字與物件繫結的方式,除此之外還有很多其他方式都起到了這樣的作用。
當我們匯入一個模組時,也會在當前上下文建立一個名字,並與被匯入物件繫結。
# 在當前上下文建立一個名字test,與被匯入的module物件繫結 import test
# 函數名circle_area與function物件繫結 def circle_area(r): return PI * r ** 2 # 類名Person與型別物件繫結 class Person(object): def __init__(self): pass
# 將名字t與module物件繫結 import test as t
問題:當我們引入一個名字之後,它的可見範圍有多大呢?
a = 1 def func1(): print(a) # 1 def func2(): a = 2 print(a) # 2 print(a) # 1
在不同的程式碼區域引入的名字,其影響範圍是不一樣的。第1行定義的a可以影響到func1,而func2中定義的a則不能。此外,一個名字可能會在多個程式碼區域中定義,但最終在某個程式碼區域中只能使用其中一個。
一個名字能夠施加影響的程式正文區域,便是該名字的作用域。在Python中,一個名字在程式中某個區域能否起作用,是由名字引入的位置決定的,而不是執行時動態決定的。因此,Python具有靜態作用域,也稱為詞法作用域。那麼,作用域具體是如何劃分的呢?
存取關係如下:
閉包的概念:在電腦科學中,閉包,又稱詞法閉包或函數閉包,是參照了自由變數的函數。這個被參照的自由變數將和這個函數一同存在,即使已經離開了創造它的環境也不例外。所以,有另一種說法認為閉包是由函數和與其相關的參照環境組合而成的實體。閉包在執行時可以有多個範例,不同的參照環境和相同的函陣列合可以產生不同的範例
程式碼範例:
>>> 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函數物件是怎麼拿到name的?
程式碼範例:
>>> 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
作用域分析:
全域性作用域對其他所有內嵌其中的作用域均可見,所以在函數Speak()中可以存取到language
類作用域和函數作用域不一樣,它對其子作用域是不可見的,所以在函數Gende()中gender是不可見的
思考:
>>> male.gender 'male' >>> Male.gender 'male' >>> male.gender = 'male2' >>> male.gender 'male2' >>> Male.gender 'male'
在Python中,類可以動態建立,甚至在函數中返回。通過在函數中建立並返回類,可以按函數引數對類進行動態客製化
程式碼範例:
>>> 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
程式碼範例:
>>> 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
作用域是語法層面的概念,是靜態的。當程式開始執行後,作用域中的名字繫結關係需要儲存起來,儲存的地方就是名稱空間。由於名字繫結關係是由名字和物件組成的鍵值對,因此用dict是理想的儲存容器(之前在介紹dict的相關內容時也有提到)
以計算圓面積的例子來認識作用域背後的執行時實體——名稱空間。程式碼範例如下:
>>> PI = 3.14 >>> def closure_print(name: str): def circle_area(r: int): print(name, PI * r * r) return circle_area
在Python中,每個模組都有一個dict物件,用於儲存全域性作用域中的名字,這就是全域性名稱空間Globals。在上述的例子中,根據我們之前對作用域的劃分,可以肯定全域性名稱空間中一定包含兩個名字:PI和closure_print。
如果其他模組也需要使用PI或closure_print函數,就需要通過import語句將模組匯入,匯入後我們就可以獲得一個模組物件:
# 假設我們在test.py中匯入上述模組testglobal.py >>> import testglobal >>> testglobal <module 'testglobal' from 'D:\myspace\code\pythonCode\mix\namespace\testglobal.py'> >>> type(testglobal) <class 'module'>
通過內建函數dir()我們可以知道模組物件下有哪些屬性可以存取:
>>> dir(testglobal) ['PI', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'closure_print'] >>> testglobal.closure_print <function closure_print at 0x000002F33B14A050>
在Python中,一個物件可以存取哪些屬性,成為物件的屬性空間。因此,模組的屬性空間和全域性名稱空間本質上就是同一個東西,都通過一個dict物件進行儲存。那麼如何找到這個dict物件呢——通過__dict__屬性:
>>> testglobal.__dict__
此外,我們也可以通過內建函數globals()來獲取當前模組的全域性名稱空間:
>>> globals()
我們分別列印它們的id,本質上就是同一個物件:
>>> id(testglobal.__dict__) 2219833831040 >>> id(globals()) 2219833831040
Python執行一個作用域內的程式碼時,需要一個容器來存取當前作用域的名字,這就是區域性名稱空間Locals
當Python執行closure_print()函數時,將分配一個棧幀物件PyFrameObject來儲存上下文資訊以及執行狀態。作為程式碼執行時必不可少的上下文資訊,全域性名稱空間和區域性名稱空間也會在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 */ };
在作用域存在巢狀的情況下,Python將內層程式碼塊依賴的所有外層名字儲存在一個容器內,這就是閉包名稱空間Enclosings
對於範例:
>>> pi = 3.14 >>> def closure_print(name: str): def circle_area(r: int): name = 1 print(name, pi * r * r) return circle_area
當Python執行到print(name, pi * r * r)語句時,按照Locals、Enclosings、Globals這樣的順序查詢語句中涉及的名字:名字name在Enclosings中找到,名字pi在Globals中找到,名字r在Locals中找到。那麼還有一個名字print是如何找到的呢?
Python在builtin模組中提供了很多內建函數和型別,構成執行時的另一個名稱空間:內建名稱空間Builtin
全域性名稱空間中有一個名字指向內建名稱空間:
>>> import builtins >>> id(testglobal.__builtins__) 3065787874688 >>> id(builtins.__dict__) 3065787874688
函數作用域對內部所有的作用域均可見,包括內部巢狀的類作用域和函數作用域(例如閉包);類作用域對內部所有的作用域均不可見,包括內部巢狀的類作用域和函數作用域。
“只要在當前Locals名稱空間中無同名變數且沒有global,nonlocal等關鍵字的宣告的話,就一定建立一個該名字的新區域性變數”,以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; } // 獲取對應的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; } // 閉包相關 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作用域與名稱空間原始碼學習筆記的詳細內容,更多關於Python作用域名稱空間的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45