解析Python中的变量、引用、拷贝和作用域的问题


Posted in Python onApril 07, 2015

在Python中,变量是没有类型的,这和以往看到的大部分编辑语言都不一样。在使用变量的时候,不需要提前声明,只需要给这个变量赋值即可。但是,当用变量的时候,必须要给这个变量赋值;如果只写一个变量,而没有赋值,那么Python认为这个变量没有定义。如下:
 

>>> a
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined

    下面我们具体讲一下Python中的变量,引用,拷贝和作用域问题。。

    一、可变对象 & 不可变对象

    在Python中,对象分为两种:可变对象和不可变对象,不可变对象包括int,float,long,str,tuple等,可变对象包括list,set,dict等。需要注意的是:这里说的不可变指的是值的不可变。对于不可变类型的变量,如果要更改变量,则会创建一个新值,把变量绑定到新值上,而旧值如果没有被引用就等待垃圾回收。另外,不可变的类型可以计算hash值,作为字典的key。可变类型数据对对象操作的时候,不需要再在其他地方申请内存,只需要在此对象后面连续申请(+/-)即可,也就是它的内存地址会保持不变,但区域会变长或者变短。

    下面是一些例子:
 

>>> a = 'xianglong.me'
>>> id(a)
140443303134352
>>> a = '1saying.com'
>>> id(a)
140443303131776
# 重新赋值之后,变量a的内存地址已经变了
# 'xianglong.me'是str类型,不可变,所以赋值操作知识重新创建了str '1saying.com'对象,然后将变量a指向了它

 

>>> a_list = [1, 2, 3]
>>> id(a_list)
140443302951680
>>> a_list.append(4)
>>> id(a_list)
140443302951680
# list重新赋值之后,变量a_list的内存地址并未改变
# [1, 2, 3]是可变的,append操作只是改变了其value,变量a_list指向没有变

    二、变量无类型,对象有类型

解析Python中的变量、引用、拷贝和作用域的问题

    三、函数值传递

    先看一个例子:
 

def func_int(a):
  a += 4
 
def func_list(a_list):
  a_list[0] = 4
 
t = 0
func_int(t)
print t
# output: 0
 
t_list = [1, 2, 3]
func_list(t_list)
print t_list
# output: [4, 2, 3]

    对于上面的输出,不少Python初学者都比较疑惑:第一个例子看起来像是传值,而第二个例子确实传引用。其实,解释这个问题也非常容易,主要是因为可变对象和不可变对象的原因:对于可变对象,对象的操作不会重建对象,而对于不可变对象,每一次操作就重建新的对象。

    在函数参数传递的时候,Python其实就是把参数里传入的变量对应的对象的引用依次赋值给对应的函数内部变量。参照上面的例子来说明更容易理解,func_int中的局部变量"a"其实是全部变量"t"所指向对象的另一个引用,由于整数对象是不可变的,所以当func_int对变量"a"进行修改的时候,实际上是将局部变量"a"指向到了整数对象"1"。所以很明显,func_list修改的是一个可变的对象,局部变量"a"和全局变量"t_list"指向的还是同一个对象。

    四、浅拷贝 & 深拷贝

    接下来的问题是:如果我们一定要复制一个可变对象的副本怎么办?简单的赋值已经证明是不可行的,所以Python提供了copy模块,专门用于复制可变对象。copy中有两个方法:copy()和deepcopy(),前一个是浅拷贝,后一个是深拷贝。浅拷贝仅仅复制了第一个传给它的对象,下面的不管了;而深拷贝则将所有能复制的对象都复制了。下面是一个例子:
 

a = [[1, 2, 3], [4, 5, 6]]
b = a
c = copy.copy(a)
d = copy.deepcopy(a)
 
a.append(15)
a[1][2] = 10
 
print a
print b
print c
print d
 
# [[1, 2, 3], [4, 5, 10], 15]
# [[1, 2, 3], [4, 5, 10], 15]
# [[1, 2, 3], [4, 5, 10]]
# [[1, 2, 3], [4, 5, 6]]

    五、作用域

    在Python程序中创建、改变或查找变量名时,都是在一个保存变量名的地方进行中,那个地方我们称之为命名空间。作用域这个术语也称之为命名空间。具体地说,在代码中变量名被赋值(Python中变量声明即赋值,global 声明的只是变量的使用域)的位置决定了该变量能被访问的范围。函数定义了本地作用域,而模块定义的是全局作用域。

    每一个模块都是全局作用域。也就是说,创建于模块文件顶层的变量具有全局作用域,对于外部访问就成了一个模块对象的属性。全局作用域的作用范围仅限于单个文件。“全局”指的是在一个文件的顶层变量名对于这个文件而言是全局的。每次对函数的调用都创建了一个新的本地作用域。Python中也有递归,即可以调用自身,每次调用都会创建五个新的本地命名空间。赋值的变量名除非声明为全局变量,否则均为本地变量。如果需要在函数内部对模块文件顶层的变量名赋值,需要在函数内部通过 global 语句声明该变量。所有的变量可归纳为本地、全局或者内置三种。范围分别为def内部,在一个模块的命名空间内部和预定义的 __builtin__ 模块提供的变量。

    变量名引用分为三个作用域进行查找:首先是本地,然后是函数内(如果有的话),之后是全局,最后是内置。在默认情况下,变量名赋值会创建或者改变本地变量。全局声明将会给映射到模块文件内部的作用域的变量名赋值。Python 的变量名解析机制也称为 LEGB 法则,具体如下:

    当在函数中使用未确定的变量名时,Python搜索4个作用域:本地作用域(L),之后是上一层嵌套结构中 def 或 lambda 的本地作用域(E),之后是全局作用域(G),最后是内置作用域(B)。按这个查找原则,在第一处找到的地方停止。如果没有找到,Python 会报错的。下图说明了搜索流程(由内及外):

    上面说了,Python中的变量是没有类型的,但Python其实是区分类型的:Python的所有变量其实都是指向内存中的对象的一个指针,都是值的引用,而其类型是跟着对象走的。总结来说:在Python中,类型是属于对象的,而不是变量, 变量和对象是分离的,对象是内存中储存数据的实体,变量则是指向对象的指针。在《Learning Python》一书中有一个观点:变量无类型,对象有类型,大概也是说的这个意思。下面是一张说明变量的图:
    

