python计数_Python的引用计数

在我所接触到的语言虚拟机中,使用引用计数法进行对象回收的,只有Python,我这节课就介绍一下Python的垃圾回收机制,以及使用Python进行编程时要注意的一些地方。

前边的课程就介绍过了,我们这个课程虽然聚焦在Java,但遇到和其他语言相通的地方,或者是可以相互借鉴的地方,也会介绍一些其他语言的知识。本节课的内容与Java没有任何关系,如果只对Java语言感兴趣,而对Python等其他语言没什么兴趣的,就可以选择跳过这节课。不学这节课对后面的课程不会造成任何影响。

阅读Python源码

Python的源码比较简单,在这里可以下载:

把源码包下载下来,解开以后,我们可以看到这些目录:

root@ecs-2f21:~/python/Python-2.7.13# ls -ls

total 1016

12 -rw-r--r-- 1 1000 1000 10914 Dec 18 2016 aclocal.m4

44 -rwxr-xr-x 1 1000 1000 43940 Dec 18 2016 config.guess

36 -rwxr-xr-x 1 1000 1000 36350 Dec 18 2016 config.sub

440 -rwxr-xr-x 1 1000 1000 443361 Dec 18 2016 configure

144 -rw-r--r-- 1 1000 1000 141651 Dec 18 2016 configure.ac

4 drwxr-xr-x 22 1000 1000 4096 Dec 18 2016 Demo

4 drwxr-xr-x 18 1000 1000 4096 Dec 18 2016 Doc

4 drwxr-xr-x 2 1000 1000 4096 Dec 18 2016 Grammar

4 drwxr-xr-x 2 1000 1000 4096 Dec 18 2016 Include

8 -rwxr-xr-x 1 1000 1000 7122 Dec 18 2016 install-sh

12 drwxr-xr-x 47 1000 1000 12288 Dec 18 2016 Lib

16 -rw-r--r-- 1 1000 1000 12767 Dec 18 2016 LICENSE

4 drwxr-xr-x 11 1000 1000 4096 Dec 18 2016 Mac

48 -rw-r--r-- 1 1000 1000 48384 Dec 18 2016 Makefile.pre.in

4 drwxr-xr-x 4 1000 1000 4096 Dec 18 2016 Misc

4 drwxr-xr-x 9 1000 1000 4096 Dec 18 2016 Modules

4 drwxr-xr-x 3 1000 1000 4096 Dec 18 2016 Objects

4 drwxr-xr-x 2 1000 1000 4096 Dec 18 2016 Parser

4 drwxr-xr-x 9 1000 1000 4096 Dec 18 2016 PC

4 drwxr-xr-x 2 1000 1000 4096 Dec 18 2016 PCbuild

36 -rw-r--r-- 1 1000 1000 35082 Dec 18 2016 pyconfig.h.in

4 drwxr-xr-x 2 1000 1000 4096 Dec 18 2016 Python

60 -rw-r--r-- 1 1000 1000 55664 Dec 18 2016 README

4 drwxr-xr-x 5 1000 1000 4096 Dec 18 2016 RISCOS

104 -rw-r--r-- 1 1000 1000 99034 Dec 18 2016 setup.py

4 drwxr-xr-x 23 1000 1000 4096 Dec 18 2016 Tools

这其中,最核心的就只有Python和Objects两个目录而已。Python目录下是虚拟机运行的各种逻辑,Python的解释器主要就是在ceval.c这个文件中。而Objects目录下则是Python对象的定义。

对象的定义

我们来看一下,Python的一个Object到底是啥。

我们以最简单的浮点数为例。在Include/floatobject.h中,python中的浮点数是这么定义的:

typedef struct {

PyObject_HEAD

double ob_fval;

} PyFloatObject;

我们继续查看PyObject_HEAD的定义:

/* PyObject_HEAD defines the initial segment of every PyObject. */

#define PyObject_HEAD \

_PyObject_HEAD_EXTRA \

Py_ssize_t ob_refcnt; \

struct _typeobject *ob_type;

