2024年6月11日 星期二

python click 參數中自訂自己的檢查類 type

python click 參數中自訂自己的檢查類 type

原生函式其實已經非常足夠了,只是如果專案比較刁鑽一點又不想把檢查函式寫在 command 函式中,而是傳入之前檢查可以自訂類來做

目標先挑一個你覺得有用的免得要重寫太多功能,這邊假設是路徑就挑 click.Path 這個類別繼承,然後在此之上加上自己的功能

# 自訂 type 類別 ExtPath 用例
class ExtPath(click.Path):
    def __init__(self, my_check=False, **kwargs):
        self.my_check = my_check
        super().__init__(**kwargs)

    def convert(self, value, param, ctx):
        print(f"ExtPath.convert():: 啟動此函數的對象參數是 {param.name} 目前值為 {value}, 自訂參數 my_check={self.my_check}")
        return super().convert(value, param, ctx)

# 群組命令3:: demo2
@entry.command()
@click.argument('name', required=True, nargs=1, type=ExtPath())
@click.argument('path', required=True, nargs=-1, type=ExtPath(my_check=True, exists=True))
def demo2(name, path):
    cli.echo_inf_message(f"[\n    name: {name}\n    path: {path}\n]")
    """獲取從cli設置的初始化信息"""

大致上會用到的部分都寫上了,自己參考著應該就能知道怎麼使用了


範例用例

(.venv) PS C:\Users\user\OneDrive\Git Repository\PyCli_Template> myapp demo2 Test setup.py *.ps1     
ExtPath.convert():: 啟動此函數的對象參數是 name 目前值為 Test, 自訂參數 my_check=False
ExtPath.convert():: 啟動此函數的對象參數是 path 目前值為 setup.py, 自訂參數 my_check=True
ExtPath.convert():: 啟動此函數的對象參數是 path 目前值為 deploy.ps1, 自訂參數 my_check=True
[
    name: Test
    path: ('setup.py', 'deploy.ps1')
]


需要講的大概就是順序了,這個類的順序在參數擴展之後的 for 迴圈之內,就算是附加在 nargs=-1 裡面也會自動跑多次,可以從範例中看到。

這裡處理完畢之後就會正式進入 comannd 函式了,可以在這裡修改變數值但不是很建議,可能會跟官方功能有衝突。





2024年6月7日 星期五

python 裝飾器 時間計時範例

python 裝飾器 時間計時範例

來自高天的講解中複製出來的代碼
【python】装饰器超详细教学,用尽毕生所学给你解释清楚,以后再也不迷茫了!

import time

def timeit(iteration):

    def inner(f):

        def wrapper(*args, **kwargs):
            start = time.time()
            for _ in range(iteration):
                ret = f(*args, **kwargs)
            print(time.time() - start)
            return ret

        return wrapper

    return inner

@timeit(1000)
def double(x):
    return x * 2

double(2)

這個例子很關鍵的地方在於事後忘記了看一眼就馬上明白了

怎麼理解最快,這個最快的辦法就是不要理解

首先第一個問號區 *args, **kwargs ,只需要記住一件事情這整個區塊叫做 完美轉發 我不管前面的人給我什麼反正我就是直接複製對方的參數設定值

剩下的直接都當作一個區塊記下來就好了,只需要知道從 1000 那個位置可以設置 for 迴圈的變數,然後在 ret 的位置就是執行原本的函式

在 wrapper 中你可以對這個函式的前跟後動手,你希望在函式執行前先弄點什麼就寫在 ret 之前,你希望在執行後再弄點什麼就寫在 ret 之後

而這個對函式前後動手的行為就是裝飾器中的一個核心價值了,只需要透過追加一行就可以包裝函式前後的動作了




如果要放在類裡面參考這篇

【python】如何在class内部定义一个装饰器?这里的坑你要么不知道,要么不会填! (youtube.com)


2024年6月6日 星期四

python click 如何避免參數中的通配符被展開

python click 如何避免參數中的通配符被展開

在這篇討論中有被提到
Expand globs in arguments on Windows? · Issue #1096 · pallets/click (github.com)

