把 Python 的 venv 移到其他機器

前言 要怎麼在不重新下載東西的情況下,把一整包 code 包含依賴本身,移到新的地方呢? 舉個之前遇到的例子:我想在學校部署一個用 Python Streamlit 框架寫的程式碼評測工具,但是在測試部署時發現一個非常頭痛的問題:學校的連外網路特別慢,導致 pip 幾乎無法正常完成安裝。在嘗試了 pip download 以及一些打包方案後,發現 Streamlit 的執行最終都會缺幾個元件導致啟動失敗,最終都還是需要用 pip install 補全依賴。最後我想到一個 workaround:拿其他同學的 Windows 筆電先把程式準備好,再把準備好的程式複製到新的地方。 不過要怎麼「準備好程式」然後「複製到新的地方」呢? Python 不像 Go 和 Rust 可以編譯成靜態的執行檔,也不像 Node.js 和 PHP 有著各個專案獨立的 node_modules 或 vendor 資料夾,可以搬到其他地方而保持程式的依賴正常運作。不過 Python 有個很類似 node_modules 的東西——Virtualenv,搞不好我們真的能像 node_modules 一樣直接把整組專案複製到其他電腦上,專案就能跑了。 但是 Stack Overflow 的文章又提到「venv 通常不能直接複製到其他電腦上」。 In general you can’t copy virtual environments anywhere, Docker or otherwise. They tend to be tied to a very specific filesystem path and a pretty specific Python installation. If you knew you had the exact same Python binary, and you copied it to the exact same filesystem path, you could probably COPY it in as-is, but the build system would be extremely fragile. 1 ...

November 25, 2024 · pan93412

搭建一個有圖床和統計功能的 Blog

tl;dr 使用 Zeabur 部署 Blog 使用 Cloudflare R2 當圖床,Cloudreve 管理 使用 Umami 進行網站資料統計 選擇性部署 CodiMD / Hedgedoc 方便行動裝置編輯 常見的方案有什麼問題 純粹的 GitHub Pages 架設靜態 Blog,搭配 Google Analytics(或 Cloudflare Web Analytics)雖然是最便宜的選擇,但對於媒體管理上和草稿編輯還是不太方便。 統計工具方面,除了 Google Analytics 之外,幾乎沒有什麼功能特別強大,可以看單一使用者流向的統計工具。 圖床方面,媒體放在 GitHub repository 上會造成 repo 迅速膨脹,但又擔心其他免費圖床如 Imgur 會倒閉。而且 Imgur 不能上傳圖片以外的資源!🥺 草稿編輯方面,GitHub 沒有提供一個比較好的 Markdown 書寫工具,要獲得好的書寫體驗,就需要在電腦上用 VS Code 等等的 Markdown editor 書寫,局限了在手機上完成草稿或者編輯文章的能力。 怎麼解決? 作為一個 self-hosted 跟偏好 Cloud Native 方案的使用者,我會這麼規劃我的 blog: 這次我使用的 Blog SSG 是 Hugo,速度確實快,而且 template 比 Hexo 簡潔和清晰一點。 ...

November 24, 2024 · pan93412

從個人網站學習專案管理的方法論

