一、问题描述

Python中的垃圾回收是以引用计数为主,分代收集为辅,引用计数的缺陷是循环引用的问题。在Python中,如果一个对象的引用数为0,Python虚拟机就会回收这个对象的内存。

sys.getrefcount(a)可以查看a对象的引用计数,但是比正常计数大1,因为调用函数的时候传入a,这会让a的引用计数+1

导致引用计数+1的情况:

  1. 对象被创建,例如a=23
  2. 对象被引用,例如b=a
  3. 对象被作为参数,传入到一个函数中,例如func(a)
  4. 对象作为一个元素,存储在容器中,例如list1=[a,a]

导致引用计数-1的情况:

  1. 对象的别名被显式销毁,例如del a
  2. 对象的别名被赋予新的对象,例如a=24
  3. 一个对象离开它的作用域,例如f函数执行完毕时,func函数中的局部变量(全局变量不会)
  4. 对象所在的容器被销毁,或从容器中删除对象

在网上看到一段有意思的例子:

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的更多相关文章

  1. 『Python』matplotlib常用函数

    1. 绘制图表组成元素的主要函数 1.1 plot()--展现量的变化趋势 import numpy as np import matplotlib.pyplot as plt import matp ...

  2. python中如何调用函数交换两个变量的值

    python中如何调用函数交换两个变量的值 所有代码来在python3.7.1版本实现 以下实例通过用户输入两个变量,并相互交换:  方法一: def swap(a,b): # 创建临时变量,并交换 ...

  3. python可变参数调用函数的问题

    已使用python实现的一些想法,近期使用python这种出现的要求,它定义了一个函数,第一种是一般的参数,第二个参数是默认,并有可变参数.在第一项研究中python时间,不知道keyword可变参数 ...

  4. 『Python』__getattr__()特殊方法

    self的认识 & __getattr__()特殊方法 将字典调用方式改为通过属性查询的一个小class, class Dict(dict): def __init__(self, **kw) ...

  5. 『Python』源码解析_源码文件介绍

    本篇代码针对2.X版本,与3.X版本细节不尽相同,由于两者架构差别不大加之本人能力有限,所以就使用2.X体验python的底层原理了. 一.主要文件夹内容 Include :该目录下包含了Python ...

  6. Python 获取被调用函数名称,所处模块,被调用代码行

    获取被调用函数名称,所处模块,被调用代码行 by:授客 QQ:1033553122 module2.py: #!/usr/bin/env python # -*- coding:utf-8 -*- _ ...

  7. python 获取当前调用函数名等log信息

    import sys funcName = sys._getframe().f_back.f_code.co_name #获取调用函数名 lineNumber = sys._getframe().f_ ...

  8. Python 通过字符串调用函数、接近属性

    需求:传入的是函数名.属性名,想通过字符串调用函数,接近属性. 通过字符串接近.变动属性 变量:model_name, field_name # 获取 model model = AppConfig. ...

  9. 『Python』源码解析_从ctype模块理解对象

    1.对象的引用计数 从c代码分析可知,python所有对象的内存有着同样的起始结构:引用计数+类型信息,实际上这些信息在python本体重也是可以透过包来一窥一二的, from ctypes impo ...

随机推荐

  1. CentOS 7 Nginx1.12.2平滑升级到新版本nginx-1.13.3

    查看当前Nginx版本信息 [root@web ~]# /usr/local/nginx/sbin/nginx -V nginx version: nginx/ built by gcc (Red H ...

  2. 递归算法+sql三种分页

    using Maticsoft.Common; using System; using System.Collections.Generic; using System.Data; using Sys ...

  3. Excel坐标自动在AutoCad绘图_1

    众所周知,Excel对数据处理的功能非常强大,它可以进行数据处理.统计分析已经辅助决策的操作,该软件已经渗透到各个领域.作为一个测绘人,GISer, 也经常利用excel完成一些测量表格的自动化计算, ...

  4. Easy methods to select MB Star, Extremely MB Star, MB SD C4, Mercedes BENZ C5 SD

    MB Star, Extremely MB SD Connect C4, MB SD C4, Mercedes BENZ C5 SD are usually analysis tools to get ...

  5. java面向对象总结(二)

    Java 封装 实现Java封装的步骤 java面向对象值继承 概念: 继承的格式: 类和类之间的关系: 继承的特点: 继承的优缺点 继承的好处: 继承的缺点: 继承的注意事项: 使用继承的步骤: J ...

  6. android sdk下载及安装教程

    转自https://www.cnblogs.com/summary-2017/p/8073225.html 1.点击这个网址https://www.androiddevtools.cn/,打开页面后选 ...

  7. bzoj3527: [Zjoi2014]力 fft

    bzoj3527: [Zjoi2014]力 fft 链接 bzoj 思路 但是我们求得是 \(\sum\limits _{i<j} \frac{q_i}{(i-j)^2}-\sum_{i> ...

  8. oracle sql生成临时递增数据

    1.生成1至10记录 select rownum from dual connect 结果: 12345678910 2.生成最近12个月记录 ), 'yyyyMM') time from dual ...

  9. Spring循环依赖问题

    什么是循环依赖? 循环依赖就是循环引用,指两个或多个bean互相持有对方,比如说TestA引用TestB.TestB引用TestA,最终形成一个闭环. 注意:循环依赖不是指循环调用. 循环调用:指方法 ...

  10. 原生JS实现轮播效果

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...