python 函数中局部变量的引用计数何时减少?
When is the reference count for a local variable in a python function decreased?
我有以下功能:
def myfn():
big_obj = BigObj()
result = consume(big_obj)
return result
BigObj() 值的引用计数何时增加/减少:
是吗:
- 当调用
consume(big_obj)
时(因为之后在 myfn 中未引用 big_obj)
- 当函数returns
- 有一点,我还没有
将最后一行更改为:
会有什么不同吗?
return consume(big_obj)
编辑(评论说明):
- 一个局部变量存在直到函数 returns
- 引用可以用del obj删除
但是临时变量(例如 f1(f2()) 是什么?
我用这段代码检查了对临时对象的引用:
import sys
def f2(c):
print("f2: References to c", sys.getrefcount(c))
def f0():
print("f0")
f2(object())
def f1():
c = object()
print("f1: References to c", sys.getrefcount(c))
f2(c)
f0()
f1()
这会打印:
f0
f2: References to c 3
f1: References to c 2
f2: References to c 4
似乎保留了对临时变量的引用。并不是说 getrefcount 给出的值比你预期的多,因为它也包含一个引用。
变量在 Python 中具有函数作用域,因此在函数 returns 之前它们不会被删除。据我所知,您不能从函数外部销毁函数中对局部变量的引用。我在示例代码中添加了一些 gc
调用来对此进行测试。
import gc
class BigObj:
pass
def consume(obj):
del obj # only deletes the local reference to obj, but another one still exists in the calling function
def myfn():
big_obj = BigObj()
big_obj_id = id(big_obj) # in CPython, this is the memory address of the object
consume(big_obj)
print(any(id(obj) == big_obj_id for obj in gc.get_objects()))
return big_obj_id
>>> big_obj_id = myfn()
True
>>> gc.collect() # I'm not sure where the reference cycle is, but I needed to run this to clean out the big object from the gc's list of objects in my shell
>>> print(any(id(obj) == big_obj_id for obj in gc.get_objects()))
False
自从 True
被打印出来后,即使在函数中的那个点之后没有对该变量的引用,即使在我们强制进行垃圾收集之后大对象仍然存在。在函数 returns 正确地确定对大对象的引用计数为 0 之后强制进行垃圾收集,因此它会清理该对象。注意:正如下面的评论所指出的,已删除对象的 ID 可能会被重复使用,因此检查相同的 ID 可能会导致误报。不过,我相信结论还是正确的。
要尽早回收内存,您可以做的一件事是使大对象成为全局对象,这样您就可以从被调用函数中删除它。
def consume():
# do whatever you need to do with the big object
big_obj_id = id(big_obj)
del globals()["big_obj"]
print(any(id(obj) == big_obj_id for obj in gc.get_objects()))
# do anything else you need to do without the big object
def myfn():
globals()["big_obj"] = BigObj()
result = consume()
return result
>>> myfn()
False
这种模式很奇怪,而且可能很难维护,所以我建议不要使用它。如果你只需要在 consume()
调用后立即删除大对象,你可以这样做,以便尽快释放大对象使用的内存。
big_obj = BigObj()
consume(big_obj)
del big_obj
您可以尝试的另一种策略是删除从 consume()
函数传入的大对象中的引用,其中 del big_obj.x
某些属性 x
.
When is the reference count for big_obj decreased
big_obj
没有引用计数。变量没有引用计数。 值 做。
big_obj = BigObj()
这行代码创建了 BigObj
class 的一个实例。该实例的引用计数可能会增加或减少多次,具体取决于该创建过程的实现细节(不一定写在 Python 中)。值得注意的是, 对名称 big_obj
的赋值增加了引用计数。
when the function returns
此时,名字big_obj
不复存在——名字不会消失只是因为它不会'不能再用了。 (在一般情况下,这真的很难检测到,而且通常没有特别的好处)。如果您必须导致一个名称在操作的特定点不复存在(例如,因为您知道这是最后一个引用并且想要触发垃圾收集;或者可能是因为您正在使用 __weakref__
) 做一些棘手的事情,那么这就是 del
语句的目的。
由于对象的名称不复存在,其引用计数减少。 如果该计数达到零,则该对象将被垃圾回收。由于各种各样的原因,它可能在其他地方存储了任意数量的引用。 (例如,在实现 class 的 C 代码中可能存在错误;或者 class 可能故意维护其自己的每个已创建实例的列表。)
请注意,以上所有内容都专门针对参考实现。在其他实现中,情况会有所不同。可能有一些其他触发垃圾收集的发生。 可能根本没有引用计数(与 Jython 一样)。
从评论来看,您担心的似乎是内存泄漏的可能性。您显示的代码不能 导致 内存泄漏 - 但它也不能修复其他地方导致的内存泄漏。在 Python 中,与一般的垃圾收集语言一样,内存泄漏的发生是因为对象保持对彼此的引用 不需要 。但是通常没有引用的“所有权”或“转让”的概念——你需要做的就是而不是做一些事情,比如“维护一个曾经创建的每个实例的列表”,而不是 a)一个很好的理由和 b) 当您想忘记它们时从列表中取出实例的方法。
局部变量,但是,根据定义,不能将对象的生命周期延长到局部范围之外。
免责声明:大部分信息来自评论。所以感谢每一位参与讨论的人。
何时删除对象通常是一个实现细节。
我将参考基于引用计数的 CPython。我 运行 使用 CPython 3.10.0 的代码示例。
- 当引用计数为零时,对象被删除。
- 从函数返回会删除所有本地引用。
- 为新值指定名称会减少旧值的引用计数
- 传递一个局部变量会增加引用计数。引用在栈上(帧)
- 从函数返回将从堆栈中删除引用
最后一点甚至对 f(g())
这样的临时引用也有效。最后对g()的引用被删除,当freturns(假设g没有在某处保存引用)see here
所以对于问题中的示例:
def myfn():
big_obj = BigObj() # reference 1
result = consume(big_obj) # reference 2 on the stack frame for
# consume. Not yet counting any
# reference inside of consume
# after consume returns: The stack frame
# and reference 2 are deleted. Reference
# 1 remains
return result # myfn returns reference 1 is deleted.
# BigObj is deleted
def consume(big_obj):
pass # consume is holding reference 3
如果我们将其更改为:
def myfn():
return consume(BigObj()) # reference is still saved on the stack
# frame and will be only deleted after
# consume returns
def consume(big_obj):
pass # consume is holding reference 2
如何可靠地检查对象是否已删除?
您不能依赖 gc.get_objects()。 gc 用于检测和回收引用循环。不是每个引用都被 gc 跟踪。
您可以创建弱引用并检查引用是否仍然有效。
class BigObj:
pass
import weakref
ref = None
def make_ref(obj):
global ref
ref = weakref.ref(obj)
return obj
def myfn():
return consume(make_ref(BigObj()))
def consume(obj):
obj = None # remove to see impact on ref count
print(sys.getrefcount(ref()))
print(ref()) # There is still a valid reference. It is the one from consume stack frame
myfn()
如何传递对函数的引用并删除调用函数中的所有引用?
您可以将引用装箱,传递给函数并从函数内部清除装箱的引用:
class Ref:
def __init__(ref):
self.ref = ref
def clear():
self.ref = None
def f1(ref):
r = ref.ref
ref.clear()
def f2():
f1(Ref(object())
我有以下功能:
def myfn():
big_obj = BigObj()
result = consume(big_obj)
return result
BigObj() 值的引用计数何时增加/减少: 是吗:
- 当调用
consume(big_obj)
时(因为之后在 myfn 中未引用 big_obj) - 当函数returns
- 有一点,我还没有
将最后一行更改为:
会有什么不同吗?return consume(big_obj)
编辑(评论说明):
- 一个局部变量存在直到函数 returns
- 引用可以用del obj删除
但是临时变量(例如 f1(f2()) 是什么?
我用这段代码检查了对临时对象的引用:
import sys
def f2(c):
print("f2: References to c", sys.getrefcount(c))
def f0():
print("f0")
f2(object())
def f1():
c = object()
print("f1: References to c", sys.getrefcount(c))
f2(c)
f0()
f1()
这会打印:
f0
f2: References to c 3
f1: References to c 2
f2: References to c 4
似乎保留了对临时变量的引用。并不是说 getrefcount 给出的值比你预期的多,因为它也包含一个引用。
变量在 Python 中具有函数作用域,因此在函数 returns 之前它们不会被删除。据我所知,您不能从函数外部销毁函数中对局部变量的引用。我在示例代码中添加了一些 gc
调用来对此进行测试。
import gc
class BigObj:
pass
def consume(obj):
del obj # only deletes the local reference to obj, but another one still exists in the calling function
def myfn():
big_obj = BigObj()
big_obj_id = id(big_obj) # in CPython, this is the memory address of the object
consume(big_obj)
print(any(id(obj) == big_obj_id for obj in gc.get_objects()))
return big_obj_id
>>> big_obj_id = myfn()
True
>>> gc.collect() # I'm not sure where the reference cycle is, but I needed to run this to clean out the big object from the gc's list of objects in my shell
>>> print(any(id(obj) == big_obj_id for obj in gc.get_objects()))
False
自从 True
被打印出来后,即使在函数中的那个点之后没有对该变量的引用,即使在我们强制进行垃圾收集之后大对象仍然存在。在函数 returns 正确地确定对大对象的引用计数为 0 之后强制进行垃圾收集,因此它会清理该对象。注意:正如下面的评论所指出的,已删除对象的 ID 可能会被重复使用,因此检查相同的 ID 可能会导致误报。不过,我相信结论还是正确的。
要尽早回收内存,您可以做的一件事是使大对象成为全局对象,这样您就可以从被调用函数中删除它。
def consume():
# do whatever you need to do with the big object
big_obj_id = id(big_obj)
del globals()["big_obj"]
print(any(id(obj) == big_obj_id for obj in gc.get_objects()))
# do anything else you need to do without the big object
def myfn():
globals()["big_obj"] = BigObj()
result = consume()
return result
>>> myfn()
False
这种模式很奇怪,而且可能很难维护,所以我建议不要使用它。如果你只需要在 consume()
调用后立即删除大对象,你可以这样做,以便尽快释放大对象使用的内存。
big_obj = BigObj()
consume(big_obj)
del big_obj
您可以尝试的另一种策略是删除从 consume()
函数传入的大对象中的引用,其中 del big_obj.x
某些属性 x
.
When is the reference count for big_obj decreased
big_obj
没有引用计数。变量没有引用计数。 值 做。
big_obj = BigObj()
这行代码创建了 BigObj
class 的一个实例。该实例的引用计数可能会增加或减少多次,具体取决于该创建过程的实现细节(不一定写在 Python 中)。值得注意的是, 对名称 big_obj
的赋值增加了引用计数。
when the function returns
此时,名字big_obj
不复存在——名字不会消失只是因为它不会'不能再用了。 (在一般情况下,这真的很难检测到,而且通常没有特别的好处)。如果您必须导致一个名称在操作的特定点不复存在(例如,因为您知道这是最后一个引用并且想要触发垃圾收集;或者可能是因为您正在使用 __weakref__
) 做一些棘手的事情,那么这就是 del
语句的目的。
由于对象的名称不复存在,其引用计数减少。 如果该计数达到零,则该对象将被垃圾回收。由于各种各样的原因,它可能在其他地方存储了任意数量的引用。 (例如,在实现 class 的 C 代码中可能存在错误;或者 class 可能故意维护其自己的每个已创建实例的列表。)
请注意,以上所有内容都专门针对参考实现。在其他实现中,情况会有所不同。可能有一些其他触发垃圾收集的发生。 可能根本没有引用计数(与 Jython 一样)。
从评论来看,您担心的似乎是内存泄漏的可能性。您显示的代码不能 导致 内存泄漏 - 但它也不能修复其他地方导致的内存泄漏。在 Python 中,与一般的垃圾收集语言一样,内存泄漏的发生是因为对象保持对彼此的引用 不需要 。但是通常没有引用的“所有权”或“转让”的概念——你需要做的就是而不是做一些事情,比如“维护一个曾经创建的每个实例的列表”,而不是 a)一个很好的理由和 b) 当您想忘记它们时从列表中取出实例的方法。
局部变量,但是,根据定义,不能将对象的生命周期延长到局部范围之外。
免责声明:大部分信息来自评论。所以感谢每一位参与讨论的人。
何时删除对象通常是一个实现细节。 我将参考基于引用计数的 CPython。我 运行 使用 CPython 3.10.0 的代码示例。
- 当引用计数为零时,对象被删除。
- 从函数返回会删除所有本地引用。
- 为新值指定名称会减少旧值的引用计数
- 传递一个局部变量会增加引用计数。引用在栈上(帧)
- 从函数返回将从堆栈中删除引用
最后一点甚至对 f(g())
这样的临时引用也有效。最后对g()的引用被删除,当freturns(假设g没有在某处保存引用)see here
所以对于问题中的示例:
def myfn():
big_obj = BigObj() # reference 1
result = consume(big_obj) # reference 2 on the stack frame for
# consume. Not yet counting any
# reference inside of consume
# after consume returns: The stack frame
# and reference 2 are deleted. Reference
# 1 remains
return result # myfn returns reference 1 is deleted.
# BigObj is deleted
def consume(big_obj):
pass # consume is holding reference 3
如果我们将其更改为:
def myfn():
return consume(BigObj()) # reference is still saved on the stack
# frame and will be only deleted after
# consume returns
def consume(big_obj):
pass # consume is holding reference 2
如何可靠地检查对象是否已删除?
您不能依赖 gc.get_objects()。 gc 用于检测和回收引用循环。不是每个引用都被 gc 跟踪。 您可以创建弱引用并检查引用是否仍然有效。
class BigObj:
pass
import weakref
ref = None
def make_ref(obj):
global ref
ref = weakref.ref(obj)
return obj
def myfn():
return consume(make_ref(BigObj()))
def consume(obj):
obj = None # remove to see impact on ref count
print(sys.getrefcount(ref()))
print(ref()) # There is still a valid reference. It is the one from consume stack frame
myfn()
如何传递对函数的引用并删除调用函数中的所有引用?
您可以将引用装箱,传递给函数并从函数内部清除装箱的引用:
class Ref:
def __init__(ref):
self.ref = ref
def clear():
self.ref = None
def f1(ref):
r = ref.ref
ref.clear()
def f2():
f1(Ref(object())