给妹子讲python-S01E11赋值与对象拷贝

简介:

我们今天的话题要从“可变对象的原处修改”这里引入,这是一个值得注意的问题。

上一集里我们谈到,赋值操作总是存储对象的引用,而不是这些对象的拷贝。由于在这个过程中赋值操作会产生相同对象的多个引用,因此我们需要意识到“可变对象”在这里可能存在的问题:在原处修改可变对象可能会影响程序中其他引用该对象的变量。如果你不想看到这种情景,则你需要明确的拷贝一个对象,而不是简单赋值。

X = [1,2,3,4,5]
L = ['a', X, 'b']
D = {'x':X, 'y':2}

print(L)
print(D)

['a', [1, 2, 3, 4, 5], 'b']
{'y': 2, 'x': [1, 2, 3, 4, 5]}

在这个例子中,我们可以看到列表[1,2,3,4,5]有三个引用,被变量X引用、被列表L内部元素引用、被字典D内部元素引用。那么利用这三个引用中的任意一个去修改列表[1,2,3,4,5],也会同时改变另外两个引用的对象,例如我利用L来改变[1,2,3,4,5]的第二个元素,运行的结果就非常明显。

X = [1,2,3,4,5]
L = ['a', X, 'b']
D = {'x':X, 'y':2}

L[1][2] = 'changed'
print(X)
print(L)
print(D)

[1, 2, 'changed', 4, 5]
['a', [1, 2, 'changed', 4, 5], 'b']
{'x': [1, 2, 'changed', 4, 5], 'y': 2}

【妹子说】有坑请绕行呀,在这些地方还真的挺容易犯错的。

引用是其他语言中指针的更高层的模拟。他可以帮助你在程序范围内任何地方传递大型对象而不必在途中产生拷贝,起到优化程序的作用。

【妹子说】可是如果我不想共享对象引用,而是想实实在在获取对象的一份独立的复制,该怎么办呢?

能想到这一层确实很不错,其实这个很简单,常用的手法有以下几种:

第一种方法:分片表达式能返回一个新的对象拷贝,没有限制条件的分片表达式能够完全复制列表

L = [1,2,3,4,5]
C = L[1:3]
C[0] = 8
print(C)
print(L)

[8, 3]
[1, 2, 3, 4, 5]

L = [1,2,3,4,5]
C = L[:]
C[0] = 8
print(C)
print(L)

[8, 2, 3, 4, 5]
[1, 2, 3, 4, 5]

可以看出,用分片表达式得到了新的列表拷贝C,对这个列表进行修改,不会改变原始列表L的值。

第二种方法:字典的copy方法也能够实现字典的完全复制:

D = {'a':1, 'b':2}
B = D.copy()
B['a'] = 888
print(B)
print(D)

{'a': 888, 'b': 2}
{'a': 1, 'b': 2}

第三种:内置函数list可以生成拷贝

L = [1,2,3,4]
C = list(L)
C[0] = 888
print(C)
print(L)

[888, 2, 3, 4]
[1, 2, 3, 4]

最后我们看一个复杂一些的例子

B通过无限制条件的分片操作得到了A列表的拷贝,B对列表内元素本身的修改,不会影响到A,例如修改数值,例如把引用换成别的列表引用:

L = [1,2,3,4]
A = [1,2,3,L]
B = A[:]
B[1] = 333
B[3] = ['888','999']
print(B)
print(A)
print(L)

[1, 333, 3, ['888', '999']]
[1, 2, 3, [1, 2, 3, 4]]
[1, 2, 3, 4]

但是如果是这种场景呢?

L = [1,2,3,4]
A = [1,2,3,L]
B = A[:]
B[1] = 333
B[3][1] = ['changed']
print(B)
print(A)
print(L)

[1, 333, 3, [1, ['changed'], 3, 4]]
[1, 2, 3, [1, ['changed'], 3, 4]]
[1, ['changed'], 3, 4]

因为B的最后一个元素也是列表L的引用(可以看做获取了L的地址),因此通过这个引用对所含列表对象元素进行进一步的修改,也会影响到A,以及L本身

所以说,无限制条件的分片操作以及字典的copy方法只能进行顶层的赋值。就是在最顶层,如果是数值对象就复制数值,如果是对象引用就直接复制引用,所以仍然存在下一级潜藏的共享引用现象。

如果想实现自顶向下,深层次的将每一个层次的引用都做完整独立的复制,那么就要使用copy模块的deepcopy方法。

import copy

L = [1,2,3,4]
A = [1,2,3,L]
B = copy.deepcopy(A)

B[3][1] = ['changed']
print(B)
print(A)
print(L)

[1, 2, 3, [1, ['changed'], 3, 4]]
[1, 2, 3, [1, 2, 3, 4]]
[1, 2, 3, 4]

这样,就实现了递归的遍历对象来复制他所有的组成成分,实现了完完全全的拷贝,彼此之间再无瓜葛。

【妹子说】嗯,真没想到简单的赋值还有这么多的坑!我自己来总结总结:普通的=赋值得到的其实仅仅是共享引用;无限条件的分片、字典copy方法和内置函数list这三种方法可以进行顶层对象的拷贝,而deepcopy可以彻底的实现自顶向下的完全拷贝。因此我们在实际使用的时候,一定要考虑都这些巨大的差别呀!

最后,再补充一个很小的知识:python中的比较、相等性和真值问题。

L1 = [1,2,['A','B']]
L2 = [1,2,['A','B']]
L3 = L1
print(L1 == L2, L1 is L2)
print(L1 == L3, L1 is L3)

True False
True True

==测试两个对象值的相等性,即递归的比较所有内嵌对象;

is 测试两个对象的一致性,即是否是在一个内存空间。

真值问题

