日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

python中的函数、生成器的工作原理

發布時間:2025/3/20 python 11 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python中的函数、生成器的工作原理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1.python中函數的工作原理

def foo():bar()def bar():pass

python的解釋器,也就是python.exe(c編寫)會用PyEval_EvalFramEx(c函數)運行foo()函數
首先會創建一個棧幀(stack Frame),在棧幀對象的上下文里面去運行這個字節碼。

import dis print(dis.dis(foo)) #打印字節碼

可以嘗試著去打印foo的字節碼:

關于字節碼的解釋:

  • LOAD_GLOBAL:首先導入bar這個函數
  • CALL_FUNCTION:執行bar函數
  • POP_TOP:從棧的頂端去把元素打印出來
  • LOAD_CONST:返回結果,這里沒有return,就是None
  • RETURN_VALUE:返回結果

打印bar的字節碼:

print(dis.dis(bar))


這個字節碼全局是唯一的,函數是全局唯一的,然后在函數里面會調用另外一個函數。

當foo調用函數bar,又會創建一個棧幀,然后將這個函數的控制權交給這個棧幀。

所有的棧幀都分配在內存中,它不是放在棧的內存上,而是放在堆的內存上,你不去釋放它就會一直存在我們的內存當中。

這就決定了棧幀可以獨立于調用者存在,比如就算函數不存在了,只要有指針指向bar這個棧幀,就可以對其進行控制。

(python中一切皆對象,棧幀也是對象,是一個字節碼對象)

''' 遇到問題沒人解答?小編創建了一個Python學習交流QQ群:531509025 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學習教程和PDF電子書! ''' import inspect frame = None #保存framedef foo():bar()def bar():global frame #引入全局變量frame = inspect.currentframe() #將bar的frame賦給全局變量foo() print(frame.f_code.co_name) #bar 函數退出之后,依然可以拿到bar函數的棧幀 caller_frame = frame.f_back print(caller_frame.f_code.co_name) #foo 也可以拿到foo函數的棧幀

2.生成器的實現原理

在靜態語言中,函數調用的時候是一個棧的形式,函數調用完成之后棧就會被銷毀。
下面是函數的調用過程:

PyEval_evalFrameEx會創建一個foo的棧幀對象,這個對象里面有兩個屬性。f_back為None,因為沒有上層函數,f_code指向foo的字節碼
同時PyEval_evalFrameEx也會創建一個bar的棧幀對象,f_back指向foo,f_code指向bar的字節碼。
最大的特點就是棧幀對象存在于堆內存中,這樣生成器才有實現的可能。

def gen_func():yield 1name = "ming"yield 2age = 28return "kebi" #在早期的生成器版本中不能使用return

當python解釋器在讀取gen_fun()這個函數的時候,發現yield關鍵字就會將其標記為生成器函數。

gen_func()

當我們來調用這個函數的時候,就會返回一個生成器對象。

這個生成器對象是將PyFrame做了一層封裝。


在PyFrameObject和PyCodeObject上面又封裝了一層PyGenObject,就是python的生成器對象。

PyGenObject中gi_frame屬性指向PyGrameObject,gi_code屬性指向PyCodeObject。

PyFrameObject又有f_lasti和f_locals屬性。

f_lasti會指向最近執行的這個代碼。

可以嘗試打印字節碼:

''' 遇到問題沒人解答?小編創建了一個Python學習交流QQ群:531509025 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學習教程和PDF電子書! ''' def gen_func():yield 1name = "ming"yield 2age = 28return "kebi" #在早期的生成器版本中不能使用returnimport dis gen = gen_func() print(dis.dis(gen))

查看結果:

這里面可以看到有兩次yield。當我們每一次對生成器做一次調用的時候,它遇到yield就會停止。
停止了之后,就會記錄f_lasti(位置)和f_locals(變量)這兩個值。

可以嘗試著調用打印取每一個值,f_lasti和f_locals的變化

print(gen.gi_frame.f_lasti) #-1 print(gen.gi_frame.f_locals) #{} next(gen) print(gen.gi_frame.f_lasti) #2 print(gen.gi_frame.f_locals) #{} next(gen) print(gen.gi_frame.f_lasti) #12 print(gen.gi_frame.f_locals) #{'name':'ming'}

與上方字節碼是一樣的。

這樣整個生成器對象就存在與堆內存中,可以獨立存在,每次執行一次函數,就會生成一個棧幀對象。
我們可以在任何地方,只要能拿到這個棧幀對象就能夠往前走。這也是python中協程的一個理論基礎。

此時我們可以知道為什么生成器是一個一個返回。

3.pyc文件

當你在執行python代碼的時候,會發現執行目錄下面會出現.pyc文件。

[root@tuoguan resources]# ls r1.py r1.pyc r2.py r3.py [root@tuoguan resources]# cat r1.pyc?:]c@s dZdS(tname_r1N(R(((s/tmp/demo/resources/r1.py<module>s

r1.pyc是一個二進制文件,當執行的文件中存在包的引入就會編譯生成二進制文件。

當python程序運行時,編譯的結果則是保存在位于內存中的PyCodeObject中,當Python程序運行結束時,Python解釋器則將PyCodeObject寫回到pyc文件中。

當python程序第二次運行時,首先程序會在硬盤中尋找pyc文件,如果找到,則直接載入,否則就重復上面的過程。

所以我們應該這樣來定位PyCodeObject和pyc文件,我們說pyc文件其實是PyCodeObject的一種持久化保存方式。

總結

以上是生活随笔為你收集整理的python中的函数、生成器的工作原理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。