可以用模仿语境管理器的词汇范围吗?

时间:2013-02-20 19:59:28

标签: python

earlier post中,我询问了如何避免中间tmp变量的模式,如:

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

...其中tmp是一个pandas对象。例如:

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。可以吗?

编辑:正如abarnert指出的那样,如果周围范围内存在预先存在的tmpdeletelexical将无法恢复它,因此很难将其视为词法范围的模拟。正确的实现必须保存周围范围内的任何现有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的帖子&amp;评论:是的,在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程序员会给出一个关于词法范围的大鼠尾巴。

1 个答案:

答案 0 :(得分: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! - 绑定let中将存在于let之外的新名称,你可以将其描述为具有隐式{{1}的词法范围在体内,但它仍然很奇怪。特别是在一种已经在重新绑定和变异之间有很大区别的语言中。)

此外,如果您已经拥有同名nonlocal everything-but-the-let-names的全局,则此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] 语句并尝试弄清楚它绑定的内容。