PYTHON 群益API 2.13.20 錯誤代碼2017 錯誤原因與解法

從2.13.16版本之後,登入前就需要 SKReplyLib_OnReplyMessage 回傳指定值 0xFFFF 才可以登入。

一開始我也是矇了,不管怎麼傳就好像沒有作用?明明照著說明做了啊!
錯誤通常分兩種,第一種就是真的忘記寫到 OnReplyMessage 然後回傳 0xFFFF或寫錯,這種就不在這裡討論。第二種就是都按照說明寫了也確認無誤,但還是不知所以然的錯誤。
到底  2017 SK_WARNING_REGISTER_REPLYLIB_ONREPLYMESSAGE_FIRST 是從何而來?網路上沒有一個明確的解釋與通用的解法,這可以是基於討論的人並不多。
的確,確實回傳值設甚麼都沒有作用,那不是錯覺,那是真的。COM接口的Py檔中 OnReplyMessage 是沒有回傳值。
於是這形成了最怪異的事情,不管怎麼樣登入,由於接口的 OnReplyMessage 沒有回傳值 sConfirmCode,所以 SKCenterLib_Login 永遠都接收不到註冊公告,永遠的錯誤代碼 2017。
問題點是錯在哪裡?本來有個見解,在 2020-03-20 被提醒之後,終於確認是暫存檔的問題。(不是dll

執行以下幾行就能夠刪除掉舊版COM的py檔模型。

import comtypes.client, os
try:
    os.remove(comtypes.client.gen_dir + r"\_75AAD71C_8F4F_4F1F_9AEE_3D41A8C9BA5E_0_1_0.py")
    os.remove(comtypes.client.gen_dir + r"\SKCOMLib.py")
except:
    pass
繼續追根究柢下去。
來講解一下運作機制。
在python中,最初的三行通常是:
import comtypes.client
comtypes.client.GetModule(os.path.split(os.path.realpath(__file__))[0] + r'\SKCOM.dll') #加此行需將API放與py同目錄
import comtypes.gen.SKCOMLib as sk
第二行是從 SKCOM.dll 取得模型,顧名思義。而第三行就是取出第二行建立的模型。

然而 GetModule() 會先從 comtypes.client.gen_dir 路徑上尋找模型,如果路徑上已經有對應的模型,就會優先讀入,沒有才會從COM中生成py檔模型。

如果以前曾經使用過  GetModule(舊版COM),那麼就會優先讀取已存在的舊版COM的py模型,而不是從新版COM去創造一個新的覆蓋掉舊的,這也就是版本差異的所導致的錯誤。

透過 print(comtypes.client.gen_dir) 可以看到模型py檔放在哪裡。

有人會在快取夾 cache,有人會在 anaconda 中 /Lib/site-packages/comtypes/gen裡。複製貼上路徑,可以找到兩個檔案 SKCOMLib.py 及 _75AAD71C-8F4F-4F1F-9AEE-3D41A8C9BA5E.py 這就是從 SKCOM.dll 生出的 .py檔,也是這次的目標。

兩者位置差異在於該專案的解譯器使用甚麼環境,從專案→屬性就能查看到。Visual Studio可供選安裝的環境通常會是在 C:\Users\使用者名稱\AppData\Roaming\Python\ 裡面,如果是官網下載的Anaconda,則會在 anaconda環境/Lib/site-packages/comtypes/gen。

刪了檔案之後,重新執行 GetModule(新版COM),就能產生新版COM的Py檔,這樣便解決到舊版COM的Py檔在OnReplyMessage沒有回傳值( [‘out’], c_short, ‘sConfirmCode’ ),所導致的錯誤代碼2017。

就能順利運行了。

雖然一開始有找到問題所在,但是我卻誤認了真相。被提醒COM版本差異之後,就回頭更注重比對不同COM版本的差異,確實如提醒所指的方向去找就把不確定性解決了。

昨日基於一個忽略,就沒有測驗不同COM版本產生的Py檔模型差異。忘記刪除掉重新產生,只在乎修改,卻沒有發覺使用GetModule並沒有影響到模型py檔,沒有被修改,沒有產生新模型去覆蓋掉舊的,更應該去注意修改時間。

網路上是有人說刪掉gen資料夾,就能順利運作,但是不一定會有這個資料夾啊!當時我用VS的Anaconda,在comtypes裡並沒有gen這個資料夾,當時沒有想知道真相的衝勁,所以也就沒有花時間多思考為什麼可行。

如今可以解釋了,不確定也在提醒之後被解決了,當初是覺得.dll -> .py 錯誤,最後只有.py錯誤。正也因為確認錯誤在哪裡,終於也有了一套滿意的解決方法,能適用於各種的python環境。

思考自己的在尋找答案的過程,一有甚麼想法就去試,如果可行很快就會結束。也就沒有好好釐清各種可能的時間,排序去做確認。

如果先入為主,先設想認為dll問題,所以找解法的過程才不會刪除補好的檔案。
但如果先把其他人的方法整理,自然就會認為是暫存檔的問題,那就會先試刪掉後再用新版COM產生。

兩者同樣都能解決錯誤,但是解決完成度是不一樣的。

官方Documentation並沒有提到會保存檔案且優先讀入已有檔案,或是可供選擇覆寫掉,如果有,那會更快找到答案,直找documentation是優先得順位。所以看來還是不得不進入看看機制為何。

從package裡順一遍邏輯再得出設想,是最硬的,不過也是最根本的,如果當初有更深入去撈,也會發現它會優先讀入已有檔案吧,並不是產生新的覆蓋過去。

Python 群益API Error in sys.excepthook

從一開始使用群益API時,就經常收到這個錯誤輸出。

Error in sys.excepthook:

Original exception was:

不以為意,因為運行不會受到任何影響。

關於這個錯誤,用GOOGLE搜尋會找到一篇文章,有做探討。

確實是直指出錯誤問題來源,但是並沒有解釋到底為什麼會出錯。

前幾天著手多進程,因此更瞭解當中的運作模式,這篇會更詳細講看法與解法。


首先要來到基本運作解釋:

 comtypes.client.GetEvents(),是可以把COM event傳到對應接收函式。

根據官方文件 COM events,comtypes.client.GetEvents() 會收到一個連接物件 ,只要保持這個物件,就能保持狀態。

群益API範例沒有寫很多,後來看確實都有意義在的。

當登入報價之後,若是只有使用 time.sleep(),是會收不到回傳資料。

於是使用 pythoncom.PumpWaitingMessages(),好讓那些訊息有進到到主線程的時間。

訊息跟主線程並不在同一條線程上,因此可想有個子線程專門接收資料,到時候在送到對應的接收函式。

comtypes.client.GetEvents()的回傳值,就相當於一個這樣功能的子線程。

重新回到 Error in sys.excepthook 錯誤上,從網路上可以很容易查到解釋,就是主線程關掉了,但子線程卻還在運行。

這個物件的有無,對應著子線程運行有無,這可以解釋探討錯誤該文章的十八格狀態。

為什麼函式內用 comtypes.client.GetEvents() 就不會報錯,而在全域用就會報錯?函式結束時,函式內部變數也就被刪除,所以變數指定透過 comtypes.client.GetEvents() 得到的連接物件也就被刪除,連接子線程跟著中止。

在全域使用,在主線程結束時,自然是不會刪去這個連接物件。

要刪去這個連接物件,只需要使用 del 就能刪除了,是相當簡單的。

導致錯誤的關鍵點,就只是有沒有在主線程結束前刪掉這個連接物件。

其實不論在哪個域或類別中使用,也只需要在主線程結束之前,刪掉這個物件,或從類別中刪除這個屬性,就不會報錯了。

Python 資料讀寫方法比較與資料壓縮比較 (二) 多進程

上一回邊寫時就在思考多進程是否能夠幫助提升資料讀寫的速度。

答案是否定的,為什麼?
首先是資料大小,資料規模不到一定程度,光是多進程的準備時間就輸了一大截。
接著是沒有考慮到資料轉傳,測試皆採取原地寫入原地讀出,未經壓縮的資料寫入比較快,同時讀出也比較快,即便它的容量可能是上百倍甚至更多,省下處理的時間就有優勢。
多進程的優勢在於啟動更多的 cpu 計算能力,對於 壓縮 與 解壓縮 需要用到 cpu 計算的,如果需要越多計算,多進程的優勢就會能體現。如 lzma 。

此外,將越多任務交給多進程處例,有助於提升速度。例如把資料json化、bytes化、壓縮化及解壓縮等盡可能在各進程上執行,主線程只是發包資料到各進程去。

雖然讀取也可以多進程(搭配多進程寫入成不同檔案),但是往往輸給主線程直接一一讀取,因為多進程的讀取結果最後還是得回到一個主線程上顯示,這使得多進程讀取最後還是會回到單核上,失去了多核的優勢。

多進程在寫入的時候,可以不等待寫完,就讓主線程繼續到讀取階段。如果資料量一大,會碰到讀取時多進程卻沒有寫完而報錯,解決方法是在多進程讀取中做判斷。由於這是寫入時偷跑,看似寫入時間少很多是假象,少掉的時間會跑到讀取時間。

mp開頭代表使用多進程,bj 和 zl 和 xz 代表主要方法,後面  j  與 z 代表多進程執行 json 與 zip 。「n」代表不等待寫完的  no wait。

像是 mpzlzjn,使用 mp 多進程 ,主要方法為 zl(Json Zlib) ,每個進程 zip 壓縮 json 化且 no wait 寫完就繼續到讀取,就有優於 json zlib單核與其他多核但多核任務量不同的速度。

當使用多進程的時候,在運行中修改目標函式的代碼,也會直接反應到目前運行的當中,這表示多進程的是從實體取出目標函式。這是我在運行中目標函示中添加sleep時,直接觀察到數據有立即的反應。

多進程是使用上也有一定的局限,使用上來說不是那麼直接。如果沒有容量限制,也沒有轉傳的需要,用 bstr json 是最好的方法。

bstr json 是將資料 bytes(json.dumps(data), encoding = “acsii”) 用 ‘wb’ 寫入 f,再用 ‘rb’ 開啟 f 以 json.loads(f) 讀出。跟 str json 的差別在於寫入 f 時是用 ‘w’ 而 bstr json 是 ‘wb’。

如果需要轉傳,使用 mpzljzn 會是非常棒的方法,光是每秒 1 MB,用 bstr json 就不知道要傳多久。而 mpxzz系列只能在可憐的(< 1MB/s)傳輸速度上有優於 mpzljzn,考量到方便性,用 zl 而不使用多核也不錯。

""" 100萬筆資料寫讀
data = [(i,"阿明", "身體狀況", {"體重":78.9, "肝功能": "正常"}, 90, 85, 97, 81, 85, True) for i in range(100000)]
[Statistics]
Method: bstr json, Compress Ratio: 0.06:1
Average Use Time: avgWtime:2.46935, avgRtime:3.14803, avgUtime:5.61739
Data size: 8697464(8.49MB), Wspeed:3.43961 MB/s, Rspeed:2.69807 MB/s, Uspeed:1.51202 MB/s
File size: 141888890(138.56MB), Wspeed:56.11319 MB/s, Rspeed:44.01585 MB/s, Uspeed:24.66687 MB/s
Method: json zlib, Compress Ratio: 2.79:1
Average Use Time: avgWtime:2.35145, avgRtime:3.29203, avgUtime:5.64348
Data size: 8697464(8.49MB), Wspeed:3.61208 MB/s, Rspeed:2.58005 MB/s, Uspeed:1.50503 MB/s
File size: 3117715(3.04MB), Wspeed:1.29480 MB/s, Rspeed:0.92485 MB/s, Uspeed:0.53950 MB/s
Method: json xz, Compress Ratio: 71.11:1
Average Use Time: avgWtime:14.73717, avgRtime:3.62832, avgUtime:18.36548
Data size: 8697464(8.49MB), Wspeed:0.57634 MB/s, Rspeed:2.34093 MB/s, Uspeed:0.46248 MB/s
File size: 122316(0.12MB), Wspeed:0.00811 MB/s, Rspeed:0.03292 MB/s, Uspeed:0.00650 MB/s
Method: mpbj, Compress Ratio: 0.06:1
Average Use Time: avgWtime:2.60734, avgRtime:3.34684, avgUtime:5.95418
Data size: 8697464(8.49MB), Wspeed:3.25758 MB/s, Rspeed:2.53781 MB/s, Uspeed:1.42650 MB/s
File size: 141888890(138.56MB), Wspeed:53.14356 MB/s, Rspeed:41.40131 MB/s, Uspeed:23.27163 MB/s
Method: mpbjn, Compress Ratio: 0.06:1
Average Use Time: avgWtime:2.19224, avgRtime:3.98733, avgUtime:6.17957
Data size: 8697464(8.49MB), Wspeed:3.87440 MB/s, Rspeed:2.13015 MB/s, Uspeed:1.37447 MB/s
File size: 141888890(138.56MB), Wspeed:63.20630 MB/s, Rspeed:34.75091 MB/s, Uspeed:22.42282 MB/s
Method: mpbjj, Compress Ratio: 0.06:1
Average Use Time: avgWtime:2.05410, avgRtime:3.13490, avgUtime:5.18900
Data size: 8697464(8.49MB), Wspeed:4.13496 MB/s, Rspeed:2.70937 MB/s, Uspeed:1.63685 MB/s
File size: 141888890(138.56MB), Wspeed:67.45707 MB/s, Rspeed:44.20021 MB/s, Uspeed:26.70329 MB/s
Method: mpbjjn, Compress Ratio: 0.06:1
Average Use Time: avgWtime:0.06299, avgRtime:4.78647, avgUtime:4.84947
Data size: 8697464(8.49MB), Wspeed:134.83090 MB/s, Rspeed:1.77450 MB/s, Uspeed:1.75145 MB/s
File size: 141888890(138.56MB), Wspeed:2199.60744 MB/s, Rspeed:28.94895 MB/s, Uspeed:28.57290 MB/s
Method: mpzl, Compress Ratio: 2.95:1
Average Use Time: avgWtime:2.93715, avgRtime:3.43182, avgUtime:6.36897
Data size: 8697464(8.49MB), Wspeed:2.89179 MB/s, Rspeed:2.47496 MB/s, Uspeed:1.33359 MB/s
File size: 2950294(2.88MB), Wspeed:0.98093 MB/s, Rspeed:0.83954 MB/s, Uspeed:0.45237 MB/s
Method: mpzlz, Compress Ratio: 2.79:1
Average Use Time: avgWtime:2.57715, avgRtime:3.46488, avgUtime:6.04203
Data size: 8697464(8.49MB), Wspeed:3.29574 MB/s, Rspeed:2.45134 MB/s, Uspeed:1.40575 MB/s
File size: 3118214(3.05MB), Wspeed:1.18159 MB/s, Rspeed:0.87886 MB/s, Uspeed:0.50399 MB/s
Method: mpzlzn, Compress Ratio: 2.79:1
Average Use Time: avgWtime:2.19316, avgRtime:3.75349, avgUtime:5.94665
Data size: 8697464(8.49MB), Wspeed:3.87278 MB/s, Rspeed:2.26286 MB/s, Uspeed:1.42830 MB/s
File size: 3118214(3.05MB), Wspeed:1.38847 MB/s, Rspeed:0.81128 MB/s, Uspeed:0.51207 MB/s
Method: mpzljz, Compress Ratio: 2.79:1
Average Use Time: avgWtime:2.07069, avgRtime:3.32371, avgUtime:5.39440
Data size: 8697464(8.49MB), Wspeed:4.10182 MB/s, Rspeed:2.55547 MB/s, Uspeed:1.57452 MB/s
File size: 3117371(3.04MB), Wspeed:1.47019 MB/s, Rspeed:0.91594 MB/s, Uspeed:0.56435 MB/s
Method: mpzljzn, Compress Ratio: 2.79:1
Average Use Time: avgWtime:0.04629, avgRtime:4.76512, avgUtime:4.81140
Data size: 8697464(8.49MB), Wspeed:183.49081 MB/s, Rspeed:1.78246 MB/s, Uspeed:1.76531 MB/s
File size: 3117371(3.04MB), Wspeed:65.76732 MB/s, Rspeed:0.63887 MB/s, Uspeed:0.63273 MB/s
Method: mpxz, Compress Ratio: 71.11:1
Average Use Time: avgWtime:14.85270, avgRtime:4.02111, avgUtime:18.87381
Data size: 8697464(8.49MB), Wspeed:0.57186 MB/s, Rspeed:2.11226 MB/s, Uspeed:0.45002 MB/s
File size: 122316(0.12MB), Wspeed:0.00804 MB/s, Rspeed:0.02971 MB/s, Uspeed:0.00633 MB/s
Method: mpxzz, Compress Ratio: 69.15:1
Average Use Time: avgWtime:7.99850, avgRtime:2.42350, avgUtime:10.42200
Data size: 8697464(8.49MB), Wspeed:1.06190 MB/s, Rspeed:3.50469 MB/s, Uspeed:0.81497 MB/s
File size: 125776(0.12MB), Wspeed:0.01536 MB/s, Rspeed:0.05068 MB/s, Uspeed:0.01179 MB/s
Method: mpxzzj, Compress Ratio: 71.62:1
Average Use Time: avgWtime:6.89620, avgRtime:3.59600, avgUtime:10.49220
Data size: 8697464(8.49MB), Wspeed:1.23164 MB/s, Rspeed:2.36197 MB/s, Uspeed:0.80952 MB/s
File size: 121444(0.12MB), Wspeed:0.01720 MB/s, Rspeed:0.03298 MB/s, Uspeed:0.01130 MB/s
Method: mpxzzjn, Compress Ratio: 71.62:1
Average Use Time: avgWtime:0.04867, avgRtime:8.94961, avgUtime:8.99828
Data size: 8697464(8.49MB), Wspeed:174.53017 MB/s, Rspeed:0.94905 MB/s, Uspeed:0.94392 MB/s
File size: 121444(0.12MB), Wspeed:2.43699 MB/s, Rspeed:0.01325 MB/s, Uspeed:0.01318 MB/s
"""

透過 win10工作排程器 定時以 potplayer 播放音樂(二)

既上一篇之後,又出現了沒有辦法自動播放的現象。

然而這次就有找到要如何觸發這種沒有辦法自動播放的現象,就是打開別的播放清單,然後方形的停止鍵,等到播放器完整停止後關掉,下次運行以開啟播放清單就不會順利自動播放。

這次又再一次面對這個問題,花時間搜尋,卻發現想找的cmd指令在播放器所在的資料夾有 CmdLine64.txt ,終於可以了解有甚麼cmd指令。

最終嘗試各種結果,我找到了更好的指令。

在工作排程器,工作,內容,動作,改成:

程式碼:PotPlayerMini64.exe
引數:"C:\BGM.dpl"
位置:J:\Program Files\DAUM\PotPlayer\

就不用再掉原本執行的批次檔 (.bat)。 上一篇

已經知道甚麼清況下無法自動播放,就很好測試各種情況下的影響。

雖然cmd指令裡面有 /autoplay 但是並不能順利解決掉自動播放的問題。上一次的引數是使用相對路徑,然而 CmdLine64.txt 裡面的例子是使用絕對路徑。

於是最新版就改成了 絕對路徑 ,同時避免中字出現造成亂碼錯誤而改成全英文路徑。

最後加上壓力測試,連續執行十五次,每一次都能夠順利播放,也不會因為此文章找到無法自動播放的操作方式給影響到。

一個使用絕對路徑,難道就真的是解決自動播放的方式嗎?

再多看看幾天,希望這一次就能夠解決。

Python 資料讀寫方法比較與資料壓縮比較

python內置的讀寫方式是透過 open() 。

如果有一筆資料,例如有五萬筆的串列,要寫入檔案並且讀出,甚麼樣的方式會是最快的?

這裡先寫結論,個人依照讀寫完成速度的私心排行:

「bstr json > json zlib > json gz > str json」
""" 完成速度排行榜
Method: bstr json, Compress Ratio: 0.06:1
Data size: 400064(0.39MB), Wspeed:4.16025 MB/s, Rspeed:2.97668 MB/s, Uspeed:1.73516 MB/s
Method: json zlib, Compress Ratio: 15.32:1, level = 6
Data size: 400064(0.39MB), Wspeed:2.99731 MB/s, Rspeed:2.76445 MB/s, Uspeed:1.43809 MB/s
Method: str json, Compress Ratio: 0.06:1
Data size: 400064(0.39MB), Wspeed:3.16940 MB/s, Rspeed:2.35598 MB/s, Uspeed:1.35141 MB/s
Method: json gz, Compress Ratio: 15.31:1, CompressLevel = 9
Data size: 400064(0.39MB), Wspeed:2.93210 MB/s, Rspeed:2.36939 MB/s, Uspeed:1.31044 MB/s
Method: json xz, Compress Ratio: 330.09:1, preset = 6
Data size: 400064(0.39MB), Wspeed:0.81887 MB/s, Rspeed:1.98104 MB/s, Uspeed:0.57938 MB/s
Method: json json, Compress Ratio: 0.06:1
Data size: 400064(0.39MB), Wspeed:0.57614 MB/s, Rspeed:2.28234 MB/s, Uspeed:0.46002 MB/s
Method: bstr list, Compress Ratio: 0.08:1
Data size: 400064(0.39MB), Wspeed:3.77832 MB/s, Rspeed:0.34935 MB/s, Uspeed:0.31978 MB/s
Method: str str(list), Compress Ratio: 0.10:1
Data size: 400064(0.39MB), Wspeed:3.58643 MB/s, Rspeed:0.33802 MB/s, Uspeed:0.30890 MB/s
Method: str str(tuple), Compress Ratio: 0.10:1
Data size: 400064(0.39MB), Wspeed:3.59002 MB/s, Rspeed:0.33762 MB/s, Uspeed:0.30860 MB/s
Method: json bz, Compress Ratio: 137.76:1, CompressLevel = 9
Data size: 400064(0.39MB), Wspeed:0.22510 MB/s, Rspeed:1.76317 MB/s, Uspeed:0.19962 MB/s
"""

先教大家如何使用 json zlib 壓縮並寫讀資料 (※其他壓縮方法大同小異

import json, zlib

data = [("阿明", "身體狀況", {"體重":78.9, "肝功能": "正常"}, 90, 85, 97, 81,  85, True)]*50000

# 寫入
with open(file, 'wb') as f:
    f.write(zlib.compress(bytes(json.dumps(data), encoding="ascii")))
f.close()

# 讀取
with open(file, "rb") as f:
    data = json.loads(zlib.decompress(f.read()))
f.close()

初學者講解:
json.dumps(data) -> 將資料轉換成json格式的string
bytes(data) -> 將資料 (string or int or list[int]) 轉換成 bytes。int 為正整數限制在 0 – 255間。
zlib.compress(data, level=-1) -> 將資料(bytes)轉換成經zlib壓縮後的帶有壓縮後資料的物件
如此以來就能用 open(file, ‘wb’) 來寫入壓縮後的資料。

zlib.decompress(data) -> 將壓縮後的資料解壓縮還原
json.loads(data) -> 將json格式資料string讀取還原。

範例程式碼:github

由於資料在 cpu 內的處理速度比硬碟讀寫速度快上許多,因此減少輸出檔案大小並讓 cpu 承擔解壓工作,將可以加速資料傳輸速度。

對於 pyhon 內建 讀寫io,最簡單的方式是將資料經 str(data) 後寫入,讀出後用 list(eval(data))還原。

用 open() 讀寫時, bytes讀寫速度都優於 string,使用mode: ‘wb’, ‘rb’ 快於 mode: ‘w’, ‘r’ 。因此將 string 解碼後用 ‘wb’, ‘rb’,雖然寫入的資料容量大小會比用 ‘w’, ‘r’ 大一些,但是會稍微快一點。

除了 str() -> list(eval)之外,另外一種能將一個有複雜結構保存的方法是使用 json。

在 python 中有 json5 跟 json,個人會比較推薦使用 json,因為 json 比較快。兩者在使用上的方法都大同小異,不過此例測試,使用 json5 不僅會報錯,壓縮還慢上數倍。

json的優勢在於它的讀取還原速度,用 json.load(fp) 比一般的 list(eval(read()) 約快上 6-7倍,但是在使用其模組的 json.dump(fp) 寫入io時,比起 open(f, ‘w’) -> f.write(str(data)) 還慢上 6-7倍,而讀取會花比較久的時間,所以 json 整體寫讀是比較快的。

既然 json 的優勢在於讀, 而 內置io 的優勢在於寫,兩者結合 string json 就能讓整體讀寫速度提升到 3-4 倍。 用 內置io  f.write(json.dumps(data)) 寫,用 json 的 json.load(fp) 讀。

""" 輸出檔案大小,依完成速度排列
Method: json zlib, Compress Ratio: 15.32:1, level = 6
Average Use Time: avgWtime:0.13035, avgRtime:0.14133, avgUtime:0.27167
File size: 26112(0.03MB), Wspeed:0.19563 MB/s, Rspeed:0.18043 MB/s, Uspeed:0.09386 MB/s
Method: str json, Compress Ratio: 0.06:1
Average Use Time: avgWtime:0.12327, avgRtime:0.16583, avgUtime:0.28910
File size: 6700000(6.54MB), Wspeed:53.07888 MB/s, Rspeed:39.45637 MB/s, Uspeed:22.63245 MB/s
Method: json gz, Compress Ratio: 15.31:1, CompressLevel = 9
Average Use Time: avgWtime:0.13324, avgRtime:0.16489, avgUtime:0.29813
File size: 26124(0.03MB), Wspeed:0.19146 MB/s, Rspeed:0.15472 MB/s, Uspeed:0.08557 MB/s
Method: json xz, Compress Ratio: 330.09:1, preset = 6
Average Use Time: avgWtime:0.47711, avgRtime:0.19721, avgUtime:0.67432
File size: 1212(0.00MB), Wspeed:0.00248 MB/s, Rspeed:0.00600 MB/s, Uspeed:0.00176 MB/s
Method: bstr list, Compress Ratio: 0.08:1
Average Use Time: avgWtime:0.10340, avgRtime:1.11832, avgUtime:1.22172
File size: 4750000(4.64MB), Wspeed:44.86032 MB/s, Rspeed:4.14789 MB/s, Uspeed:3.79683 MB/s
Method: str str(list), Compress Ratio: 0.10:1
Average Use Time: avgWtime:0.10894, avgRtime:1.15582, avgUtime:1.26476
File size: 4100000(4.00MB), Wspeed:36.75500 MB/s, Rspeed:3.46412 MB/s, Uspeed:3.16575 MB/s
Method: json bz, Compress Ratio: 137.76:1, CompressLevel = 9
Average Use Time: avgWtime:1.73560, avgRtime:0.22158, avgUtime:1.95718
File size: 2904(0.00MB), Wspeed:0.00163 MB/s, Rspeed:0.01280 MB/s, Uspeed:0.00145 MB/s
"""

不過本來資料大小為 400064 bytes(0.39MB),不論用 str() 或是 json.dump(),輸出的檔案都大上十倍多來到 4100000 bytes(4.00MB)、6700000(6.54MB)。因為檔案大,這會使得硬碟讀寫速度影響比較大,這不符合減少硬碟負擔並讓 cup 承擔的想法。

將資料壓縮後寫入,將壓縮後資料讀出後還原。這符合加速資料讀寫的設想。

python的資料壓縮有四個常見套件:zlib, gzip, lzma, bz2。
四者使用上大同小異,不過表現卻各有不同。共同點是,經過壓縮之後,檔案大小都遠小於沒有壓縮過且直接輸出的資料。這裡四者壓縮時皆使用預設的壓縮等級。

資料壓縮後優勢的檔案大小,彌補了在讀取速度上的劣勢,反而更優於沒有壓縮的資料寫讀。

到底影響速度上限,如果是資料壓縮,那麼就是cpu,如果直接輸出,那麼就是硬碟寫讀速度。個人是用SSD硬碟,以下加大資料筆數進行HDD跟SSD的測試讀寫。

""" 資料筆數1000000
Method:[SSD][壓縮] json zlib, Compress Ratio: 15.39:1
Average Use Time: avgWtime:2.45219, avgRtime:3.07391, avgUtime:5.52610
Data size: 8000064(7.81MB), Wspeed:3.18596 MB/s, Rspeed:2.54157 MB/s, Uspeed:1.41376 MB/s
File size: 519928(0.51MB), Wspeed:0.20706 MB/s, Rspeed:0.16518 MB/s, Uspeed:0.09188 MB/s
Method:[HDD] [壓縮]json zlib, Compress Ratio: 15.39:1
Average Use Time: avgWtime:2.47452, avgRtime:3.15085, avgUtime:5.62538
Data size: 8000064(7.81MB), Wspeed:3.15720 MB/s, Rspeed:2.47951 MB/s, Uspeed:1.38881 MB/s
File size: 519928(0.51MB), Wspeed:0.20519 MB/s, Rspeed:0.16114 MB/s, Uspeed:0.09026 MB/s
Method:[SSD][直輸] str json, Compress Ratio: 0.06:1
Average Use Time: avgWtime:2.52982, avgRtime:3.39660, avgUtime:5.92642
Data size: 8000064(7.81MB), Wspeed:3.08819 MB/s, Rspeed:2.30011 MB/s, Uspeed:1.31826 MB/s
File size: 134000000(130.86MB), Wspeed:51.72675 MB/s, Rspeed:38.52662 MB/s, Uspeed:22.08069 MB/s
Method:[HDD] [直輸] str json, Compress Ratio: 0.06:1
Average Use Time: avgWtime:3.08978, avgRtime:3.42463, avgUtime:6.51441
Data size: 8000064(7.81MB), Wspeed:2.52852 MB/s, Rspeed:2.28129 MB/s, Uspeed:1.19927 MB/s
File size: 134000000(130.86MB), Wspeed:42.35231 MB/s, Rspeed:38.21123 MB/s, Uspeed:20.08767 MB/s
"""

在直輸方面,可以看到HDD的速度就比在SSD上慢了。
在壓縮方面,其實在HDD跟SSD上差不多,兩者在誤差範圍內。

資料壓縮的速度上限取決於 cpu,而資料壓縮程度 compresslevel 的高低會有不同的工作量。由於python執行若沒有特殊設定之下,是使用單核,資料壓縮與解壓縮時通常這個單核會吃爆,因此降低壓縮程度,減少單核的運算工作,可以提升資料寫讀速度。

""" 資料筆數1000000,壓縮程度 6 vs 3
Method: json zlib, Compress Ratio: 15.39:1, level = 6
Average Use Time: avgWtime:2.45219, avgRtime:3.07391, avgUtime:5.52610
Data size: 8000064(7.81MB), Wspeed:3.18596 MB/s, Rspeed:2.54157 MB/s, Uspeed:1.41376 MB/s
File size: 519928(0.51MB), Wspeed:0.20706 MB/s, Rspeed:0.16518 MB/s, Uspeed:0.09188 MB/s
Method: json zlib, Compress Ratio: 8.21:1, level = 3
Average Use Time: avgWtime:2.10440, avgRtime:3.07207, avgUtime:5.17648
Data size: 8000064(7.81MB), Wspeed:3.71248 MB/s, Rspeed:2.54309 MB/s, Uspeed:1.50924 MB/s
File size: 974407(0.95MB), Wspeed:0.45218 MB/s, Rspeed:0.30975 MB/s, Uspeed:0.18383 MB/s
"""

對於這四個套件: zlib, gzip, lzma, bz2,在此例上的表現。

壓縮率最好的是 lzma,產生的 .xz ,大小又小於zlib及gzip許多,速度也優於直輸。

然而 bz2,在壓縮上面的速度太劣勢,而且壓縮比在此例也不如 lzma,速度也輸給直輸。

而 zlib 和 gzip 不論在壓縮比跟速度都很接近,因為 gzip 是引入 zlib 用,所以 zlib 速度總是略贏 gzip。

Python @ Property 修飾符 初學者速理解

初學者第一次看到「@」,像我,就滿頭問號。

網路上教學很多,而且不難懂,只是沒有我的風格。(推薦: 官方文件)

@又名Property屬性,property() 是python的內置函數。在定義 類別class 的時候,也經常設定其相關屬性。

Property由四個部分組成:getter setterdeleter、 doc。
以a為例,getter、 setter、 deleter分別對應:

a # call getter
a = 1 # call setter
del a # call deleter

發生以上三種狀況,就會分別執行對應的函式。

依照官方的範例稍微改寫,例1:

class C(object):
    def __init__(self):
        self._x = None
    def getx(self):
        print("得了")
        return self._x
    def setx(self, value):
        print("設了")
        self._x = value
    def delx(self):
        print("刪了")
        del self._x
    x = property(getx, setx, delx, "docstring")

a = C()
a.x # a.getx()
a.x = 1 # a.setx()
del a.x # a.delx()

得到以下輸出。

得了
設了
刪了

使用Property後可以減少代碼,除了例1的寫法,Property可以寫得更簡化。
用上「@」,更快速使用Property,簡化代碼。例1也可以寫成例2:

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        print("得了")
        return self._x

    @x.setter
    def x(self, value):
        print("設了")
        self._x = value

    @x.deleter
    def x(self):
        print("刪了")
        del self._x

@property 就是將下方函式定義為 x 的 getter, 然後 x = property(getter),下方函式名 x 就會變成屬性。
看例2,@property 為 getter。 setter也可以寫成@屬性.setter 、 deleter 寫成@屬性.deleter。
getter 最常被使用也一定要設置,setter、 deleter  就不一定要設置。

知道使用 @property 可以將 a.getx() 省略成 a.x 。a.x 就等於 a.getx() 的回傳值。

舉一個實例,如果要定義一個長方形類別,可能會在 __init__() 中,設定其長與寬,然後透過長與寬計算出面積。

class Rectangle():
    def __init__(self, width, height):
        self.width = width
        self.height = height
        self.area = width * height

如果 width = 5、 height = 4,那麼 area = 20。

但是,要是後面只改變了 width 或是 height, area 並不會自動更新數值,仍是舊的 20。
如果用上 @property 設定 area,就會在呼叫 area 時,重新計算數值。

class Rectangle():
    def __init__(self, width, height):
        self.width = width
        self.height = height

    @property
    def area(self):
        return self.width * self.height

使用  @property 可將像 area 這類動態數值的過程簡化成屬性名,除了大幅減少代碼篇幅,使用上也變得更直覺,。

學到這裡,講完了property的基本運作,現在來看一下其他教學中的舉例。
如果有好好從前面讀懂,這個範例應該是很好理解,範例3:

def f1(f2):
    return 1

@f1
def f2(x):
    return 2
 f2(1) # Error 

先不講結果,先順著 property 的邏輯看。
@f1,getter 為 f1,f2 = property(f1),下方函式名 f2 變成屬性。
於是 f2 == 「f1的回傳值」 == 1、f2 + f2 == 2。
行7嘗試 f2(1) ,就會報錯,無法理解 f2(1) 為什麼會錯, 就換成 1(1) 理解看看吧。

行1寫成 f1(f2),是方便理解整個 f2函式 都會傳入到 f1。
由於函式內外部差異,可以將傳入的 f2 跟外部的 f2 視作不同的東西。
因此呼叫傳入的 f2 並不會觸發外部帶有@的 f2。

最後範例,如果無法理解,就回過頭看上方的說明吧。範例4:

def addition(f):
    answer = 0
    for num in f():
        answer += num
    return answer

@addition
def threePlusFive():
    return [3, 5]

@addition
def threePlusFivePlusEight():
    return [3, 5, 8]

@addition
def threePlusFivePlusEight2():
    return [threePlusFive, 8]

print(threePlusFive)
print(threePlusFivePlusEight)
print(threePlusFivePlusEight2)

Python pyqt + logging 整合範例1

當有了圖形化介面的時候,發現工作列卡一個 cmd 實在想隱藏起來。
用 pythonw + .pyw,就能不卡 cmd。
但是一旦將 cmd 隱藏起來,就看不到輸出了甚麼。
logging可以將紀錄在輸出並同時保存下來,操作起來比 open() 還便捷。

GUI的操作功能得寫在一塊,格局被侷限了。
如果想整合更多的功能,而且是單獨寫過寫好的部分,或是打開音樂播放器聽音樂與執行某串程式碼等。
大夥都進來專案,會讓專案顯得異常混雜,也可能產生多個同名同功能但不同亞種的函式,搞得你混淆誰新誰舊。
此外,這些功能不需要傳入或只有固定參數,啟動後就能自動獨立運行完成。

那些其他程式運作情形,可以靠logging紀錄,不一定要回傳給主程式。
主程式透過查閱記錄,就能在GUI上觀察到程式進度。

然而可預期的錯誤能被處理,不可預期的錯誤會讓程式跳掉而無法用logging傳出ERROR以上的紀錄。
為了避免看不到錯誤訊息的憾事,得將錯誤訊息輸出保存下來以供來日查閱。

範例1:githubyoutube

展示以下功能:
1.透過整合logging,看見額外程式運行狀況。
2.能篩選紀錄,顯示一定level以上的紀錄。
3.有做錯誤處理且ERROR以上的紀錄,會跳出通知視窗。
4.萬一沒有做錯誤處理,會把錯誤訊息保存下來。

對於這個範例不是的某些部份若不是很瞭解,請找我最近寫了幾篇範例與文章。
我想唯有這個 cmd “python pyfile.py > output.txt 2>&1 “沒有講到。
簡單來說,把pyfile.py的結果標準輸出到output.txt,cmd上有甚麼,txt就大概會有甚麼。
但是沒有錯誤訊息,因為錯誤訊息不在標準輸出裡面。而 2>&1 意思是把錯誤輸出重定向到標準輸出,錯誤訊息才會輸出到output.txt。

程式敘述:

有一個介面,上面有一個清單會顯示過去10秒的紀錄,以及對應四個程式的四個按鈕,按下去會執行對應的程式,及其他按鈕等。

四個程式分別為ProgramA、ProgramB、ProgramC、ProgramD,都有整合logging,會把紀錄傳到主程式查閱的.log檔案。

ProgramA,有錯誤處理,而且有錯誤發生
ProgramB,有錯誤處理,但沒有錯誤發生
ProgramC,沒有錯誤處理,但有錯誤發生
ProgramD,無限循環。

執行這四個程式時,會在 ./error 生成一個 txt ,如果順利執行, txt 的大小會是0,然後被刪除掉。
ProgramA 與 ProgramB 順利執行完畢,而 ProgramC 會報錯,因此 ./error 會有記錄錯誤訊息的 ProgramC 的 txt。
而ProgramD,無限循環,觀察是否使用多核。會。

語:
花了好幾天,一點一滴摸索最終達到這個架構。

如果要做一個任務管理器,執行各種任務,自然得靠多線程,不然會看到主程式執行下去動彈不得就難堪。雖然多線程無法讓主程式使用多核,但用此例方法執行的程式,不會跟主程式擠在同一核,以往寫python需要引入多進程才能用到多核,這方法經測試過後,python程式會自動分配到其他核上。

關於logging的設定想了很久,雖然上一篇教學大開使用的自由度,但是卻沒有規劃一定的架構。從好幾種設定探索,並且探索其可能遇到的問題,最後把能想到的問題都有了解法。

這範例中各程式都引入同一個自訂Logging,這避免產生同名同功能的亞種,不論哪個專案,都是引入這個。之前我就遇到複製太多造成維護不易,更新了一個,還要手動複製貼上到其他的兄弟姊妹實在太費工。

這範例對我來說,就是改善過去不良的架構,為更好維護的架構打下基礎。

Python logging 初學者速理解

給對於 logging 未曾接觸過,或剛接觸過的人。

logging 可以將記錄輸出,也可同時將記錄保存。除錯除了靠print大法,也可以靠logging保存下來的日誌中找出錯誤點。

有些程式運行會因為時間改變環境而出現問題,出現嚴重錯誤就會跳掉。沒有辦法總是在編寫或除錯模式下運行,出錯就告訴你哪裡有問題。若程式未能紀錄到當下運行狀況,就少了線索去發現問題所在。

logging有很多教學,也有繁體中文教學,寫得也不錯。

這裡就簡單寫下一點筆記,幫助初學者快速理解logging運作。


logging有程度(level)之分:最嚴重CRITIAL > ERROR > WARNING > INFO > DEBUG > 最輕微 NOTSET。

假如我只想保留 ERROR 以上的記錄在 log,顯示 INFO 以上的記錄在終端。

假設我有一個程式,命名為 HELLO.py。例1:

Import logging

loggerName = "HELLO"
fileName = "hello.log"

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(loggerName) 
FileHandler = logging.FileHandler(fileName)
FileHandler.setLevel("ERROR")
logger.addHandler(FileHandler)

logger.info("哈囉")
logger.debug("這個不會顯示出來 INFO > DEBUG")
logger.warning("注意下一個記錄")
logger.error("只有這個會被保存在log")

行6,先跳過。
行7,得到一個名字為 “HELLO” 的 Logger 物件。
行8,得到一個 FileHandler 物件。Handler 功能是把 Logger 的記錄傳出去,看官網文件,Handler的種類有很多種,這次用的 FileHandler ,簡單來說就是把 Logger 的記錄傳到指定檔案。
行6,logging基本設定,level是指 root 的 level,會自動生成一個 StreamHandler 添加到 root 上去。上行解釋 Handler 功能,這自動生成的 StreamHandler 會將記錄傳出然後顯示在終端。至於要多高層級的訊息才會顯示,就看這裡設定 root 的 level,之後新增 Logger 默認level也會跟這裡設定的相同 。官網文件
行9,設定 FileHandler 的 level 為 ERROR。
行10,讓 logger 添加 FileHandler。

行12~15,用 logger 傳入不同 level 紀錄,看看實際效果。

INFO:test:哈囉
WARNING:test:注意下一個訊息
ERROR:test:只有這個會被保存在log

複習一下:CRITIAL > ERROR > WARNING > INFO > DEBUG >  NOTSET
或者:CRITIAL(50) > ERROR(40)  > WARNING(30)  > INFO(20)  > DEBUG(10)

因自動生成 StreamHandler 的 level 為 INFO ,故只將行12、行14、行15紀錄輸出到終端。而行13的 level 為 DEBUG,小於 StreamHandler 的 level  INFO,所以被刷掉。
FileHandler 的 level 為 ERROR ,所以只有行15被保存在 hello.log 裡。

注意:這裡顯示在終端的紀錄,是經由 root 的 StreamHandler ,不是 logger 的,這可以利用 Logger.handlers 查看 logger有甚麼 handlers。root 是最上層的 Logger,可觀察到將資料傳入 logger 時,也會往上層傳到 root。

瞭解了 例1 之後,其實我們能把每個 Logger 顯示在終端的 level 有不同設定。如果有兩個 Logger ,一個要 INFO 以上,一個 ERROR 以上,只要分別針對兩者的 StreamHandler 有不同設定。

例2:

import logging

loggerName = "test"
loggerName2 = "test2"
fmt = logging.Formatter("%(name)s - %(levelname)s - %(message)s")
logger = logging.getLogger(loggerName)
logger2 = logging.getLogger(loggerName2)
logger.setLevel("DEBUG")
logger2.setLevel("DEBUG")
StreamHandler = logging.StreamHandler()
StreamHandler.setLevel("INFO")
StreamHandler.setFormatter(fmt)
StreamHandler2 = logging.StreamHandler()
StreamHandler2.setLevel("ERROR")
StreamHandler2.setFormatter(fmt)
logger.addHandler(StreamHandler)
logger2.addHandler(StreamHandler2)

for lg in [logger, logger2]:
    print(lg.getEffectiveLevel())
    lg.info("哈囉")
    lg.debug("這個如何")
    lg.warning("那這個呢")
    lg.error("最後一個")

執行結果為:

10
test - INFO - 哈囉
test - WARNING - 那這個呢
test - ERROR - 最後一個
10
test2 - ERROR - 最後一個

這裡沒有用到 logging.basicConfig() ,所以 Logger 的 level 默認為 WARNING。
若不在行8、行9設定 Logger 的 level,結果會變成如下:

30
test - WARNING - 那這個呢
test - ERROR - 最後一個
30
test2 - ERROR - 最後一個

Logger 默認的 level 為 WARNING 換算等級數值為 30,如果有logging.basicConfig()就會先以這裡設定的 level 為主,於是行21 INFO、行22 DEBUG的紀錄在 logger 這關就會被默認的 level WARNING 濾掉,接下來就沒有辦法進到 StreamHandler。

每個 level 都有對應的數值,在 Logger.setLevel() 時,也可以輸入正整數, Logger.setLevel(30) == Logger.setLevel(“WARNING”)。

透過 例2 ,瞭解如何分別設定顯示在終端的條件,自然也可以分別設定輸出檔案跟級別等。

雖然可以分別設定 Logger 但會讓程式碼看起來相當的長,不過也有一個方法,只要兩句,然後就能簡化成 logging.getLogger() 解決掉。

from logging import config
config.fileConfig(configName)

這邊在資料夾要多新增一個檔案名為 logging.conf,裡面寫 Logging 的設定,可以用記事本讀寫。

logging.conf:

[loggers]
keys=root, test, test2

[handlers]
keys=consoleHandler, StreamHandler, StreamHandler2

[formatters]
keys= default

[logger_root]
level=WARNING
handlers=consoleHandler

[logger_test]
level=DEBUG
handlers=StreamHandler
qualname=test
propagate=1

[logger_test2]
level=DEBUG
handlers=StreamHandler2
qualname=test2
propagate=1

[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=default
args=(sys.stdout,)

[handler_StreamHandler]
class=StreamHandler
level=INFO
formatter=default
args=(sys.stdout,)

[handler_StreamHandler2]
class=StreamHandler
level=ERROR
formatter=default
args=(sys.stdout,)

[formatter_default]
format=%(name)s - %(levelname)s - %(message)s
datefmt=

內容很明顯,就是將 例2 的那些設定寫進來,於是 例2 可以簡化成這個樣子。

例2改:

import logging
from logging import config

configName = "logging.conf"
loggerName = "test"
loggerName2 = "test2"

config.fileConfig(configName)
logger = logging.getLogger(loggerName)
logger2 = logging.getLogger(loggerName2)

for lg in [logger, logger2]:
    print(lg.getEffectiveLevel())
    lg.info("哈囉")
    lg.debug("這個如何")
    lg.warning("那這個呢")
    lg.error("最後一個")

這樣是不是就簡潔很多了。

這邊簡單整理一下 logging 的流程,舉個例:

假設我傳入一個 warning 的紀錄到 logger, logger 的級別若是超過 30,就會被濾掉。
沒有,則該紀錄傳往其所屬 handlers 跟上層的 root  handler 傳入。
若 logger handler 的級別小於等於 30,就會把該紀錄傳出去。
若 root handler 的級別小於等於 30,就會把該紀錄傳出去。
不重要:若 root 的級別超過 30與否都不影響,並沒有在判斷流程。

官網上也有流程圖,那裡寫得會更完整。

為了讓各位更清楚看見流程細節,最後一個範例4:

import logging
from logging import config
    
configName = "logging.conf"
loggerName = "test"
loggerName2 = "test2"

config.fileConfig(configName)
logger = logging.getLogger(loggerName) 
logger2 = logging.getLogger(loggerName2) 

fmt = logging.Formatter("%(name)s - %(levelname)s - %(message)s - root SH 20")
StreamHandler = logging.StreamHandler()
StreamHandler.setFormatter(fmt)
StreamHandler.setLevel(20)
fmt2 = logging.Formatter("%(name)s - %(levelname)s - %(message)s - root SH2 40")
StreamHandler2 = logging.StreamHandler()
StreamHandler2.setFormatter(fmt2)
StreamHandler2.setLevel(40)
logging.root.addHandler(StreamHandler)
logging.root.addHandler(StreamHandler2)

logger.setLevel(10)
logger2.setLevel(30)
logging.root.setLevel(20)

for lg in [logger, logger2]:
    lg.info("20")
    lg.debug("10")
    lg.warning("30")
    lg.error("40")

這裡將 root level = 20,並加了兩個 StreamHandler ,其 level 分別為 20、40。

test - INFO - 20
test - INFO - 20 - root SH 20
test - WARNING - 30
test - WARNING - 30 - root SH 20
test - ERROR - 40
test - ERROR - 40 - root SH 20
test - ERROR - 40 - root SH2 40
test2 - WARNING - 30 - root SH 20
test2 - ERROR - 40
test2 - ERROR - 40 - root SH 20
test2 - ERROR - 40 - root SH2 40

第一眼眼花撩亂,不要忘記:
test level = 10, test SH level = 20
test2 level = 30, test2 SH level = 40
root level = 20, root SH level = 20, root SH2 level = 40

順著 logger -> logger handlers & root handlers 這樣的流程就可以慢慢湊出每行輸出是怎麼來的。

如果不想要讓 Logger 往上傳遞,可以將其propagate設為 False。

總結一下:
如何設定 Logger 指定其輸出檔案跟終端的方式。
如何分別設定 Logger 。
如何使用 config.fileConfig(),簡化 Logger 設定。
描述 logging flow,瞭解傳播的方向與規則。

例1~例4的程式碼放在 github 。

祝各位學習順利,若有建議或想法可以留言告知。

透過 win10工作排程器 定時以 potplayer 播放音樂

用 工作排程器 在早上自動播放是我的習慣,不過有一個問題,它不一定每次都能播放出來。

這邊講一下基本準備:

將想要播放的音樂清單,用 potplayer 輸出成播放清單(.dpl)。
操作:把音樂擺入播放清單,按F2儲存。

在win10系統,螢幕左下有放大鏡,搜尋「工作排程器」就可以叫出來。
不然「開始」→「windows 系統管理工具」→「工作排程器」。

透過建立工作,觸發條件自行設定,我個人是設定每日早上七點。

動作新增一個:
啟動程式
程式或指令碼輸入程式名也就是 PotPlayerMini64.exe
引數為播放清單的位置如 xxx/xxx/xxx.dpl
開始位置為執行程式所在資料料夾,x:\Program Files\DAUM\PotPlayer


用cmd的概念講白話。
「cd x:\Program Files\DAUM\PotPlayer」進入到 x:\Program Files\DAUM\PotPlayer
「PotPlayerMini64.exe xxx/xxx/xxx.dpl」打開 PotPlayerMini64.exe 傳入 xxx/xxx/xxx.dpl

這樣的設定,在運行上是沒問題的,對著建立好的工作按右鍵執行,可以看到 potplayer 順利載入播放清單。

BUT!!!

如果剛開機或是重新開機,卻沒有辦法順利播放。可以看到清單載了,卻沒有播放。但這僅限於第一次,如果再重新執行工作一次,這次就能播放出來了。

基於觀察到的現象,個人做了許多不同變種的嘗試,都沒有辦法確保一定能播放出來。只有一種土法煉鋼的方法是一定可行的。

寫成批次檔(.bat),然後工作的動作就執行這個批次檔,這邊直接放上內容。

J:

cd J:\Program Files\DAUM\PotPlayer\

start ""PotPlayerMini64.exe 音樂.dpl"

timeout /t 4

taskkill /f /im PotPlayerMini64.exe

start ""PotPlayerMini64.exe 音樂.dpl"

exit

這裡逐行解釋:
第1行,進入J槽。因為我的播放器在J槽。
第3行,進入 J:\Program Files\DAUM\PotPlayer\ 。播放器所在的位置。
第5行,執行 PotPlayerMini64.exe 傳入 播放清單。這邊我把播放清單放在跟播放器同一個資料夾。用 start “”file”,能夠執行後繼續下一行,如果直接用 file,cmd就會卡在這一行指令。
第7行,等帶4秒。給第5行執行的potplayer跑好的時間。
第9行,關掉potplayer。因為開機後第一次執行不會播放,所以關掉重開。
第11行,同第5行。
第13行,離開cmd。

既然第一次不會自動播放,那就載入後關掉,再開第二次就能解決了。

雖然個人覺得這方法很醜,不過個人程度也就到這裡了。如果有更好的作法,歡迎留言告訴我。

Python pyqt5 與 matplotlib 結合示範之範例2

範例2是關於一元二次方程的線圖。

展示以下功能
1.可以添加一元二次方程至清單或從清單刪除之
2.依清單內的項目勾選狀況顯示或隱藏一元二次方程
3.在狀態列會顯示滑鼠指到線的相關資料

這次的重心,是要做出一個可調整的清單,也可分別勾選項目顯示與否。

如果線的數量未知,那就得以可擴充可變動的前提去寫。

對於不同情況,可能想看的線是不同的。有時候會覺得太多線,而想減少顯示在繪圖區的線。

這次各線都呈現在一個軸上,就沒有做區分。要控多軸,就再加點條件判斷就可以了。

因為範例1的cmd不會被錄到,所以這次把資訊顯示在狀態列上。

以下是範例2:程式碼(github) 展示(youtube)

因為有範例1,所以就沒有特別標上範例1有寫到的說明。

程式敘述:

有一個介面,有繪圖區跟控制區,控制區有清單,添加與刪除的按鈕等。

預設會添加三條方程,分別為: 「2x^2 + 1x^1 – 2」「0x^2 + 1x^1 – 2」「0x^2 + 0x^1 – 2」。

從控制區的清單,可以決定要顯示哪幾條線。

可以添加新的線,也可以刪除舊的線。

圖例可以顯示也可以隱藏,一旦線圖有變動,圖例也會變動。

在狀態列會顯示滑鼠指到的該線相關資料。

Adobe Acrobat Reader DC 造成的記憶體不足

最近買了一條記憶體,跟我電腦上的是同樣大小8GB。原因是電腦出現記憶體不足崩潰的現象。

記憶體比兩年前便宜很多,順便一看SSD也是,這題外了。

查了一下解法,既然記憶體便宜許多,那就多裝一條不就解了?我看我的記憶體使用率普遍達70%以上,一個要認真操起來,絕對爆掉。

遺憾是沒有因為裝上一條新的記憶體而解決問題。

尋找根源,我學習到一些找出真兇的方法。可是那時我並沒有找到出來,狀況也隨裝上記憶體好轉。

今天再發生一次,很幸運的是,這次只有提示「記憶體不足」,沒有像之前整台電腦崩潰,看來裝新記憶體還是有幫助的。

在工作管理員的「效能」頁面
記憶體「使用中」的部分沒有全滿。
記憶體「已認可」的部分達到上限!

這樣子,推論出只要「已認可」用罄就會造成記憶體不足的現象發生。

尋找「誰」吃掉記憶體的方法:
打開工作管理員,開啟資源監視器,留意誰是吃爆記憶體的元兇。

有一個程式,吃掉1GB的量,叫做「AcroRd32.exe」。
程式全名叫 Adobe Acrobat Reader DC,是開 .pdf 的程式,可以用來看看文件或是電子書等。

只要用它打開特定電子書,就會發現記憶體「已認可」不斷上升!只要把這程式關掉,狂吃記憶體的現象就會馬上解決。

回想有甚麼怪異之處?

當我打開電子書時,它通知,大概是這樣的意思:

「文件內有些部分需要下載字體才能正常顯示。」

那時我沒有下載,因為關掉通知後,一切看起來很正常,沒有亂碼或缺少會影響閱讀。

試了新版本與用edge打開文件,edge打開是沒有狂吃記憶體。而新版本也一樣會跳通知,吃記憶體的現象會發生。綜合判斷,需要下載字體了。

下載完字體安裝之後,再打開那本電子書,沒有跳通知,也沒有吃記憶體的問題發生。

問題已解決。

話說從需要下載字體的通知下載的安裝檔,會跑出當前的Adobe Acrobat Reader DC版本不能安裝,雖然我是從官網上下載也從程式內檢查更新過都是最新了,版本號還是不符合字體檔要求的。得找過去的字體安裝檔,就能解決。

為什麼我新購記憶體前沒有發現?因為那時一旦出現記憶體不足,整個電腦就會崩潰無法使用,只剩重新開機一途,當然也就沒有辦法開工作管理員及時觀察。也因為有裝新記憶體,才給我能及時找到兇手的機會。

Python pyqt5 與 matplotlib 結合示範之範例1

對於寫 python 的人來說,為了讓程式更方便使用,會想要寫點圖形化介面。

對於 python 來說,最基本的圖形化介面是 tkinter ,網路上也有許多相關教學。不過用程式碼去定義視窗與物件排版,是需要比較學習與辛苦的。

之前我寫過 c# ,用 visual studio 去寫。按鈕、標籤可以直接拖曳到版面上,效率是比較高的,也易懂執行出來的成果會長怎樣。

對於 python 來說,有 qt 可以直接設計界面的,用 qt designer 也生成一個版面,將按鈕、標籤等拖曳到版面中排版,基本樣貌可以直接反應出來。

想要學習 pyqt ,建議直接找書,按部就班,基本上就能學習到讓 python + pyqt 搭配 qt designer 設計。

寫這範例,並不是手把手從零教學,而是展示一些搭配的效果。適合給初學者想再進階的人。

像是將 matplotlib 的繪圖鑲入,並且可以用qt物件去操控繪圖。

如果想要寫股票走勢,用選單選取股票,就能讓繪圖區跑出折線圖或K線圖,甚至可以做即時走勢,如果有辦法弄到即時資料就絕對可以。

以下是範例1:程式碼(github) 、 展示(youtube)

敘述:

有一個介面,繪圖區有兩條sin線,第一條為 sin(x)*2,第二條為 sin(x)*-1,兩條線在不同的軸上。看起來兩條線對稱於 y=0,但實際上兩條線的y值並沒有,兩條線分處的軸上的y軸範圍是不同的。

介面上有按鈕,可以控制 qtimer,qtimer每跑過一個intetval,兩條線就會新增一個值。所以按下 start按鈕,就可以看到兩條線動起來。而按下 stop按鈕,就可以停住qtimer。

介面有一條scrollbar,可以移動圖表X軸範圍,可以看舊的資料,也可以看新的資料。
也有 +按鈕 與  -按鈕 ,可以改變範圍寬度,最短 1 最長 30。

有一個追蹤最新的qcheckbox,勾選後,會以最新資料的x為範圍上限。如果開啟qtimer時有勾選,就可以保持在圖表能顯示最新資料。

滑鼠在圖表區,可以查詢線名、所在軸名、x值與y值。

滑鼠在圖表區,會有一條水平虛線輔助線追蹤滑鼠位置,可以按下 顯示輔助線 切換。
(這代表能夠讓使用者選擇想要顯示甚麼線在圖上)