解析Python中的变量、引用、拷贝和作用域的问题

    Python像PHP一样提供了一个global语法,global定义的本地变量会变成其对应全局变量的一个别名,即是同一个变量。下面的例子可以帮你更好的理解:
 

a = 44
 
def test1():
  a = 14
  print a
test1() # 输出:14
 
def test2():
  global a
  print a
test2() # 输出:44

Python 相关文章推荐
Python解释执行原理分析
Aug 22 Python
python实现查询苹果手机维修进度
Mar 16 Python
详解Python3中的Sequence type的使用
Aug 01 Python
python编程开发之日期操作实例分析
Nov 13 Python
python迭代器与生成器详解
Mar 10 Python
Python+matplotlib实现华丽的文本框演示代码
Jan 22 Python
spark: RDD与DataFrame之间的相互转换方法
Jun 07 Python
Python 实现子类获取父类的类成员方法
Jan 11 Python
使用PIL(Python-Imaging)反转图像的颜色方法
Jan 24 Python
Django 大文件下载实现过程解析
Aug 01 Python
python实现图片,视频人脸识别(opencv版)
Nov 18 Python
Selenium浏览器自动化如何上传文件
Apr 06 Python
在Python中利用Pandas库处理大数据的简单介绍
Apr 07 #Python
详解Python中的join()函数的用法
Apr 07 #Python
Python中用于去除空格的三个函数的使用小结
Apr 07 #Python
简单介绍Python中的len()函数的使用
Apr 07 #Python
Python中endswith()函数的基本使用
Apr 07 #Python
举例详解Python中的split()函数的使用方法
Apr 07 #Python
Python中用startswith()函数判断字符串开头的教程
Apr 07 #Python
You might like
PHP数据对象PDO操作技巧小结
2016/09/27 PHP
CI框架(CodeIgniter)实现的数据库增删改查操作总结
2018/05/23 PHP
php中pcntl_fork创建子进程的方法实例
2019/03/14 PHP
JQuery对checkbox操作 (循环获取)
2011/05/20 Javascript
JS实现点击复选框将按钮或文本框变为灰色不可用的方法
2015/08/11 Javascript
javascript针对不确定函数的执行方法
2015/12/16 Javascript
AngularJS控制器controller正确的通信的方法
2016/01/25 Javascript
IE下JS保存图片的简单实例
2016/07/15 Javascript
javascript将中国数字格式转换成欧式数字格式的简单实例
2016/08/02 Javascript
AngularJS使用ng-repeat指令实现下拉框
2016/08/23 Javascript
node.js中的事件处理机制详解
2016/11/26 Javascript
JS获取本周周一,周末及获取任意时间的周一周末功能示例
2017/02/09 Javascript
微信小程序之页面拦截器的示例代码
2017/09/07 Javascript
webpack4+react多页面架构的实现
2018/10/25 Javascript
VUE UPLOAD 通过ACTION返回上传结果操作
2020/09/07 Javascript
Python设计模式中单例模式的实现及在Tornado中的应用
2016/03/02 Python
利用Python爬虫给孩子起个好名字
2017/02/14 Python
python 通过类中一个方法获取另一个方法变量的实例
2019/01/22 Python
Opencv实现抠图背景图替换功能
2019/05/21 Python
python实现各种插值法(数值分析)
2019/07/30 Python
python爬虫增加访问量的方法
2019/08/22 Python
git查看、创建、删除、本地、远程分支方法详解
2020/02/18 Python
Python3爬虫中Splash的知识总结
2020/07/10 Python
[原创]赚疯了!转手立赚800+?大佬的python「抢茅台脚本」使用教程
2021/01/12 Python
Python爬虫获取op.gg英雄联盟英雄对位胜率的源码
2021/01/29 Python
CSS实现半透明边框与多重边框的场景分析
2019/11/13 HTML / CSS
html5实现输入框fixed定位在屏幕最底部兼容性
2020/07/03 HTML / CSS
JD Sports芬兰:英国领先的运动鞋和运动服饰零售商
2018/11/16 全球购物
Seavenger官网:潜水服、浮潜、靴子和袜子
2020/03/05 全球购物
Feelunique中文官网:欧洲最大化妆品零售电商
2020/07/10 全球购物
史上最全面的Java面试题汇总!
2015/02/03 面试题
应届中专生自荐书范文
2014/02/13 职场文书
企业管理毕业生求职信范文
2014/03/07 职场文书
党的群众路线教育实践活动整改落实情况自查报告
2014/10/28 职场文书
男方婚礼答谢词
2015/01/20 职场文书
Django 如何实现文件上传下载
2021/04/08 Python