Python 新手常犯错误
Python 新手常犯错误(第二部分)
转发自:http://blog.jobbole.com/43826/
作用域
在这篇文章里,我们来关注作用域在Python被误用的地方。通常,当我们定义了一个全局变量(好吧,我这样说是因为讲解的需要——全局变量是不好的),我们用一个函数访问它们是能被Python理解的:
|
1
2
3
|
bar = 42def foo(): print bar |
在这里,我们在foo函数里使用了全局变量bar,然后它也如预想的能够正常运行:
|
1
2
|
>>> foo()42 |
这样做很酷。通常,我们在使用了这个特性之后就想在所有的代码里用上它。如果像以下的例子中使用的话还是能够正常运行的:
|
1
2
3
4
5
6
7
|
bar = [42]def foo(): bar.append(0)foo()>>> print bar[42, 0] |
但是,如果我们把bar变一下呢:
|
1
2
3
4
5
6
|
>>> bar = 42... def foo():... bar = 0... foo()... print bar42 |
我们可以看到foo函数运行的好好的并且没有抛出异常,但是当我们打印bar的值的时候会发现它的值仍然是42。造成这种情况的原因就是 bar=0 这行代码,它没有改变全局变量bar的值,而是创建了一个名字也叫bar的局部变量并且它的值为0。这是个很难发现的bug,这会让没有真正理解Python作用域的新手非常痛苦。为了理解Python是如何处理局部变量和全局变量的,我们来看一种更少见的,但是可能会更让人困惑的错误,我们在打印bar的值后定义一个叫bar这个局部变量:
|
1
2
3
4
|
bar = 42def foo(): print bar bar = 0 |
这样写应该是不会出错的,不是吗?我们在打印了值之后定义了相同名称的变量,所以这应该是不会影响的(Python毕竟是一种解释型语言),真的是这样吗?
出错了
这怎么可能呢?好吧,这里有两处错误。第一点就是关于Python的,作为一种解释型语言(非常酷,我们都同意这一点),是一行一行地执行的。而事实上,Python是一个声明一个声明执行的。为了让你对我想表达的意思有点感觉,赶紧打开你最爱的shell,然后输入以下代码:
|
1
|
def foo(): |
按回车键。正如你看到的,shell里面并没有打出任何输出而是等着让你继续函数的定义。Shell里会一直这样直到你停止定义函数。这是因为定义函数是一个声明。好吧,这是一个混合的声明,里面包含了一些其他的声明,但它仍然是一个声明。直到函数被调用,不然这个函数里的内容是不会执行的。真正执行的是一个function类型的对象被创建出来了。
这引导我们来关注第二点。再强调一下,Python的动态性和解释型的特性让我们相信当 print bar 这行被执行的时候,Python会在首先在局部作用域里寻找叫bar的变量然后再去寻找全局作用域里的。但实际上发生的是局部作用域不是完全动态的。当def 这个声明执行的时候,Python会静态地从这个函数的局部作用域里获取信息。当来到 bar=0 这行的时候(不是执行到这行代码,而是当Python解释器读到这行代码的时候),它会把’bar’这个变量加入到foo函数的局部变量列表里。当foo函数执行并且Python准备执行print bar这行的时候,它就会在局部的作用域里寻找这个变量,由于这个过程是静态的,Python知道这个变量还没有被赋值,这个变量没有值,所以抛出了异常。
你可能会问:为什么不能在声明函数的时候抛出这个异常呢?Python可以知道预先知道bar这个变量在赋值前被引用了。这个问题的答案就是Python无法知道这个局部变量bar是否被赋值了。看看下面的例子:
|
1
2
3
4
5
|
bar = 42def foo(baz): if baz > 0: print bar bar = 0 |
Python在动态和静态之间玩了一个微妙的游戏。它唯一知道的事情就是bar是被赋值了,但它不知道在赋值前被引用这个异常是否存在直到它真的发生。好吧,老实说,它根本就不知道这个变量是否被赋值!
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
bar = 42def foo(): print bar if False: bar = 0>>> foo()Traceback (most recent call last): File "<pyshell#17>", line 1, in <module> foo() File "<pyshell#16>", line 3, in foo print barUnboundLocalError: local variable 'bar' referenced before assignment |
看到上面的代码里面,虽然我们作为一种智能生物能够很清楚的知道不会给bar赋值。Python无视了那个事实而是仍然声明了bar这个局部变量。
关于这个问题我已经说了够长了。我们需要的是解决方案,我会在这里给出两个解决方法。
|
1
2
3
4
5
6
7
8
9
10
|
>>> bar = 42... def foo():... global bar... print bar... bar = 0... ... foo()42>>> bar0 |
第一就是使用global关键字。这是不言自明的。这会让Python知道bar是一个全局变量而不是局部变量。
第二个方法,也是更推荐使用的,就是不要使用全局变量。在我的大量Python开发工作中从来没有用到global这个关键字。能知道怎么用它就行了,但最终还是要尽量避免使用它。如果你想保存在代码里至始至终用到的值的时候,把它定义为一个类的属性。用这种方法的话就完全不需要用global了,当你要用这个值的时候,通过类的属性来访问就可以了:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
>>> class Baz(object):... bar = 42... ... def foo():... print Baz.bar # global... bar = 0 # local... Baz.bar = 8 # global... print bar... ... foo()... print Baz.bar4208 |
Python 新手常犯错误的更多相关文章
- Python 新手常犯错误(第二部分)
转发自:http://blog.jobbole.com/43826/ 在之前几个月里,我教一些不了解Python的孩子来慢慢熟悉这门语言.渐渐地,我发现了一些几乎所有Python初学者都会犯的错误,所 ...
- Python 新手常犯错误(第一部分)
转载自:http://blog.jobbole.com/42706/ 在之前几个月里,我教一些不了解Python的孩子来慢慢熟悉这门语言.渐渐地,我发现了一些几乎所有Python初学者都会犯的错误,所 ...
- Python 新手常犯错误(第一部分)转载
觉得这篇文章针对python的默认参数写的不错,翻译的也不错,故转载下. 原文链接: Amir Rachum 翻译: 伯乐在线- 伯乐在线读者译文链接: http://blog.jobbole.c ...
- 使用DX绘制3D物体时新手常犯错误,看不见物体时可以一一排查
1.镜头不对: 物体不在镜头范围内,检查视图矩阵,世界矩阵,投影矩阵. 2.颜色全黑: 打开光照情况下,MATERIAL全为0, 或,在没有打开光照情况下,颜色值为0,造成全黑.检查当前Materia ...
- python中常犯错误之字符串列表下标问题
下标用得是中括号[] 不是小括号() 1,python中的小括号( ):代表tuple元组数据类型,元组是一种不可变序列.创建方法很简单,大多时候都是用小括号括起来的. 2.python中的中括号[ ...
- 零基础怎么学Python编程,新手常犯哪些错误?
Python是人工智能时代最佳的编程语言,入门简单.功能强大,深获初学者的喜爱. 很多零基础学习Python开发的人都会忽视一些小细节,进而导致整个程序出现错误.下面就给大家介绍一下Python开发者 ...
- C#新手常犯的错误
虽然这篇post的标题是新手常犯的错误,实际上很多有经验的程序员也经常犯这些错误,我整理了一下,就当是笔记.1.遍历List的错误,比如如下代码: List<String> strList ...
- scanf()常犯错误
------------------------------------------------------------------------ <> 本意:接收字符串. 写成代码:voi ...
- Python开发最常犯错误总结10种
不管是在学习还是工作过程中,人都会犯错.虽然Python的语法简单.灵活,但也一样存在一些不小的坑,一不小心,初学者和资深Python程序员都有可能会栽跟头.本文是Toptal网站的程序员梳理的10大 ...
随机推荐
- Android Studio+SVN配置生成apk文件
Android Studio 是谷歌推出一个Android集成开发工具,基于IntelliJ IDEA. 类似 Eclipse ADT,Android Studio 提供了集成的 Android 开发 ...
- 使用 %matplotlib inline 出错?
%matplotlib inline 是一个魔法函数(Magic Functions).官方给出的定义是:IPython有一组预先定义好的所谓的魔法函数(Magic Functions),你可以通过命 ...
- webpack 环境搭建基础框架
一.安装babel相关 1,安装依赖 cnpm i -D babel-core babel-loader babel-preset-env babel-preset-stage- babel-plug ...
- bullet, iOS真机编译错误error: identifier or immediate expression expected解决方法
刚才发现c3dEngine2(http://git.oschina.net/wantnon2/c3dEngine2 或 https://github.com/wantnon2/c3dEngine2)的 ...
- 机器学习经典算法具体解释及Python实现--线性回归(Linear Regression)算法
(一)认识回归 回归是统计学中最有力的工具之中的一个. 机器学习监督学习算法分为分类算法和回归算法两种,事实上就是依据类别标签分布类型为离散型.连续性而定义的. 顾名思义.分类算法用于离散型分布预測, ...
- 88. Merge Sorted Array【easy】
88. Merge Sorted Array[easy] Given two sorted integer arrays nums1 and nums2, merge nums2 into nums1 ...
- codeforces #364b Cells Not Under Attack
比赛的时候 long long sum=n*n,计算不出1e10长度到数,没有搞掉. 哎,以后要注意这个地方.这个题其实不难: 统计能被攻击到的个数,然后用总的个数减掉就可以了.注意有些地方重复计算, ...
- Java中List的排序和List的MAp
这里是一个类中类去实现条件优先排序的问题 package com.sun; import java.util.ArrayList; import java.util.Arrays; import ja ...
- 源代码分析Fragmentd的BackStack管理过程
1. Fragment基本使用方法 为了管理Activity中的fragments.须要调用Activity中的getFragmentManager()方法.由于FragmentManager的API ...
- IOS设计模式的六大设计原则之里氏替换原则(LSP,Liskov Substitution Principle)
定义 里氏替换原则的定义有两种,据说是由麻省理工的一位姓里的女士所提出,因此以其名进行命名. 定义1:如果对一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1所定义的程序P中在o1全都替换 ...