1."魔法"方法的内存占用
众所周知, Python的对象中有一些以__双下划线开头的属性,
如调用函数的__call__属性, 和调用函数, 结果是相同的。
>>> def f(x):print('hello world',x)
>>> f(1)
hello world 1
>>> f.__call__(1)
hello world 1
>>> f.__call__
<method-wrapper '__call__' of function object at 0x02AF4348>
但函数的__call__
属性和函数本身不是同一个对象,__call__
方法的类型实际上是method-wrapper
,意为方法包装器。
先来看看这段代码 :
def f():pass
old_f=f
lst=[]
while True:
lst.append(f)
f=f.__call__
运行一段时间后, 程序占用内存会迅速增大,
按下Ctrl+C, 查看列表lst的长度, 有几百万个对象,
调用列表的最后一个对象(lst[-1]()
), 出现了RecursionError
。
原因分析
每次获取对象的__call__属性时, Python会自动产生一个方法包装器(method-wrapper), 会使程序占用内存增大。
调用列表的最后一个对象时, Python会调用它的前一个对象, 以此类推, 直到出现RecursionError
。
补充
但是__call__
返回的方法包装器有一个__self__
属性, 标识这个方法包装器的父对象。
def old_f():pass
wrapper=old_f.__call__
print(old_f)
print(wrapper.__self__)
结果输出:
<function old_f at 0x0404D108>
<function old_f at 0x0404D108>
说明old_f
和wrapper.__self__
是同一个对象。
2.对象的引用计数,以及垃圾回收
在python中,每个对象都有存有该对象的引用总数,即引用计数。Python中的引用计数主要用于垃圾回收和内存管理机制。
Python中,当一个对象被创建时会调用该对象的__init__
方法。
与之对应,在对象引用计数降为0,而被清除时,这回调用该对象的__del__
方法。
比如这个示例:
import sys,gc,time,traceback
def debug(dict_):
# 用来在交互式提示符中调试,参数dict_为命名空间
while 1:
s=input('>>> ').rstrip()
if s:
if s=='continue':break
try:exec(compile(s,'<shell>','single'),dict_)
except Exception:
traceback.print_exc()
#lst=[]
class C:
count=0
def __init__(self):
self.id=self.count
C.count+=1
print("{} was born".format(self))
def __repr__(self):
return object.__repr__(self)[:-1]+" id:%d>"%self.id
def __del__(self):
#lst.append(self)
print("{} died".format(self),'is_finalizing',sys.is_finalizing())
# 提示:解释器关闭过程中产生的所有异常都会被忽略,
# 即显示Exception ignored in:...
if sys.is_finalizing():
debug(locals())
def test():
while True:
c=C()
print()
#time.sleep(0.1)
if __name__=="__main__":
sys.c=C()
test()
部分运行结果:
<__main__.C object at 0x01F56F30 id:684> was born
<__main__.C object at 0x01F301F0 id:683> died is_finalizing False
<__main__.C object at 0x01B208D0 id:685> was born
<__main__.C object at 0x01F56F30 id:684> died is_finalizing False