变量全都是引用

跟其他编程语言不同,Python的变量不是盒子,不会存储数据,它们只是引用,就像标签一样,贴在对象上面。

比如:

  1. >>> a = [1, 2, 3]
  2. >>> b = a
  3. >>> a.append(4)
  4. >>> b
  5. [1, 2, 3, 4]
  6. >>> b is a
  7. True

a变量和b变量引用的是同一个列表[1, 2, 3]。b可以叫做a的别名。

比较来看:

  1. >>> a = [1, 2, 3]
  2. >>> c = [1, 2, 3]
  3. >>> c == a
  4. True
  5. >>> c is a
  6. False

c引用的是另外一个列表,虽然和a引用的列表的值相等,但是它们是不同的对象。

浅复制与深复制

浅复制是指只复制最外层容器,副本中的元素是源容器中元素的引用。如果所有元素都是不可变的,那么这样没有问题,还能节省内容。但是,如果有可变的元素,那么结果可能会出乎意料之外。构造方法或[:]做的都是浅复制。

示例:

  1. >>> x1 = [3, [66, 55, 44], (7, 8, 9)]
  2. # x2是x1的浅复制
  3. >>> x2 = list(x1)
  4. # 不可变元素没有影响
  5. >>> x1.append(100)
  6. >>> x1
  7. [3, [66, 55, 44], (7, 8, 9), 100]
  8. >>> x2
  9. [3, [66, 55, 44], (7, 8, 9)]
  10. # x1[1]是列表,可变元素会影响x2
  11. # 因为它们引用的是同一个对象
  12. >>> x1[1].remove(55)
  13. >>> x1
  14. [3, [66, 44], (7, 8, 9), 100]
  15. >>> x2
  16. [3, [66, 44], (7, 8, 9)]
  17. # x2[1]也会反过来影响x1
  18. >>> x2[1] += [33, 22]
  19. >>> x1
  20. [3, [66, 44, 33, 22], (7, 8, 9), 100]
  21. >>> x2
  22. [3, [66, 44, 33, 22], (7, 8, 9)]
  23. # 不可变元组也不会有影响
  24. # +=运算符创建了一个新元组
  25. >>> x2[2] += (10, 11)
  26. >>> x1
  27. [3, [66, 44, 33, 22], (7, 8, 9), 100]
  28. >>> x2
  29. [3, [66, 44, 33, 22], (7, 8, 9, 10, 11)]

深复制是指我们常规理解的复制,副本不共享内部对象的引用,是完全独立的一个副本。这可以借助copy.deepcopy来实现。

示例:

  1. >>> a = [10, 20]
  2. >>> b = [a, 30]
  3. >>> a.append(b)
  4. >>> a
  5. [10, 20, [[...], 30]]
  6. >>> from copy import deepcopy
  7. >>> c = deepcopy(a)
  8. >>> c
  9. [10, 20, [[...], 30]]

即使是有循环引用也能正确复制。

注意copy.copy()是浅复制,copy.deepcopy()是深复制。

函数传参

Python唯一支持的参数传递模式是共享传参,也就是指函数的各个形式参数获得实参中各个引用的副本。因为Python的变量全都是引用。对于不可变对象来说没有问题,但是对于可变对象就不一样了。

示例:

  1. >>> def f(a, b):
  2. ... a += b
  3. ... return a
  4. ...
  5. # 数字不变
  6. >>> x = 1
  7. >>> y = 2
  8. >>> f(x, y)
  9. 3
  10. >>> x, y
  11. (1, 2)
  12. # 列表变了
  13. >>> a = [1, 2]
  14. >>> b = [3, 4]
  15. >>> f(a, b)
  16. [1, 2, 3, 4]
  17. >>> a, b
  18. ([1, 2, 3, 4], [3, 4])
  19. # 元组不变
  20. >>> t = (10, 20)
  21. >>> u = (30, 40)
  22. >>> f(t, u)
  23. (10, 20, 30, 40)
  24. >>> t, u
  25. ((10, 20), (30, 40))

由此可以得出一条警示:函数参数尽量不要使用可变参数,如果非用不可,应该考虑在函数内部进行复制。

示例:

  1. class TwilightBus:
  2. """A bus model that makes passengers vanish"""
  3. def __init__(self, passengers=None):
  4. if passengers is None:
  5. self.passengers = []
  6. else:
  7. self.passengers = passengers
  8. def pick(self, name):
  9. self.passengers.append(name)
  10. def drop(self, name):
  11. self.passengers.remove(name)

