可以用上下文管理器模拟词汇范围吗?

2024-10-03 21:32:18 发布

您现在位置:Python中文网/ 问答频道 /正文

在一篇earlier post中,我询问了如何避免中间变量{}在如下模式中:

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

…其中tmp是pandas对象。例如:

^{pr2}$

我对这种模式的兴趣主要来自于对词汇范围的诚实到善良的渴望,这种渴望不会消失,即使在编写了多年的Python之后。在Python2中,我只需要显式地调用del

在我看来,使用上下文管理器来模拟Python中的词法作用域是可能的。它看起来像这样:

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

为了能够模拟词法作用域,上下文管理器类需要有一种方法来del在调用作用域中对变量进行del赋值,该变量由其(上下文管理器的)'enter方法返回。在

例如,如果有大量的作弊行为:

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];
}();

编辑:回应abarner的评论:是的,在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程序员对词法范围的界定很感兴趣。在


Tags: 方法df管理器myaswithresult作用域
1条回答
网友
1楼 · 发布于 2024-10-03 21:32:18

在等效的JavaScript中,可以执行以下操作:

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

换句话说,为了获得额外的词法范围,您需要创建一个新的局部函数并使用它的作用域。在Python中可以执行完全相同的操作:

^{pr2}$

而且效果完全一样。在

而这种影响并不像你想象的那样。超出范围只意味着可以收集垃圾。这正是一个真正的词法范围所能给你的,但它不是你想要的(一种在某个时刻确定地摧毁某个东西的方法)。是的,在cpython2.7中它通常会做你想做的事情,但这不是语言特性,而是实现细节。在

但是你的想法在仅仅使用一个函数的问题的基础上又增加了一些问题。在

您的想法使with语句中的所有内容都被定义或反弹。JS的等价物没有做到这一点。您所说的更像是C++范围保护宏,而不是^ {< CD2> }语句。(一些不纯的语言允许你set!-在一个let内绑定新的名字,这个名字将存在于let之外,你可以把它描述为一个在体内有一个隐式nonlocal everything-but-the-let-names的词法范围,但这仍然很奇怪。尤其是在一种已经对重新绑定和变异有着强烈区分的语言中。)

另外,如果已经有一个同名的全局变量tmp,这个with语句将删除它。这不是let语句或任何其他常见形式的词汇范围界定所做的。(如果tmp是局部变量而不是全局变量呢?)在

如果您想用上下文管理器模拟词法作用域,您真正需要的是一个上下文管理器,它在退出时还原globals和/或{}。或者只是一种在临时globals和/或locals中执行任意代码的方法。(我不确定这是否可行,但是您得到的想法是将with的主体作为code对象并将其传递给exec。)

或者,如果您希望允许重新绑定跳出作用域,而不是新绑定,请遍历globals和/或{}并删除所有新的绑定。在

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

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

没有理由将表达式推送到with语句中并试图找出它绑定到了什么。在

相关问题 更多 >