91357


表題の通りCGIに以下の記述でインポートしようとするとエラーになります。

[code]

    import matplotlib.pyplot as plt 
    
[/code]

**環境**  
Windows Server 2012  
Python 3.10.5  
Apache 2.4.41

下記のCGIを動かすと Internal Server Error 500 になります。

Apacheのエラーログには RuntimeError: Could not determine home directory. となっています。

[code]

    #!D:/Python/python.exe
    
    import os
    import mysql.connector as mydb
    import matplotlib.pyplot as plt
    
    print('Content-Type: text/html')
    print('')
    print('test')
    
[/code]

上記CGIから `import matplotlib.pyplot as plt` を除くと正常に動きます。

また、コマンドプロンプト上では以下でグラフが正常に表示されます。

[code]

    import matplotlib.pyplot as plt
    
    x=[1,2,3,4]
    y=[10,20,30,40]
    
    plt.bar(x,y)
    plt.show()
    
[/code]

これまでもxlwings等でエクセルにデータを出力しています。  
今回グラフを挿入しようと思いmatplotlibをインストールしてみたのですが、  
上記のようにエラーになっています。

原因が分からなくて困っています。よろしくお願いいたします。

**追記**  
これまでxlwingsを使ってエクセルに出力出来ていたのですが、  
`pip install matplotlib` とインストールしたら、`import xlwings as xw` で同じエラーになってしまいます。  
matplotlibをアンインストールすると、このエラーは解消されます。

**追記２**  
oririさん、kunifさん、quickquipさん  
ありがとうございます。

