3

以前の投稿で、tmp次のようなパターンで中間変数を回避する方法について質問しました。

tmp = <some operation>
result = tmp[<boolean expression>]
del tmp

...tmpパンダオブジェクトはどこにありますか。例えば:

tmp = df.xs('A')['II'] - df.xs('B')['II']
result = tmp[tmp < 0]
del tmp

このパターンについての私のボンネットのミツバチは、基本的に、何年にもわたって Python をプログラミングした後でも、死ぬことのない正直なレキシカル スコーピング1への憧れから来ています。Python 2では、 への明示的な呼び出しで間に合わせますdel

コンテキスト マネージャーを使用して、Python のレキシカル スコープを模倣できるのでないかと思います。次のようになります。

with my(df.xs('A')['II'] - df.xs('B')['II']) as tmp:
    result = tmp[tmp < 0]

レキシカル スコープを模倣できるようにするために、コンテキスト マネージャ クラスには、(コンテキスト マネージャの) ' enter ' メソッドdelによって返される値が割り当てられる呼び出しスコープ内の変数を取得する方法が必要です。

たとえば、大量のチートを使用すると、次のようになります。

import contextlib as cl

# herein lies the rub...
def deletelexical():
    try: del globals()['h']
    except: pass

@cl.contextmanager
def my(obj):
    try: yield obj
    finally: deletelexical()

with my(2+2) as h:
    print h
try:
    print h
except NameError, e:
    print '%s: %s' % (type(e).__name__, e)
# 4
# Name error: name 'h' is not defined

もちろん、問題はdeletelexical実際に実装することです。それはできますか?

tmp編集: abarnert が指摘したように、周囲のスコープに既存のものdeletelexicalがある場合、それを復元しないため、レキシカル スコープのエミュレーションとはほとんど見なされません。tmp正しい実装では、既存の変数を周囲のスコープに保存し、with ステートメントの最後でそれらを置き換える必要があります。


1たとえば、Perl では、上記を次のようにコーディングします。

my $result = do {
    my $tmp = $df->xs('A')['II'] - $df->xs('B')['II'];
    $tmp[$tmp < 0]
};

または JavaScript で:

var result = function () {
    var tmp = df.xs('A')['II'] - df.xs('B')['II'];
    return tmp[tmp < 0];
}();

編集: abarnert の投稿とコメントへの応答: はい、Python で定義できます

def tmpfn():
    tmp = df.xs('A')['II'] - df.xs('B')['II']
    return tmp[tmp < 0]

...そして、これは確かに今後役に立たない名前で名前空間を混乱させることを防ぎますが、今後は役に立たない名前tmpで名前空間を混乱させることによってそうしますtmpfn。JavaScript (および Perl も、BTW なども) では無名関数を使用できますが、Python では使用できません。いずれにせよ、JavaScript の無名関数は、レキシカル スコープを取得するためのやや面倒な方法であると考えています。それは確かに何もないよりはましであり、私はそれを頻繁に使用しますが、Perl のものほど優れているわけではありません (後者とは、Perl のdoステートメントだけでなく、レキシカルとダイナミックの両方でスコープを制御するために提供される他のさまざまな方法も意味します)。

2 Python プログラマーのごく一部だけがレキシカル スコープについてネズミの尻尾を与えているという事実を思い出す必要はありません。

4

1 に答える 1

3

同等の JavaScript では、次のようにします。

var result = function () {
    var tmp = df.xs('A')['II'] - df.xs('B')['II'];
    return tmp[tmp < 0];
}();

つまり、追加のレキシカル スコープを取得するには、新しいローカル関数を作成し、そのスコープを使用します。Python でもまったく同じことができます。

def tmpf():
    tmp = df.xs('A')['II'] - df.xs('B')['II']
    return tmp[tmp < 0]
result = tmpf()

そして、それはまったく同じ効果があります。

そして、その効果はあなたが思っているようなものではありません。範囲外になるということは、収集できるのはガベージであることを意味するだけです。これはまさに、実際のレキシカルスコープが提供するものですが、それはあなたが望むものではありません (ある時点で決定論的に何かを破壊する方法)。はい、通常は CPython 2.7 で必要なことを行うことができますが、それは言語機能ではなく、実装の詳細です。

しかし、あなたのアイデアは、関数を使用するだけの問題に加えて、さらにいくつかの問題を追加します。

withあなたのアイデアは、ステートメント内のすべての定義済みまたはリバウンドを変更したままにします。同等の JS はそれを行いません。あなたが話していることは、letステートメントというよりも C++ スコープ ガード マクロに似ています。(一部の不純な言語では、の外側に存在するset!a 内の新しい名前をバインドできます。これは、本体内に暗黙的な を持つレキシカル スコープとして記述できますが、それでもかなり奇妙です。特に、既に を持っている言語では再結合と変異は明確に区別されます。)letletnonlocal everything-but-the-let-names

また、同じ名前のグローバルが既にある場合tmp、このwithステートメントはそれを消去します。これは、letステートメントやその他の一般的な形式の字句スコープが行うことではありません。(そしてtmp、グローバル変数ではなくローカル変数だったら?)

コンテキスト マネージャーでレキシカル スコープをシミュレートしたい場合、本当に必要なのは、復元globalsおよび/またはlocals終了するコンテキスト マネージャーです。globalsまたは、一時的なand/or内で任意のコードを実行する方法にすぎないかもしれませんlocals。(これが可能かどうかはわかりませんが、 の本体をオブジェクトとして取得して に渡すなど、アイデアは得withられcodeますexec。)

または、再バインドを許可してスコープをエスケープしたいが、新しいバインドは許可しない場合は、globalsand/orlocalsをたどって新しいものをすべて削除します。

または、特定のものだけを削除したい場合は、deletingコンテキスト マネージャーを記述します。

with deleting('tmp'):
    tmp = df.xs('A')['II'] - df.xs('B')['II']
    result = tmp[tmp < 0]

式をステートメントに押し込んで、withそれが何にバインドされるかを理解しようとする理由はありません。

于 2013-02-20T20:24:49.173 に答える