2014年5月12日

Sebastian Raschka编写

这是一篇关于采用LEGB规则实现Python变量命名空间及范围解析的简短教程。下面章节将会提供简短的可以说明问题的示例代码块来简要阐述问题。您可以简单的从头至尾阅读本教程,但我鼓励您去执行这些代码段。你可以复制粘贴这些代码段,但是为了方便您也可以下载IPython笔记

章节

• 章节

• 目标

• 命名空间和范围介绍

o 命名空间

o 范围

o 提示:

o 通过LEGB规则解析变量名的范围

•1. LG-本地和全局范围

o 原因:

o 原因:

•2. LEG – 局部、封闭和全局范围

o 原因:

•3. LEGB – 局部、封闭、全局、内置

o 原因:

• 自由练习

• 结论

o 经验法则

o 解决方案

o 警告:对于循环变量“leaking”加入全局命名空间

目标

•命名空间和范围:Python是从哪里寻找变量名?

•我们可以在同一时间定义或重用多个对象的变量名吗?

•Python是通过哪种方式为变量名搜索不同的命名空间的呢?

命名空间和范围介绍

命名空间

大致来说,命名空间只是将名称映射到对象的容器。正如你可能已经听过,Python中所有的字符串、列表、函数、类等等都是对象。如此,“对象与名称”的映射关系允许我们使用已经为这个对象分配的名称来访问这个对象。例如,如果我们做一个简单的字符串分配,可以通过:a_string = “Hello string”, 我们创建了一个关于“Hello string”的对象,从此以后我们就可以通过它的变量名a_string来访问它。

我们可以想象一个命名空间为Python字典结构,字典的键在哪里代表它的名字以及字典的值在哪里代表对象本身(这也是目前在Python中命名空间该如何实现),例如,

a_namespace = {'name_a':object_1, 'name_b':object_2, ...}

