在函数中调用locals()不直观?

时间:2014-04-28 15:28:57

标签: python locals

这可能是基本的,但可以帮助我理解命名空间。  一个很好的解释可能会逐步完成  执行函数定义,然后执行后续操作  当执行函数 object 时。  递归可能使事情变得复杂。

结果对我来说并不明显;我原以为:

locals_1将包含var;    locals_2将包含var和locals_1;和    locals_3将包含var,locals_1和locals_2

# A function calls locals() several times, and returns them ...
def func():
  var = 'var!'
  locals_1 = locals()
  locals_2 = locals()
  locals_3 = locals()
  return locals_1, locals_2, locals_3

# func is called ...
locals_1, locals_2, locals_3 = func()

# display results ...
print 'locals_1:', locals_1
print 'locals_2:', locals_2
print 'locals_3:', locals_3

结果如下:

locals_1: {'var': 'var!', 'locals_1': {...}, 'locals_2': {...}}
locals_2: {'var': 'var!', 'locals_1': {...}, 'locals_2': {...}}
locals_3: {'var': 'var!', 'locals_1': {...}, 'locals_2': {...}}

模式似乎是,(n)调用 locals ,所有的  返回的locals-dicts是相同的,它们都包括在内  第一个(n-1)本地人 - dicts。

有人可以解释一下吗?

更具体地说:

为什么locals_1会包含自己?

为什么locals_1包含locals_2?是什么时候分配locals_1     func 是创建还是执行?

为什么locals_3不包含在任何地方?

“{...}”是否表示“无休止的递归”?有点像    那些镜子面对面的照片?

4 个答案:

答案 0 :(得分:5)

让我们运行此代码:

def func():
  var = 'var!'
  locals_1 = locals()
  print(id(locals_1), id(locals()), locals())
  locals_2 = locals()
  print(id(locals_2), id(locals()), locals())
  locals_3 = locals()
  print(id(locals_3), id(locals()), locals())
  return locals_1, locals_2, locals_3


func()

这将在输出中:

44860744 44860744 {'locals_1': {...}, 'var': 'var!'}
44860744 44860744 {'locals_2': {...}, 'locals_1': {...}, 'var': 'var!'}
44860744 44860744 {'locals_2': {...}, 'locals_3': {...}, 'locals_1': {...}, 'var': 'var!'}

locals()此处按预期增长,但您将引用分配给locals(),而不是locals()每个变量。

每次分配后locals() 都会更改,但引用不会,因此每个变量都指向同一个对象。在我的输出中,所有对象id都相等,这就是证据。

更长的解释

这些变量与该对象具有相同的链接(引用)。基本上,Python中的所有变量都是引用(与指针类似的概念)。

        locals_1            locals_2                 locals_3
            \                    |                      /
             \                   |                     /
              V                  V                    V
            ---------------------------------------------
            |            single locals() object         |
            ---------------------------------------------

他们完全不知道locals()有什么价值,他们只知道在需要时(在某处使用变量时)从何处获取它。 locals()上的更改不会影响这些变量。

在您运行的最后,您将返回三个变量,这就是您打印它们时发生的情况:

print(locals_N) -> 1. Get object referenced in locals_N
                   2. Return the value of that object

请参阅?因此,这就是为什么它们具有完全相同的值,即locals()print所具有的值。

如果再次更改locals()(以某种方式)然后运行打印语句,将打印3次?是的,locals()的新值。

答案 1 :(得分:2)

我喜欢你的问题,非常好。

是的,locals()有点神奇,但是按照你的方法,你很快就能得到它并且会喜欢它。

关键概念

字典是通过引用而非值

分配的
In [1]: a = {"alfa": 1, "beta": 2}

In [2]: b = a

In [3]: b
Out[3]: {'alfa': 1, 'beta': 2}

In [4]: b["gama"] = 3

In [5]: b
Out[5]: {'alfa': 1, 'beta': 2, 'gama': 3}

In [6]: a
Out[6]: {'alfa': 1, 'beta': 2, 'gama': 3}

如您所见,ab被修改的时刻间接更改,因为ab都指向内存中的相同数据结构。 / p>

locals()正在返回包含所有局部变量的字典

编辑:澄清此dict何时更新

因此locals()来电时存在的所有局部变量都存在于此。如果您对locals()进行后续调用,则此字典会在通话时更新。

回答您的问题

为什么locals_1包含自己?

因为locals_1是对所有本地定义变量的字典的引用。只要locals_1成为本地命名空间的一部分,它就会获得返回的字典locals()的一部分。

为什么locals_1包含locals_2?创建或执行func时是否分配了locals_1?

与前一个相同的答案适用。

为什么locals_3不包含在任何地方?

这是你问题中最困难的部分。经过一些研究后,我找到了关于这个主题的优秀文章:http://nedbatchelder.com/blog/201211/tricky_locals.html

