56393


# 環境

  * Python 3.6

# 背景

PythonでCLIツールを作成しています。  
あるWebサービスから情報を取得して、以下のようなフォーマットでCSVファイルを出力するようなCLIです。  
実際には20～40列あります。

[code]

    name,a_seconds,b_seconds
    Alice,3600,1800
    Bob,3600,3600
    
[/code]

コマンドは以下のように実行します。

[code]

    $ python print_csv.py 
    
[/code]

# やりたいこと

CSVのいくつかの列は、ユーザがカスタマイズして出力できるようにしたいです。  
たとえば、`a_b_hours`列を出力したい場合、以下のような設定をコマンドの外から渡せるようにしたいです。

[code]

    column_name = "a_b_hours"
    
    def column_func(df):
        df[column_name] = df["a_secons"] + df["b_seconds"]
    
[/code]

出力結果

[code]

    name,a_seconds,b_seconds,a_b_hours
    Alice,3600,1800,1.5
    Bob,3600,3600,2
    
[/code]

# 質問

上記の機能を提供するために、コマンドの外から、Pythonスクリプトを渡すにはどうすればよいでしょうか？  
以下のようなコマンドを実行できるようにしたいです。

[code]

    $ python main.py 3
    num = 3
    
    $ python main.py 3 --script custom.py
    num * num = 9
    
[/code]

main.py

[code]

    def custom_print(num: int):
        """実際には、10～100行の処理を想定している"""
        print(f"num = {num}")
    
    if if __name__ == "__main__":
        parser = argparse.ArgumentParser()
        parser.add_argument("num", type=int, help="出力する整数")
        parser.add_argument("--script", help="外部から注入するスクリプト")
        args = parser.parse_args()
    
        if args.script is not None:
            # args.scriptを使ってcustom_printを変更する
            custom_print = ...
    
        custom_print(args.num)
    
    
[/code]

custom.py  
カスタマイズしたい場合、Pythonスクリプトを作成して、このファイルをコマンドに渡します。

[code]

    def custom_print(num: int):
        print(f"num * num = {num*num}")
    
    
[/code]

### 考えたこと

  * `exec`を使えばよいんですかね？


CC BY-SA 4.0
2019-07-04T05:32:46.113
56371
2019-07-08T17:02:41.807
2019-07-08T17:02:41.807
3060
19524
question
python command-line
CLIツールにPythonスクリプトを渡して、柔軟にカスタマイズできるようにしたい


`exec`でいいんじゃない?と思いますが、ちゃんと実装するならば`importlib`モジュールを使いたいところです。(何を持って「ちゃんと実装」なのか、と突っ込まれると辛いところですが)

そんなわけで興味があったので、調べました。  
調べた、と言っても、リファレンスマニュアルを眺めていたら[そのまんまのサンプル](https://docs.python.org/ja/3/library/importlib.html#importing-
a-source-file-directly)があっただけなんですが。

[code]

    # coding: utf-8
    # hoge.py
    
    import sys, importlib.util, os
    
    if __name__ == '__main__':
        if len(sys.argv) <= 1:
            sys.stderr.write('%s <script>\n' % sys.argv[0])
            sys.exit(1)
    
        filename = sys.argv[1]
        name = os.path.splitext(os.path.basename(filename))[0]
    
        print('load module.')
        spec = importlib.util.spec_from_file_location(name, filename)
        module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(module)
        sys.modules[name] = module
    
        print('call function.')
        module.func()
    
[/code]

[code]

    # coding: utf-8
    # piyo.py
    
    import sys
    
    print('load piyo')
    
    def func():
        print('piyo.func().')
    
[/code]

実行結果。

[code]

    $ python3 hoge.py piyo.py
    load module.
    load piyo.
    call function.
    piyo.func()
    
[/code]


CC BY-SA 4.0
2019-07-04T13:34:20.303
56393
2019-07-04T13:34:20.303
15473
56371
answer