Python把任意的空数据结构视为假,把任何非空数据结构视为真

使用None,一般都起到一个空的占位符的作用,在真值判断的时候为false

L = [None] * 10
print(L)

[None, None, None, None, None, None, None, None, None, None]

内置bool函数可以测试一个对象是真还是假

print(bool([1,2]))
print(bool(True))
print(bool(None))
print(bool('abcde'))

True
True
False
True

原文发布时间为:2018-08-15
本文作者:给妹子讲python
本文来自云栖社区合作伙伴“Python爱好者社区”,了解相关信息可以关注“Python爱好者社区”。

相关文章
|
27天前
|
索引 Python
Python 列表切片赋值教程:掌握 “移花接木” 式列表修改技巧
本文通过生动的“嫁接”比喻,讲解Python列表切片赋值操作。切片可修改原列表内容,实现头部、尾部或中间元素替换,支持不等长赋值,灵活实现列表结构更新。
104 1
|
2月前
|
安全 大数据 程序员
Python operator模块的methodcaller:一行代码搞定对象方法调用的黑科技
`operator.methodcaller`是Python中处理对象方法调用的高效工具,替代冗长Lambda,提升代码可读性与性能。适用于数据过滤、排序、转换等场景,支持参数传递与链式调用,是函数式编程的隐藏利器。
103 4
|
3月前
|
安全 JavaScript Java
Python中None与NoneType的真相:从单例对象到类型系统的深度解析
本文通过10个真实场景,深入解析Python中表示“空值”的None与NoneType。从单例模式、函数返回值,到类型注解、性能优化,全面揭示None在语言设计与实际编程中的核心作用,帮助开发者正确高效地处理“无值”状态,写出更健壮、清晰的Python代码。
324 3
|
3月前
|
Python
解决Python中AttributeError:'image'对象缺少属性'read_file'的问题策略。
通过上述策略综合考虑,您将能够定位问题并确定如何解决它。记住,Python社区很庞大,也很乐于帮助解决问题,因此不要害怕在求助时提供尽可能多的上下文和您已经尝试过的解决方案。
110 0
|
7月前
|
Python
解决Python报错:DataFrame对象没有concat属性的多种方法(解决方案汇总)
总的来说,解决“DataFrame对象没有concat属性”的错误的关键是理解concat函数应该如何正确使用,以及Pandas库提供了哪些其他的数据连接方法。希望这些方法能帮助你解决问题。记住,编程就像是解谜游戏,每一个错误都是一个谜题,解决它们需要耐心和细心。
335 15
|
7月前
|
安全 测试技术 开发者
Python中的“空”:对象的判断与比较
在Python开发中,判断对象是否为“空”是常见操作,但其中暗藏诸多细节与误区。本文系统梳理了Python中“空”的判定逻辑,涵盖None类型、空容器、零值及自定义对象的“假值”状态,并对比不同判定方法的适用场景与性能。通过解析常见误区(如混用`==`和`is`、误判合法值等)及进阶技巧(类型安全检查、自定义对象逻辑、抽象基类兼容性等),帮助开发者准确区分各类“空”值,避免逻辑错误,同时优化代码性能与健壮性。掌握这些内容,能让开发者更深刻理解Python的对象模型与业务语义交集,从而选择最适合的判定策略。
232 5
|
7月前
|
人工智能 Python
[oeasy]python083_类_对象_成员方法_method_函数_function_isinstance
本文介绍了Python中类、对象、成员方法及函数的概念。通过超市商品分类的例子,形象地解释了“类型”的概念,如整型(int)和字符串(str)是两种不同的数据类型。整型对象支持数字求和,字符串对象支持拼接。使用`isinstance`函数可以判断对象是否属于特定类型,例如判断变量是否为整型。此外,还探讨了面向对象编程(OOP)与面向过程编程的区别,并简要介绍了`type`和`help`函数的用法。最后总结指出,不同类型的对象有不同的运算和方法,如字符串有`find`和`index`方法,而整型没有。更多内容可参考文末提供的蓝桥、GitHub和Gitee链接。
168 11
|
11月前
|
Python
[oeasy]python055_python编程_容易出现的问题_函数名的重新赋值_print_int
本文介绍了Python编程中容易出现的问题,特别是函数名、类名和模块名的重新赋值。通过具体示例展示了将内建函数(如`print`、`int`、`max`)或模块名(如`os`)重新赋值为其他类型后,会导致原有功能失效。例如,将`print`赋值为整数后,无法再用其输出内容;将`int`赋值为整数后,无法再进行类型转换。重新赋值后,这些名称失去了原有的功能,可能导致程序错误。总结指出,已有的函数名、类名和模块名不适合覆盖赋新值,否则会失去原有功能。如果需要使用类似的变量名,建议采用其他命名方式以避免冲突。
162 14
|
10月前
|
存储 Python
[oeasy]python056_python中下划线是什么意思_underscore_理解_声明与赋值_改名字
在Python中,下划线有多种用途。单下划线(_)常用于避免命名冲突,如将`max`改为`max_`以保留内置函数功能。双下划线(__var__)通常用于特殊方法和属性,如`__name__`、`__doc__`等。此外,单下划线(_)在交互式解释器中表示上一次表达式的值,但不建议作为普通变量名使用。总结:下划线用于避免命名冲突及特殊标识。
215 1
|
11月前
|
存储 数据处理 Python
Python如何显示对象的某个属性的所有值
本文介绍了如何在Python中使用`getattr`和`hasattr`函数来访问和检查对象的属性。通过这些工具,可以轻松遍历对象列表并提取特定属性的所有值,适用于数据处理和分析任务。示例包括获取对象列表中所有书籍的作者和检查动物对象的名称属性。
233 2

推荐镜像

更多