解析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基础教程之基本内置数据类型介绍
Feb 20 Python
pandas 实现将重复表格去重,并重新转换为表格的方法
Apr 18 Python
PyTorch学习笔记之回归实战
May 28 Python
对python当中不在本路径的py文件的引用详解
Dec 15 Python
PyQt5内嵌浏览器注入JavaScript脚本实现自动化操作的代码实例
Feb 13 Python
详解python深浅拷贝区别
Jun 24 Python
如何使用python爬虫爬取要登陆的网站
Jul 12 Python
详解Django配置优化方法
Nov 18 Python
python 爬取古诗文存入mysql数据库的方法
Jan 08 Python
ffmpeg+Python实现B站MP4格式音频与视频的合并示例代码
Oct 21 Python
python如何获得list或numpy数组中最大元素对应的索引
Nov 16 Python
详解pandas中利用DataFrame对象的.loc[]、.iloc[]方法抽取数据
Dec 13 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算开始时间到过期时间的相隔的天数
2011/01/12 PHP
基于PHP生成静态页的实现方法
2013/05/10 PHP
10个php函数实用却不常见
2015/10/13 PHP
PHP登录验证功能示例【用户名、密码、验证码、数据库、已登陆验证、自动登录和注销登录等】
2019/02/25 PHP
PHP7导出Excel报ERR_EMPTY_RESPONSE解决方法
2019/04/16 PHP
JavaScript 基础知识 被自己遗忘的
2009/10/15 Javascript
JS 两日期相减,获得天数的小例子(兼容IE,FF)
2013/07/01 Javascript
使用jQuery仿苹果官网焦点图特效
2014/12/23 Javascript
Jquery全选与反选点击执行一次的解决方案
2015/08/14 Javascript
javascript深拷贝和浅拷贝详解
2017/02/14 Javascript
ExtJs异步无法向外传值和赋值的完美解决办法
2017/06/14 Javascript
使用AngularJS对表单提交内容进行验证的操作方法
2017/07/12 Javascript
深入理解React中何时使用箭头函数
2017/08/23 Javascript
关于vue的npm run dev和npm run build的区别介绍
2019/01/14 Javascript
基于Vue实现平滑过渡的拖拽排序功能
2019/06/12 Javascript
学习python (2)
2006/10/31 Python
Python程序中用csv模块来操作csv文件的基本使用教程
2016/03/03 Python
Python中import机制详解
2017/11/14 Python
Python爬虫文件下载图文教程
2018/12/23 Python
Python Django的安装配置教程图文详解
2019/07/17 Python
python分布式计算dispy的使用详解
2019/12/22 Python
python实现对变位词的判断方法
2020/04/05 Python
基于Keras 循环训练模型跑数据时内存泄漏的解决方式
2020/06/11 Python
python如何实现word批量转HTML
2020/09/30 Python
pycharm进入时每次都是insert模式的解决方式
2021/02/05 Python
易程科技软件测试笔试
2013/03/24 面试题
建材业务员岗位职责
2013/12/08 职场文书
毕业生的自我评价分享
2013/12/18 职场文书
《孙权劝学》教学反思
2014/04/23 职场文书
学校安全责任书范本
2014/07/23 职场文书
公安派出所所长四风问题个人对照检查材料
2014/10/04 职场文书
民事诉讼答辩状范文
2015/05/21 职场文书
历史博物馆观后感
2015/06/05 职场文书
情况说明书格式及范文
2019/06/24 职场文书
爱心捐款倡议书:点燃希望,传递温暖
2019/11/04 职场文书
详解Spring Security中的HttpBasic登录验证模式
2022/03/17 Java/Android