在Python 2中,如何在父范围中写入变量?


77

我在函数内有以下代码:

stored_blocks = {}
def replace_blocks(m):
    block = m.group(0)
    block_hash = sha1(block)
    stored_blocks[block_hash] = block
    return '{{{%s}}}' % block_hash

num_converted = 0
def convert_variables(m):
    name = m.group(1)
    num_converted += 1
    return '<%%= %s %%>' % name

fixed = MATCH_DECLARE_NEW.sub('', template)
fixed = MATCH_PYTHON_BLOCK.sub(replace_blocks, fixed)
fixed = MATCH_FORMAT.sub(convert_variables, fixed)

添加元素可以stored_blocks正常工作,但是我不能num_converted在第二个子功能中增加:

UnboundLocalError:分配前已引用局部变量“ num_converted”

我可以使用,global但是全局变量很丑陋,我真的根本不需要该变量是全局变量。

因此,我很好奇如何在父函数范围内写入变量。 nonlocal num_converted可能会完成这项工作,但是我需要一个与Python 2.x兼容的解决方案。


4
与某种流行的看法相反(根据此类问题判断)def不是定义名称空间的唯一关键字:还有class
2011年

Answers:


87

问题:这是因为Python的作用域规则不正确。+=赋值运算符的存在将目标标记num_converted为封闭函数作用域的局部对象,并且在Python 2.x中没有声音方法可以从那里访问仅一个作用域级别。只有global关键字可以将变量引用提升到当前范围之外,并且直接将您带到顶部。

修正:num_converted成一个单一的元素数组。

num_converted = [0]
def convert_variables(m):
    name = m.group(1)
    num_converted[0] += 1
    return '<%%= %s %%>' % name

7
您能解释为什么这是必要的吗?我本来希望OPs代码能够正常工作。
比约恩博动

36
因为Python的作用域规则是不合理的。+=赋值运算符的存在将目标标记num_converted为封闭函数作用域的局部对象,并且在Python 2.x中没有声音方法可以从那里访问仅一个作用域级别。只有global关键字可以将变量引用提升到当前范围之外,并且直接将您带到顶部。
Marcelo Cantos

6
这不是很聪明,这实际上是很糟糕的代码。有课程(请参阅问题下的注释)。此版本使用全局变量,您应始终避免使用该变量。不使用global并不表示您没有全局变量。
schlamar

15
@schlamar:有问题的变量在任何意义上都不是全局的。OP的开篇句子规定,他们提供的整个代码块都在函数内部。
Marcelo Cantos

2
@schlamar:一个变量需要很多脚手架。如果您需要的答案比我的答案所提供的更加清晰(是吗?),我建议scope = {'num_converted':0} … scope['num_converted'] += 1
Marcelo Cantos

29

(有关修改后的答案,请参见下文)

您可以使用类似:

def convert_variables(m):
    name = m.group(1)
    convert_variables.num_converted += 1
    return '<%%= %s %%>' % name

convert_variables.num_converted = 0

这样,就num_converted可以作为convert_variable方法的类似于C的“静态”变量


(已编辑)

def convert_variables(m):
    name = m.group(1)
    convert_variables.num_converted = convert_variables.__dict__.get("num_converted", 0) + 1
    return '<%%= %s %%>' % name

这样,您无需在主过程中初始化计数器。


3
对。请注意,您必须convert_variables.num_converted 定义函数创建属性,尽管这样做似乎很奇怪。
Marc van Leeuwen 2013年

@PabloG对这个问题最满意的答案,除了3.x中的非本地性;使用可变类型[]是便宜的解决方法。
user2290820

9

使用global关键字很好。如果您写:

num_converted = 0
def convert_variables(m):
    global num_converted
    name = m.group(1)
    num_converted += 1
    return '<%%= %s %%>' % name

...num_converted 不会成为“全局变量”(即在任何其他意外的地方都不会显示),仅表示可以在内部对其进行修改convert_variables。那似乎正是您想要的。

为了把它的另一种方式,num_converted已经一个全局变量。所有global num_converted语法所做的就是告诉Python“在此函数内,不要创建局部num_converted变量,而应使用现有的全局变量。


4
global2.x中的工作原理nonlocal与3.x中的工作原理差不多。
Daniel Roseman

2
“换句话说,num_converted已经是一个全局变量了”-我的代码在一个函数中运行..因此它当前不是全局的。
ThiefMaster

2
啊,我没有注意“函数内”部分,对不起-在这种情况下,Marcelo的长度为一的列表可能是一个更好(但很丑陋)的解决方案。
Emile,

7

使用类实例保存状态呢?您实例化一个类并将实例方法传递给sub,这些函数将引用self ...


7
听起来有些矫kill过正,就像Java程序员提供的解决方案一样。; p
ThiefMaster

1
@ThiefMaster为什么这个过大?如果要访问父作用域,则应在Python中使用一个类。
schlamar

2
@schlamar因为在具有一流功能支持(JS,功能语言)的所有其他理智的语言中闭包都可以正常工作。
Dzugaru

6

我有几句话。

首先,在处理诸如xml.parsers.expat之类的原始回调时,会出现一个用于此类嵌套函数的应用程序。(图书馆作者选择这种方法可能令人反感,但是……尽管如此,仍有理由使用它。)

第二:在一个类中,数组(num_converted [0])有很多更好的选择。我想这就是塞巴斯蒂安所说的。

class MainClass:
    _num_converted = 0
    def outer_method( self ):
        def convert_variables(m):
            name = m.group(1)
            self._num_converted += 1
            return '<%%= %s %%>' % name

仍然需要在代码中添加注释,这仍然很奇怪。但是变量至少在类中是局部的。


嘿,欢迎您使用Stack Overflow-但是在此处实际上不能执行“备注”作为答案。我们对此有评论(但是,您需要一些代表才能发表评论-但不要仅仅因为您没有足够的代表发表评论而发表答案)
ThiefMaster

5
嗨,也欢迎您!我不理解您的评论,或者您使用的几个术语。我是一个忙碌的人,只是想提供帮助!
史蒂夫·怀特

没问题-不过请看一下stackoverflow.com/about。总是乐于助人,尽管发表的评论多好,最终都会被删除。
ThiefMaster 2013年

0

修改自:https : //stackoverflow.com/a/40690954/819544

您可以利用该inspect模块访问调用范围的全局指令并将其写入。这意味着甚至可以利用此技巧从导入的子模块中定义的嵌套函数访问调用范围。

import inspect 

def get_globals(scope_level=0):
    return dict(inspect.getmembers(inspect.stack()[scope_level][0]))["f_globals"]

num_converted = 0
def foobar():
    get_globals(0)['num_converted'] += 1

foobar()
print(num_converted) 
# 1

scope_level根据需要使用参数。设置scope_level=1适用于子模块中定义的功能,子模块scope_level=2中的装饰器中定义的内部功能等。

注意:仅仅因为您可以这样做,并不意味着您应该这样做。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.