一、赋值、引用

在python中赋值语句总是建立对象的引用值,而不是复制对象。因此,python变量更像是指针,而不是数据存储区域

这点和大多数语音类似吧,比如C++、Java等

1、先看个例子:

values=[0,1,2]
values[1]=values
print(values) # [0, [...], 2]

预想应该是:[0,[0,1,2],2],但结果却为何要赋值无限次?

可以说 Python 没有赋值,只有引用。你这样相当于创建了一个引用自身的结构,所以导致了无限循环。为了理解这个问题,有个基本概念需要搞清楚。
Python 没有「变量」,我们平时所说的变量其实只是「标签」,是引用。
执行:values=[0,1,2]的时候,python做的事情是首先创建一个列表对象[0,1,2],然后给它贴上名为values的标签。如果随后执行values=[3,4,5]
的话,python做的事情是创建另一个列表对象[3,4,5],然后把刚才那张名为values的标签从前面的[0,1,2]对象上撕下来,重新贴到[3,4,5]这个对象上。

至始至终,并没有一个叫做 values 的列表对象容器存在,Python 也没有把任何对象的值复制进 values 去。过程如图所示:

执行:values[1]=values的时候,python做的事情则是把values这个标签所引用的列表对象的第二个元素指向values所引用的列表对象本身。执行完毕后,values

标签还是指向原来那个对象,只不过那个对象的结构发生了变化,从之前的列表[0,1,2]变成了[0,?,2],而这个?则是指向那个对象本身的

一个引用。如图所示:

要达到你所需要的效果,即得到 [0, [0, 1, 2], 2] 这个对象,你不能直接将 values[1] 指向 values 引用的对象本身,而是需要吧 [0, 1, 2] 这个对象「复制」一遍,得到一个新对象,再将 values[1] 指向这个复制后的对象。Python 里面复制对象的操作因对象类型而异,复制列表 values 的操作是

values[:]   # 生成对象的拷贝或者是复制序列,不再是引用和共享变量,但此法只能顶层复制

所以你需要执行:values[1]=values[:]

Python 做的事情是,先 dereference 得到 values 所指向的对象 [0, 1, 2],然后执行 [0, 1, 2][:] 复制操作得到一个新的对象,内容也是 [0, 1, 2],然后将 values 所指向的列表对象的第二个元素指向这个复制二来的列表对象,最终 values 指向的对象是 [0, [0, 1, 2], 2]。过程如图所示:

往更深处说,values[:] 复制操作是所谓的「浅复制」(shallow copy),当列表对象有嵌套的时候也会产生出乎意料的错误,比如

a=[0,[1,2],3]
b=a[:]
a[0]=8
a[1][1]=9
print(a) # [8, [1, 9], 3]
print(b) # [0, [1, 9], 3]

b 的第二个元素也被改变了。想想是为什么?不明白的话看下图

正确的复制嵌套元素的方法是进行「深复制」(deep copy),方法是

import copy

a = [0, [1, 2], 3]
b = copy.deepcopy(a)
a[0] = 8
a[1][1] = 9

2、引用vs拷贝

(1)没有限制条件的分片表达式(L[:])能够复制序列,但此法只能浅层复制。

(2)字典 copy 方法,D.copy() 能够复制字典,但此法只能浅层复制

(3)有些内置函数,例如 list,能够生成拷贝 list(L)

(4)copy 标准库模块能够生成完整拷贝:deepcopy 本质上是递归 copy

(5)对于不可变对象和可变对象来说,浅复制都是复制的引用,只是因为复制不变对象和复制不变对象的引用是等效的(因为对象不可变,当改变时会新建对象重新赋值)。所以看起来浅复制只复制不可变对象(整数,实数,字符串等),对于可变对象,浅复制其实是创建了一个对于该对象的引用,也就是说只是给同一个对象贴上了另一个标签而已。

3、增强赋值以及共享引用

x = x + y,x 出现两次,必须执行两次,性能不好,合并必须新建对象 x,然后复制两个列表合并