ApacheはローカルのAdministrator（.\Administrator）で実行しています。  
サーバーにログインしているユーザーもローカルのAdministratorです。  
[![画像の説明をここに入力](https://i.stack.imgur.com/J6vAl.png)](https://i.stack.imgur.com/J6vAl.png)  
また、仮想環境は構築していません。

C:\Users\Administrator.matplotlib フォルダには、  
fontlist-v330.json というファイルがありました。

教えていただいたTracebackの結果ですが以下のようになりました。  
（Apacheのエラーログと同じです）

[code]

    Traceback (most recent call last):
      File "D:\WEB\recent_py\master\M99_F01M.py", line 9, in <module>
        import matplotlib.pyplot as plt
      File "D:\Python\lib\site-packages\matplotlib\__init__.py", line 890, in <module>
        dict.update(rcParams, _rc_params_in_file(matplotlib_fname()))
      File "D:\Python\lib\site-packages\matplotlib\__init__.py", line 567, in matplotlib_fname
        for fname in gen_candidates():
      File "D:\Python\lib\site-packages\matplotlib\__init__.py", line 564, in gen_candidates
        yield os.path.join(get_configdir(), 'matplotlibrc')
      File "D:\Python\lib\site-packages\matplotlib\__init__.py", line 273, in wrapper
        ret = func(**kwargs)
      File "D:\Python\lib\site-packages\matplotlib\__init__.py", line 510, in get_configdir
        return _get_config_or_cache_dir(_get_xdg_config_dir)
      File "D:\Python\lib\site-packages\matplotlib\__init__.py", line 471, in _get_config_or_cache_dir
        configdir = Path.home() / ".matplotlib"
      File "D:\Python\lib\pathlib.py", line 1000, in home
        return cls("~").expanduser()
      File "D:\Python\lib\pathlib.py", line 1440, in expanduser
        raise RuntimeError("Could not determine home directory.")
    RuntimeError: Could not determine home directory.
    
[/code]

皆さんがおっしゃるようにApacheの環境が影響しているのかと思いますが。。。  
他に何か確認すべきところがありましらご教示下さい。

**追記３**  
みなさんからアドバイスをいただきホームディレクトリが取得できていないことが原因かと思いCGI上で以下を試してみたら同様のエラーが出ました。  
（matplotlibはアンインストールしています）

[code]

    from pathlib import Path
    print(str(Path.home()))
    
[/code]

エラー内容

[code]

    Traceback (most recent call last):
      File "D:\WEB\recent_py\master\M99_F01M.py", line 25, in <module>
        import matplotlib.pyplot as plt
    ModuleNotFoundError: No module named 'matplotlib'
    Traceback (most recent call last):
      File "D:\WEB\recent_py\master\M99_F01M.py", line 40, in <module>
        home2 = str(Path.home())
      File "D:\Python\lib\pathlib.py", line 1000, in home
        return cls("~").expanduser()
      File "D:\Python\lib\pathlib.py", line 1440, in expanduser
        raise RuntimeError("Could not determine home directory.")
    RuntimeError: Could not determine home directory.
    
[/code]

コマンドプロンプトから同様の事をやると正常にパスが表示されます。

[code]

    from pathlib import Path
    print(str(Path.home()))
    C:\Users\Administrator
    
[/code]

このPath.home()が正常に取得、もしくは設定できれば問題が解決できそうなのですが、その方法が分からない状態です。


CC BY-SA 4.0
2022-09-26T07:55:13.817
91309
2022-09-29T05:06:54.520
2022-09-29T05:06:54.520
3060
54548
question
python matplotlib import
CGIから import matplotlib.pyplot as plt をインポートするとエラーになる


コードを書きたかったので回答欄に書きますが、Pythonの例外なら、以下のようなコードで完全なTracebackを保管して確認すると原因にせまれると思います。  
保管場所は適宜変更してください。

matplotlibのバックエンドかfontの設定あたりをホームディレクトリから読み出そうとしているのかと想像しましたが探せなかったので、完全なTracebackが欲しいと思いました。

[code]

    try:
        import matplotlib.pyplot as plt 
    except Exception as e:
        import traceback
        with open(r'/somewhere/exception.log', 'a') as w:
            traceback.print_exception(e, file=w)
    
[/code]

* * *

追記  
[https://github.com/matplotlib/matplotlib/blob/v3.5.2/lib/matplotlib/
**init**.py#L551](https://github.com/matplotlib/matplotlib/blob/v3.5.2/lib/matplotlib/__init__.py#L551)

[code]

        def gen_candidates():
            # rely on down-stream code to make absolute.  This protects us
            # from having to directly get the current working directory
            # which can fail if the user has ended up with a cwd that is
            # non-existent.
            yield 'matplotlibrc'
            try:
                matplotlibrc = os.environ['MATPLOTLIBRC']
            except KeyError:
                pass
            else:
                yield matplotlibrc
                yield os.path.join(matplotlibrc, 'matplotlibrc')
            yield os.path.join(get_configdir(), 'matplotlibrc')
            yield os.path.join(get_data_path(), 'matplotlibrc')
    
[/code]

この順番でファイルmatplotlibrcの実在をチェックするようなので`get_configdir()`する前で返るように、「カレントディレクトリを調べてそこにmatplotlibrcをコピーしてくる」か「存在するmatplotlibrcを環境変数`MATPLOTLIBRC`に指定する」が近道な気はします。(Windows関連の設定は不明なのでこのくらいで)

デフォルトのmatplotlibrcは、  
`D:\Python\lib\site-packages\matplotlib\mpl-data\`  
の中にあると思います。


CC BY-SA 4.0
2022-09-27T02:02:39.773
91323
2022-09-27T07:22:59.033
2022-09-27T07:22:59.033
12274
12274
91309
answer


> RuntimeError: Could not determine home directory

に関してはスクリプト先頭近くに環境変数設定するとよいかもです

[code]

    import os
    
    os.environ['HOME'] = 'D:/Python'
    
[/code]

* * *

> `pip install matplotlib` とインストールしたら、`import xlwings as xw` で同じエラーになってしまいます。

こちらは不明  
というかどんなエラーでしょうか？ 上記 "RuntimeError 〜" と同じ？  
"Could not determine home directory" であるなら直るかも


CC BY-SA 4.0
2022-09-27T07:59:17.257
91332
2022-09-27T07:59:17.257
43025
91309
answer


情報を整理して原因と対処をまとめると以下になります。

matplotlibがimportの際の初期化処理で以下のディレクトリ・ファイルを探してフォントの情報を取得しますが、その中で実行しているユーザーのホームディレクトリの情報を環境変数から読み取ろうとして、それが無いためにエラーとなっているようです。

[【Python】matplotlibで日本語フォントを使用する](https://izadori.net/python-matplotlib-
fonts/)

> Windowsの場合は...`C:\Users\（ユーザー名）\.matplotlib`というディレクトリが作成されているはずです。

>
> .matplotlibディレクトリ内に`fontlist-v{バージョン}.json`というファイルがあり、この中に利用可能なフォントの一覧が記録されています。

Pythonのエラーメッセージ `configdir = Path.home() / ".matplotlib"`と`return
cls("~").expanduser()` から、使われているメソッドと機能：  
[Path.home](https://docs.python.org/ja/3/library/pathlib.html#pathlib.Path.home)

> ユーザーのホームディレクトリ
> ([os.path.expanduser()](https://docs.python.org/ja/3/library/os.path.html#os.path.expanduser)での`~`の返り値)
> を表す新しいパスオブジェクトを返します。ホームディレクトリが解決できない場合は、`RuntimeError`を送出します。

[os.path.expanduser(path)](https://docs.python.org/ja/3/library/os.path.html#os.path.expanduser)

> On Windows, USERPROFILE will be used if set, otherwise a combination of
> HOMEPATH and HOMEDRIVE will be used. An initial ~user is handled by checking
> that the last directory component of the current user's home directory
> matches USERNAME, and replacing it if so.  
>  Windows
> では、設定されている場合は`USERPROFILE`が使用されます。設定されていない場合は、`HOMEPATH`と`HOMEDRIVE`の組み合わせが使用されます。
> 最初の`~user`は、現在のユーザーのホーム ディレクトリの最後のディレクトリ
> コンポーネントが`USERNAME`と一致するかどうかを確認し、一致する場合はそれを置き換えることによって処理されます。

エラーメッセージに記録されているメソッドの呼び出しスタックと発生している`RuntimeError`の通知と上記説明が合っています。

これは Apache から CGI としてPythonスクリプトが実行される際に、Apacheの環境変数(USERPROFILE, HOMEDRIVE,
HOMEPATH)が子プロセスのPythonに引き継がれないからでしょう。  
なお、質問記事によるとApacheはサービスとして登録・自動起動され、`Administrator`ユーザーの権限で動作しているとのこと。

ちなみにxlwingsでも発生するのは、以下xlwingsのsetup.pyにmatplotlibの名前があるので、installされていなければ使わないが、あればimportを試みるといった動作をしているのでしょう。  
[xlwings/setup.py](https://github.com/xlwings/xlwings/blob/main/setup.py)

[code]

    ...
    extras_require = {
    ...
            "matplotlib",
    ...
    
[/code]

* * *

対策としては以下の2種類が考えられます。Apacheでの設定の方が正統的な感じがします。

  * ApacheでCGIのPython起動時に環境変数を引き継ぐよう(あるいは新規)に設定する。
  * CGIのPythonスクリプト側で補完して設定する。

■Apache側での設定では、mod_envを有効にしたり、CGIへ渡す環境変数をhttpd.confか.htaccessの関連に設定する必要があるでしょう。  
[Apache の環境変数](https://httpd.apache.org/docs/current/ja/env.html)  
[環境変数を設定する(あるいは取り除く)](https://l-w-i.net/t/apache/env_001.txt)

PHPとかPerlの資料ですがPassEnvの記述があったので：  
[httpd.confへ設定](https://qiita.com/yuta_vamdemic/items/5ea69fd51c5870960917#httpdconf%E3%81%B8%E8%A8%AD%E5%AE%9A)  
[デスクトップCGIでWebとデスクトップを融合する 第1回](https://codezine.jp/article/detail/397)

.htaccessでの設定も出来るもよう：  
[.htaccess -
mod_env](https://murashun.jp/article/programming/htaccess/htaccess-
mod_env.html)

■Pythonスクリプト側での対処としては、matplotlibをimportする前に環境変数に`USERPROFILE`を設定してしまうことですね。  
コメントでは直接的な値を無条件に設定するよう書きましたが、汎用的には既に存在するかを考慮して対応できるようにした方が良いでしょう。  
[os.getenv(key,
default=None)](https://docs.python.org/ja/3.9/library/os.html#os.getenv)

> 環境変数 key が存在すればその値を返し、存在しなければ default を返します。key、default、および返り値は文字列です。

出来れば、起動したユーザーは誰かも取得出来れば良いのですが、結局環境変数を使うようなので難しいですね。  
[Is there a portable way to get the current username in
Python?](https://stackoverflow.com/q/842059/9014308)  
[getpass.getuser()](https://docs.python.org/ja/3/library/getpass.html)

> この関数は環境変数 LOGNAME USER LNAME USERNAME の順序でチェックして、最初の空ではない文字列が設定された値を返します。

関連するモジュールと記述の順番は以下になります。他は適当に問題無い場所に置けば良いでしょう。：

[code]

    import os
    
    if os.getenv('USERPROFILE') is None: #### 既に有ればそのまま使う、存在しない場合に以下で設定
        os.environ['USERPROFILE'] = r'C:\Users\Administrator'
        #### 可能ならばimport getpass; os.environ['USERPROFILE'] = 'C:\\Users\\' + getpass.getuser() に変更
    
    import matplotlib.pyplot as plt
    
[/code]


CC BY-SA 4.0
2022-09-28T11:36:09.080
91357
2022-09-28T11:36:09.080
26370
91309
answer


最終的に以下の方法で動作を確認できました。

**方法１**  
CGIの文頭に以下の1行を追加

[code]

    os.environ['USERPROFILE'] = r'C:\Users\Administrator'
    
[/code]

**方法２**  
Apacheのhttpd.conf に以下の1行を追加

[code]

    SetEnv USERPROFILE C:\Users\Administrator
    
[/code]


CC BY-SA 4.0
2022-09-29T04:48:38.190
91367
2022-09-29T04:48:38.190
54548
91309
answer