一、问题描述

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. hi-nginx-1.4.9正式发布,支持javascript后端开发

    hi-nginx-1.4.9已经发布. 更新: 支持javascript后端开发 修复脚本搜索的一个bug 从这一版开始,hi-nginx开始支持javascript,这意味着把javascript应 ...

  2. python 之路 day5 - 常用模块

    模块介绍 time &datetime模块 random os sys shutil json & picle shelve xml处理 yaml处理 configparser has ...

  3. CentOS7──xxx is not in the sudoers file

    提示"xxx is not in the sudoers file. This incident will be reported.其中 ”XXX“是你的用户名,也就是你的用户名没有权限使用 ...

  4. fusioncharts的3D饼图固定大小和角度

    3D饼图的pieRadius和startingAngle属性 pieRadius:饼图的半径 startingAngle:饼图的角度(旋转) 在固定大小的div里面,饼图上如果显示label或者val ...

  5. 【SparkStreaming学习之四】 SparkStreaming+kafka管理消费offset

    环境 虚拟机:VMware 10 Linux版本:CentOS-6.5-x86_64 客户端:Xshell4 FTP:Xftp4 jdk1.8 scala-2.10.4(依赖jdk1.8) spark ...

  6. Ant构建原理及build.xml文档描述

    最近在改写jmeter,用到ant构建,记录一下. Ant的概念Make命令是一个项目管理工具,而Ant所实现功能与此类似.像make,gnumake和nmake这些编译工具都有一定的缺陷,但是Ant ...

  7. centos6.5 MySQL数据库的安装

    <div id="home"><div id="header"> <div id="blogTitle"> ...

  8. .NET Core 微服务

    github 上的资源: EshopOnContainers AiurSoft : 微服务框架 Server酱:是一款「程序员」和「服务器」之间的通信软件. http://sc.ftqq.com/3. ...

  9. Liunx中三种网络模式配置及Xshell连接

    Liunx网络配置 NAT模式下的网络配置: 首先打开网络配置文件:vi   /etc/sysconfig/network-scripts/ifcfg-ens33 修改网卡信息,配置动态Ip过程中,只 ...

  10. window 10 删除带有管理员权限的Oracle文件夹

    因为文件已经被删除就不附图解释了 因为文件安装的方式错误,所以本是按照正常步骤卸载Oracle,前面的禁用Orace服务与删除Oracle注册表都没有出错,但到最后一步---------Oracle文 ...