日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

动态加载___import__动态加载技术

發布時間:2023/12/19 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 动态加载___import__动态加载技术 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

__import__ 可以實現模塊的動態加載,Python中很多框架都使用了這種能力,如Flask的插件系統、APScheduler定時任務框架等。

這里簡單看一下APScheduler定時任務框架是怎么使用 __import__ 重新載入任務對象的。

首先,補一下背景知識,簡單了解一下APScheduler是如何使用的。

一開始,定義一個類。

from?datetime?import?datetime
import?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()?#?創建調度器
????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:
????????#?這是在這里模擬應用程序活動(使主線程保持活動狀態)。
????????while?True:
????????????time.sleep(2)
????except?(KeyboardInterrupt,?SystemExit):
????????#?關閉調度器
????????scheduler.shutdown()

上述代碼中,我們創建了BackgroundScheduler類型的調度器,然后通過add_job()方法將A().tick作為需要被執行的任務加入,這個任務的實例默認會存儲到內存中,到指定的時間,再被取出運行。

如果程序異常崩潰,內存中存儲的任務也會丟失,為了避免這種情況,就需要將定時任務存儲在外部,比如存儲在MongoDB中。

當我們指定APScheduler后存儲端使用MongoDB存儲時,其具體效果如下。

{
????"_id"?:?"brorherhood_timed_task",
????"next_run_time"?:?1604581734.31826,
????"job_state"?:?{?"$binary"?:?"gASV+gEAAAAAAAB9lCiMB3ZlcnNpb26USwGMAmlklIwWYnJvcmhlcmhvb2RfdGltZWRfdGFza5SMBGZ1bmOUjC1hcHAubG9naWNzLmJyb3RoZXJob29kOmJyb3JoZXJob29kX3RpbWVkX3Rhc2uUjAd0cmlnZ2VylIwdYXBzY2hlZHVsZXIudHJpZ2dlcnMuaW50ZXJ2YWyUjA9JbnRlcnZhbFRyaWdnZXKUk5QpgZR9lChoAUsCjAh0aW1lem9uZZSMBHB5dHqUjAJfcJSTlCiMDUFzaWEvU2hhbmdoYWmUTehxSwCMA0xNVJR0lFKUjApzdGFydF9kYXRllIwIZGF0ZXRpbWWUjAhkYXRldGltZZSTlEMKB+QLAxAINgTbOZRoDyhoEE2AcEsAjECNDU1SUdJRSlIaUUpSMCGVuZF9kYXRllE6MCGludGVydmFslGgVjAl0aW1lZGVsdGGUk5RLAE0IB0sAh5RSlIwGaml0dGVylE51YowIZXhlY3V0b3KUjAdkZWZhdWx0lIwEYXJnc5QpjAZrd2FyZ3OUfZSMBG5hbWWUaAOMEm1pc2ZpcmVfZ3JhY2VfdGltZZRLAYwIY29hbGVzY2WUiIwNbWF4X2luc3RhbmNlc5RLA4wNbmV4dF9ydW5fdGltZZRoF0MKB+QLKRUINgTbOZRoG4aUUpR1Lg==",?"$type"?:?"00"?}
}

簡單而言,程序由動態執行狀態轉為靜態狀態。

如何讓其從靜態狀態再次轉為動態狀態呢?這就需要使用我們的主角 __import__ 方法。

為了避免額外的復雜性,我自己編寫了一個簡單的示例代碼來演示相同的效果。

首先,我們創建hello.py文件(python中單個py文件也稱為模塊),在其中創建一個簡單的類,代碼如下。

class?HelloWorld(object):
????def?run(self,?name):
????????print(f"Hello?{name}!")

然后,需要將其靜態化,對應模塊中類的靜態化需要分2步走,第一步:獲得模塊路徑、類名等信息,第二步:是將類實例序列化的保存到本地,序列化的具體操作可以交由pickle庫實現,該庫簡單使用方式如下。

In?[1]:?import?pickle

In?[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__ 方法將其動態導入。

為了可以動態導入類,我們需要通過如下代碼實現類實例的序列化。

#所在文件:test_import1.py

import?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,然后再序列化存儲這些信息。

其對應反序列化動態導入的代碼如下。

#?所在文件:test_import2.py

import?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模塊動態導入了。

一個有趣的細節是,如果我們多次導入hello.pickle文件,獲得的對象是相同的對象還是不同的對象?另一種問法,如果HelloWorld類有 __init__ 方法話,該方法會被調用一次還是多次?

答案是,導入多次,獲得的是相同的對象,其 __init__ 方法并不會被調用,我們通過print(id(obj))可以獲得obj對應的唯一id,可以發現是相同的。

這樣好像有點不對,我都多次動態導入了,每次導入不應該都是獨立的嗎?那每個obj應該也是獨立的呀?

其實每次導入都是獨立的,但我們導入后并沒有實例化HelloWorld類,而是使用hello.pickle序列化文件中的self對象作為類方法的第一個參數,即我們使用的類實例其實是同一個,所以getattr(obj, name)獲得的是同一個類實例的屬性方法。

因為我們沒有實例化HelloWorld類,所以該類的 __init__ 方法并不會被調用,該方法在序列化時已經被調用了。

一日一技系列的特點是短,而這篇文章有點長,所以就單獨成文啦,下篇一日一技我們來討論一下如何妙用循環來實現樹的遍歷。

我是二兩,我們下篇文章見。

總結

以上是生活随笔為你收集整理的动态加载___import__动态加载技术的全部內容,希望文章能夠幫你解決所遇到的問題。

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