Python3.0以降では、リスト内包でエラーが発生すると、トレースバックに <listcomp>
という不思議な関数名が表示されることがありました。
>>> def func():
... [1/0 for i in range(10)]
...
>>> f()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in func
File "<stdin>", line 2, in <listcomp>
ZeroDivisionError: division by zero
>>>
Python3.0から3.11までのリスト内包は、見えない関数を作成し、その関数内で値を生成する式を評価します。 例えば、[x**2 for x in range(10)]
という式は、次のように実行されていました。
def listcomp(x):
return x**2
result = []
for x in range(1):
result.append(listcomp(x))
辞書や集合の内包表記でも同じように関数が作られます。
これはリスト内包で使われている x
と同じ名前の変数が上書きされてしまうのを避けるためで、
x = 100
result = [x**2 for x in range(10)]
print(x)
という処理は、Python 2.7では 10
が出力されますが、3.0以降では 100
が出力されます。
ただ、暗黙的に関数を呼び出すのはパフォーマンスの低下をもたらし、実装上の理由でエラー時のトレースバックに不要な関数名が表示されるなどの問題も散見されるので、PEP 709: Comprehension inlining では関数の呼び出しを不要とし、単に変数が内包式の外側には影響を残さないように変更されました。
パフォーマンス¶
関数呼び出しが不要になったため、リスト内包のパフォーマンスは30%~60%程度向上しました。
PEP 709では 「2倍速くなった!」 と主張していますが、測定した限りでは2倍早くなるのはリスト内包の長さが 1
のときだけで、それ以外はすべて60%未満でした。
クラススコープ問題¶
Python3.0でリスト内包で関数呼び出しが必要となったため、副作用として次のクラス定義でエラーが発生するようになってしまいました。
class C:
X = 100
lst = [X*i for i in range(10)]
このクラス定義はPython2.7では正常に動きますが、3.0以降では NameError: name 'X' is not defined
というエラーになります。クラススコープで定義した関数からは、そのクラスに定義されている名前を参照できないためです。例えば、次のクラス定義も同様なエラーが発生します。
class D:
X = 100
def f():
return X
f() # raises NameError
このエラーについては、以前 別のサイト で解説記事を書いたのでご参照ください。
PEP 709では内包式から関数呼び出しがなくなったので、Python3.12以降ではクラス C
は実行できるようになりました。 しかし、この変化を許すと、
X = 100
class E:
X = 200
lst = [X for i in range(1)]
と書かれていた場合、Python3.11では E.lst == [100]
で、3.12では E.lst == [200]
となってしまいます。
この点はPEPで触れられていなかったのでどうなるかと思っていましたが、PEP承認後に これはちょっと、ということで、3.11と同じく E.lst == [100]
となるように変更され、結局Python3.0と同じようにクラス C
は実行できなくなってしまいました。