为什么PyCharm会警告可变的默认参数?我该如何解决它们?

gr8qqesn  于 2022-11-08  发布在  PyCharm
关注(0)|答案(5)|浏览(365)

我正在使用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)

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

默认参数值是可变的

此检查会侦测何时在参数的预设值中侦测到清单或字典等可变值。
默认参数值在函数定义时只计算一次,这意味着修改参数的默认值将影响函数的所有后续调用。
这意味着什么?如何解决?

4bbkushb

4bbkushb1#

如果你不改变“可变的默认参数”,或者把它传递到任何可以改变的地方,就忽略这个消息,因为没有什么需要“修复”的。
在你的例子中,你 * 只 * 解包(这是一个隐式复制)“可变的默认参数”-所以你是安全的。
如果您想“删除该警告消息”,可以使用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)

只是为了解释“它意味着什么”:Python中的一些类型是不可变的(intstr,...),其他类型是可变的(如dictsetlist,...)。如果你想改变不可变对象,则会创建另一个对象-但如果你改变可变对象,则对象保持不变,但其内容会发生变化。
需要注意的是,类变量和缺省参数是在函数加载时创建的(而且只创建一次),这意味着对“可变缺省参数”或“可变类变量”的任何更改都是永久性的:

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}

PyCharm显示此警告可能是因为很容易不小心出错(例如,请参阅Why do mutable default arguments remember mutations between function calls?和所有相关问题)。但是,如果您是故意这样做的(Good uses for mutable function argument default values?),则此警告可能会很烦人。

deyfvvtc

deyfvvtc2#

你可以用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)

这是因为None的计算结果为False,所以我们分配了一个空字典。
一般情况下,您可能希望显式检查None,因为其他值也可能计算为False,例如0''set()[]等都是False-y。例如,如果您的默认值不是0,而是5,那么您就不会希望将0作为有效参数传递:

def function(param=None):
    param = 5 if param is None else param
3bygqnnd

3bygqnnd3#

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

>>> a={}
>>> b=a
>>> b['foo']='bar'
>>> a
{'foo': 'bar'}

如果对象通过任何引用被改变,无论是在调用函数的过程中、单独的调用中,还是在函数外部,都将影响以后对函数的调用。如果你不希望函数的行为在运行时改变,这可能是导致bug的原因。每次调用函数时,都是同一个名称被绑定到同一个对象。(事实上,我不确定它是否每次都经历了整个名称绑定过程?我认为它只是得到了另一个引用。)

(可能不需要的)行为

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

>>> def mutable_default_arg (something = {'foo':1}):
    something['foo'] += 1
    print (something)

>>> mutable_default_arg()
{'foo': 2}
>>> mutable_default_arg()
{'foo': 3}

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

胶带问题...

在你的例子中,正如另一个答案所指出的,你是通过隐式复制而得救的,但是依赖隐式行为从来都不是一个好主意,尤其是意想不到的隐式行为,因为它可能会改变。这就是为什么我们说"explicit is better than implicit"。除此之外,隐式行为往往会隐藏正在发生的事情,这可能会导致你或其他程序员删除胶带。

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

你可以完全避免这个bug磁铁,并满足警告,正如其他人所建议的,使用一个不可变的类型,如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)

由于不可变类型会强制您替换它们(从技术上讲,您是在将新对象绑定到相同的名称。在上面的例子中,当附件被重新绑定到 new 空字典时,对None的引用会被覆盖)而不是更新它们,因此您知道attachment将始终以None开头,除非在调用参数中指定,这样就避免了对默认值进行意外更改的风险。
(As顺便说一句,当怀疑一个对象是否与另一个对象相同时,可以将它们与is进行比较,或者检查id(object)。前者可以检查两个引用是否指向同一个对象,后者可以通过打印对象的唯一标识符(通常是内存位置)来进行调试。)

yptwkmov

yptwkmov4#

重新表述警告:每次调用这个函数,如果它使用默认值,将使用相同的对象。只要你从来没有改变这个对象,它是可变的这一事实并不重要。但如果你 * 做 * 改变它,那么随后的调用将从 modified 值开始,这可能不是你想要的。
避免此问题的一个解决方案是将默认值设为不可变类型(如None),如果使用该默认值,则将参数设置为{}

def put_wall_post(self,message,attachment=None,profile_id="me"):
    if attachment==None:
        attachment={}
    return self.put_object(profile_id,"feed",message = message,**attachment)
06odsfpq

06odsfpq5#

  • 列表是可变的,因为在编译时用def在声明中声明default会将一个可变列表赋给某个地址上的变量
def abc(a=[]):
    a.append(2)
    print(a)

abc() #prints [2]
abc() #prints [2, 2] as mutable thus changed the same assigned list at func delaration points to same address and append at the end
abc([4]) #prints [4, 2] because new list is passed at a new address
abc() #prints [2, 2, 2] took same assigned list and append at the end
  • 要更正此问题,请执行以下操作:
def abc(a=None):
     if not a:
         a=[]
     a.append(2)
     print(a)
  • 这与每次创建新列表时一样,并且不将旧列表引用为始终为空的值,从而在新地址分配新列表

相关问题