RHEL8/9系ディストリビューションでPython 3を使う

# 1. RHEL8で大きく変わったPython
RHEL8では、Pythonを取り巻く状況が大きく変わった。Python 3がデフォルトになり、Software Collectionsに代わってAppStreamが導入された。それ以外にも違う部分が多い。

そこで今回はRHEL8系ディストリビューションにおけるPython 3を説明する。ほとんどの内容はRHEL9でも同じだ。なお、RHEL7のPython事情は以下の記事に書いている。参考になる部分も多いので興味のある方はどうぞ。

* [RHEL7やCentOS 7、Amazon Linux 2でPython 3を使う](https://qiita.com/yamada-hakase/items/a4ad39b67a6d1625f9c7)

### 1-1. 対象環境

* Red Hat Enterprise Linux 8
* Red Hat Enterprise Linux 9
* CentOS 8 / Oracle Linux 8 / AlmaLinux 8などのRHEL8互換ディストリビューション
* Oracle Linux 9 / AlmaLinux 9 / RockyLinux 9などのRHEL9互換ディストリビューション

※ Amazon Linux 2はRHEL7に近いので、ここに書いたことは当てはまらない。
※ CentOS 8のyumリポジトリは停止している。そのため、先に「[CentOS 8でyum/dnfに失敗！](https://qiita.com/yamada-hakase/items/cb1b6124e11ca65e2a2b)」に書いてあることを実施する必要がある。

### 1-2. 参考情報

1. [Red Hat Enterprise Linux 8 のPython事情 - 赤帽ブログ](https://rheb.hatenablog.com/entry/rhel8-python)
2. [RHEL 8/Fedora 28で導入されたModularity - 赤帽ブログ](https://rheb.hatenablog.com/entry/201812-modularity)
3. [Python in RHEL 8 - Red Hat Developer](https://developers.redhat.com/blog/2018/11/14/python-in-rhel-8/)
4. [Red Hat Enterprise Linux 8 ご紹介 - Speaker Deck](https://speakerdeck.com/moriwaka/red-hat-enterprise-linux-8-goshao-jie)
5. [Red Hat Enterprise Linux 8のライフサイクルを理解する - Speaker Deck](https://speakerdeck.com/moriwaka/red-hat-enterprise-linux-8falseraihusaikuruwoli-jie-suru)

# 2. RHEL8/9におけるPythonの変更点

### 2.1. RHEL8
RHEL8における変更点は以下のとおり。それぞれについて説明する。また、参考資料1、3も興味があれば読んでほしい。ただし、いずれもBetaの時期に書かれたものなのでGA版と異なる部分がある。

* **RHEL8系ディストリビューションには次の3種類のPythonがある**
  * システムが利用するPython（Platform-Python）
  * Python 3。RHEL8系のデフォルトは3.6。RHEL9系は3.9
  * Python 2。おもに移行用
* **Platform-Pythonは`/usr/libexec/platform-python`にインストールされ、ユーザーが利用することは推奨されない。そのためパスも切られていない**
* **ミニマムインストールではPython 3がインストールされない。そのためインストール環境によっては`python`や`python3`を実行してもPythonが起動しない可能性がある**
* **新しいバージョンのソフトウエアを提供する仕組みとして、これまでのSoftware Collectionsに代わってAppStreamが導入された**
* **ユーザーが使うPythonはAppStreamからインストールする**
* **RHEL6やRHEL7にあったSoftware Colletionsは提供されない**

### 2.2 RHEL9

RHEL9における変更点は以下のとおり。もっとも大きな点は「Python 2」がサポートされなくなったことである。

* **Python 2がサポートされなくなった**
* **Python 3.9がデフォルトとなり、Full Application Streams Release Life Cycle。つまり、RHEL9のサポート期間中サポート**
* **9.2以降はPython 3.11がApplication Streams Release Life Cycle。Fullとは異なり、数年のサポ－トで今後切り変わる予定**

# 3. Pythonがインストールされていないことがある

RHEL8系ディストリビューションでは、Python 3.6がデフォルトだ。しかし、`python`コマンドを実行できないことがある。このあたりの状況を説明する。

```shell-session
$ python
-bash: python: command not found
```

### 3-1. システムが利用するPlatform-Python
RHEL8系ディストリビューションには次の3種類のPythonがある。

* システムが利用するPython（Platform-Python）
 * Python 3。RHEL8系のデフォルトは3.6。RHEL9系は3.9
* Python 2。おもに移行用


Platform-Pythonは、dnfなどシステムツールが内部的に利用するもので、必ずインストールされている。ただし、利用者が直接使うことは推奨されていないので、パスは切られていない。

```shell-session
$ /usr/libexec/platform-python -V
Python 3.6.8
```

しかし、Platform-Python以外はインストールオプションによってインストールされないことがある。そのため利用環境によっては`python`や`python3`コマンドを実行できない可能性がある。

```shell-session:こんな状況もあり得る
$ python
-bash: python: command not found

$ python2
-bash: python2: command not found

$ python3
-bash: /usr/bin/python3: No such file or directory
```

当然、環境によっては次のように表示されることもある。この種明かしは後ほど。

```shell-session
$ python -V　　★バージョンを指定しないで3.6.8になっていることに注目
Python 3.6.8

$ python2 -V
Python 2.7.17

$ python3 -V
Python 3.6.8
```

### 3-2. ユーザーが利用するPython（RHEL8のみ）

ユーザーが利用可能なPythonは以下のコマンドで確認できる。出力を見ると、2.7、3.6、3.8の3バージョンがあることがわかる。

```shell-session
$ dnf module list 'python*'

AppStream
Name      Stream      Profiles          Summary
python27  2.7 [d]     common [d]        Python programming language, version 2.7
python36  3.6 [d][e]  build, common [d] Python programming language, version 3.6
python38  3.8 [d]     build, common [d] Python programming language, version 3.8

Hint: [d]efault, [e]nabled, [x]disabled, [i]nstalled
```

次に現在インストールされているPythonを調べてみよう。一番簡単な方法は次のコマンドだろう。このように表示されたときはPython 3.6がインストールされている。

```shell-session
$ ls /usr/bin/python*
/usr/bin/python  /usr/bin/python3  /usr/bin/python3.6  /usr/bin/python3.6m
```

### 3-3. ユーザーが利用するPython（RHEL9のみ）

RHEL8ではAppStreamとしてPythonが提供されていたが、RHEL9では提供されていない。そのため`list`や`search`で検索する。

```shell-session
$ dnf list "python*"
★中略
python3.x86_64                          3.9.10-2.el9          @anaconda
★中略
python3.11.x86_64                       3.11.2-2.el9_2.1      appstream
```

```shell-session
$ ls /usr/bin/python*
/usr/bin/python  /usr/bin/python3  /usr/bin/python3.9
```

### 3-3. RHEL8/9のPythonまとめ
これまでわかったことは以下のとおり。次にインストール方法や切り替え方法を説明する。

* Platform-Pythonは必ずインストールされているが、利用者が使うことは推奨されない
* ユーザーが利用できるPythonはインストールされていないことがある
* インストールされていないときは、利用者が明示的にインストールする必要がある
* リポジトリからインストール可能なPythonのバージョンは2.7、3.6、3.8である（RHEL8のみ）
* リポジトリからインストール可能なPythonのバージョンは3.9、3.11である（RHEL9のみ）

# 4. Pythonのインストールとalternativesの活用（RHEL8）

Python 3がインストールされていないときは、明示的にインストールする必要がある。そこで、Python 3をインストールするとともに、シンボリックリンクを切り替えるalternativesについて説明する。

:::note warn
なお、RHEL9ではPythonを複数バージョンインストールしたときでもalternativesの設定は入っていない。そのため明示的にバーションを指定するときは`python3.11`のように実行する。もしくは、手動でalternativesを設定する（参考：[Use Alternatives command to set Python versions](https://www.centlinux.com/2023/03/use-alternatives-command-set-python-versions.html)）

インストールするときも、デフォルトの3.9以外は明示的にバージョンを指定する。
```
dnf install python3.11
```
:::

### 4.1. Python 3をインストールする

Python 3をインストールする。`python3`でなく`python36`を指定しても同じだ。

```shell-session
# dnf install python3 -y
```

インストールが終わると、バージョン3.6.8がインストールされたことがわかる。

```shell-session
$ python3 -V
Python 3.6.8
```

ただし、バージョンを指定しないと起動できない。このことは、お作法的に正しいことのだが、どうしても起動したいときもあるだろう。その方法を次で説明する。

```shell-session
$ python
-bash: python: command not found
```

### 4-2. alternativesによるシンボリックリンクの切り替え
Python 3をインストールしても`python`コマンドは無効のままだ。これを変更するにはシンボリックリンクを切り替える`alternatives`を使う必要がある。


### 4-2-1. alternativesの設定確認

現在の`alternatives`の設定を確認する。表示の見方は一番左が入力するコマンドで、一番右が実際に実行されるコマンドだ。

```shell-session
$ alternatives --list | grep python
python                  auto    /usr/libexec/no-python　★no-python
python3                 auto    /usr/bin/python3.6
```

この結果を見ると`python`は`/usr/libexec/no-python`を参照している。名前からもわかるように何も実行されない。


`python`で`python3`を起動する方法は`man unversioned-python`で紹介されている。

```shell-session:manの内容
UNVERSIONED-PYTHON(8)       System Manager's Manual      UNVERSIONED-PYTHON(8)

NAME
       unversioned-python - info on how to set up the `python` command.

SYNOPSIS
       unversioned-python

DESCRIPTION
       unversioned-python The "unversioned" `python` command (/usr/bin/python)
       is missing by default.   We  recommend  using  `python3`  or  `python2`
       instead.   If using the explicit versioned command is inconvenient, you
       can use `alternatives` to configure `python` to launch either Python  3
       or Python 2.

       Note:  The  `python3` or `python2` package needs to be installed before
       its functionality is selected.

EXAXPLES
       alternatives --config python

           Interactively select what the `python` command runs.

       alternatives --set python /usr/bin/python3

           Configure the `python` command to run Python 3

           Note: this is non-standard behavior according to [PEP 394].
```

### 4-2-2. alternativesの設定変更

`alternatives`の設定を変更するには次の二つの方法がある。今回は対話的に設定する。

対話的に実行：`alternatives --config python`
指定して実行：`alternatives --set python /usr/bin/python3`

```shell-session:対話的に設定する
# alternatives --config python

There are 2 programs which provide 'python'.

  Selection    Command
-----------------------------------------------
*+ 1           /usr/libexec/no-python
   2           /usr/bin/python3

Enter to keep the current selection[+], or type selection number: 2 ★2を入力
```

変更が終わると`python`で3.6を実行できるようになる。

```shell-session
$ python -V
Python 3.6.8

$ alternatives --list | grep python
python                  manual  /usr/bin/python3
python3                 auto    /usr/bin/python3.6
```

### 4-2-3. alternativesの設定詳細

`alternatives`について、もう少し詳しく説明する。シンボリックリンクを切り替えるだけならば`ln -s`コマンドでもよさそうだが、`alternatives`には`ln -s`にはない優れたメリットがある。そのことについて説明する。

`alternatives --display ＜設定名＞`で設定の詳細を確認できる。わかりづらいかもしれないが、`python`コマンドから`/usr/bin/python3`が起動されることがわかる。

```shell-session
$ alternatives --display python
python - status is manual.
 link currently points to /usr/bin/python3　★これが現在の設定
/usr/libexec/no-python - priority 404
 slave unversioned-python: (null)
 slave unversioned-python-man: /usr/share/man/man1/unversioned-python.1.gz
/usr/bin/python3 - priority 300
 slave unversioned-python: /usr/bin/python3 ★上記設定からリダイレクトされ、これが実行される
 slave unversioned-python-man: /usr/share/man/man1/python3.1.gz
Current `best' version is /usr/libexec/no-python.
```

次に`python3`の設定を確認してみよう。するとmanに加え、pip3など他のコマンドも表示される。

```shell-session
$ alternatives --display python3
python3 - status is auto.
 link currently points to /usr/bin/python3.6
/usr/bin/python3.6 - priority 1000000
 slave easy_install-3: /usr/bin/easy_install-3.6
 slave pip-3: /usr/bin/pip-3.6
 slave pip3: /usr/bin/pip3.6
 slave pydoc-3: /usr/bin/pydoc3.6
 slave pydoc3: /usr/bin/pydoc3.6
 slave pyvenv-3: /usr/bin/pyvenv-3.6
 slave python3-man: /usr/share/man/man1/python3.6.1.gz
Current `best' version is /usr/bin/python3.6.
```

つまり`alternatives`によって、一括でシンボリックリンクを変更できるだけでなく、システムとして統一したインターフェースを提供することで操作ミスを防止できるのだ。

`alternatives`はJDKの切り替えにもよく使われ、Ubuntuでは同等の`update-alternatives`が利用できる。

# 5. Python 3.8をインストールする

ここまでの説明が理解できれば2.7と3.8にも対応できると思うが、Python 3.8について説明する。

1. Python 3.8をインストールする。

```shell-session
# dnf install python38
```

2.`python3.8`コマンドでは3.8が起動するが、`python3`コマンドでは3.6が起動する。

```shell-session
$ python3.8 -V
Python 3.8.3

$ python3 -V
Python 3.6.8
```

**<font color="blue">ヒント：** パッケージ名は`python38`だがコマンドは`python3.8`であることに注意。</font>


3.設定を変更するには、同様に`alternatives`を使えばいい。

```shell-session
$ alternatives --display python3
python3 - status is auto.
 link currently points to /usr/bin/python3.6 ★この行が重要。3.6を指している。
/usr/bin/python3.6 - priority 1000000
 slave easy_install-3: /usr/bin/easy_install-3.6
 slave pip-3: /usr/bin/pip-3.6
 slave pip3: /usr/bin/pip3.6
 slave pydoc-3: /usr/bin/pydoc3.6
 slave pydoc3: /usr/bin/pydoc3.6
 slave pyvenv-3: /usr/bin/pyvenv-3.6
 slave python3-man: /usr/share/man/man1/python3.6.1.gz
/usr/bin/python3.8 - priority 3800
 slave easy_install-3: /usr/bin/easy_install-3.8
 slave pip-3: /usr/bin/pip-3.8
 slave pip3: /usr/bin/pip3.8
 slave pydoc-3: /usr/bin/pydoc3.8
 slave pydoc3: /usr/bin/pydoc3.8
 slave pyvenv-3: (null)
 slave python3-man: /usr/share/man/man1/python3.8.1.gz
Current `best' version is /usr/bin/python3.6.
```

4.`alternatives`で切り替えると`python3`コマンドが3.8を参照するようになる。

```shell-session
# alternatives --set python3 /usr/bin/python3.8

$ python3 -V
Python 3.8.3
```

# 6. Pythonのバージョンを明示的に指定するべし

manに書かれている「[PEP 394 -- The "python" Command on Unix-Like Systems](https://www.python.org/dev/peps/pep-0394/)」では、いくつかのリコメンデーションが紹介されている。

* `python2`コマンドはPython 2を起動
* `python3`コマンドはPython 3を起動
* `python`コマンドはインストールされているPython 3を起動

ここで重要なのは、スクリプトのトラブルを防止するために、Shebang（シェバン）――スクリプト先頭の`#!/path/to/file`――では`#!/usr/bin/python3`のように明示的にバージョンを指定するべきだと。

また、これまでの経緯として`/usr/bin/python`のようにバージョンを指定しないのは行儀が悪いとも書かれている。

まとめると、以下のとおり。

* **「/usr/bin/python3」のように明示的にバージョンを指定する**
* **小数点1桁までバージョンを指定するときは「/usr/bin/python3.8」のように指定する**
* **「/usr/bin/python」で起動するPythonのバージョンは環境依存なので、なるべく使用しない**

# 7. まとめ

* **RHEL8系ディストリビューションには次の3種類のPythonがある**
  * システムが利用するPython 3.6（Platform-Python）
  * Python 3。デフォルトは3.6。他に3.8も利用可能
  * Python 2。おもに移行用
* **RHEL9系ディストリビューションには次の2種類のPythonがある**
  * Python 3.9。Platform-Pythonでありフルサポート
  * Python 3.11。期間限定サポート
* **環境によってはPython 3がインストールされていないことがある。そのときには自分でインストールする必要がある**
* **複数バージョンのPythonを切り替えるにはalternativesを使用する(RHEL8のみ)**
* **Shebangにおける指定方法**
  * 「/usr/bin/python3」のように明示的にバージョンを指定する
  * 小数点1桁までバージョンを指定するときは「/usr/bin/python3.8」のように指定する
  * 「/usr/bin/python」で起動するPythonのバージョンは環境依存なので、なるべく使用しない