小時候,常被一些可笑的問題困擾——盡管成年以后面臨的疑惑更多,但似乎是因為已經適應了在迷茫中前行,對于未解的問題反倒是失去了那種急于想知道答案的迫切感,比如,站在兩面相對的鏡子中間,會看到無數個自己嗎?對于少時的我,這的確是一個非常魔幻的問題,直到理解了光量子能量衰減,才算找到了答案,

近日,有同學咨詢Python物件的回圈參考以及垃圾回收問題,結合前些日子遇到的回圈呼叫和回圈匯入問題,在整理答案的時候,我忽然意識到,這幾個問題居然和困惑我多年的“兩面鏡子”問題居然有相通之處:看起來都有些魔幻,轉身即是真實的世界!
1. 走向毀滅的函式回圈呼叫
如果多個函式相互呼叫,構成倍訓,就形成了函式的回圈呼叫,下面的例子中,函式a在其函式體中呼叫了函式b,而函式b在其函式體中又呼叫了函式a,這就是典型的函式回圈呼叫,
>>> def a():
print('我是a')
b()
>>> def b():
print('我是b')
a()
>>> a()
此種情況下,呼叫函式(無論是a函式還是b函式),會發生什么呢?
>>> a()
我是a
我是b
我是a
我是b
...... # 此處省略了一千余行
Traceback (most recent call last):
File "<pyshell#64>", line 1, in <module>
a()
File "<pyshell#59>", line 3, in a
b()
...... # 此處省略了兩千余行
RecursionError: maximum recursion depth exceeded while pickling an object
很快你就會發現,運行出現了問題,系統連續拋出例外,大約滾動了幾千行之后,終于結束了運行,最后的提示是:
RecursionError: maximum recursion depth exceeded while pickling an object
意思是說,發生了遞回錯誤,在序列化(pickle)物件時超過了最大遞回深度,
原來,回圈呼叫類似于遞回呼叫,為了保護堆疊不會溢位,Python環境一般都會設定遞回深度保護,一旦查過遞回深度,就會拋出遞回錯誤,然后再一層一層退出堆疊,這就是螢屏滾動幾千條錯誤資訊的原因,
關于Python環境遞回深度,可以通過sys模塊查看和設定,
>>> import sys
>>> sys.getrecursionlimit()
1000
>>> sys.setrecursionlimit(500)
>>> sys.getrecursionlimit()
500
2. 同生共死的物件回圈參考
函式的回圈呼叫不難理解,而物件的回圈參考就有點費解了,什么是物件的回圈參考呢?當一個物件被創建時(比如實體化一個類),Python會為這個物件設定一個參考計數器,如果這個物件被參考,比如被關聯到一個變數名,則該物件的參考計數器加1,如果關聯關系取消,則該物件的參考計數器減1,當一個物件的參考計數器為1時(關于這一點,僅憑個人觀察得出,未見權威說法),系統將自動回收該物件,這就是Python的垃圾回識訓制,下面的代碼,借助于sys模塊,可以直觀地看到一個串列物件的參考計數器的變化,
>>> import sys
>>> a = list('abc')
>>> sys.getrefcount(a)
2
>>> b = a
>>> sys.getrefcount(a)
3
>>> del b
>>> sys.getrefcount(a)
2
當多個物件存在相互間的成員參考,一旦形成倍訓的時候,就會發生所謂物件的回圈參考,我們來看一個例子:a和b是類A的兩個實體物件,del這兩個物件的時候,將會呼叫物件的__del__方法,最后顯示“運行結束”,
class A:
def __init__(self, name, somebody=None):
self.name = name
self.somebody = somebody
print('%s: init'%self.name)
def __del__(self):
print('%s: del'%self.name)
a = A('a')
b = A('b')
del a
del b
print('運行結束')
運行結果正如我們所希望的那樣,
a: init
b: init
a: del
b: del
運行結束
然而,當我們創建了實體a和b之后,如果將a.somebody指向b,將b.somebody指向a,那么就產生了實體間成員相互參考形成倍訓的情況,
class A:
def __init__(self, name, somebody=None):
self.name = name
self.somebody = somebody
print('%s: init'%self.name)
def __del__(self):
print('%s: del'%self.name)
a = A('a')
b = A('b')
a.somebody = b
b.sombody = a
del a
del b
print('運行結束')
運行這段代碼,你會發現,del這兩個物件的時候,物件的__del__方法并沒有被立即執行,而是程式結束之后才被執行的,
a: init
b: init
運行結束
a: del
b: del
這意味著,在程式運行期間,應該被回收的記憶體并沒有正確回收,這樣的問題,屬于記憶體泄漏,應該給予高度重視,通常,我們可以使用gc模塊強制回收記憶體,
import gc
class A:
def __init__(self, name, somebody=None):
self.name = name
self.somebody = somebody
print('%s: init'%self.name)
def __del__(self):
print('%s: del'%self.name)
a = A('a')
b = A('b')
a.somebody = b
b.sombody = a
del a
del b
gc.collect()
print('運行結束')
再看運行結果,一切正常了,
a: init
b: init
a: del
b: del
運行結束
3. 轉圈推磨的模塊回圈匯入
相對而言,模塊的回圈匯入的情況一般極少發生,如果發生,一定是模塊的功能分割不合理造成的,通過調整模塊的定義,可以很容地解決問題,下面用一個最精簡的例子,來演示一下模塊回圈匯入是如何產生的,
名為a.py的腳本檔案內容如下:
import b
MODULE_NAME = 'a'
print(b.MODULE_NAME)
名為b.py的腳本檔案內容如下:
import a
MODULE_NAME = 'b'
print(a.MODULE_NAME)
兩個腳本互相參考,并且各自使用了對方定義的常量MODULE_NAME,無論我們運行哪個腳本,都會因為模塊的回圈匯入而無法正確執行,
Traceback (most recent call last):
File “a.py”, line 1, in
import b
File “D:\temp\csdn\b.py”, line 1, in
import a
File “D:\temp\csdn\a.py”, line 4, in
print(b.MODULE_NAME)
AttributeError: module ‘b’ has no attribute ‘MODULE_NAME’
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/225872.html
標籤:其他