背景 最近在做一個有簡歷功能的個人網站。 大學的網頁課程要上傳一個 styled 的 profile。起初兩週我想了許多種方案,比如「直接把之前超前進度的作業1 改成亮色系,然後繳出去」、「用 ChatGPT 幫我寫一份 styled 的網頁。」但我覺得都太敷衍,再加上當時有一些事情要處理,所以我就先放著——直到繳交前 3 天。 繳交前夕,我突然有個打算:「要不要試著超前上課進度2?」如果作業還繼續用 Vanilla JS 也實在太沒有新意,而且我這時突然想到一個很有意思的主意:做出一個可以分享給別人(也方便自我介紹)的簡歷。 我面試的時候常常被問到「你的作品集在哪裡」、「你的簡歷在哪」。我是有個人形象頁面,但幾乎沒有說明自己的經歷:這也就導致沒有人——包括我自己——知曉我究竟參與了哪些專案、製作了哪些 projects,以及設計了什麼東西。如果能藉由這次作業的機會打造出一個可以分享給別人(也方便自我介紹)的簡歷,那肯定會讓這個作業更有價值。 「做一個好看的簡歷網站」和「用現代前端工程標準製作專案」,便成為這個專案的兩大目的。 「滾動式開發」:逐步趨近完美 專案本身沒有用到傳統意義上的敏捷開發,但我在這個專案實踐了一些敏捷開發的「滾動式」精神。 「滾動式」的概念其實就是「慢慢達成趨近完美」:應該要先專注在核心的部分(比如這次的專案就是以個人介紹為核心),然後再慢慢地把其他的東西(比如作品集、開源貢獻)加進去。重構也是:一開始不用追求到完全的 best practice——細節可以留待日後再來改善,但如果為了細節而導致核心部分的缺失,便是本末倒置。 時間安排的重要性 雖然這麼說,但我仍然想追求完美主義:我很討厭承認一個長得超醜、超難維護的 snippet 是我寫的,我希望可以帶給閱聽者極其完美的體驗。正是因為如此,這次的作業依然是花了相當多的時間重構。可是比起細節,內容的完善明顯比較重要——更何況我因為這個 project 荒廢了太多其他的事情,排除掉學習到的經驗,投入的時間成本其實早就超出了這個專案可以帶來的價值。 **「時間安排」,在無論是在專案管理、還是日常生活的方方面面都十分重要。**安排任務的優先級、並且專注在完成最核心的事情,為專案的管理之道。我覺得我時間管理一直都做得不太好——想法太多,時間卻是有限的——是時候逼迫自己從無限的想法中切分,而不是讓一個想法佔據掉太多的時間。 勇於發問,明白自己的不足 寫網站的時候我就有和好幾位朋友共同研討「如何做出舒適的設計」,並且共同討論如何做出優秀的動效設計。這次我就和一位有著不小聲望的專業前端工程師和設計師,共同討論網頁每一處細節的設計。 另外,網站在開發後期也有遇到以及在遇到效能問題時與幾位更專業的前端工程師共同查出問題。在網頁撰寫末尾,我就有遇到一個很嚴重的效能問題:滑到卡片的時候會嚴重掉幀,但 Profile 裡面並沒有指出具體是哪個函數導致問題。後來和朋友討論之後,鎖定是 shadow 動效導致的問題,而在解決之後,效能問題也就臨刃而解。 不要過度工程化 (over-engineering) 之前看過一篇文章,說:「Best Practice 是總結出來的,不代表一開始就得導入這些東西。」工程更像是事情在遇到事情或需要擴充時的解決方法,而不是在沒必要的時候就無止境導入的玩意。 另外,不完美是難免的:像是我的 codebase 就重構了數次,即將發起的一次是發現自己違背了 Tailwind CSS 的 Atomic CSS 理念,打算全數改回 Component 的形式。重構是個學習,不要害怕不完美——不完美才能學習,學習才能成長。 目前 App 還會繼續改進的地方 除了上面提到的 bad smell 之外,我還打算加上一些更多 GitHub 的元素(比如個人的貢獻狀況、以及貢獻圖表)。不過把 bad smell 做完之後應該就是直接 promote 然後暫時放著了——有更多更重要的專案得做啊。 ...

December 16, 2022 · pan93412

什麼時候該引入 Lockfile?

原文撰於 Twitter。 撰文初衷 第一次看到沒有 commit package-lock.json 的 repo TL;DR 事實上這樣在 library 上沒有什麼問題。Lockfile 的追蹤有個小原則: 應用程式建議追蹤 lockfile:不追蹤,下次 npm install 就無法確定具體的依賴版本是什麼。 函式庫可以不用追蹤,因為使用者安裝套件時,套件管理器會根據依賴自動選取最適合的版本,而你自己的 lockfile 會被忽略1。不過建議追蹤,見下文。 函式庫「該不該」追蹤 lockfile? 假如函式庫有用到諸如 ESLint 的工具,追蹤一下可以避免之後設定開發依賴的麻煩, 所以像 NPM 官方就是推薦 無論如何都追蹤 lockfile。 不過也有預設不推薦在 library 情境下追蹤 lock 的例子,比如 Rust 的 Cargo 套件管理器2。不過 Cargo 的開發工具主要都是作為 submodule 安裝在系統中,通常不會跟著 repo 一起追蹤,所以不太適合放在一起比較。 不過要注意:這時候的 lockfile 就不是追給下游應用程式看的,主要是為了自己開發方便。 為什麼「應用程式」就該追蹤 lockfile? package.json 通常不是描述固定的版本,而是一個版本區間:舉個例子:你可能在 package.json 裡面描述 vue: "^2.4.0",但實際上 NPM 幫你選了 2.7.13。這個行為是可以預測的,可以參考 NPM 官方的 Semver 計算機:https://semver.npmjs.com。 ...

October 18, 2022 · pan93412

The comparison of Linter and Formatter

