17

Pythonはクラス属性をどの程度正確に評価しますか?説明したい興味深い癖(Python 2.5.2)に出くわしました。

以前に定義された他の属性に関して定義されたいくつかの属性を持つクラスがあります。ジェネレータオブジェクトを使おうとするとPythonはエラーをスローしますが、普通のリスト内包表記を使えば問題ありません。

これが簡素化された例です。唯一の違いは、リスト内包表記を使用するのBrieに対し、ジェネレータ式を使用することです。Cheddar

# Using a generator expression as the argument to list() fails
>>> class Brie :
...     base = 2
...     powers = list(base**i for i in xrange(5))
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in Brie
  File "<stdin>", line 3, in <genexpr>
NameError: global name 'base' is not defined

# Using a list comprehension works
>>> class Cheddar :
...     base = 2
...     powers = [base**i for i in xrange(5)]
... 
>>> Cheddar.powers
[1, 2, 4, 8, 16]

# Using a list comprehension as the argument to list() works
>>> class Edam :
...     base = 2
...     powers = list([base**i for i in xrange(5)])
...
>>> Edam.powers
[1, 2, 4, 8, 16]

(私の実際のケースはもっと複雑で、辞書を作成していましたが、これは私が見つけることができる最小限の例です。)

私の唯一の推測は、リスト内包はその行で計算されますが、ジェネレータ式はクラスの終了後に計算され、その時点でスコープが変更されています。しかし、ジェネレータ式がクロージャとして機能せず、ベースへの参照を行のスコープに格納しない理由はわかりません。

これには理由がありますか?もしそうなら、クラス属性の評価メカニズムをどのように考えるべきですか?

4

2 に答える 2

15

ええ、それは少し危険です、これ。クラスは実際には新しいスコープを導入していません。それは、それがそうであるように少しだけ見えます。このような構成は違いを明らかにします。

ジェネレータ式を使用する場合、ラムダを使用するのと同じです。

class Brie(object):
    base= 2
    powers= map(lambda i: base**i, xrange(5))

または明示的に関数ステートメントとして:

class Brie(object):
    base= 2

    def __generatePowers():
        for i in xrange(5):
            yield base**i

    powers= list(__generatePowers())

この場合、それが;baseの範囲外であることは明らかです。両方の例外が発生します(グローバルを作成__generatePowersするのに不運だった場合を除きます。その場合、間違いが発生します)。base

これは、リスト内包表記の評価方法に関する内部的な詳細のために発生しませんが、Python 3ではその動作がなくなり、どちらの場合も同じように失敗します。ここでいくつかの議論。

回避策は、nested_scopesの前の古き良き時代に私たちが頼っていたのと同じ手法でラムダを使用して行うことができます。

class Brie(object):
    base= 2
    powers= map(lambda i, base= base: base**i, xrange(5))
于 2009-11-20T22:46:45.153 に答える
1

PEP 289から:

多くの可能性を検討した後、バインディングの問題を理解するのは困難であり、ユーザーは引数をすぐに消費する関数内でジェネレーター式を使用することを強く推奨する必要があるというコンセンサスが生まれました。より複雑なアプリケーションの場合、スコープ、存続期間、およびバインディングについて明確であるという点で、完全なジェネレーター定義が常に優れています[6]。

[6](1、2)SourceForgeのパッチディスカッションと代替パッチhttp://www.python.org/sf/872326

私が理解できる限り、ジェネレータ式のスコープはこのようになっています。

于 2009-11-20T22:49:50.637 に答える