测试一下:

  1. >>> basketball_team = ['Sue', 'Tina', 'Maya', 'Diana', 'Pat']
  2. >>> bus = TwilightBus(basketball_team)
  3. >>> bus.drop('Tina')
  4. >>> bus.drop('Pat')
  5. >>> basketball_team
  6. ['Sue', 'Maya', 'Diana']

TwilightBus下车的学生,竟然从basketball_team中消失了。这是因为self.passengers引用的是同一个列表对象。修改方法很简单,复制个副本:

  1. def __init__(self, passengers=None):
  2. if passengers is None:
  3. self.passengers = []
  4. else:
  5. self.passengers = list(passengers) # 使用构造函数复制副本

del和垃圾回收

del语句删除的是引用,而不是对象。但是del可能会导致对象没有引用,进而被当做垃圾回收。

示例:

  1. >>> import weakref
  2. >>> s1 = {1, 2, 3}
  3. # s2和s1引用同一个对象
  4. >>> s2 = s1
  5. >>> def bye():
  6. ... print("Gone")
  7. ...
  8. # 监控对象和调用回调
  9. >>> ender = weakref.finalize(s1, bye)
  10. >>> ender.alive
  11. True
  12. # 删除s1后还存在s2引用
  13. >>> del s1
  14. >>> ender.alive
  15. True
  16. # s2重新绑定导致{1, 2, 3}引用归零
  17. >>> s2 = "spam"
  18. Gone
  19. # 对象被销毁了
  20. >>> ender.alive
  21. False

在CPython中,对象的引用数量归零后,对象会被立即销毁。如果除了循环引用之外没有其他引用,两个对象都会被销毁。

弱引用

某些情况下,可能需要保存对象的引用,但不留存对象本身。比如,有个类想要记录所有实例。这个需求可以使用弱引用实现。

比如上面示例中的weakref.finalize(s1, bye),finalize就持有{1, 2, 3}的弱引用,虽然有引用,但是不会影响对象被销毁。

其他使用弱引用的方式是WeakDictionary、WeakValueDictionary、WeakSet。

示例:

  1. class Cheese:
  2. def __init__(self, kind):
  3. self.kind = kind
  4. def __repr__(self):
  5. return 'Cheese(%r)' % self.kind
  1. >>> import weakref
  2. >>> stock = weakref.WeakValueDictionary()
  3. >>> catalog = [Cheese('Red Leicester'), Cheese('Tilsit'),
  4. ... Cheese('Brie'), Cheese('Parmesan')]
  5. ...
  6. >>> for cheese in catalog:
  7. # 用作缓存
  8. # key是cheese.kind
  9. # value是cheese的弱引用
  10. ... stock[cheese.kind] = cheese
  11. ...
  12. >>> sorted(stock.keys())
  13. ['Brie', 'Parmesan', 'Red Leicester', 'Tilsit']
  14. # 删除catalog引用,stock弱引用不影响垃圾回收
  15. # WeakValueDictionary的值引用的对象被销毁后,对应的键也会自动删除
  16. >>> del catalog
  17. >>> sorted(stock.keys()) # 还存在一个cheese临时变量的引用
  18. ['Parmesan']
  19. # 删除cheese临时变量的引用,stock就完全清空了
  20. >>> del cheese
  21. >>> sorted(stock.keys())
  22. []

注意不是每个Python对象都可以作为弱引用的目标,比如基本的list和dict就不可以,但是它们的子类是可以的:

  1. class MyList(list):
  2. pass
  3. a_list = MyList(range(10))
  4. weakref_to_a_list = weakref.ref(a_list)

小结

本文首先阐述了Python变量全部都是引用的这个事实,这意味着在Python中,简单的赋值是不创建副本的。如果要创建副本,可以选择浅复制和深复制,浅复制使用构造方法、[:]copy.copy(),深复制使用copy.deepcopy()。del删除的是引用,但是会导致对象没有引用而被当做垃圾回收。有时候需要保留引用而不保留对象(比如缓存),这叫做弱引用,weakref库提供了相应的实现。

参考资料:

《流畅的Python》

