『Python』为什么调用函数会令引用计数+2
一、问题描述
Python中的垃圾回收是以引用计数为主,分代收集为辅,引用计数的缺陷是循环引用的问题。在Python中,如果一个对象的引用数为0,Python虚拟机就会回收这个对象的内存。
sys.getrefcount(a)
可以查看a对象的引用计数,但是比正常计数大1,因为调用函数的时候传入a,这会让a的引用计数+1
导致引用计数+1的情况:
- 对象被创建,例如
a=23
- 对象被引用,例如
b=a
- 对象被作为参数,传入到一个函数中,例如
func(a)
- 对象作为一个元素,存储在容器中,例如
list1=[a,a]
导致引用计数-1的情况:
- 对象的别名被显式销毁,例如
del a
- 对象的别名被赋予新的对象,例如
a=24
- 一个对象离开它的作用域,例如f函数执行完毕时,func函数中的局部变量(全局变量不会)
- 对象所在的容器被销毁,或从容器中删除对象
在网上看到一段有意思的例子:
import sys def func(c):
print ('in func function', sys.getrefcount(c) - 1)
print (id(func.__globals__['a'])) print ('init', sys.getrefcount(11) - 1)
a = 11
# print (id(a))
print ('after a=11', sys.getrefcount(11) - 1)
b = a
print ('after b=a', sys.getrefcount(11) - 1)
func(11)
print ('after func(a)', sys.getrefcount(11) - 1)
list1 = [a, 12, 14]
print ('after list1=[a,12,14]', sys.getrefcount(11) - 1)
a=12
print ('after a=12', sys.getrefcount(11) - 1)
del a
print ('after del a', sys.getrefcount(11) - 1)
del b
print ('after del b', sys.getrefcount(11) - 1)
# list1.pop(0)
# print 'after pop list1',sys.getrefcount(11)-1
del list1
print ('after del list1', sys.getrefcount(11) - 1)
输出的init不一定一致,作为计数基础即可(小数int 在python中会默认维护,因为python很多内置量都是小数int,即计数不可能为0),输出中有一点比较奇怪:在传入函数中后计数增加为2,而非设想的1,这是为什么?
init 153
after a=11 154
after b=a 155
in func function 157
after func(a) 155
after list1=[a,12,14] 158
after a=12 155
after del a 155
after del b 154
after del list1 153
我们对函数进行修改:
def func(c):
print ('in func function', sys.getrefcount(c) - 1)
# print (id(func.__globals__['a']))
for attr in dir(func):
print (attr, getattr(func, attr))
替换掉之前的函数,运行之可以发现func.__globals__属性中记录了全局变量键值对 {'a': 11} 这样(以及其他信息),这就是额外的计数来历:局部变量和全局变量的值是相同的,这导致计数+2。
我们知道,函数也是对象,即使不在函数体内我们也可以调用函数的属性、方法,我们把下面一句从函数体中拿出来单独运行,就发现,由于脱离了函数作用域,函数的__globals__属性中对于全局变量的记载('a'、'b')都不见了,这可以理解,脱离了作用域,局部变量和全局变量都失去了意义(两者都是针对某个作用域的概念)。
for attr in dir(func):
print (attr, getattr(func, attr))
测试发现__globals__中记录的{'a': 11}和函数体外的变量 a 是同一个对象(id相同),且在外面增加 b 的时候引用计数差值并没有增加,所以这个解释是不对的,实际上另一个引用是函数栈保存了入参对形参的引用(知乎找到的解释)。
二、代码分析
看到了知乎的解释,我决定自行验证一下,测试代码如下:
import sys def func(c):
print ('in func function', sys.getrefcount(c)-1) print ('init', sys.getrefcount(11) - 1)
func(11)
print ('init', sys.getrefcount(11) - 1)
init 106
in func function 108
init 106
进一步分析一下:
from dis import dis order = \
"""
def func(c):
print ('in func function', sys.getrefcount(c)-1) print ('init', sys.getrefcount(11) - 1)
func(11)
print ('init', sys.getrefcount(11) - 1)
""" dis(order)
返回值如下,
2 0 LOAD_CONST 0 (<code object func at 0x0000029849AD5D20, file "<dis>", line 2>)
2 LOAD_CONST 1 ('func')
4 MAKE_FUNCTION 0
6 STORE_NAME 0 (func) 5 8 LOAD_NAME 1 (print)
10 LOAD_CONST 2 ('init')
12 LOAD_NAME 2 (sys)
14 LOAD_ATTR 3 (getrefcount)
16 LOAD_CONST 3 (11)
18 CALL_FUNCTION 1
20 LOAD_CONST 4 (1)
22 BINARY_SUBTRACT
24 CALL_FUNCTION 2
26 POP_TOP 6 28 LOAD_NAME 0 (func)
30 LOAD_CONST 3 (11)
32 CALL_FUNCTION 1
34 POP_TOP 7 36 LOAD_NAME 1 (print)
38 LOAD_CONST 2 ('init')
40 LOAD_NAME 2 (sys)
42 LOAD_ATTR 3 (getrefcount)
44 LOAD_CONST 3 (11)
46 CALL_FUNCTION 1
48 LOAD_CONST 4 (1)
50 BINARY_SUBTRACT
52 CALL_FUNCTION 2
54 POP_TOP
56 LOAD_CONST 5 (None)
58 RETURN_VALUE
着重看6:
6 28 LOAD_NAME 0 (func)
30 LOAD_CONST 3 (11)
32 CALL_FUNCTION 1
34 POP_TOP
这里将函数 func 和常量11压入了函数栈,会导致引用计数 +1。
我们再看下面代码:
dis(func)
返回的是 func 函数内部操作:
4 0 LOAD_GLOBAL 0 (print)
2 LOAD_CONST 1 ('in func function')
4 LOAD_GLOBAL 1 (sys)
6 LOAD_ATTR 2 (getrefcount)
8 LOAD_FAST 0 (c)
10 CALL_FUNCTION 1
12 LOAD_CONST 2 (1)
14 BINARY_SUBTRACT
16 CALL_FUNCTION 2
18 POP_TOP
20 LOAD_CONST 0 (None)
22 RETURN_VALUE
这里会读取变量 c(偏移量8的操作码),最终导致了增加计数为 2。
『Python』为什么调用函数会令引用计数+2的更多相关文章
- 『Python』matplotlib常用函数
1. 绘制图表组成元素的主要函数 1.1 plot()--展现量的变化趋势 import numpy as np import matplotlib.pyplot as plt import matp ...
- python中如何调用函数交换两个变量的值
python中如何调用函数交换两个变量的值 所有代码来在python3.7.1版本实现 以下实例通过用户输入两个变量,并相互交换: 方法一: def swap(a,b): # 创建临时变量,并交换 ...
- python可变参数调用函数的问题
已使用python实现的一些想法,近期使用python这种出现的要求,它定义了一个函数,第一种是一般的参数,第二个参数是默认,并有可变参数.在第一项研究中python时间,不知道keyword可变参数 ...
- 『Python』__getattr__()特殊方法
self的认识 & __getattr__()特殊方法 将字典调用方式改为通过属性查询的一个小class, class Dict(dict): def __init__(self, **kw) ...
- 『Python』源码解析_源码文件介绍
本篇代码针对2.X版本,与3.X版本细节不尽相同,由于两者架构差别不大加之本人能力有限,所以就使用2.X体验python的底层原理了. 一.主要文件夹内容 Include :该目录下包含了Python ...
- Python 获取被调用函数名称,所处模块,被调用代码行
获取被调用函数名称,所处模块,被调用代码行 by:授客 QQ:1033553122 module2.py: #!/usr/bin/env python # -*- coding:utf-8 -*- _ ...
- python 获取当前调用函数名等log信息
import sys funcName = sys._getframe().f_back.f_code.co_name #获取调用函数名 lineNumber = sys._getframe().f_ ...
- Python 通过字符串调用函数、接近属性
需求:传入的是函数名.属性名,想通过字符串调用函数,接近属性. 通过字符串接近.变动属性 变量:model_name, field_name # 获取 model model = AppConfig. ...
- 『Python』源码解析_从ctype模块理解对象
1.对象的引用计数 从c代码分析可知,python所有对象的内存有着同样的起始结构:引用计数+类型信息,实际上这些信息在python本体重也是可以透过包来一窥一二的, from ctypes impo ...
随机推荐
- dede织梦手机站m文件夹功能基础详解
织梦2015年6月8日更新后,就添加了很多针对手机移动端的设计,最大的设计就是添加了生成二维码的织梦标签和织梦手机模板功能,织梦更新后,默认的 default模板中就包含手机模板,所以我们可以给织梦网 ...
- Centos7 安装redis
1.下载redis安装包 wget http://download.redis.io/releases/redis-4.0.9.tar.gz 2.检查及下载gcc gcc -v yum -y inst ...
- 【JavaScript】for循环小练习
1.输出1-100的和 var sum = 0; for(var i=1;i<=100;i++){ sum = sum + i; } document.write(sum); 2.输出1-100 ...
- Jupyter Notebooks 是数据科学/机器学习社区内一款非常流行的工具
Jupyter Notebooks 是数据科学/机器学习社区内一款非常流行的工具.Jupyter Notebooks 允许数据科学家创建和共享他们的文档,从代码到全面的报告都可以.李笑来 相当于拿他来 ...
- PHP 异常处理 throw new exception
当异常被抛出时,其后的代码不会继续执行,PHP 会尝试查找匹配的 "catch" 代码块. 如果异常没有被捕获,而且又没用使用 set_exception_handler() 作相 ...
- java中的getStackTrace和printStackTrace的区别
getStackTrace()返回的是通过getOurStackTrace方法获取的StackTraceElement[]数组,而这个StackTraceElement是ERROR的每一个cause ...
- python3 error 机器学习 错误
AttributeError: 'NoneType' object has no attribute 'sqrt' 这个错误其实是因为 plt.scatter(x[:,0],x[:,1],x[:,2] ...
- P3440 [POI2006]SZK-Schools(费用流)
P3440 [POI2006]SZK-Schools 每所学校$i$开一个点,$link(S,i,1,0)$ 每个编号$j$开一个点,$link(i,T,1,0)$ 蓝后学校向编号连边,$link(i ...
- Docker 运维高级应用管理
Docker 基本应用 1.Docker 介绍及安装 2.Docket 使用命令 3.Docker run命令参数整理 4.Docker 构建镜像 Docker Compose 高级应用 1.Doc ...
- linux install Openvino
recommend centos7 github Openvino tooltiks 1. download openvino addational installation for ncs2 ncs ...