- Preface 前言
- 第1章 引论
- 第2章 编程惯用法
- 第3章 基础语法
- 建议19:有节制地使用 from…import 语句
- 建议20:优先使用 absolute import 来导入模块
- 建议21:i+=1 不等于 ++i
- 建议22:使用 with 自动关闭资源
- 建议23:使用 else 子句简化循环(异常处理)
- 建议24:遵循异常处理的几点基本原则
- 建议25:避免 finally 中可能发生的陷阱
- 建议26:深入理解 None 正确判断对象是否为空
- 建议27:连接字符串应优先使用 join 而不是 +
- 建议28:格式化字符串时尽量使用 .format 方式而不是 %
- 建议29:区别对待可变对象和不可变对象
- 建议30:[]、() 和 {}:一致的容器初始化形式
- 建议31:记住函数传参既不是传值也不是传引用
- 建议32:警惕默认参数潜在的问题
- 建议33:慎用变长参数
- 建议34:深入理解 str() 和 repr() 的区别
- 建议35:分清 staticmethod 和 classmethod 的适用场景
- 第4章 库
- 建议36:掌握字符串的基本用法
- 建议37:按需选择 sort() 或者 sorted()
- 建议38:使用 copy 模块深拷贝对象
- 建议39:使用 Counter 进行计数统计
- 建议40:深入掌握 ConfigParser
- 建议41:使用 argparse 处理命令行参数
- 建议42:使用 pandas 处理大型 CSV 文件
- 建议43:一般情况使用 ElementTree 解析 XML
- 建议44:理解模块 pickle 优劣
- 建议45:序列化的另一个不错的选择 JSON
- 建议46:使用 traceback 获取栈信息
- 建议47:使用 logging 记录日志信息
- 建议48:使用 threading 模块编写多线程程序
- 建议49:使用 Queue 使多线程编程更安全
- 第5章 设计模式
- 第6章 内部机制
- 建议54:理解 built-in objects
- 建议55:init() 不是构造方法
- 建议56:理解名字查找机制
- 建议57:为什么需要 self 参数
- 建议58:理解 MRO 与多继承
- 建议59:理解描述符机制
- 建议60:区别 getattr() 和 getattribute() 方法
- 建议61:使用更为安全的 property
- 建议62:掌握 metaclass
- 建议63:熟悉 Python 对象协议
- 建议64:利用操作符重载实现中缀语法
- 建议65:熟悉 Python 的迭代器协议
- 建议66:熟悉 Python 的生成器
- 建议67:基于生成器的协程及 greenlet
- 建议68:理解 GIL 的局限性
- 建议69:对象的管理与垃圾回收
- 第7章 使用工具辅助项目开发
- 第8章 性能剖析与优化
建议56:理解名字查找机制
在Python中,所有所谓的变量,其实都是名字,这些名字指向一个或多个Python对象。比如以下代码:
>>> a = 1 >>> b = a >>> c = 'china' >>> id(a) == id(b) True >>> id(a) == id(c) False
从中我们可以看出,名字a和b指向同一个Python对象,即一个int类型的对象,这个对象的值为1;而c则指向另一个Python对象,它是一个str类型的对象。所有的这些名字,都存在于一个表里(又称为命名空间),一般情况下,我们称之为局部变量(locals),可以通过locals()函数调用看到。
>>> locals() {'a': 1, 'c': 'china', 'b': 1, '__builtins__': <module '__builtin__' (built-in)>, '__package__': None, '__name__': '__main__', '__doc__': None}
现在我们是直接在Python shell中执行这一些代码,实际上这些变量也是全局的,所以在一个叫globals的表里也可以看到。
>>> globals() {'a': 1, 'c': 'china', 'b': 1, '__builtins__': <module '__builtin__' (built-in)>, '__package__': None, '__name__': '__main__', '__doc__': None}
如果我们在一个函数里面定义这些变量,情况会有所不同。
>>> def foo(x): ... e = 1 ... f = e ... g = 'china' ... print locals() ... print '#' * 10 ... print globals() ... print '#' * 10 ... print c ①打印全局变量c ... >>> foo(1) {'e': 1, 'g': 'china', 'f': 1, 'x': 1} ########## {'a': 1, 'c': 'china', 'b': 1, '__builtins__': <module '__builtin__' (built- in)>, '__package__': None, '__name__': '__main__', 'foo': <function foo at 0x104fad320>, '__doc__': None} ########## china
可以看到函数中的locals()返回值并不包含之前定义在全局中的a、b、c等名字,只有定义在函数内的e、f、g和函数形参x,这是什么原因呢?要回答这个问题,首先要理解Python中变量的作用域。
Python中所有的变量名都是在赋值的时候生成的,而对任何变量名的创建、查找或者改变都会在命名空间(namespace)中进行。变量名所在的命名空间直接决定了其能访问到的范围,即变量的作用域。Python中的作用域自Python2.2之后分为局部作用域(local)、全局作用域(Global)、嵌套作用域(enclosing functions locals)以及内置作用域(Build-in)这4种。
局部作用域:一般来说函数的每次调用都会创建一个新的本地作用域,拥有新的命名空间。因此函数内的变量名可以与函数外的其他变量名相同,由于其命名空间不同,并不会产生冲突。默认情况下函数内部任意的赋值操作(包括=语句、import语句、def语句、参数传递等)所定义的变量名,如果没用global语句,则申明都为局部变量,即仅在该函数内可见。
全局作用域:定义在Python模块文件中的变量名拥有全局作用域,需要注意的是这里的全局仅限单个文件,即在一个文件的顶层的变量名仅在这个文件内可见,并非所有的文件,其他文件中想使用这些变量名必须先导入文件对应的模块。当在函数之外给一个变量名赋值时是在其全局作用域的情况下进行的。
嵌套作用域:一般在多重函数嵌套的情况下才会考虑到。需要注意的是global语句仅针对全局变量,在嵌套作用域的情况下,如果想在嵌套的函数内修改外层函数中定义的变量,即使使用global进行申明也不能达到目的,其结果最终是在嵌套的函数所在的命名空间中创建了一个新的变量。示例如下:
var = 'a' def inner(): global var var = 'b' print 'inside inner, var is ', var inner() print 'inside outer function, var is ', var
上述程序的输出如下:
inside inner, var is b inside outer function, var is a
内置作用域:这个相对简单,它是通过一个标准库中名为__builtin__的模块来实现的。
回到前面代码中标注①的语句print c,仍然正确输出了china这个值。这是因为当访问一个变量的时候,其查找顺序遵循变量解析机制LEGB法则,即依次搜索4个作用域:局部作用域、嵌套作用域、全局作用域以及内置作用域,并在第一个找到的地方停止搜寻,如果没有搜到,则会抛出异常。因此当存在多个同名变量的时候,操作生效的往往是搜索顺序在前的。具体来说Python的名字查找机制如下:
1)在最内层的范围内查找,一般而言,就是函数内部,即在locals()里面查找。
2)在模块内查找,即在globals()里面查找。
3)在外层查找,即在内置模块中查找,也就是在__builtin__中查找。
至此,我们可以理解清楚能够在foo()函数中访问到名字c的原因在于当Python在局部变量中找不到c时,它会尝试在模块级的全局变量中查找,并成功地找到该名字。
不过,当我们试图改变全局变量的值时,事情可能跟想象的稍有不同。
>>> def bar(): ... c = 'america' ... print c ... >>> bar() america >>> print c china
真奇怪!不是吗?在bar()函数中修改c的值,并没有修改到全局变量的c,而是好像bar()函数有了一个局部变量c一样!事实上确实如此,在CPython的实现中,只要出现了赋值语句(或者称为名字绑定),那么这个名字就被当作局部变量来对待。所以在这里如果需要改变全局变量c的值,就需要使用global关键字。
>>> def bar(): ... global c ... c = 'america' ... print c ... >>> bar() america >>> print c america
不过,随着更多Python特性的加入,事情变得更加复杂起来。比如在Python闭包中,有这样的问题:
>>> def foo(): ... a = 1 ... def bar(): ... b = a * 2 ... a = b + 1 ... print a ... return bar ... >>> foo()() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in bar UnboundLocalError: local variable 'a' referenced before assignment
从上例中可以看出,在闭包bar()中,在编译代码为字节码时,因为存在a=b+1这条语句,所以a被当作了局部变量看待,而执行时因为b=a*2先执行,此时局部变量a尚不存在,所以产生了一个UnboundLocalError。在Python2.x中可以使用global关键字解决部分问题,先把a创建为一个模块全局变量,然后在所有读写(包括只是访问)该变量的作用域中都要先使用global声明其为全局变量。
>>> a = 1 >>> def foo(x): ... global a ... a = a * x ... def bar(): ... global a ... b = a * 2 ... a = b + 1 ... print a ... return bar ... >>> foo(1)() 3
这种方案抛开编程语言并不提倡全局变量不谈,有的时候还影响业务逻辑。此外,还有把a作为容器的一个元素来对待的方案,但也都相当复杂。真正的解决方案是Python3引入的nonlocal关键字,通过它能够解决这方面的问题。
>>> def foo(x): ... a = x ... def bar(): ... nonlocal a ... b = a * 2 ... a = b + 1 ... print(a) ... return bar ... >>> bar1 = foo(1)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论