现在,棘手有部分是在Python中我们有多个独立的命名空间,并且名称可以被不同的命名空间重复使用(只有对象是唯一的,例如,

a_namespace = {'name_a':object_1, 'name_b':object_2, ...

b_namespace = {'name_a':object_3, 'name_b':object_4, ...}

例如,当我们调用一个循环或定义一个函数时,它将创建自己的命名空间。命名空间也有不同的层次(所谓的“范围”),我们将在下一节中更详细地讨论。

范围

在上述章节中,我们已经了解到命名空间可以独立存在,彼此和他们的结构在一定的层次,这给我们带来了“范围”。Python 中的“范围”在我们为“命名对象”映射搜索命名空间时定义了“层级”。

例如,让我们思考一下下面的这段代码:

在这里,我们仅仅只对变量名i定义了两次,一次是是在foo函数中。

•foo_namespace = {'i':object_3, ...}

•global_namespace = {'i':object_1, 'name_b':object_2, ...}

所以,如果我们想要打印变量i的值,Python是如何知道应该查找哪个命名空间呢?这个地方就需要Python的LEGB规则来实现了,我们将在下个章节讨论。

提示:

如果我们想要打印出字典的全局变量和局部变量的映射,我们可以使用global()和local()函数。

通过LEGB规则解析变量名范围

我们已经看到,多个命名空间可以彼此独立存在,它们可以在不同的层次水平包含相同的变量名。“范围”定义在哪个层次,Python为其相关对象搜索了一个特定的“变量名”。现在,下一个问题是:Python是采用什么方式在它找到对象名称的映射之前搜索命名空间的不同级别呢?

答案是:Python使用LEGB规则,即

     局部->封闭->全局->内置

箭头应该指向命名空间层次结构搜索顺序的方向。

      •局部可以在函数内部或类方法中,例如。

       •封闭可以是它的enclosing方法,例如,一个方法被包含在另一个方法中。

       •全局是指执行脚本的最高级别,以及

       •内置是Python自己保留的特殊名称。

所以,如果一个特定的名称:对象映射不能在本地命名空间中找到,下一步封闭范围的命名空间将会被搜索。如果在封闭范围的搜索也是不成功的,Python将会到全局命名空间去搜索,最终,它将搜索内置命名空间(附注:如果名称在任何的命名空间都找不到,系统将报告NameError)。

注:

命名空间还可以嵌套,例如如果我们导入的模块,或者如果我们定义新类。在这种情况下,我们必须使用前缀来访问这些嵌套的命名空间。让我在下面的代码块中说明这个概念:

(这也是为什么我们在通过“from a module import *”时必须注意,因为在加载变量名到全局命名空间时很可能会覆盖已经存在的变量名。)

1. LG – 局部和全局范围

例 1.1

作为热身练习,让我们先忘记在LEGB规则中封闭(E)和内置(B)的范围,来看一看LG----局部和全局范围。

下面的代码将打印什么呢?

原因:

我们首先调用a_func(),这应该是打印a_var值。根据LEGB规则,这个方法将会首先在局部范围(L)查看是否a_var是被定义的。因为a_func()没有定义自己a_var,它会向上一级全局范围(G)查找,直到a_var之前定义的范围。

例 1.2

现在,让我们在全局和局部范围定义变量a_var.

你能猜到下面的代码将会产生什么?

原因:

当我们调用a_func()时,首先它会在局部范围(L)中查找a_var,因为a_var已经在局部范围a_func中定义,它的赋值local varible就会被打印。注意这不会影响在不同范围的全局变量。

然后,它也在可能会修改全局的,例如,如果我们在重新赋值时使用全局关键字。下面的例子将会说明:

但是我们必须小心这个顺序:如果我们没有明确的告诉Python我们想要使用全局范围来尝试修改变量值就很容易出现UnboundLocalError错误。(记住,赋值操作先执行的是右边的操作):

2. LEG – 局部, 封闭和全局范围

现在,让我们来介绍一下封闭范围(E)的概念。按照“局部->封闭->全局”的顺序,你能猜出下面的代码将打印什么吗?

例 2.1

原因:

让我们快速概括我们在做什么: 我们调用outer(), 其变量a_var在局部被定义(a_var在全局存在)。接下来, outer()函数调用同样定义了名字为a_var的变量的inner()函数。Inner()中的print()函数在它的范围层级结构之前先在局部范围中查找(L->E),因此它打印了在局部范围内分配的值。

类似于我们已经在前面章节中看到的global关键字的概念,我们可以使用包含在内置函数的关键字nonlocal来明确地访问外部(封闭)范围的变量,以修改它的变量值。

注意nonlocal关键字已经添加到了Python 3.x, 在Python 2.x中还没有实现。

 

3. LEGB – 局部,封闭,全局,内置

总结LEGB规则,让我们进入内置范围。在这里,我们定义了我们“own”的长度函数,这恰好为内建的len()函数具有相同的名称。如果我们执行了下面的代码,结果是什么?

例 3

原因:

因为相同的名称可用于映射不同的对象,只要这个名字在不同的名字空间并且没有重用的名字来定义自己的函数len长度的问题(这只是用于演示,不推荐)。当我们回顾Python的L - >E> G > B层次,函数a_func()发现在它尝试搜索内置(B)命名空间之前len()已经在全局范围(G)。

自由练习

现在,在我们经过几次联系之后,让我们快速检查我们掌握到哪里?因此,多一次的练习:下面的代码将打印出什么呢?

 

结论

我希望通过这个简短的教程可以有助于理解Python利用LEGB规则来进行范围解析顺序的基本的概念。我想鼓励你(作一次小的自由练习)明天再去看看代码段,再检查一下你是否能正确的预测出它们的结果。

经验法则

在实践中,在函数范围修改全局变量通常是一个坏想法,因为它往往会引起一些混乱和奇怪的错误,并且很难调试。

如果你想通过函数修改一个全局变量,建议你通过参数传入该全局变量并将返回值重新指定给全局变量。

例如:

 

解决方案

为上防止你无意的破坏,我已经用二进制格式编写了解决方案。 为了显示这些字符表示,你只需要执行下面的几行代码:

 

警告:对于循环变量“leaking”加入全局命名空间

和其他的变成语言相反,for-loops将使用他们存在的范围并留下他们已经定义的循环变量。

这也使用于如果我们明确在全局命名空间之前定义for-loop变量!在这种情况下,它会重新绑定存在的变量。

无论如何,在Python 3.x中,我们可以使用闭包来防止循环变量将其分割到全局命名空间中。

为什么我提到“Python 3.x”?  因为当问题发生时,相同的代码在Python 2.x的执行结果是这样的:

英文原文:http://sebastianraschka.com/Articles/2014_python_scope_and_namespaces.html
译者:linkxu1989

初学者教程之命名空间,范围解析及LEDB规则的更多相关文章

  1. Xamarin XAML语言教程XAML文件结构与解析XAML

    Xamarin XAML语言教程XAML文件结构与解析XAML XAML文件结构 在上文中,我们创建XAML文件后,会看到类似图1.16所示的结构 图1.16  结构 其中,.xaml文件和.xaml ...

  2. XAML实例教程系列 - 命名空间(NameSpace) 三

    XAML实例教程系列 - 命名空间(NameSpace) 2012-05-28 14:14 by jv9, 2205 阅读, 10 评论, 收藏, 编辑 上一篇曾提及XAML中,每个对象元素的声明是对 ...

  3. PANDAS 数据分析初学者教程

    Pandas 初学者教程       2018-05-19 六尺巷人 对于数据科学家,无论是数据分析还是数据挖掘来说,Pandas是一个非常重要的Python包.它不仅提供了很多方法,使得数据处理非常 ...

  4. Go GraphQL初学者教程

    Go GraphQL初学者教程 https://tutorialedge.net/golang/go-graphql-beginners-tutorial/ https://tutorialedge. ...

  5. 《SLAM for Dummies》中文版《SLAM初学者教程》

    SLAM for Dummies  SLAM初学者教程A Tutorial Approach to Simultaneous Localization and Mapping  一本关于实时定位及绘图 ...

  6. SLAM for Dummies SLAM初学者教程 中文翻译 1到4章

    SLAM for Dummies  SLAM初学者教程A Tutorial Approach to Simultaneous Localization and Mapping  一本关于实时定位及绘图 ...

  7. Java容器--2021面试题系列教程(附答案解析)--大白话解读--JavaPub版本

    Java容器--2021面试题系列教程(附答案解析)--大白话解读--JavaPub版本 前言 序言 再高大上的框架,也需要扎实的基础才能玩转,高频面试问题更是基础中的高频实战要点. 适合阅读人群 J ...

  8. iptables详细教程:基础、架构、清空规则、追加规则、应用实例(转)

    iptables防火墙可以用于创建过滤(filter)与NAT规则.所有Linux发行版都能使用iptables,因此理解如何配置iptables将会帮助你更有效地管理Linux防火墙.如果你是第一次 ...

  9. Github初学者教程(一)

    如果你是一名程序员,或者是相关专业的学生,那么Github你不应不知道.很多开源组织和大神,会选择在Github这个平台上,发布他们的开源项目,学会使用Github将能够给你的学习和工作带来巨大帮助! ...

随机推荐

  1. 轻量级封装DbUtils&Mybatis之二Dbutils

    DbUtils入门 Apache出品的极为轻量级的Jdbc访问框架,核心类只有两个:QueryRunner和ResultSetHandler. 各类ResultSetHandler: ArrayHan ...

  2. Linux的POSIX线程属性

    创建POSIX线程的函数为 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routin ...

  3. STL所有算法简介 (转) http://www.cnblogs.com/yuehui/archive/2012/06/19/2554300.html

    STL所有算法简介 STL中的所有算法(70个) 参考自:http://www.cppblog.com/mzty/archive/2007/03/14/19819.htmlhttp://hi.baid ...

  4. java内存占用问题(一)

    Nocturne 2012-12-24 java数组内存占用问题. 30 Contact[] ca = new Contact[10];   while(x<10){     ca[x]=new ...

  5. 阻塞队列之四:ArrayBlockingQueue

    一.ArrayBlockingQueue简介 一个由循环数组支持的有界阻塞队列.它的本质是一个基于数组的BlockingQueue的实现. 它的容纳大小是固定的.此队列按 FIFO(先进先出)原则对元 ...

  6. MySQL 5.7 坑爹参数 – log_timestamps

    官网原话: This variable was added in MySQL 5.7.2. Before 5.7.2, timestamps in log messages were written ...

  7. 如何将字符串去重复demo工具

    //方法一:使用集合的indexOf方法 public static void one(){ String string="aaaaaakkkkkkmnf";//需去重复的字符串s ...

  8. da分布式算法

    参考学习<数字信号处理的FPGA实现> 思想如图: 在下半部分可以看到:是将N阶的数B bit,一位一位的移入LUT然后经过累加器.其中N个数需要2.^N次方长度的LUT,B bit表示需 ...

  9. 堆、栈、free

    转自:http://codeup.org/archives/212 http://bbs.bccn.net/thread-82212-1-1.html http://www.cppblog.com/o ...

  10. Oracle播放多条 INSERT ALL

    Oracle INSERT ALL 语句介绍 Oracle INSERT ALL 语句用来用一个 INSERT 语句添加多行.该行可以只使用一个SQL命令插入到一个表或多个表. 语法 Oracle I ...