仍然对 Python 中的可变默认参数值 "gotcha" 感到困惑
Still confused about mutable default parameter value "gotcha" in Python
我知道不应该在 Python 中使用 mutable default parameter value(有一些例外),因为这个值只在定义函数时计算和存储一次,而不是每次函数被计算和存储稍后调用。
我的理解是这样的(使用下面的例子;请原谅我的语言不精确,因为我只是 Python 编程的初学者,因此卡在了我教科书的函数章节) :
def f(x = [1, 2, 3]):
x.append(4)
print(x)
f()
f()
1)定义了函数f,x(f中的局部变量)默认为[1,2,3]变量(甚至在调用函数之前)
2) 当 f() 被调用时,x 仍然是 [1, 2, 3] 因为没有参数传递给它,x 继续有它的默认值
3) x 被 append 原地修改,变成 [1, 2, 3, 4],并打印成这样
然而,这就是我的困惑所在。我假设:
4) 当 f 结束时,x 被销毁(在堆栈中或任何你称之为的东西中)并且不再与列表对象相关联 [1, 2, 3, 4]**
5) 列表对象 [1, 2, 3, 4] 被回收,因为不再有引用它的变量
因此,
6) 第二次调用 f() 时,我希望 Python 输出错误,因为 x 现在不再具有与之关联的值。换句话说,Python 如何重用上次评估的默认值 reclaimed/destroyed?
感谢您的帮助和解释!
** 我从 Ned Batchelder's page on variable name assignment 那里得到的理解(见下文)
虽然在您看来,默认值 x 在执行结束时会被释放,但事实并非如此。
事实上,Python 有一个全局命名空间,其中包含所有可供您使用的名称(内置函数、类 以及您导入或定义的函数)。
这个命名空间的内容是由对象组成的。函数也是对象。
作为测试,如果您在脚本或 python 命令行中尝试此操作,您会明白我的意思:
def f(x = [1, 2, 3]):
x.append(4)
print(x)
print dir(f)
你会看到函数f的对象性质。作为对象,默认值在属性 f.func_defaults
中引用,因此它们始终可用,如果可变,它们会保留更改,给您带来您可能不想要的副作用。
编辑:在 python 3 中,属性已被替换为 f.__defaults__
在你的例子中有两个对列表的引用,一个是作为参数 x
的默认值存储在函数的后台。
当在没有 x
的情况下调用函数时,将创建对 same 列表的新引用作为局部变量 x
。然后您通过第二个引用附加到列表。在调用之后,第二个引用被垃圾收集。第一个引用仍然指向同一个列表,现在多了一个元素。
或者简而言之:始终只有一个列表。
我知道不应该在 Python 中使用 mutable default parameter value(有一些例外),因为这个值只在定义函数时计算和存储一次,而不是每次函数被计算和存储稍后调用。
我的理解是这样的(使用下面的例子;请原谅我的语言不精确,因为我只是 Python 编程的初学者,因此卡在了我教科书的函数章节) :
def f(x = [1, 2, 3]):
x.append(4)
print(x)
f()
f()
1)定义了函数f,x(f中的局部变量)默认为[1,2,3]变量(甚至在调用函数之前)
2) 当 f() 被调用时,x 仍然是 [1, 2, 3] 因为没有参数传递给它,x 继续有它的默认值
3) x 被 append 原地修改,变成 [1, 2, 3, 4],并打印成这样
然而,这就是我的困惑所在。我假设:
4) 当 f 结束时,x 被销毁(在堆栈中或任何你称之为的东西中)并且不再与列表对象相关联 [1, 2, 3, 4]**
5) 列表对象 [1, 2, 3, 4] 被回收,因为不再有引用它的变量
因此,
6) 第二次调用 f() 时,我希望 Python 输出错误,因为 x 现在不再具有与之关联的值。换句话说,Python 如何重用上次评估的默认值 reclaimed/destroyed?
感谢您的帮助和解释!
** 我从 Ned Batchelder's page on variable name assignment 那里得到的理解(见下文)
虽然在您看来,默认值 x 在执行结束时会被释放,但事实并非如此。
事实上,Python 有一个全局命名空间,其中包含所有可供您使用的名称(内置函数、类 以及您导入或定义的函数)。
这个命名空间的内容是由对象组成的。函数也是对象。
作为测试,如果您在脚本或 python 命令行中尝试此操作,您会明白我的意思:
def f(x = [1, 2, 3]):
x.append(4)
print(x)
print dir(f)
你会看到函数f的对象性质。作为对象,默认值在属性 f.func_defaults
中引用,因此它们始终可用,如果可变,它们会保留更改,给您带来您可能不想要的副作用。
编辑:在 python 3 中,属性已被替换为 f.__defaults__
在你的例子中有两个对列表的引用,一个是作为参数 x
的默认值存储在函数的后台。
当在没有 x
的情况下调用函数时,将创建对 same 列表的新引用作为局部变量 x
。然后您通过第二个引用附加到列表。在调用之后,第二个引用被垃圾收集。第一个引用仍然指向同一个列表,现在多了一个元素。
或者简而言之:始终只有一个列表。