質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Q&A

解決済

3回答

2034閲覧

クラス変数定義を行う際、リスト内包表記の式にクラス変数を使用するとエラーが発生する

koki3

総合スコア12

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

2グッド

4クリップ

投稿2018/03/27 00:28

前提・実現したいこと

下記のソースでエラーが発生する理由が知りたいです。
どなたか教えて頂けないでしょうか。宜しくお願い致します。

発生している問題・エラーメッセージ

リスト内包表記でクラス変数に代入する際に
リスト内包表記の式で別のクラス変数"x"を使用すると
変数xが未定義である旨のエラーメッセージを出力します。

エラーメッセージ Traceback (most recent call last): File "C:\Users\harad\Desktop\Copal.tmp", line 5, in <module> class class_demo: File "C:\Users\harad\Desktop\Copal.tmp", line 7, in class_demo y = [x for i in range(5)] File "C:\Users\harad\Desktop\Copal.tmp", line 7, in <listcomp> y = [x for i in range(5)] NameError: name 'x' is not defined

該当のソースコード

python3.6 エラーが発生するソースコード class class_demo: x = 6 y = [x for i in range(5)] #以下試した際のコード #その1 class class_demo: x = 6 y = [i for i in range(x)] print(y) #[0, 1, 2, 3, 4, 5] #その2 class class_demo: x = 6 def make_listcomp(self): y = [class_demo.x for i in range(1)] print(y) #[6]

試したこと

上記ソース:その1 リスト内包表記のforステートメントにクラス変数は使用可能でした。
上記ソース:その2 クラスではなくメソッドの中に記述すると正常に出力されます。

LouiS0616, tachikoma👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tachikoma

2018/03/27 00:46

なんでその1が動くんだろ・・・
koki3

2018/03/27 07:19

tachikomaさん mkgreiさんと議論して頂いて大変助かりました。 おかげで理解が深まりました。 今後とも宜しくお願いします。
guest

回答3

0

ベストアンサー

https://stackoverflow.com/questions/4198906/python-list-comprehension-rebind-names-even-after-scope-of-comprehension-is-thi

リスト内包表記がリークしなくなった結果の気がしますね。


python3.xだと定義されず、python2.xだとエラーなく実行できますね。


Stackoverflowの回答。

https://stackoverflow.com/questions/13905741/accessing-class-variables-from-a-list-comprehension-in-the-class-definition

結論は、

The best work-around is to just use __init__ to create an instance variable instead

非常に紛らわしいので、どうしてもクラス変数として定義しなければならない場合を除いて、
インスタンス変数にすべきです。


クラス変数ではなく、グローバルにxが定義してあると、そっちが入ってくるのでますます混乱しそうですね。


追記

変数名の探索の際にクラスの名前空間はスキップされます。
https://www.python.org/dev/peps/pep-0227/

python

1#--- global scope 2x = 1 # x1 3class Dummy: 4 #--- class scope 5 x = 2 # x2 6 y = [x for i in range(3)] #ここのxはx1になります 7 8 y = [i for i in range(x)] #ここのxはx2になります 9 10 def y(x): # x3 11 #--- function scope 12 return x # ここのxはx3になります 13 y = y(x) 14 15 def y(x): # x3 16 #--- function scope 17 return [x for i in range(3)] # ここのxはx3になります 18 y = y(x) 19 20 def y(dummy): 21 #--- function scope 22 return x # ここのxはx1になります 23 y = y(0) 24 25 def y(dummy): 26 #--- function scope 27 return [x for i in range(3)] # ここのxはx1になります 28 y = y(0)

lambdaは無名関数なのでdefと同じ。

グローバルスコープにxが定義していなければ、x1は存在しないので、その部分は未定義エラーになります。

投稿2018/03/27 01:27

編集2018/03/27 04:27
mkgrei

総合スコア8560

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tachikoma

2018/03/27 02:21

エラーが発生するコード、その1のコードともにリスト内包表記の前の行にxへの代入があるので、内包表記のリークとは別の話のような?
mkgrei

