Skip to content

Instantly share code, notes, and snippets.

@yumemio
Created July 30, 2023 10:08
Show Gist options
  • Save yumemio/78aa742bbcaabe6cc2930910e5e0e070 to your computer and use it in GitHub Desktop.
Save yumemio/78aa742bbcaabe6cc2930910e5e0e070 to your computer and use it in GitHub Desktop.
Google Colab の変数について.ipynb
Display the source blob
Display the rendered blob
Raw
{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"provenance": [],
"toc_visible": true,
"authorship_tag": "ABX9TyPVZbFI21rh8rHBc/o3xzhe",
"include_colab_link": true
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python"
}
},
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "view-in-github",
"colab_type": "text"
},
"source": [
"<a href=\"https://colab.research.google.com/gist/yumemio/78aa742bbcaabe6cc2930910e5e0e070/google-colab.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
]
},
{
"cell_type": "markdown",
"source": [
"# [Colab, Jupyter] シェルコマンドや %env の中で変数を使う\n",
"\n",
"> このノートブックと同じ内容を、[ブログ yumem.io の記事](https://yumem.io/posts/jupyter-variables/) でも紹介しています。\n"
],
"metadata": {
"id": "Hq7xGOzsQ6MN"
}
},
{
"cell_type": "markdown",
"metadata": {
"id": "dF4veGYPveuH"
},
"source": [
"## TL;DR\n",
"\n",
"Colab や Jupyter Notebook の**シェルコマンド・`%env` マジック**で、**Python 変数と環境変数**を正しく扱う方法を紹介します。\n",
"\n",
"説明はいいから概要だけ知りたい、という多忙なエンジニア/データサイエンティストの皆さんは、次の表をご覧ください。\n",
"\n",
"### 表: Colab / Jupyter におけるPython変数・環境変数のアクセス方法\n",
"\n",
"|| Python の変数にアクセス| 環境変数にアクセス|\n",
"|---|---|---|\n",
"|シェルコマンド内で|`!echo {py_var}` / `!echo $py_var`†|`!echo $$PATH`|\n",
"|`%env` コマンド内で|`%env key={py_var}` / `%env key=$py_var`|`%env key={os.environ['PATH']}`|\n",
"|Python のセル内で|`py_var`|`os.environ['PATH']`|\n",
"\n",
"> † **個人的には非推奨**。`py_var` が Python 変数として定義されていない場合、`$py_var` がシェルの変数と解釈されて、紛らわしいことになる。 \n",
"\n",
"再設定頻度が高い環境変数2強の `PATH` と `PYTHONPATH` (個人の感想です)は、次のように設定するとよいです。\n",
"\n",
"* `PATH` の設定\n",
" ```python\n",
" import os\n",
" %env PATH=/path/to/my/bin:{os.environ['PATH']}\n",
" ```\n",
"* `PYTHONPATH` の設定(`%env PYTHONPATH` は使わない)\n",
" ```python\n",
" import sys\n",
" sys.path.append('/path/to/my/pylib')\n",
" ```"
]
},
{
"cell_type": "markdown",
"source": [
"## まえがき\n",
"\n",
"Jupyter Notebook、皆さんは使ってますか?\n",
"\n",
"Python などのコードと Markdown で書いた文章を1つのドキュメント形式で扱えるツールです。\n",
"[Google Colabolatory](https://colab.research.google.com) のドキュメント形式としても採用されているので、見たことがある人も多いと思います。\n",
"\n",
"ところで、Jupyter Notebook の中で環境変数をセットしたり、シェルコマンド内で変数展開したりする場合、注意すべきポイントがあります。\n",
"\n",
"例えば...\n",
"\n",
"* 環境変数を設定するときは、独自のコマンド(`%env`コマンド)を使う\n",
"* 環境変数の設定時に Python の変数を利用できる\n",
"* Python 変数をシェルコマンド内で使うことができる(しかも記法が2通りある)\n",
"\n",
"などなど。\n",
"\n",
"この記事では、素の Python とも UNIX シェルとも違う、Jupyter Notebook 独特の変数操作についてまとめます。"
],
"metadata": {
"id": "q_NcBjX24qEk"
}
},
{
"cell_type": "markdown",
"source": [
"## はじめに - 用語の整理\n",
"\n",
"Colab やら Jupyter やら、いろいろな用語が出てくるので、ざっくり整理しておきましょう。\n",
"そんなの知ってるわい、って人は読み飛ばしてください。\n",
"\n",
"* **Colab**: Python コードを実行できる仮想サーバ。Google が運営。\n",
"* **Jupyter Notebook**: コードと Markdown テキストをまとめたファイル(ノートブック)を編集できる**Web アプリ**\n",
"* **IPython**: Python シェル (`python3` コマンドで出てくる対話的なやつ) の高機能版\n",
"\n",
"IPython を Web UI で操作できるようにしたのが Jupyter Notebook で、それを Google がホスティングしてくれるのが Colab。\n",
"...と考えれば、この記事を読むうえでは問題ありません。\n",
"\n",
"記事のなかで「IPython では云々」という説明が登場しますが、これは IPython が裏で動いている Jupyter Notebook や、Jupyter Notebook をベースにしている Colab にも当てはまります。\n",
"\n",
"細かいことを言えば、Jupyter Notebook のカーネルは Python 以外に R や Julia も選べるんですが、それは今回は考慮しない、ということで...。"
],
"metadata": {
"id": "fH5ybS-aJ43D"
}
},
{
"cell_type": "markdown",
"metadata": {
"id": "e9OR2RvENOmF"
},
"source": [
"## 環境変数を定義するには `%env` を使う\n",
"\n",
"Jupyter Notebook や Colab を使い始めてしばらく経つと、「環境変数をどうやって定義するのか」という問題が出てきます。\n",
"\n",
"普通は `bash` などのUNIXシェルを立ち上げて...\n",
"\n",
"```console\n",
"$ export MYAPP_SECRET=/var/lib/myapp/secret.json\n",
"```\n",
"\n",
"などと定義すればいいのですが、\n",
"ノートブックのセルに書けるのは基本的に Python のコードなので、困るわけです。\n",
"\n",
"[IPython のドキュメンテーション](https://ipython.readthedocs.io/en/stable/interactive/reference.html#system-shell-access)を調べると、`!ls` のように `!` を先頭につけるとシェルコマンドを実行できる、ということがわかります。\n",
"そうすると、`!export my_var=value` で環境変数を定義できそうな気がしますが、これは**誤り**。\n",
"\n",
"[%env](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-env) というマジックコマンド(`%` で始まる IPython の特殊コマンド)を使う必要があります。\n",
"\n",
"> 👍 環境変数を設定するには `%env name=value` を使う(引用符は**不要**) \n",
"> (`%env name value` もOK)\n",
">\n",
"> 🚫 `!export` では環境変数をセットできない\n",
">\n",
"> 🚫 `!name=value` でシェル変数をセットすることもできない"
]
},
{
"cell_type": "code",
"metadata": {
"id": "bmbXHMfwNvFt",
"colab": {
"base_uri": "https://localhost:8080/"
},
"outputId": "4820e5a6-386c-4717-82ea-8a8782b3e32f"
},
"source": [
"# ✅ 環境変数を定義するには %env を使う\n",
"%env my_config=/content/config.yaml\n",
"!env | grep my_config\n",
"# (出力)→ my_config=/content/config.yaml\n",
"\n",
"# ❌ !export はだめ\n",
"!export my_images=\"/content/train/\"\n",
"!env | grep my_images\n",
"# → (空)\n",
"\n",
"# ❌ !<name>=<value> もだめ\n",
"!my_json=\"/content/labels.json\"\n",
"!env | grep my_json\n",
"# → (空)"
],
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"env: my_config=/content/config.yaml\n",
"my_config=/content/config.yaml\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"なぜ `!export` や `!<name>=<value>` がダメかというと、`!cmd` に書いたコマンドは個別のサブシェル内で実行されるためです。\n",
"\n",
"つまり `!export myvar=value` と定義しても、`export` コマンドの実行が終わると同時にサブシェルが終了してしまうので、設定が引き継がれないわけですね。"
],
"metadata": {
"id": "LWiOzH0U5W_r"
}
},
{
"cell_type": "markdown",
"source": [
"## 変数を使う\n",
"\n",
"せっかく定義した変数も、使い方が分からなければ意味がありません。\n",
"\n",
"というわけで、今度は変数展開について見ていきましょう。"
],
"metadata": {
"id": "EMtiOh1J5cnk"
}
},
{
"cell_type": "markdown",
"source": [
"### Python 変数展開機能\n",
"\n",
"IPython は、シェルコマンド(`!cmd`)や `%env` マジック内で **Python の変数を展開する** という便利機能をもっています。\n",
"\n",
"書き方は2通りで、`$my_var` と `{my_var}` があります。\n",
"\n",
"...と説明するよりも、実例を見た方が早いですね。\n"
],
"metadata": {
"id": "2YYmcZOD5dsu"
}
},
{
"cell_type": "code",
"source": [
"# Python の変数を定義\n",
"my_var = \"gs://foo/bar\"\n",
"\n",
"# {my_var} や $my_var で展開できる\n",
"!echo \"秘密のファイル: {my_var}\"\n",
"# → 秘密のファイル: gs://foo/bar\n",
"!echo \"秘密のファイル: $my_var\"\n",
"# → 秘密のファイル: gs://foo/bar"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "4ZZAa9we5fo9",
"outputId": "40bf509b-ae4c-4f08-fec8-707836b6f55b"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"秘密のファイル: gs://foo/bar\n",
"秘密のファイル: gs://foo/bar\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"`{my_var}` 記法の場合、`{my_secret['height']}` や `{129.3 / 1.293 ** 2}` のように式を書くことも可能です。\n",
"\n",
"(`$my_var` では複雑な式の評価はできず、変数の展開しかできない)\n",
"\n",
"> 📔 **シェルコマンドでは、`$my_var` が環境変数として展開されることもある**\n",
">\n",
"> `$my_var` 記法は `bash` のパラメタ展開と被るじゃん、と思ったあなたは鋭い。\n",
">\n",
"> シェルコマンドでは、`my_var` が Python 変数と環境変数の両方で定義されている場合、Python 変数が優先されます。\n",
">\n",
"> Python 側で未定義の変数のみ、環境変数として展開されます。"
],
"metadata": {
"id": "i2rgpQCT5hE1"
}
},
{
"cell_type": "code",
"source": [
"%env runtime=bash\n",
"!echo \"$runtime の勝ち\"\n",
"# → bash の勝ち\n",
"\n",
"runtime = \"Python\"\n",
"!echo \"$runtime の勝ち\"\n",
"# → Python の勝ち\n",
"\n",
"# %env を再定義しても Python 変数が優先\n",
"%env runtime=bash\n",
"!echo \"$runtime の勝ち\"\n",
"# → Python の勝ち"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "4KmElAB25kfE",
"outputId": "fb54e3f6-bb5f-4b1b-b2b7-e6368e0e237f"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"env: runtime=bash\n",
"bash の勝ち\n",
"Python の勝ち\n",
"env: runtime=bash\n",
"Python の勝ち\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"> このように、`$my_var` 記法は Python 変数と環境変数のどちらなのか見た目ではわからない、という欠点があります。\n",
">\n",
"> `{my_var}` はこのような曖昧さがないので、個人的には `{my_var}` 記法がオススメです"
],
"metadata": {
"id": "42_GQXSm5pyk"
}
},
{
"cell_type": "markdown",
"source": [
"### シェルコマンドで環境変数にアクセス: `$$MY_VAR`\n",
"\n",
"シェルコマンドで環境変数にアクセスしたい場合、`$$PATH` のように `$` をエスケープするとよいです。\n",
"\n",
"こうすると、たとえ `PATH` という名前の Python 変数があったとしても、必ず環境変数の `PATH` が呼び出されます。\n",
"\n",
"(そんな紛らわしい変数名をつける人がいるのか、という点は置いておいて...)"
],
"metadata": {
"id": "soUVM2TH5r98"
}
},
{
"cell_type": "code",
"source": [
"PATH = \"/path/to/my/secret/video.m4a\" # まぎらわしい変数\n",
"\n",
"# ❌ $PATH は Python 変数として展開される場合がある\n",
"!echo $PATH\n",
"# → /path/to/my/secret/video.m4a\n",
"\n",
"# ✅ $$PATH は必ず環境変数として展開される\n",
"!echo $$PATH\n",
"# → /opt/bin:/usr/local/nvidia/bin:/usr/local/cuda/bin:/usr/local/sbin:..."
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "ykXQ-HeQ5uNr",
"outputId": "c961cb12-3d2a-4d7d-911a-a744a248a8f9"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"/path/to/my/secret/video.m4a\n",
"/opt/bin:/usr/local/nvidia/bin:/usr/local/cuda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/tools/node/bin:/tools/google-cloud-sdk/bin\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"ちなみに、`${my_var}` のように書くと期待通りに動かないので、避けるのが賢明です。\n",
"\n",
"(Jupyter が `{my_var}` を Python 変数として展開した後に、シェルが `$my_varの値` を展開しようとする、いわゆる「変数名が変数」状態になる)\n"
],
"metadata": {
"id": "0aS_ThF15zCi"
}
},
{
"cell_type": "markdown",
"source": [
"### `%env` マジックで環境変数にアクセス: `{os.environ['MY_VAR']}`\n",
"\n",
"`%env` マジック内では動作が少し異なり、 `$$PATH` も `$$SHELL` も環境変数としては展開されません。\n",
"\n",
"`%env` の定義時に環境変数を使いたい場合、 Python の `os.environ` から取得するのがベストです。\n",
"\n",
"例えば `MYAPP_ROOT` という環境変数を参照して `MYAPP_LIB` を定義したい場合、次のようにするとうまくいきます。"
],
"metadata": {
"id": "QiJetVYa5xzy"
}
},
{
"cell_type": "code",
"source": [
"# MYAPP_ROOT の定義\n",
"%env MYAPP_ROOT=/path/to/my_app\n",
"\n",
"# MYAPP_LIB の定義\n",
"import os\n",
"%env MYAPP_LIB={os.environ['MYAPP_ROOT']}/lib"
],
"metadata": {
"id": "ose2ae5751rV",
"colab": {
"base_uri": "https://localhost:8080/"
},
"outputId": "6bb7d5cd-d615-449f-9cab-c77988e773b5"
},
"execution_count": 25,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"env: MYAPP_ROOT=/path/to/my_app\n",
"env: MYAPP_LIB=/path/to/my_app/lib\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"### よくあるケース: `PATH` を通す\n",
"\n",
"この手法を使う典型例が、`PATH` にディレクトリを追加してコマンドを呼び出しやすくする、というケース。\n",
"いわゆる「[パスを通す](https://linuc.org/study/knowledge/349/)」ってやつですね。\n",
"\n",
"`bash` だと、次のようなコマンドですが...\n",
"\n",
"```console\n",
"$ export PATH=$PATH:/path/to/my/bin\n",
"```\n",
"\n",
"Jupyter Notebook でこのコマンドに相当するのが、`os.environ['PATH']` を使った次の書き方です。"
],
"metadata": {
"id": "BXioW0jROsBv"
}
},
{
"cell_type": "code",
"source": [
"import os\n",
"%env PATH={os.environ['PATH']}:/path/to/my/bin"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "NAa1x6P3OvAX",
"outputId": "67f2b065-fb7e-4e8c-e3b9-31a1985d8ec4"
},
"execution_count": 26,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"env: PATH=/opt/bin:/usr/local/nvidia/bin:/usr/local/cuda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/tools/node/bin:/tools/google-cloud-sdk/bin:/path/to/my/bin\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"### `PYTHONPATH` を変更するときは `sys.path.append()` を使う\n",
"\n",
"パスつながりで、Python のライブラリ検索時に使う `PYTHONPATH` についても一言。\n",
"\n",
"**`PYTHONPATH` を `%env` で再定義しても Jupyter セッションは追加したパスを見てくれません**。\n",
"\n",
"`PYTHONPATH` は Jupyter Notebook の立ち上げ時に読み出されるためです。\n",
"\n",
"`PYTHONPATH` にパスを追加するときは、`sys.path.append()` を使いましょう。"
],
"metadata": {
"id": "oRK3oiPHOwyy"
}
},
{
"cell_type": "code",
"source": [
"# /content/my_lib に inquisitor モジュールを置く\n",
"!mkdir -p /content/my_lib\n",
"script = \"\"\"def inquisite():\n",
" print('まさかの時のスペイン宗教裁判!')\"\"\"\n",
"!echo \"{script}\" > /content/my_lib/inquisitor.py\n",
"\n",
"# ❌ PYTHONPATH は、Jupyter セッション内で再定義しても意味がない\n",
"import os\n",
"%env PYTHONPATH=/content/my_lib:{os.environ['PYTHONPATH']}\n",
"\n",
"import traceback\n",
"try:\n",
" import inquisitor\n",
" inquisitor.inquisite()\n",
"except:\n",
" print(traceback.format_exc())\n",
"# → ModuleNotFoundError: No module named 'inquisitor'\n",
"\n",
"# ✅ sys.path.append() を使う\n",
"import sys\n",
"sys.path.append(\"/content/my_lib/\")\n",
"import inquisitor\n",
"inquisitor.inquisite()\n",
"# → まさかの時のスペイン宗教裁判!"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "0FuP8YqxO0WM",
"outputId": "269ea905-9bae-4a89-996a-227acf5e70af"
},
"execution_count": 29,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"env: PYTHONPATH=/content/my_lib:/content/my_lib:/content/my_lib:/env/python\n",
"Traceback (most recent call last):\n",
" File \"<ipython-input-29-f8ca0b76a534>\", line 13, in <cell line: 12>\n",
" import inquisitor\n",
"ModuleNotFoundError: No module named 'inquisitor'\n",
"\n",
"まさかの時のスペイン宗教裁判!\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"### 未展開の変数が1つでもあれば、展開はされない\n",
"\n",
"余談ですが、**展開できない(未定義の)変数が1個でもあれば、同じ行内にあるすべての変数が展開されません**。"
],
"metadata": {
"id": "FZRcs6iMO19_"
}
},
{
"cell_type": "code",
"source": [
"superhero = \"バットマン\"\n",
"villain = \"ジョーカー\"\n",
"\n",
"# ❌ sidekick が未定義なので、どの変数も展開されない\n",
"!echo \"{superhero} が {sidekick} の助けを借りて {villain} と戦う物語\"\n",
"# → {superhero} が {sidekick} の助けを借りて {villain} と戦う物語"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "Gw1-xqitPNmm",
"outputId": "d55db74b-584b-4c21-fcdf-59d580824b20"
},
"execution_count": 30,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"{superhero} が {sidekick} の助けを借りて {villain} と戦う物語\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"「`!xargs -I{}`」 の `{}` とか、 「`# このAPIは高額なので注意 $$$`」 というコメントの `$$$` も対象なので、ご注意を。"
],
"metadata": {
"id": "Ckwg3eP8QdNq"
}
},
{
"cell_type": "markdown",
"source": [
"## まとめ\n",
"\n",
"...という諸々の仕様をまとめたのが、冒頭の表になります。\n",
"\n",
"せっかくなので再掲しましょう。\n",
"\n",
"| | Python の変数にアクセス | 環境変数にアクセス |\n",
"|--------------------------------|-------------------------|-------------------------------------|\n",
"| シェルコマンド内で | `!echo {py_var}`{{< br >}}`!echo $py_var`{{< sup >}}†{{< /sup >}} | `!echo $$PATH` |\n",
"| `%env` コマンド内で | `%env key={py_var}`{{< br >}}`%env key=$py_var` | `%env key={{< br >}}{os.environ['PATH']}` |\n",
"| Python のセル内で | `py_var` | `os.environ['PATH']` |\n",
"\n",
"> † **個人的には非推奨**。`py_var` が Python 変数として定義されていない場合、`$py_var` がシェルの変数と解釈されて、紛らわしいことになる。 \n",
"\n",
"### `PATH` の設定\n",
"\n",
"```python\n",
"import os\n",
"%env PATH=/path/to/my/bin:{os.environ['PATH']}\n",
"```\n",
"\n",
"### `PYTHONPATH` の設定(`%env PYTHONPATH` は使わない)\n",
"\n",
"```python\n",
"import sys\n",
"sys.path.append('/path/to/my/pylib')\n",
"```"
],
"metadata": {
"id": "RmPTwV0gPQBL"
}
},
{
"cell_type": "markdown",
"source": [
"◆"
],
"metadata": {
"id": "JmFz6gc0QjXb"
}
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment