6

这可能是基本的,但可以帮助我理解名称空间。一个好的解释可能会逐步介绍函数定义执行时会发生什么,然后是函数对象执行后会发生什么时会发生什么。递归可能会使事情复杂化。

结果对我来说并不明显;我本来期望:

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': {...}}

该模式似乎是,对于 locals 的 (n) 调用所有
返回的 locals-dicts 都是相同的,并且它们都包括第一个 (n-1) 个 locals-dicts。

有人可以解释一下吗?

进一步来说:

为什么 locals_1 包含自己?

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

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

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

4

4 回答 4

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() 被更改后,但引用没有,所以每个变量都指向同一个对象。在我的输出中,所有 objectid都相等,这就是证明。

更长的解释

这些变量与该对象具有相同的链接(引用)。基本上,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()

于 2014-04-28T15:32:50.790 回答
3

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

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}

如您所见,在被修改a的那一刻间接地b被改变,因为两者ab指向内存中的相同数据结构。

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

编辑:澄清这个字典什么时候更新

所以locals()调用时存在的所有局部变量都住在这里。如果您对 进行后续调用locals(),则此字典会在调用时更新。

回答您的问题

为什么 locals_1 包含自己?

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

为什么 locals_1 包含 locals_2?locals_1 是在创建或执行 func 时分配的吗?

与上一个答案相同。

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

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

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

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

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

呃。

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

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

奖励 - 在 string.format() 中使用 **locals()

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

name = "frost"
surname = "national"
print "The guy named {name} {surname} got great question.".format(**locals())
于 2014-04-28T17:49:29.530 回答
1

frostnational 和 Jan Vlcinsky 已经很好地解释了幕后发生的事情。这是关于实现您最初期望的行为的一个小补充。您可以使用该copy方法创建locals()字典的副本。该副本在更新时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()被调用。

于 2014-04-29T09:42:55.997 回答
0

我最初的问题是,“到底是 locals()什么?” 这是我目前(推测)的理解,用 Pythonese 编写:

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

Python 的 locals() 的本质是什么?

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

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

它不是一个简单的字典,也不是实际的名称表,但它实际上是“活动的”,并且在任何时候被引用时都会从活动表中立即更新(当跟踪打开时,它会随每个语句更新)。退出作用域时,此对象消失,并在下次调用
时为当前作用域重新创建。locals

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

因此,L = locals()将名称绑定L到本地命名空间表的这个“替身”;随后,任何时候L引用,都会刷新并返回此对象。
并且,绑定到(在同一范围内)的任何其他名称locals()都将是同一对象的别名

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

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

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

(为什么locals()设计为“实时”而不是“快照”是另一个主题)。

总之:

locals()是一个独特的、专门化的对象(以字典形式);它是 Python 对当前本地命名空间表的实时表示(不是冻结的快照)

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

那么,获得我所期望的结果的一种方法是在每个步骤中生成(这里使用 dict.copy)的副本:locals()

# 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

func被调用,并显示返回:

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!'}}}

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

其他可能的复制方式locals()(此处为“L”)是dict(L),copy.copy(L)copy.deepcopy(L).

于 2014-05-05T17:31:34.487 回答