Twelve-Factor Appがデファクトになりつつある中、pythonのアプリを作る際も、設定はコードから切り離し環境変数に格納することも多いと思います。
staging環境やproduction環境は、service化するなりdocker化するなり、何らかの方法で環境変数を外部から与えると思いますが、ローカル開発環境においてどうやって環境変数を与えるかは結構悩みどころです。
この記事では、その辺を考えてみたいと思います。

考慮ポイント

基本的には上にあるものほど、優先度が高いと考えました。

  1. 環境を汚さない(環境変数が残ったままにしない)
    • 環境変数が残ったままになると、別のプログラムを同じterminalで実行して、不正な動作をしたり事故につながる
  2. 開発のスピードを落とさない
    • 実装から動作確認までのイテレートが短ければ短いほどよい(環境立ち上げに時間がかかるのは嫌)
  3. 運用の手間を増やさない
    • 余計な設定ファイルを管理したくない(.envいいけど、DockerFileはしんどい)
  4. 環境変数はコードの外側で設定したい
    • コードから切り離したい
    • 個人的にはコードの中で環境変数を設定するのはとても違和感があるww
  5. Windowsでも使える

対応案

direnv

direnv は、シンプルで強力なツールです。

  • 環境変数定義(.envrc)を置いてあるディレクトリ配下にcdした時だけ、定義を読み込み環境変数を設定してくれる
  • 子供や孫階層のディレクトリでも設定され、兄弟や親階層ではunloadされる

ただし、Windowsでは素直には使えないみたいで、Git BashやCygwinを使う必要があるっぽいです(issue参照のこと)。
考慮ポイント「1, 2, 3, 4」はOKで、「5」だけ微妙です。

python-dotenv

python-dotenvは、node.jsのdotenvのpython版で、.envに記載されているパラメータを、load_dotenv()するだけで環境変数に設定してくれるとてもシンプルなライブラリです。
考慮ポイント「1, 2, 3, 5」はOKですが、「4」がNGです。
「4」の違和感に目をつぶれば、シンプルで良さそうです。

[参考] python-dotenvの使い方

まず、ライブラリをインストールします。

pip install python-dotenv

あとはプログラムの中で環境変数を読み込みます。

from dotenv import load_dotenv
load_dotenv()

これだけです。

実際には、setenv.pyを作って

from dotenv import load_dotenv
load_dotenv(override=True)  # overrideで既存の環境変数があっても上書き

としておき、実行したいpythonプログラムの中で

import setenv

とすれば、良いと思います。

flake8を使っている場合は、'setenv' imported but unusedと怒られるので、

import setenv  # NOQA

として、チェック対象外にすると良いと思います。

.env

.envexport HOGE=fugaのように書いておき、そのスクリプトをsource .envで読み込み環境変数を設定してから、pythonのプログラムを実行します。
だだし、Windowsでは環境変数の設定方法がset HOGE=fugaになるし、source .envみたいなことができるかどうかがよく分かりません(長いことwindows触ってないので...)。
考慮ポイント「2, 3, 4」はOKですが、最優先事項である「1:環境を汚さない」が実現できないので、今回は除外しました。

Forman

ForemanはRubyGemsで公開されているライブラリです。Procfileにコマンド名: スクリプトの形式でコマンドを定義しておき、foreman run コマンド名を実行することで、コマンドを実行でき、その際に.envに記載されているパラメータを環境変数に設定してくれるツールです。
RubyGemsなのできっとWindowsでも使えるでしょう。
考慮ポイント「1, 2, 4, 5」はOKなのですが、「3:運用の手間」つまり、Procfileの管理という手間が増える点が微妙です。

Docker / Vagrant

pythonの公式イメージをベースにライブラリ依存関係を解決し環境変数も設定したイメージを作って、そのイメージを仮想VM上で動かしてsshで入ってpythonプログラムを実行するイメージです。
考慮ポイント「1, 4, 5」はOKなのですが、DockerFileなりVagrantFileなりを管理する「3:運用の手間」が増えるのと、イメージをロードする時間などを考えると「2:開発スピード」も落ちる可能性がありますので、今回は除外しました。

結論

Windowsのメンバーがいない場合は、direnv一択です。

Windowsのメンバーがいる場合はとても悩ましいですね。CygwinやGit Bashを使うことにメンバーが了承してくれるのであれば、direnvでいいと思います。
それが難しい場合は、python-dotenvforemanのいずれかになると思います。

所感

Dockerなどの仮想VMで動かさない場合、

  • pyenvでpythonのバージョンを変更する(仮想環境構築時に利用するため)
  • poetry で仮想環境を構築して、パッケージ管理をする
  • direnvで環境変数を管理する

という形で、pythonのローカル環境環境の独立性はほぼ守られるのではないかなと感じています。