事实是,locals()返回一个字典,其中包含对所有局部变量的引用。但棘手的部分是,它不是直接的结构,它是一个字典,仅在此刻更新,locals()被调用。

这解释了结果中缺少locals_3。所有结果都指向同一个字典,但在引入locals_3变量后,它不会得到更新。

当我在返回之前添加另一个打印locals()时,我发现它在那里,没有它。

UFF。

" {...}"表示无休止的递归'?有点像镜子面对面的照片吗?

我会把它读成"还有更多的东西"。但我认为,你是对的,这是打印递归数据结构的解决方案。没有这样的解决方案,字典就无法在有限的时间内打印出来。

奖励 - 在string.format()

中使用** locals()

有一个习惯用法,其中locals()正在缩短你的代码,在string.format()

name = "frost"
surname = "national"
print "The guy named {name} {surname} got great question.".format(**locals())

答案 2 :(得分:1)

frostnational和Jan Vlcinsky已经对幕后发生的事情给出了很好的解释。这是实现您最初预期行为的一小部分。您可以使用copy方法创建locals() dict的副本。更新locals()时不会更新该副本,因此它包含"快照"你期望的那样:

In [1]: def func():
   ...:     var = 'var!'
   ...:     locals1 = locals().copy()
   ...:     locals2 = locals().copy()
   ...:     locals3 = locals().copy()
   ...:     return locals1, locals2, locals3
   ...:

In [2]: locals1, locals2, locals3 = func()

In [3]: locals1
Out[3]: {'var': 'var!'}

In [4]: locals2
Out[4]: {'locals1': {'var': 'var!'}, 'var': 'var!'}

In [5]: locals3
Out[5]:
{'locals1': {'var': 'var!'},
 'locals2': {'locals1': {'var': 'var!'}, 'var': 'var!'},
 'var': 'var!'}

正如所料,每个副本仅包含在调用locals()之前定义的变量。

答案 3 :(得分:0)

我原来的问题归结为'只是什么是 locals()?' 这是我目前的(推测)理解,用Pythonese写的:

+++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++

Python的本地人()的性质是什么?

每个本地命名空间都有自己的命名空间表,可以是 使用 locals 内置函数查看总计。 (实际上, namepace-table 就像一个包含“identifier”:object 条目的dict;对于每个项目,键是名称(以字符串形式) )为对象分配(或“绑定”)。)

在非全局级别调用时,locals返回解释器对当前本地命名空间表的唯一表示形式:“动态”,始终保持最新状态,专门的,类似dict的对象。

它不是一个简单的dict,也不是实际的名称表,但它实际上是“活着的”,并且只要它被引用就会立即从活动表中更新(当跟踪打开时,它会随每个语句一起更新)。
在退出范围时,此对象将消失,并在下次调用locals时为当前范围重新创建。

(在全局(模块化)级别调用时,locals会返回 globals() ,Python的全局命名空间表示,可能具有不同的性质)。

因此,L = locals()将名称L绑定到本地命名空间表的'stand-in';随后,只要引用L,就会刷新并返回此对象 并且,locals()绑定(在同一范围内)的任何其他名称将是此同一对象的别名

请注意,分配给L的{​​{1}}必然会成为“无限递归”对象(显示为dict locals()),这对您来说可能重要,也可能不重要。但是,您可以随时制作{...}的简单字典副本 L的某些属性(例如 locals() )也会返回简单对象。

要捕获函数中locals()的“原始”快照,请使用不进行任何本地分配的技术;例如,将副本作为参数传递给函数,该函数将其pickle到文件中。

keys的细节,以及它的行为方式;它包含来自函数块的自由变量,但不包括类,并且文档警告不要试图改变L的内容(它可能不再“镜像”名称表)。
也许应该只读(复制等)

(为什么L被设计为'实时',而不是'快照',是另一个主题。)

总结:

locals() 是一个独特的专业对象(以dict形式);它是Python当前本地命名空间表的 live 表示(不是冻结快照)

+++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++

获得我期望的结果的一种方法是在每一步产生locals()副本(这里使用dict.copy):

locals()
调用

func ,并显示返回值:

# A function copies locals() several times, and returns each result ...
def func():
    var = 'var!'
    locals_1 = locals().copy()
    locals_2 = locals().copy()
    locals_3 = locals().copy()
    return locals_1, locals_2, locals_3

返回的是简单的dict对象,它捕获本地命名空间的增长阶段 这就是我的意图。

复制locals_1: {'var': 'var!'} locals_2: {'var': 'var!', 'locals_1': {'var': 'var!'}} locals_3: {'var': 'var!', 'locals_1': {'var': 'var!'}, 'locals_2':{'var':'var!','locals_1': {'var': 'var!'}}} (此处为“L”)的其他可能方式有locals()dict(L)copy.copy(L)