属于复制/拷贝

x += y,x 只出现一次,也只会计算一次,性能好,不生成新对象,只在内存块末尾增加元素。

当 x、y 为list时, += 会自动调用 extend 方法进行合并运算,in-place change。

属于共享引用

二、深拷贝deepcopy与浅拷贝copy

python中的对象之间赋值时是按引用传送的,如果需要拷贝对象,需要使用标准库中的copy模块

1、copy.copy 浅拷贝,只拷贝父对象,不会拷贝对象的内部的子对象。(子对象(数组)修改,也会修改)

2、copy.deepcopy 深拷贝,拷贝对象及其子对象(原始对象)

import copy
a=[,,[,],{'a':}] # 原始对象
b=a # 赋值,传对象的引用
c=copy.copy(a) # 对象拷贝,浅拷贝
d=copy.deepcopy(a) # 对象拷贝,深拷贝
e=a[:] # 能复制序列,浅拷贝 a.append('add1')  # 修改对象a
a[].append('add2')  # 修改对象a中的[3,4]数组对象
a[]=''
print('a:',a)
print('b:',b)
print('c:',c)
print('d:',d)
print('e:',e)
"""
执行结果:
a: [1, 2, [3, 4, 'add2'], '666', 'add1']
b: [1, 2, [3, 4, 'add2'], '666', 'add1']
c: [1, 2, [3, 4, 'add2'], {'a': 1}]
d: [1, 2, [3, 4], {'a': 1}]
e: [1, 2, [3, 4, 'add2'], {'a': 1}]
解释:copy.copy 浅拷贝 只拷贝父对象,不会拷贝对象的内部的子对象。子对象(数组)修改,也会修改
copy.deepcopy 深拷贝 拷贝对象及其子对象(原始对象)
"""

三、深入理解python变量作用域及其陷阱

1、可变对象&不可变对象

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

a='hello'
print(id(a)) # 1991608735200
a='python'
print(id(a)) # 1991608735368 # 重新赋值之后,变量a的内存地址已经变了
# 'hello'是str类型,不可变,所以赋值操作知识重新创建了str 'python'对象,然后将变量a指向了它 l1=[1,2,3]
print(id(l1)) # 2262493958280
l1.append(4)
print(id(l1)) # 2262493958280 # list重新赋值之后,变量l1的内存地址并未改变
# [1, 2, 3]是可变的,append操作只是改变了其value,变量l1指向没有变

  

2、函数值传递

def func_int(a):
a+= def func_list(l1):
l1[]= t=
func_int(t)
print(t) # t_list=[,,]
func_list(t_list)
print(t_list) # [, , ]

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

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

3、陷阱:使用可变的默认参数

我多次见到过如下的代码:
def foo(a, b, c=[]):
# append to c
# do some more stuff
永远不要使用可变的默认参数,可以使用如下的代码代替:
def foo(a, b, c=None):
    if c is None:
        c = []
    # append to c
    # do some more stuff
‍‍与其解释这个问题是什么,不如展示下使用可变默认参数的影响:‍‍
In[]: def foo(a, b, c=[]):
...        c.append(a)
...        c.append(b)
...        print(c)
...
In[]: foo(, )
[, ]
In[]: foo(, )
[, , , ]
In[]: foo(, )
[, , , , , ]
同一个变量c在函数调用的每一次都被反复引用。这可能有一些意想不到的后果。

参考:http://www.cnblogs.com/jiangzhaowei/p/5740913.html

