动态加载___import__动态加载技术
__import__ 可以實現模塊的動態(tài)加載,Python中很多框架都使用了這種能力,如Flask的插件系統(tǒng)、APScheduler定時任務框架等。
這里簡單看一下APScheduler定時任務框架是怎么使用 __import__ 重新載入任務對象的。
首先,補一下背景知識,簡單了解一下APScheduler是如何使用的。
一開始,定義一個類。
from?datetime?import?datetimeimport?random
class?A(object):
????def?__init__(self):
????????self.t?=?self.gen_random()
????def?tick(self):
????????print('Tick!?The?time?is:?%s'?%?datetime.now())
????????print(self.t)
????def?gen_random(self):
????????return?random.randint(1,?100)
我們希望A類的tick方法每3秒運行一次,通過APScheduler的實現方式如下。
if?__name__?==?'__main__':????scheduler?=?BackgroundScheduler()?#?創(chuàng)建調度器
????scheduler.add_job(A().tick,?'interval',?seconds=3)?#?添加一個任務,3秒后運行
????scheduler.start()?#?啟動
????print('Press?Ctrl+{0}?to?exit'.format('Break'?if?os.name?==?'nt'?else?'C'))
????try:
????????#?這是在這里模擬應用程序活動(使主線程保持活動狀態(tài))。
????????while?True:
????????????time.sleep(2)
????except?(KeyboardInterrupt,?SystemExit):
????????#?關閉調度器
????????scheduler.shutdown()
上述代碼中,我們創(chuàng)建了BackgroundScheduler類型的調度器,然后通過add_job()方法將A().tick作為需要被執(zhí)行的任務加入,這個任務的實例默認會存儲到內存中,到指定的時間,再被取出運行。
如果程序異常崩潰,內存中存儲的任務也會丟失,為了避免這種情況,就需要將定時任務存儲在外部,比如存儲在MongoDB中。
當我們指定APScheduler后存儲端使用MongoDB存儲時,其具體效果如下。
{????"_id"?:?"brorherhood_timed_task",
????"next_run_time"?:?1604581734.31826,
????"job_state"?:?{?"$binary"?:?"gASV+gEAAAAAAAB9lCiMB3ZlcnNpb26USwGMAmlklIwWYnJvcmhlcmhvb2RfdGltZWRfdGFza5SMBGZ1bmOUjC1hcHAubG9naWNzLmJyb3RoZXJob29kOmJyb3JoZXJob29kX3RpbWVkX3Rhc2uUjAd0cmlnZ2VylIwdYXBzY2hlZHVsZXIudHJpZ2dlcnMuaW50ZXJ2YWyUjA9JbnRlcnZhbFRyaWdnZXKUk5QpgZR9lChoAUsCjAh0aW1lem9uZZSMBHB5dHqUjAJfcJSTlCiMDUFzaWEvU2hhbmdoYWmUTehxSwCMA0xNVJR0lFKUjApzdGFydF9kYXRllIwIZGF0ZXRpbWWUjAhkYXRldGltZZSTlEMKB+QLAxAINgTbOZRoDyhoEE2AcEsAjECNDU1SUdJRSlIaUUpSMCGVuZF9kYXRllE6MCGludGVydmFslGgVjAl0aW1lZGVsdGGUk5RLAE0IB0sAh5RSlIwGaml0dGVylE51YowIZXhlY3V0b3KUjAdkZWZhdWx0lIwEYXJnc5QpjAZrd2FyZ3OUfZSMBG5hbWWUaAOMEm1pc2ZpcmVfZ3JhY2VfdGltZZRLAYwIY29hbGVzY2WUiIwNbWF4X2luc3RhbmNlc5RLA4wNbmV4dF9ydW5fdGltZZRoF0MKB+QLKRUINgTbOZRoG4aUUpR1Lg==",?"$type"?:?"00"?}
}
簡單而言,程序由動態(tài)執(zhí)行狀態(tài)轉為靜態(tài)狀態(tài)。
如何讓其從靜態(tài)狀態(tài)再次轉為動態(tài)狀態(tài)呢?這就需要使用我們的主角 __import__ 方法。
為了避免額外的復雜性,我自己編寫了一個簡單的示例代碼來演示相同的效果。
首先,我們創(chuàng)建hello.py文件(python中單個py文件也稱為模塊),在其中創(chuàng)建一個簡單的類,代碼如下。
class?HelloWorld(object):????def?run(self,?name):
????????print(f"Hello?{name}!")
然后,需要將其靜態(tài)化,對應模塊中類的靜態(tài)化需要分2步走,第一步:獲得模塊路徑、類名等信息,第二步:是將類實例序列化的保存到本地,序列化的具體操作可以交由pickle庫實現,該庫簡單使用方式如下。
In?[1]:?import?pickleIn?[2]:?d?=?{'name':?'二兩',?'age':?30,?'hobby':?'code'}
#?將變量序列化到文件中
In?[3]:?with?open('example.pickle',?'wb')?as?f:
???...:?????pickle.dump(d,?f)
???...:
#?將變量反序列化到文件中
In?[4]:?with?open('example.pickle',?'rb')?as?f:
???...:?????d1?=?pickle.load(f)
???...:
In?[5]:?d1
Out[5]:?{'name':?'二兩',?'age':?30,?'hobby':?'code'}
上述代碼中,通過pickle.dump方法將字典序列化到本地,又通過pickle.load方法將其反序列化的載入,用法非常簡單。
那為何不直接序列化存儲類實例呢?還有獲取它的模塊信息和類信息干啥?
我們確實可以直接序列化類實例,但無法將其正常的反序列化,當我們開始反序列化時,會報出無法導入該類的錯誤,即Python解釋器會嘗試導入當前類實例對應的類對象,但我們沒有import該類,所以會報錯。
一個解決方法就是在反序列類實例的py文件頭部加上相關的import,但這種方法有比較大的局限性,為此我們可以利用模塊和類的信息通過 __import__ 方法將其動態(tài)導入。
為了可以動態(tài)導入類,我們需要通過如下代碼實現類實例的序列化。
#所在文件:test_import1.pyimport?pickle
from?hello?import?HelloWorld
def?obj_to_ref(obj,?args):
????#?類實例方法對應的類名和方法名
????#?HelloWorld().run?->?HelloWorld.run
????name?=?obj.__qualname__?
????#?調用類方法時需要傳入的self(實例本身)以及其他參數
????args?=?(obj.__self__,)?+?tuple(args)?
????return?{
????????'obj':?f"{obj.__module__}:{name}",?#?類實例所在的模塊:類實例方法對應的名稱
????????'args':?args
????}
ref?=?obj_to_ref(obj=HelloWorld().run,?args=("ayuliao",))
print(ref)
#?序列化
with?open('hello.pickle',?'wb')?as?f:
????pickle.dump(ref,?f)
上述代碼中,獲得了類實例方法「HelloWorld().run」對應的模塊名、類名、方法以及類實例對象self,然后再序列化存儲這些信息。
其對應反序列化動態(tài)導入的代碼如下。
#?所在文件:test_import2.pyimport?pickle
with?open('hello.pickle',?'rb')?as?f:
????ref?=?pickle.load(f)
????
obj_ref?=?ref['obj']
args?=?ref['args']
# modulename 模塊名:hello.py
# rest 類實例方法所在的路徑:HelloWorld.run
modulename,?rest?=?obj_ref.split(':',?1)
#?導入模塊模塊,相當于import?hello
obj?=?__import__(modulename,?fromlist=[rest])
for?name?in?rest.split('.'):
????#?獲得方法本身
????obj?=?getattr(obj,?name)
#?調用方法
obj(*args)
這樣,我們就實現了python模塊動態(tài)導入了。
一個有趣的細節(jié)是,如果我們多次導入hello.pickle文件,獲得的對象是相同的對象還是不同的對象?另一種問法,如果HelloWorld類有 __init__ 方法話,該方法會被調用一次還是多次?
答案是,導入多次,獲得的是相同的對象,其 __init__ 方法并不會被調用,我們通過print(id(obj))可以獲得obj對應的唯一id,可以發(fā)現是相同的。
這樣好像有點不對,我都多次動態(tài)導入了,每次導入不應該都是獨立的嗎?那每個obj應該也是獨立的呀?
其實每次導入都是獨立的,但我們導入后并沒有實例化HelloWorld類,而是使用hello.pickle序列化文件中的self對象作為類方法的第一個參數,即我們使用的類實例其實是同一個,所以getattr(obj, name)獲得的是同一個類實例的屬性方法。
因為我們沒有實例化HelloWorld類,所以該類的 __init__ 方法并不會被調用,該方法在序列化時已經被調用了。
一日一技系列的特點是短,而這篇文章有點長,所以就單獨成文啦,下篇一日一技我們來討論一下如何妙用循環(huán)來實現樹的遍歷。
我是二兩,我們下篇文章見。
總結
以上是生活随笔為你收集整理的动态加载___import__动态加载技术的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 重磅回归之作!魅族20和20 PRO正式
- 下一篇: 前端月薪过万需要哪些技术_Web前端月薪