这是一个宏定义,那个EXTRA,在正常编译时,是空的。所以,直接展开所有宏,那么PyFloatObject的定义就是这样子的:

typedef struct {

Py_ssize_t ob_refcnt;

struct _typeobject *ob_type;

double ob_fval;

} PyFloatObject;

这样就很清楚了,ob_refcnt就是引用计数,而ob_val则是真正的值。例如我写一行这样的代码:

a = 1000.0

a = 2000.0

在执行第一句时,Python虚拟机真正执行的逻辑是创建一个PyFloatObject对象,然后使它的ob_fval为1000.0,同时,它的引用计数为1。

当执行到第二句时,创建一个值为2000.0的PyFloatObject对象,并且使这个对象的引用计数为1,而前一个对象的引用计数就要减1,从而变成0。那么前一个对象就会被回收。

引用计数

在Python中,引用计数的维护是通过这两个宏来实现的:

#define Py_INCREF(op) ( \

_Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA \

((PyObject*)(op))->ob_refcnt++)

#define Py_DECREF(op) \

do { \

if (_Py_DEC_REFTOTAL _Py_REF_DEBUG_COMMA \

--((PyObject*)(op))->ob_refcnt != 0) \

_Py_CHECK_REFCNT(op) \

else \

_Py_Dealloc((PyObject *)(op)); \

} while (0)

这两个宏位于Include/object.h中。这里面的的DEBUG宏大家可以自己去看,不是特别关键,重要的在于ob_refcnt增一和减一的操作。

增一就不说了,没什么特别的,重点是减一,如果减完了以后ob_refcnt为0,就会执行_Py_Dealloc来释放内存空间。这一点与上节课我们介绍过的是引用计数为0的情况是一致的。_Py_Dealloc还是一个宏,而且使用了函数指针,如果对这个问题有兴趣的话,可以继续查看下去,我这里就不再介绍了。

我们再来看一下,调用的地方。

def fun():

global a

a = 1

如果使用dis来反编译,就会看到这样的字节码:

>>> dis.dis(test.fun)

3 0 LOAD_CONST 1 (1)

3 STORE_GLOBAL 0 (a)

6 LOAD_CONST 0 (None)

9 RETURN_VALUE

那么,我们再看一下STORE_GLOBAL的实现:

TARGET(STORE_GLOBAL)

{

w = GETITEM(names, oparg);

v = POP();

err = PyDict_SetItem(f->f_globals, w, v);

Py_DECREF(v);

if (err == 0) DISPATCH();

break;

}

STORE_GLOBAL的意义就是把栈顶上的值,也就是v,设置进globals表里。一直翻看PyDict_SetItem的实现,最终会追踪到这个地方:

static int

dict_set_item_by_hash_or_entry(register PyObject *op, PyObject *key,

long hash, PyDictEntry *ep, PyObject *value)

{

register PyDictObject *mp;

register Py_ssize_t n_used;

mp = (PyDictObject *)op;

assert(mp->ma_fill <= mp->ma_mask); /* at least one empty slot */

n_used = mp->ma_used;

Py_INCREF(value);

Py_INCREF(key);

if (ep == NULL) {

if (insertdict(mp, key, hash, value) != 0)

return -1;

}

else {

if (insertdict_by_entry(mp, key, hash, ep, value) != 0)

return -1;

}

在这个函数里,对key, value都执行了增加了一次引用计数。而在insertdict_by_entry中,会对原来的引用减一。更加详细的内容大家可以自己去看,我这里就不介绍了。

当然,使用了引用计数的地方,就会存在循环引用。那么Python肯定会提供其他的方案来解决。本节课就先不介绍了。

这里只提一点,就是在写Python代码的时候,我们应该尽量避免循环引用。要做到这个,就要注意两点,第一,就是在设计的时候,尽量使用单向引用;第二,在对象失效以后,将它对其他对象的引用,都设为null。

当然,我们这里说的是尽量这么做,并不是说,不这么做就会带来多么严重的问题,只是一种推荐做法。好了,今天就到这里了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值