Motivation src: https://twitter.com/bystartw/status/1568217424658583553?s=61&t=UVzhpuQaGcgZkGpySz_yVQ Noticing some developers are confused with “Linter” and “Formatter,” I made a simple figure showing what messages Linter and Formatter prefer to print. Thanks to @nonoesp for his incisive introduction to Linter and Formatter!

September 9, 2022 · pan93412

善用 Swift 的字串擷取功能簡化 I18n 流程

撰文原由 在 威注音 2.1.0 SP1 的更新日誌 下看到了這麼一段話: Interface Builder 給每個標籤的命名都是隨機的,手動改起來又低效又容易改壞,對多語言本地化而言簡直是天災。 事實上在 WWDC21 中,Apple 就為 Xcode 推出了一項可以解決這項痛點的新功能:「使用編譯器擷取字串」。與早期透過從 Swift 原始碼拉取可翻譯字串,經常漏掉一些字串的方式不同——這個功能會先編譯原始碼,然後從編譯出的檔案中判斷可以翻譯的字串。 啟用功能 這項功能在新專案是預設啟用的,但舊專案可以 opt-in:點開專案設定,進入 “Build Settings”,展開所有功能(All),尋找 “Swift” 然後找到 Localization 的 “Use Compiler to Extract Swift Strings”,將其設定為 Yes 即可。 匯出字串 開啟之後的國際化與本地化方式,跟先前會不太一樣。以往需要手動改 strings 檔案,現在可以先到 “Product” > “Export Localization…” 匯出編譯器擷取出的所有字串: 接下來指定匯出的路徑,然後等待 Swift 完成編譯並擷取字串。若是從舊專案 opt-in,擷取字串的過程中可能會拋出一些錯誤,這個時候就得修正(如果只是警告的話也可以忽略): 翻譯流程 接下來就可以進入選擇好的資料夾,使用 Xcode 點開對應語系的 xcloc 檔案: ...

August 27, 2022 · pan93412

文科生也能懂的 Rust async 机制

背景 https://twitter.com/repsiace/status/1554103778994900992/ 修改一下:work stealing, thread-per-core, waker, mpsc, task queue 只有他们懂… 正常人不可能看懂 – @twicemoemoe, 22-08-02 作为一个文科生,其实觉得 async 真的没有想象中的这么困难 ⋯⋯ 😂 或许搭配一些图片会好懂很多吧。 TL;DR 不废话版本 Sync(同步):一件事情做完之后,再做下一件事情。 blocking(堵塞):指“等一件事情”的行为。 Async(异步):一件事情还没完成,可以做其他不冲突的事情。 concurrency(并发):程序 架构 中,各个任务可以 独立运行 的特性。 future:Rust 中的一个异步任务的表示。 polling:不停地询问任务,确认事情是否已经完成。 event-driven:事情完成后,任务自己发通知表明完成。 parallelism(并行):同时 运行 数个程序的行为。 thread(线程、线程):系统进程(任务集)的基本单元。thread 通常是交由 CPU 内核运行。 spawn(生成):指产生 thread 的行为。 thread pool(线程池):将 thread 高效分配给每个任务的地方。 Async runtime: 以 tokio 为例 join (macro):并发运行 async 函数,并在全部完成后回传。 select (macro):哪个 async 函数快,回传那个 async 函数的结果。 main (attribute macro):在 main() 初始化 runtime。 block_on:在 sync 上运行 async 函数。 spawn:并行运行 async 函数。 spawn_blocking:在异步函数里面,为一个高耗时且同步 (blocking) 的函数另辟新线程 (thread)。 同步 (Synchronous) 跟异步 (Asynchoronous) “同步”就是整个程序等一件事情完成(blocking,堵塞)。“异步”则是一件事情还没完成,可以做其他不冲突的事情。 ...

August 7, 2022 · pan93412

文組也能懂的 Rust async 機制