2018/03/27 02:26 編集

リークしないように実装し直されたので、キャプチャーもしなくなって、リスト内包表記内では定義されてないように振る舞うようになったのかと。 ただ一応グローバルスコープだけは見ている様子。 for文よりも左側だけですが。
tachikoma

2018/03/27 03:28

for文よりも左側だけですと・・・
mkgrei

2018/03/27 03:44

一番外側のfor文より左側からリスト内包表記のスコープで、クラス変数が見えないです。
koki3

2018/03/27 07:22

mkgreiさん ご回答有難うございます。 大変助かりました。 やっと理解できました。 今後とも宜しくお願いします。 >非常に紛らわしいので、どうしてもクラス変数として定義しなければならない場合を除いて、 インスタンス変数にすべきです。 本当にそうですね。極力クラス変数にリスト内包表記は使用すべきではないですね! >クラス変数ではなく、グローバルにxが定義してあると、そっちが入ってくるのでますます混乱しそうですね。 たしかに、グローバル変数と同じ名前だと非常にわかり辛いし、コードのメンテナンスが大変になりそうです。 (今回のような仕様を理解している開発者がどれだけいるか。。。) 仰るとおり極力リスト内包表記でクラス変数に代入するのは避けるべきですね!
guest

0

理屈は考えてもよくわかりませんでしたが、回避策を思いついたので書いておきます。

python

1>>> class class_demo: 2... x = 6 3... y = [x]*5 4... print(y) 5... 6[6, 6, 6, 6, 6]

こっちは通りました。ということはきっと、リスト内包がローカルスコープを見ようとしてコケてるのでしょう。えーっと、なんでだろう?

追記:

同じ話題を見つけました。
python - Accessing class variables from a list comprehension in the class definition - Stack Overflow
クラススコープの扱いが特殊で、スコープがネストされたとき内側からクラス内は見ないよ、とかなんとか・・・そしてリスト内包は関数スコープと等価のスコープなので、アウトになるっぽいです。

 見るからにダメな例。

python

1>>> class Hoge: 2... x = 3 3... def fuga(self): 4... print(x) 5... 6>>> h = Hoge() 7>>> h.fuga() 8NameError: name 'x' is not defined

これと同じ扱いになるということか。かといってリスト内包で使う場合、Hogeの定義が終わってないので、Hoge.xとも書けません。リンク先ではlambdaで解決しろといった提案がされています。

python

1>>> class class_demo: # 方法1 2... x = 6 3... y = (lambda x:[x for i in range(5)])(x) 4... print(y) 5... 6[6, 6, 6, 6, 6] 7>>> class class_demo: # 方法2 8... x = 6 9... y = (lambda x=x:[x for i in range(5)])() 10... print(y) 11... 12[6, 6, 6, 6, 6]

投稿2018/03/27 03:27

編集2018/03/27 03:47
hayataka2049

総合スコア30933

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

koki3

2018/03/27 07:21

hayataka2049さん ご回答有難うございます。 レスが遅くなりましたすみません。 クラススコープの特殊な問題だったんですね! なるほど。わかり易かったです。 代替策についても納得しました。 実はlambdaを使用している例を先ほど見つけて理由が 思いつかずに悩んでました。 助かりました! 今後とも宜しくお願いします。
guest

0

回避策だけ。(内包表記を使わない逃げ)

python

1from itertools import repeat 2 3class class_demo: 4 x = 6 5 y = list(repeat(x, 5))

#追記: 逃げない回避策 (注意、役には立ちません)

python

1from inspect import currentframe 2 3class class_demo: 4 x = 6 5 y = [currentframe().f_back.f_locals['x'] for i in range(5)]

投稿2018/03/27 05:13

編集2018/03/27 05:23
YouheiSakurai

総合スコア6142

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

koki3

2018/03/27 07:20

YouheiSakuraiさん ご回答有難うございます。 レスが遅くなりましたすみません。 inspectモジュールのcurrentframeというメソッドの存在を初めて知りました。 参考にさせて頂きます。 今後とも宜しくお願いします。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問