Python变量小秘密的更多相关文章

  1. Python 变量类型

    Python 变量类型 变量存储在内存中的值.这就意味着在创建变量时会在内存中开辟一个空间. 基于变量的数据类型,解释器会分配指定内存,并决定什么数据可以被存储在内存中. 因此,变量可以指定不同的数据 ...

  2. Python变量、数据类型6

    1.Python变量 变量,即代表某个value的名字. 变量的值存储在内存中,这意味着在创建变量时会在内存中开辟一个空间. !!!即值并没有保存在变量中,它们保存在计算机内存的深处,被变量引用.所以 ...

  3. Python变量类型

    Python变量类型 变量是存储在内存中的值,因此在创建变量时会在内存中开辟一个空间. 基于变量的数据类型,解释器会分配指定的内存,并决定什么数据可以被存储在内存中. 因此变量可以指定不同的数据类型, ...

  4. C与Python变量的区别

    C中变量有类型,代表一定内存. 而Python变量只是封装过的指针,没有类型.如果不指向对象,就没有意义,更谈不上类型. python中 a=b,和C中 a=b是完全不同的两个操作.前者只是指针(引用 ...

  5. Python变量类型(l整型,长整形,浮点型,复数,列表,元组,字典)学习

    #coding=utf-8 __author__ = 'Administrator' #Python变量类型 #Python数字,python支持四种不同的数据类型 int整型 long长整型 flo ...

  6. python——变量

    参考资料: Python程序设计与实现 变量名的命名规则 仅仅由大.小写英文字母,下划线(_),数字(不可作为变量名的开头)组合而成: 不能使用Python关键字和函数名作为变量名: 变量名不能包含空 ...

  7. 【python系列】--Python变量和数据类型

    python数据类型 一.整数 Python可以处理任意大小的整数,当然包括负整数,在Python程序中,整数的表示方法和数学上的写法一模一样,例如:1,100,-8080,0,等等. 计算机由于使用 ...

  8. [Python]基础教程(4)、Python 变量类型

    Python 变量类型 变量存储在内存中的值.这就意味着在创建变量时会在内存中开辟一个空间. 基于变量的数据类型,解释器会分配指定内存,并决定什么数据可以被存储在内存中. 因此,变量可以指定不同的数据 ...

  9. python变量与基础数据类型

    一.什么是变量 变量是什么?  变量:把程序运行的中间结果临时的存在内存里,以便后续的代码调用.在python中一切都是变量. 1.python变量命名的要求 1,必须有数字,字母,下划线任意组合. ...

随机推荐

  1. SwiftUI 简明教程之指示器

    本文为 Eul 样章,如果您喜欢,请移步 AppStore/Eul 查看更多内容. Eul 是一款 SwiftUI & Combine 教程 App(iOS.macOS),以文章(文字.图片. ...

  2. VBO、VAO和EBO

    Vertex Buffer Object 对于经历过fixed pipeline的我来讲,VBO的出现对于渲染性能提升让人记忆深刻.完了,暴露年龄了~ //immediate mode glBegin ...

  3. 容器进阶:OCI与容器运行时

    Blog:博客园 个人 什么是容器运行时(Container Runtime) Kubernetes节点的底层由一个叫做容器运行时的软件进行支撑,它负责比如启停容器 这样的事情.最广为人知的容器运行时 ...

  4. 03.28,周六,12:00-17:00,ICPC训练联盟周赛,选用试题:UCF Local Programming Contest 2016正式赛。

    A. Majestic 10 题意:三个数均大于10则输出"triple-double",如果两个数大于10则输出"double-double",如果一个大于1 ...

  5. Windows 程序自动更新方案: Squirrel.Windows

    Windows 程序自动更新方案: Squirrel.Windows 1. Squirrel Squirrel 是一组工具和适用于.Net的库,用于管理 Desktop Windows 应用程序的安装 ...

  6. 消息队列RabbitMQ(二):RabbitMQ的系统架构概述

    前言 RabbitMQ是基于AMQP协议的,要想深入理解RabbitMQ,就必须先了解AMQP是个什么东东? AMQP协议 AMQP即Advanced Message Queuing Protocol ...

  7. java基础——多维数组和稀疏数组

    多维数组 多维数组可以堪称数组的数组,比如二维数组就是一个特殊的一维数组,其中每一个元素都是一个以为数组 而且数组 int a[][]= new int[2][3]; public class Arr ...

  8. Linux下script命令录制、回放和共享终端操作script -t 2> timing.log -a output.session # 开始录制

    Linux下script命令录制.回放和共享终端操作 [日期:2018-09-04] 来源:cnblogs.com/f-ck-need-u  作者:骏马金龙 [字体:大 中 小]   另一篇终端会话共 ...

  9. QT相关书籍

    文章转载自:http://www.cctry.com/thread-290005-1-1.html 最近一段时间,陆陆续续给大家更新了不少基于Qt开发的不错的书籍,可以说每本都不错.不过放在这一堆大家 ...

  10. 查询登录信息 w, who*, id, tty, last, finger

    查询登录信息 w, who*, id, tty, last, finger Wavky2016.12.14 16:19:37字数 813阅读 85w [options] [user...]显示所有已登 ...