最近的工作是基於 Apache HUE 做二次開發.剛接手 HUE 的代碼的時候,內心是崩潰的:開源的代碼,風格很多種, 代碼比較雜亂; 雖是基於 Django 開發的,但是項目的結構改變很大; 很多地方留下了坑; 前人基於此項目做了一些開發, 考慮欠佳, 雜亂中又增添了些雜亂...... 沒辦法, ...
最近的工作是基於 Apache HUE 做二次開發.剛接手 HUE 的代碼的時候,內心是崩潰的:開源的代碼,風格很多種, 代碼比較雜亂; 雖是基於 Django 開發的,但是項目的結構改變很大; 很多地方留下了坑; 前人基於此項目做了一些開發, 考慮欠佳, 雜亂中又增添了些雜亂......
沒辦法,既然參與了進來,就貢獻自己的一份力量.
今天在優化 Lib Sentry 的時候,不經意間就出現了一個 Bug. 項目中,有處使用了全局鎖的形式,來將 Sentry 的鏈接存入到全局變數中. 我試著用 Django 緩存的形式將其替換,以提高代碼的效率.但是, run 起來的時候,很快就出現了調用棧溢出的現象.為什麼會出現這種情況? 難道是導入不合理?先就是一頓 import review. 發現並沒有類似的迴圈導入, 目錄結構也還OK啊.那問題出現哪呢? 沒辦法,藉助日誌, 發現了一些問題:
File "/home/hp/Project/platform/desktop/core/src/desktop/lib/thrift_util.py" in __getattr__
364. superclient = _connection_pool.get_client(self.conf,
File "/home/hp/Project/platform/desktop/core/src/desktop/lib/thrift_util.py" in __getattr__
364. superclient = _connection_pool.get_client(self.conf,
File "/home/hp/Project/platform/desktop/core/src/desktop/lib/thrift_util.py" in __getattr__
364. superclient = _connection_pool.get_client(self.conf,
File "/home/hp/Project/platform/desktop/core/src/desktop/lib/thrift_util.py" in __getattr__
364. superclient = _connection_pool.get_client(self.conf,
File "/home/hp/Project/platform/desktop/core/src/desktop/lib/thrift_util.py" in __getattr__
364. superclient = _connection_pool.get_client(self.conf,
File "/home/hp/Project/platform/desktop/core/src/desktop/lib/thrift_util.py" in __getattr__
364. superclient = _connection_pool.get_client(self.conf,
File "/home/hp/Project/platform/desktop/core/src/desktop/lib/thrift_util.py" in __getattr__
364. superclient = _connection_pool.get_client(self.conf,
File "/home/hp/Project/platform/desktop/core/src/desktop/lib/thrift_util.py" in __getattr__
364. superclient = _connection_pool.get_client(self.conf,
File "/home/hp/Project/platform/desktop/core/src/desktop/lib/thrift_util.py" in __getattr__
364. superclient = _connection_pool.get_client(self.conf,
日誌的信息顯示,在 thrift_utils.py 文件中,發現一直有個方法在執行,且是同一行.為什麼?看源碼.
class PooledClient(object):
"""
A wrapper for a SuperClient
"""
def __init__(self, conf):
self.conf = conf
def __getattr__(self, attr_name):
if attr_name in self.__dict__:
return self.__dict__[attr_name]
# Fetch the thrift client from the pool
superclient = _connection_pool.get_client(self.conf,
get_client_timeout=self.conf.timeout_seconds)
# Fetch the attribute. If it's callable, wrap it in a wrapper that re-gets
# the client.
try:
attr = getattr(superclient, attr_name)
if callable(attr):
return self._wrap_callable(attr_name)
else:
return attr
finally:
self._return_client(superclient)
這是 HUE 源碼的片段, 拋錯就是從這裡出現的. 發現一直在執行 superclient = _connection_pool.get_client(...
這塊.WHY? 難道是 conf 沒有?試著去加些列印信息,發現果然是沒有 conf. 不能啊!為什麼會沒有 conf 呢?
於是,再看下Django拋出的 error 信息,發現了一些信息:
py2.7.egg/django/core/cache/backends/locmem.py" in get
48. return pickle.loads(pickled)
程式是執行到這之後,才一直在重覆執行上面的錯誤的.為什麼 loads 的時候會出錯呢? 首先猜想的是, loads 的時候,因為什麼原因導致了 PooledClient 的 object 沒有 conf 屬性. 那就看下 pickle.loads. 看完之後,再藉助了 log 信息, 發現其是因為去尋找 __setstate__
屬性的時候才導致了這種錯誤.好了,至此,問題就得以描述清楚了.
之所以調用 Django core cache
導致了調用棧溢出, 是因為 Django
在 cache get 的方法中將存儲的數據反序列化成對象,而這個對象在此時還沒有生成,且序列化的時候要去調用 __setstate__
方法, 但是類中沒有定義,只是定義了 __getattr__
方法.而 __getattr__
方法中又使用了 conf 方法, 這時候 conf 還沒有, 所以,又觸發了 __getattr__
方法的執行.如此反覆,導致了最終的調用棧溢出現象.
好了,既然找到問題了,那就解決吧.
我這裡是自己實現了 __getstate__
, __setstate__
的魔術方法,這樣,就可以解決了找不到 __setstate__
的問題. 還有一種解決方法,就是將 conf 定位為 類屬性. 這樣是從找不到 conf 源頭解決問題.
問題解決,開始總結下 Python 魔術方法.
__setstate__
, __getstate__
方法在 pickle 序列化和反序列化的時候會觸發執行. getattr 是當 object 的某個屬性找不到的時候觸發執行.
下麵是我模擬的測試代碼:
# coding=utf8
import pickle
import StringIO
class PeopleObject(object):
def __init__(self, name, age):
self.name = name
self.age = age
def display(self):
print 'name:', self.name, 'address:', self.age
def __getattr__(self, attr_name):
if attr_name in self.__dict__:
return self.__dict__[attr_name]
else:
print self.name
def __getstate__(self):
state = self.__dict__.copy()
return state
# def __setstate__(self, state):
# print state
# self.__dict__.update(state)
hanmeimei = PeopleObject("Han Meimei", 18)
hanmeimei.display()
store_file = StringIO.StringIO()
pickle.dump(hanmeimei, store_file, 0) # 序列化
# del Person #反序列的時候,必須能找到對應類的定義。否則反序列化操作失敗。
store_file.seek(0)
hanmeimei_ins = pickle.load(store_file) # 反序列化
hanmeimei_ins.display()
store_file.close()
執行會發現,很快就會出現同樣的錯誤.
關於魔術方法,詳見: