Python中的(deI)语句和Python行为

4
当执行del语句时:
del var 它难道不应该从已知变量列表中删除,并且Python解释器不会报告“未解决的引用”错误吗?
还是说它只是删除对象,让名称(var)不指向任何位置?为什么这种行为有用?在什么情况下使用?
另外,我只是简单地谈论删除单个变量。 不是`del list [3]`或类似的东西。
注意:我问的是这种Python行为是否是有意的。 在什么情况下,它仍然会有用。
编辑:Charles Addis给出了详细的解释。 我也承认我的错误,将pycharm行为误认为是官方Python的行为。 我现在正在尝试与官方Python交互式shell一起使用ipython。 尽管这是我的错,但我很高兴学到了很多关于Python变量以及一些Python调试命令。

1
如果你输入 del var;var,那么就会发生这种情况。你期望 del var 尝试返回已删除的变量吗? - Mark Ransom
3
嗯...它已经实现了你所建议的功能。根据上下文,会引发NameErrorUnboundLocalError异常。一旦执行del varvar的本地名称就消失了。如果其他名称引用了相同的对象,则该对象仍将存在,只是不再使用该名称。 - ShadowRanger
我曾经吃过亏,使用pycharm实际上误导了我。而Python解释器确实像你们所说的那样处理它。我现在除了使用Python官方提供的交互式shell之外,还尝试了ipython。我原来不知道我错过了什么。 - theMobDog
3个回答

2

不确定你在问什么,因为很明显那就是发生的事情...

>>> x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined
>>> x = 10
>>> 'x' in vars()
True
>>> vars()['x']
10
>>> del x
>>> 'x' in vars()
False
>>> x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined

正如您所见,Python 允许从当前作用域中访问 locals() 中的有效标识符(vars() 也类似 - 还有 dir(obj),它将显示对象的属性)... 所以 del 实际上将其从内存中删除,这也从这些函数返回的数据结构中删除了它。因此,该标识符不再有效。它不像在 C 语言中可能释放一些内存并将值设置为 NULL

>>> x = 10
>>> def func():
...   global x
...   del x
...
>>> x
10
>>> func()
>>> x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined
>>>

更新:

当Raymond提到对象本身(即指针指向的实际内存数据,缺乏更详细的解释)只有在引用计数为0时才会被释放时,他提出了一个很好的观点。在我看来,他可以在python解释器中更好地详细说明这一点,所以我来试试。

我们将使用ID函数来证明这一点:

Help on built-in function id in module __builtin__:

id(...)
    id(object) -> integer

    Return the identity of an object.  This is guaranteed to be unique among
    simultaneously existing objects.  (Hint: it's the object's memory address.)

注意这里发生的情况,你会发现对于不可变类型,不同的变量引用相同的内存(在Python中,字符串是不可变的并且被内部化,这意味着每个唯一字符串只存储一份副本 - 在Ruby中,符号是内部化的字符串)。

>>> import sys
>>> x = 10     # 10 is a common value, probably exists in memory already
>>> sys.getrefcount(x)
26
>>> id(x)      # memory location of x
140266396760096
>>> y = x
>>> id(y) == id(x)
True
>>> z = 10
>>> id(z) == id(y) == id(x)
True
>>> sys.getrefcount(y)
28
>>> sys.getrefcount(z)
28
>>> del y, z
>>> sys.getrefcount(x)
26
>>> del x
>>> x = 'charlie'
>>> id(x)
4442795056
>>> y = 'charlie'
>>> z = x
>>> id(x) == id(y) == id(z)
True
>>> sys.getrefcount(x)
4
>>> sys.getrefcount(y)
4
>>> sys.getrefcount(z)
4
>>> del y
>>> del x
>>> sys.getrefcount(z)     # will be two because this line is an additional reference
2
>>> id(z)                  # pay attention to this memory location because this 
4442795056                 # is the last remaining reference to 'charlie', and
>>> del z                  # when it goes out of scope 'charlie' is removed from
>>>                        # memory.
>>> id('charlie')          # This has a different memory location because 'charlie'
4442795104                 # had to be re-created.

首先,我们将标识符“x”设置为10,这是一个常见的整数值。由于10是一个常见的值,几乎可以保证进程中的某些内存已经有了该值。由于在Python中整数是不可变的,因此我们只需要在内存中存储每个唯一值的一个副本。在这种情况下,内存中还有24个对10的引用。设置x = 10创建第25个引用,并且调用sys.getrefcount(x)是第26个引用(尽管它很快就超出了范围)。当我们设置y = 10z = x时,我们知道它们都指向相同的数据,因为它们都具有相同的内存位置。调用del会改变引用计数,但即使删除所有3个引用,整数10仍然存在于内存中。

接下来,我们创建x ='charlie',然后是y ='charlie',最后是z = x。您可以看到所有这些变量都具有相同的内存地址。一旦我们删除了所有这些变量,就没有对'charlie'的引用了。我们可以通过调用id('charlie')来验证这一点,它将产生一个不同的内存地址,这意味着在调用该函数时字符串不存在于内存中。

还有一件事要注意,即'charlie'10在内存中的位置。 10具有比'charlie'更高的内存地址。这是因为它们存在于内存的不同位置。'charlie'存在于堆上,而10存在于栈上。

>>> hex(id(10))         # high address, this is on the stack
'0x7f9250c0b820'
>>> hex(id('charlie'))  # lower address, this is on the heap
'0x108cfac60

1
挑剔一点:locals()(以及等效的无参数vars)允许您查看有效标识符及其值,但这并不是Python实际用于大部分内容的方式。 Python大部分时间实际上并没有将本地变量存储在dict中,只有在调用locals()时才会根据需要为您创建它(每个本地变量访问的dict查找都会减慢所有操作,生成dict也会使速度变慢)。在任何情况下,这都不是描述问题所必需的; NameError清楚地表明它已经消失了。 - ShadowRanger
啊,好的。我忘了Python是按需执行的。谢谢你的澄清。 - theMobDog
1
@shadowranger 是的,我知道,我只是在高层次上向他介绍。那些只是访问进程数据的接口。 - Charles D Pantoga
1
@theMobDog 这就是为什么我不使用集成开发环境的原因。当我刚开始学习软件开发时,它们非常有价值,但在职业生涯中使用 Vim 一段时间后,我已经无法使用其他任何工具了。在我看来,终端比我曾经使用过的任何 IDE 都更强大。你可以获得 shell 的强大功能、交互式 Python 解释器(ipython 很棒)以及在 Vim 中快速开发的能力。 - Charles D Pantoga
@theMobDog 我知道,我有C和C++编程经验。我不确定Python解释器的进程内存是什么样子的,但我想象中内存中有许多值等于10,或者基本上是小整数。不确定它们是什么。 - Charles D Pantoga
显示剩余2条评论

1
这正是发生的事情。一旦你使用del删除了某个东西,下一个对它的引用将会引发NameError错误。
在Python中,情况有些不同。没有像其他语言中你可能习惯的"变量"。
有对象空间,数据存储在其中,还有命名空间,名称存储在其中。名称通常被我们称为其他语言中的"变量",但在Python中它们只是指向数据空间中对象的标签。

使用 del 删除标签(名称)仅仅是删除指向对象的标签。对象本身(具有值)仍然存在,除非它没有被其他名称引用,此时 Python 将对其进行垃圾回收。名称只是指向对象空间中对象的轻量级标签,是我们唯一可以访问这些对象的方式。

以下是一个示例以说明这一点:

>>> x = 5
>>> y = x
>>> del x
>>> y
5

现在我已经删除了x,但是由于y仍然指向与x相同的对象(整数5),所以它保留下来,我可以再次访问它;但是如果我尝试访问x
>>> x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined

谢谢。我误把PyCharm的行为和Python混淆了。在PyCharm中,使用del语句后,即使变量已经不存在,您仍然可以检查它。 - theMobDog
现在Python的命名/变量和对象设置方式非常合理,这一点比C/C++要简单得多。 - theMobDog

1
“del”从当前命名空间中删除名称。如果底层对象的引用计数降至零,则对象本身将被释放。
>>> import sys
>>> x = 123456
>>> y = x               # create a second reference to the number
>>> dir()               # known variables include "x" and "y"
['__builtins__', '__doc__', '__name__', '__package__', 'sys', 'x', 'y']
>>> sys.getrefcount(x)
3
>>> del x               # remove "x" as a known variable
>>> dir()               # known variables includes only "y"
['__builtins__', '__doc__', '__name__', '__package__', 'sys', 'y']
>>> sys.getrefcount(y)  # reference count is now lower by 1
2
>>> del y               # remove "y" as a known variable
>>> dir()               # known variables no longer include "x" and "y"
['__builtins__', '__doc__', '__name__', '__package__', 'sys']
>>> x                   # unresolved variable raises a "NameError"

Traceback (most recent call last):
  File "<pyshell#12>", line 1, in <module>
    x                  # unresolved variable raises a "NameError"
NameError: name 'x' is not defined

1
谢谢你的建议。我一直想知道底层是什么东西。这些以后会很有用。 - theMobDog
1
@RaymondHettinger -- 我更新了我的回答,以更详细地说明你的回答。 - Charles D Pantoga

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接