背景 https://twitter.com/repsiace/status/1554103778994900992/ 修改一下:work stealing, thread-per-core, waker, mpsc, task queue 只有他们懂… 正常人不可能看懂 – @twicemoemoe, 22-08-02 作為一個文組,其實覺得 async 真的沒有想像中的這麼困難 ⋯⋯ 😂 或許搭配一些圖片會好懂很多吧。 TL;DR 不廢話版本 Sync(同步):一件事情做完之後,再做下一件事情。 blocking(堵塞):指「等一件事情」的行為。 Async(非同步):一件事情還沒完成,可以做其他不衝突的事情。 concurrency(並行、併發):程式 架構 中,各個任務可以 獨立執行 的特性。 future:Rust 中的一個非同步任務的表示。 polling:不停地詢問任務,確認事情是否已經完成。 event-driven:事情完成後,任務自己發通知表明完成。 parallelism(平行):同時 執行 數個程式的行為。 thread(執行緒、線程):系統處理程式(任務集)的基本單元。thread 通常是交由 CPU 核心執行。 spawn(生成):指產生 thread 的行為。 thread pool(執行緒池):將 thread 高效分配給每個任務的地方。 Async runtime: 以 tokio 為例 join (macro):並行執行 async 函數,並在全部完成後回傳。 select (macro):哪個 async 函數快,回傳那個 async 函數的結果。 main (attribute macro):在 main() 初始化 runtime。 block_on:在 sync 上執行 async 函數。 spawn:平行執行 async 函數。 spawn_blocking:在非同步函數裡面,為一個高耗時且同步 (blocking) 的函數另闢新執行緒 (thread)。 同步 (Synchronous) 跟非同步 (Asynchoronous) 「同步」就是整個程式等一件事情完成(blocking,堵塞)。「非同步」則是一件事情還沒完成,可以做其他不衝突的事情。 ...

August 7, 2022 · pan93412

Rust 的 crate/super/self 關係

假設 rust_hello_world 的目錄架構長這樣:(範例源自於我手邊的某個 production 專案) rust_hello_world Cargo.toml src cli opt.rs lib.rs cli.rs logger.rs 「crate」概括來說就是一個專案,用 Cargo.toml 區分。每個 mod 都是一個層級,super 就是上個層級,self 就是本層級。上面的圖已經把 crate / super / self 的對應關係寫得很清楚了,以下寫範例: cli::opt::Opt 想要讀取 LoggingLevel,路徑可以這樣走: super::super::logger::LoggerLevel crate::logger::LoggerLevel cli::opt::Opt 想要讀取 PROG_NAME,路徑可以這樣走: super::PROG_NAME crate::cli::PROG_NAME 事實上 Rust 的模組關係也沒這麼複雜。把上面的例子變成目錄: [crate] / [mod] cli/ [mod] opt/ [struct] Opt.txt [const &str] PROG_NAME.txt [[pub] mod] logger/ [[pub] enum] LoggingLevel.txt super 等價於 ..,self 等價於 .,而 crate 則類似 /。

October 13, 2021 · pan93412

VBA 筆記:選取事件

有天我突然想寫一個 Excel 按鈕,但是又不想要做一個 Form,而想到最好的辦法,就是按 Excel 中的格子。那該怎麼做,才可以做出這種偽按鈕呢? 原理思考 Excel 中,按一下儲存格代表? 就是選取「單一」儲存格。就跟你拖曳選取一堆儲存格、選取整整一行或一列所代表的含意是相同的。 那怎麼知道使用者選取了什麼? 用 VBA 的事件 (Event)。只要使用者做了什麼動作(觸發),Excel 都會通知你發生新事件,告訴你使用者做了什麼。 首先,Excel 遇到新事件時,會先看看你有沒有在指定的地方放下動作(Sub,子程式),如果沒放就等同忽略,而有放就會觸發(Trigger)這個動作。所以我們只要知道「選取儲存格後該在哪裡接收事件」,就完成了。 正文 建立接收事件的子程式 VBA 中有一個跟儲存格變更有關的事件,叫做 Worksheet_SelectionChange,長得像這樣: Private Sub Worksheet_SelectionChange(ByVal Target As Range) 別急著複製。VBA 其實有提供一個非常簡單的事件選取工具。首先開啟 VBA 編輯器,找到你想要監聽的工作表,然後按一下上方的 [(一般)],之後選擇 [Worksheet]。 選擇 Worksheet 然後選擇 [SelectionChange],搞定! 選擇 SelectionChange 了解子程式的結構 首先,我們來看看產生的程式碼。 Private Sub Worksheet_SelectionChange(ByVal Target As Range) End Sub 你會發現到裡面出現了非常多奇怪的東西。什麼是 Private 和 ByVal?Target 是什麼?As Range 又是什麼東西? 首先,每個工作表都有自己獨立的事件,也就是你在 A 工作表選取東西,不干 B 工作表的事。如果用 Public,就代表 A 工作表的事件也會影響其他工作表。這不應該發生。而 Private 就可以限定這個事件是 A 工作表 only 的。 而 ByVal 則需要一點資料結構的知識。VBA 傳遞參數有兩種形式,一個是 ByVal,建立物件的副本,另一個則是 ByRef,傳遞指向原物件的參考。有興趣可以看看〈VBA 中 ByVal 和 ByRef 的基础用法和区别〉。 ...

October 18, 2020 · pan93412