python中拷贝对象的区别的更多相关文章

  1. python中生成器对象和return 还有循环的区别

    python中生成器对象和return 还有循环的区别 在python中存在这么一个关键字yield,这个关键字在项目中经常被用到,比如我写一个函数不想它只返回一次就结束那我们就不能用return,因 ...

  2. Python中__repr__和__str__区别

    Python中__repr__和__str__区别 看下面的例子就明白了 class Test(object): def __init__(self, value='hello, world!'): ...

  3. python 中is和= = 的区别

    Python中的对象包含三要素:id.type.value,其中id用来唯一标识一个对象,type标识对象的类型,value是对象的值. is判断的是a对象是否就是b对象,是通过id来判断的: ==判 ...

  4. 在python 中is和= = 的区别

    Python中的对象包含三要素:id.type.value其中id用来唯一标识一个对象,type标识对象的类型,value是对象的值is判断的是a对象是否就是b对象,是通过id来判断的==判断的是a对 ...

  5. python中// 和/有什么区别

    python中// 和/有什么区别 通常C/C++中,"/ " 算术运算符的计算结果是根据参与运算的两边的数据决定的,比如: 6 / 3 = 2 ; 6,3都是整数,那么结果也就是 ...

  6. PyObject and PyTypeObject - Python 中的 '对象' 们

    1 PyObject, PyTypeObject - Python 中的 '对象' 们 '一切皆对象' - 这是 Python 的学习和使用者们最最常听到一句, 可谓 博大精深 - '勃大精深'. ' ...

  7. python中的对象拷贝

    python中.进行函数參数传递或者返回值时,假设是一般的变量,会拷贝传递.假设是列表或字典则是引用传递.那python怎样对列表和字典进行拷贝传递呢:标准库的copy模块提供了两个方法:copy和d ...

  8. 【转】python中的对象拷贝

    转自:https://www.cnblogs.com/bhlsheji/p/5352330.html python中.进行函数參数传递或者返回值时,假设是一般的变量,会拷贝传递.假设是列表或字典则是引 ...

  9. Python中is和==的区别

    Python中有很多运算符,今天我们就来讲讲is和==两种运算符在应用上的本质区别是什么. 在讲is和==这两种运算符区别之前,首先要知道Python中对象包含的三个基本要素,分别是:id(身份标识) ...

随机推荐

  1. firefox插件Firebug的使用教程

    什么是Firebug 从事了数年的Web开发工作,越来越觉得现在对WEB开发有了更高的要求.要写出漂亮的HTML代码:要编写精致的CSS样式表展示每个页面模块:要调试 javascript给页面增加一 ...

  2. iOS-封装UIPickerView

    创建类WJPickerView继承与UIView ProvinceModel是省市的model,包含属性 @property (nonatomic, strong) NSString *provinc ...

  3. SSL 重点SSL会话步骤

    SSL.TLS协议 在wiki百科查看下,两者的区别 实现SSL协议的软件 OpenSSL开源软件 SSL会话步骤 1:客户端向服务端索取CA证书,然后验证证书   2:客户端与服务端约定一个通信中使 ...

  4. Linux的压缩/解压缩文件处理 zip & unzip

    Linux的压缩/解压缩命令详解及实例 压缩服务器上当前目录的内容为xxx.zip文件 zip -r xxx.zip ./* 解压zip文件到当前目录 unzip filename.zip 另:有些服 ...

  5. CentOS安装crontab及使用方法(转)

    CentOS安装crontab及使用方法(转)    安装crontab:[root@CentOS ~]# yum install vixie-cron[root@CentOS ~]# yum ins ...

  6. 初探Android动画之门

    原文地址:http://www.cnblogs.com/kross/p/3376451.html 最近自学了下动画的相关知识,总结为今天的文章,希望对大家有帮助. Android中的动画大致分为三种: ...

  7. ADO.NET DBHelper 类库

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.D ...

  8. 【EF】Entity Framework Core 软删除与查询过滤器

    本文翻译自<Entity Framework Core: Soft Delete using Query Filters>,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 注意 ...

  9. 【JavaScript】checkBox的多选行<tr>信息获取

    页面的列表table显示(后台model.addAttribute("page", page);传来page信息,page通过foreach标签迭代展示表格数据): <!-- ...

  10. hadoop(三)HDFS基础使用

    一.HDFS前言 1. 设计思想          分而治之:将大文件,大批量文件,分布式的存放于大量服务器上.以便于采取分而治之的方式对海量数据进行运算分析     2. 在大数据系统架构中的应用  ...