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

我对这种模式的看法基本上来自对诚实至善的词法作用域1的渴望,即使经过多年的 Python 编程,它也不会消亡。在 Python 2中,我通过显式调用del,

我突然想到可以使用上下文管理器来模拟 Python 中的词法作用域。它看起来像这样:

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

为了能够模拟词法作用域,上下文管理器类需要有一种方法来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 等价物不这样做。您所说的更像是 C++ 范围保护宏而不是let语句。(一些不纯的语言允许您set!在 a 中 -bind 新名称,该名称let将存在于 之外let,您可以将其描述为在主体内隐含的词法范围nonlocal everything-but-the-let-names,但这仍然很奇怪。尤其是在已经有 a 的语言中重新绑定和变异之间的强烈区别。)

此外,如果您已经有一个具有相同名称的全局变量tmp,则此with语句将删除它。这不是let语句或任何其他常见形式的词法作用域所做的。(如果tmp是局部变量而不是全局变量呢?)

如果您想使用上下文管理器模拟词法范围,那么您真正需要的是一个可以恢复globals和/或locals退出时的上下文管理器。或者可能只是一种在临时globals和/或locals. (我不确定这是否可能,但你明白了——比如将 的主体with作为code对象并将其传递给exec。)

或者,如果您想允许重新绑定以逃避范围,但不允许新绑定,请遍历globals和/或locals并删除所有新内容。

或者,如果您只想删除特定的东西,只需编写一个deleting上下文管理器:

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

没有理由将表达式推送到with语句中并尝试找出它绑定的内容。

于 2013-02-20T20:24:49.173 回答