click預設會通過 glob 展開通配符這是為了確保在 Windows 或 Linux 上能獲得一致的效果
不過反過來說如果你就是要傳入通配符,其實沒有任何手段可以避免的,原作似乎就沒開放了

討論的最後有提到改進在這裡
expand patterns in Windows args by davidism · Pull Request #1830 · pallets/click (github.com)

那就沒辦法只能硬幹了,就把這代碼幹掉

import click

# 定義一個新的 _expand_args 函數,直接返回原始參數
def _no_expand_args(args):
    return args
# 猴子補丁覆蓋 Click 的 _expand_args 方法
from click import core
core._expand_args = _no_expand_args

@click.command()
@click.argument('path', type=str)
def process_path(path):
    click.echo(f"Received path: {path}")

if __name__ == '__main__':
    process_path()


這樣一來就不會被騷擾了,反過來說你也可以在這裡寫上屬於自己的條件

上面是簡寫完整一點的整個函式其實是長這樣的

# 定義一個新的 _expand_args 函數,直接返回原始參數
import typing as t
def _no_expand_args(
    args: t.Iterable[str],
    *,
    user: bool = True,
    env: bool = True,
    glob_recursive: bool = True,
) -> t.List[str]:
    return list(args)
# 猴子補丁覆蓋 Click 的 _expand_args 方法
from click import core
core._expand_args = _no_expand_args


原始代碼可以在這裡看,可以參考是如何用 glob 展開變數的
click/src/click/utils.py at 923d197b56caa9ffea21edeef5baf1816585b099 · pallets/click (github.com)



補充說明一下流程,我覺得 click 原作這個解不夠漂亮,但確實已經是沒有辦法中的一個還算可以的辦法了。

原作的做法是收到來自命令的參數時,就先進行展開,不管是 option 還是 augument 都會被展開,這個有個問題是

在 linux 上 bash 會直接展開後才輸入,所以實際 click 拿到的參數已經是展開後的,但是有個例外是在 bash 中如果輸入雙引號就不會被展開

問題就在這裡,click是拿到參數之後不管如何總之每個都過一輪 glob 就對了,所以最終還是會被展開,等於摁還是會被展開。

好處就是統一了 windwos 跟 linux 的結果,犧牲輸入*號的可能性,click這一層無法關閉沒機會輸入了。


不過其實有bug可以卡,但不在討論範圍的內先拉出來講,就別槓這個了。透過這個方式 `myapp -p"*.py"` 或是全寫也行,這樣過 glob 的時時候由於長的不是路徑樣就不會被展開了


繼續回來原本的話題,這個要比較合理的解決,估計還是只能開放 glob 的自由度吧,預設不要開,自己決定要不要展開,而且最好還是可以決定系統層級的 `glob_win=Ture`, `glob_unix=Ture`。

這樣至少 linux 上還可以保持透過雙引號控制要不要展開,而Windwos這沒辦法了終端機本來就不會展開了,不管雙不雙引號都一樣。

兩邊系統的統一性我覺得不是那個重要,對於 win 本來就要知道系統不會幫你展開,對於 unix 本來就要知道系統會擅自展開。

函式只需要提供一個快捷讓你自己決定要不要賭這個洞。





PowerShell 通配符 Get-Item 無法帶有方括號名稱的檔案

PowerShell 通配符 Get-Item 無法帶有方括號名稱的檔案

這算是 PowerShell 5.1 預設給的方便的,不過在不知情的情況下就造成bug了

假設有一個 [1]File.txt 的檔案,於是就這樣獲取

Get-Item [1]File.txt

居然什麼都沒有,於是乎聰明如你,用引號總能解吧?

Get-Item "[1]File.txt"
Get-Item '[1]File.txt'


想不到吧很遺憾還是沒有辦法

這東西叫做通配符,內建在 Get-Item 裡的所才沒法通過引號處理的
about Wildcards - PowerShell | Microsoft Learn

下面是兩個能解的範例

# 使用函式接口 LiteralPath 
Get-Item -LiteralPath [1]File.txt

# 連帶反轉意符號一起傳進去 (雙引號不行傳入前就會被解掉了)
Get-Item '.\`[1`]File.txt'

方括號出現概率不是很高,但基本上只要量夠多基本就一定會有的東西,不知道的話有一天絕對會踩到的