关于PyCharm中可变默认参数的警告

bin*_*bin 43 python warnings pycharm

我正在使用PyCharm(Python 3)编写一个Python函数,它接受一个字典作为参数attachment={}.

def put_object(self, parent_object, connection_name, **data):
    ...

def put_wall_post(self, message, attachment={}, profile_id="me"):
    return self.put_object(profile_id, "feed", message=message, **attachment)
Run Code Online (Sandbox Code Playgroud)

在IDE中,attachment={}为黄色.将鼠标移到它上面会显示警告.

默认参数值是可变的

此检查检测何时在参数的默认值中检测到可变值作为列表或字典.

默认参数值仅在函数定义时计算一次,这意味着修改参数的默认值将影响函数的所有后续调用.

这意味着什么,我该如何解决?

MSe*_*ert 47

如果您不改变"可变默认参数"或将其传递到可以更改的任何地方,则忽略该消息,因为没有任何内容可以"修复".

在您的情况下,您只需解压缩(隐式副本)"可变默认参数" - 所以您是安全的.

如果您要"删除该警告消息",您可以将其None用作默认设置并将其设置为以下{}时间None:

def put_wall_post(self,message,attachment=None,profile_id="me"):
    if attachment is None:
        attachment = {}

    return self.put_object(profile_id,"feed",message = message,**attachment)
Run Code Online (Sandbox Code Playgroud)

只是为了解释"是什么意思":某些类型在Python是不可改变的(int,str,...)等是可变的(如dict,set,list,...).如果要更改不可变对象,则会创建另一个对象 - 但如果更改了可变对象,则该对象保持不变,但其内容会发生更改.

棘手的部分是在加载函数时(并且只加载一次)创建类变量和默认参数,这意味着对"可变默认参数"或"可变类变量"的任何更改都是永久性的:

def func(key, value, a={}):
    a[key] = value
    return a

>>> print(func('a', 10))  # that's expected
{'a': 10}
>>> print(func('b', 20))  # that could be unexpected
{'b': 20, 'a': 10}
Run Code Online (Sandbox Code Playgroud)

PyCharm可能会显示此警告,因为很容易意外地弄错(例如,参见"最小惊讶"和Mutable默认参数以及所有相关问题).但是,如果你是故意这样做的(好的用于可变函数参数默认值?)警告可能很烦人.


Twi*_*ode 13

这是来自解释器的警告,因为您的默认参数是可变的,如果就地修改它,您最终可能会更改默认值,这在某些情况下可能会导致意外结果。默认参数实际上只是对您指示的对象的引用,就像您将列表别名为两个不同的标识符一样,例如,

\n
>>> a={}\n>>> b=a\n>>> b[\'foo\']=\'bar\'\n>>> a\n{\'foo\': \'bar\'}\n
Run Code Online (Sandbox Code Playgroud)\n

如果通过任何引用更改对象,无论是在调用该函数期间、单独调用还是在函数外部,都将影响该函数的未来调用。如果您不希望函数的行为在运行时发生变化,这可能是导致错误的原因。每次调用该函数时,都会将相同的名称绑定到同一个对象。(事实上​​,我不确定它是否每次都经历整个名称绑定过程?我认为它只是得到另一个参考。)

\n

(可能不需要的)行为

\n

您可以通过声明以下内容并调用几次来查看其效果:

\n
>>> def mutable_default_arg (something = {\'foo\':1}):\n    something[\'foo\'] += 1\n    print (something)\n\n\n>>> mutable_default_arg()\n{\'foo\': 2}\n>>> mutable_default_arg()\n{\'foo\': 3}\n
Run Code Online (Sandbox Code Playgroud)\n

等等,什么?是的,因为参数引用的对象在调用之间不会更改,所以更改其元素之一会更改默认值。如果您使用不可变类型,则不必担心这一点,因为在标准情况下,不可能更改不可变的数据。我不知道这是否适用于用户定义的类,但这就是为什么通常只用“无”来解决这个问题(即,您只需要它作为占位符,仅此而已。为什么要在某些东西上花费额外的 RAM更复杂?)

\n

管道胶带问题...

\n

就您而言,正如另一个答案指出的那样,您是通过隐式副本保存的,但依赖隐式行为,尤其是意外的隐式行为,从来都不是一个好主意,因为它可能会发生变化。这就是为什么我们说“显式优于隐式”。除此之外,隐式行为往往会隐藏正在发生的事情,这可能会导致您或其他程序员撕掉胶带。

\n

...使用简单(永久)的解决方案

\n

正如其他人所建议的那样,您可以完全避免此错误磁铁并通过使用不可变类型(例如 )来满足警告None,在函数开始时检查它,如果找到,请立即在函数开始之前替换它:

\n
def put_wall_post(self, message, attachment=None, profile_id="me"):\n    if attachment is None:\n        attachment = {}\n    return self.put_object(profile_id, "feed", message=message, **attachment)\n
Run Code Online (Sandbox Code Playgroud)\n

由于不可变类型强制您替换它们(从技术上讲,您将一个新对象绑定到相同的名称。在上面,当附件反弹到新的字典时,对 None 的引用将被覆盖)而不是更新它们,您知道attachment会除非在调用参数中指定,否则始终以 as 启动None,从而避免默认值意外更改的风险。

\n

(顺便说一句,当不确定一个对象是否与另一个对象相同时,请将它们与is或 checkid(object)进行比较。前者可以检查两个引用是否引用同一个对象,后者可以通过打印唯一标识符来进行调试\xe2\x80\x94通常是对象的内存位置\xe2\x80\x94。)

\n


Pet*_*ood 5

您可以将可变的默认参数替换为None。然后在函数内部检查并分配默认值:

def put_wall_post(self, message, attachment=None, profile_id="me"):
    attachment = attachment if attachment else {}

    return self.put_object(profile_id, "feed", message=message, **attachment)
Run Code Online (Sandbox Code Playgroud)

这行得通,因为None计算结果为,False因此我们分配了一个空字典。

一般来说,你可能要显式检查None的其他值也可以评估到False,例如0''set()[],等,都是False-y。如果您的默认值不是05例如),那么您将不希望0被作为有效参数传递:

def function(param=None):
    param = 5 if param is None else param
Run Code Online (Sandbox Code Playgroud)

  • 甚至更短的`attachment =附件或{}` (11认同)
  • @MSeifert 我从来没有发现短路非常具有可读性,如果我使用它,我不希望其他人理解我的代码。我认为来自 C++ 背景,我希望布尔表达式产生真/假。也许我需要训练自己不要被它排斥(c: (2认同)
  • @Matthias 该函数需要一个字典。如果你传递其他东西,你就会遇到更大的问题。 (2认同)
  • @PeterWood:根据功能,我可能可以传递其他内容(关键字:_duck 打字_)。但您是对的:在这种特殊情况下,使用字符串或列表将是错误的。 (2认同)