PyCharm 为什么要警告可变的默认参数? 我怎样才能绕过它们?

我使用 PyCharm (Python3)来编写一个 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={}是黄色的。将鼠标移动到它上面会显示一个警告。

默认参数值是可变的

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

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

这意味着什么? 我该如何解决这个问题?

51814 次浏览

如果你没有改变“可变的默认参数”,或者把它传递到任何可以改变的地方,只要忽略消息,因为没有什么需要“修复”。

在您的情况下,您的 只有解压缩(这是一个隐式副本)的“可变的默认参数”-所以您是安全的。

如果你想“删除警告信息”,你可以默认使用 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 可能会显示这个警告,因为很容易出现意外错误(参见例子 为什么可变的默认参数会记住函数调用之间的变化?和所有相关问题)。但是,如果你故意这样做(可变函数参数默认值的良好使用?)的警告可能是恼人的。

重新措辞警告: 每次调用这个函数,如果它使用默认值,将使用相同的对象。只要你不改变那个对象,它是可变的这个事实就无关紧要了。但是如果您更改 ,那么后续调用将以 修改过的值开始,这可能不是您想要的。

避免这个问题的一个解决方案是将缺省类型设置为不可变类型,如果使用了这个缺省类型,则将参数设置为 {}:

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)

你可以用 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
  • List 是可变的,因为在编译时声明 def 时声明 def 将为某个地址的变量分配一个可变的 List

    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)
    

     

    • 这与每次创建新列表时的情况一样,并且不将旧列表引用为值,而是始终为 null,从而在新地址分配新列表

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

>>> 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}

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

胶带问题..。

在您的例子中,正如另一个答案所指出的,您是通过隐式副本保存的,但是依赖于隐式行为从来都不是一个好主意,尤其是意想不到的隐式行为,因为它可能会发生变化。这就是为什么我们说 “显性比隐性好”。除此之外,隐性行为倾向于隐藏正在发生的事情,这可能导致您或其他程序员删除管道胶带。

用简单(永久)的方法

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

因为不可变类型强制您替换它们(从技术上讲,您将一个新对象绑定到相同的名称。在上面的例子中,当附件被反弹到 新的空字典时,对 Nothing 的引用会被覆盖)而不是更新它们,你知道除非在调用参数中指定,否则 attachment总是以 None开始,这样就避免了默认值发生意外变化的风险。

(顺便说一句,如果不确定一个物体是否与另一个物体相同,可以将它们与 is进行比较或检查 id(object)。前者可以检查两个引用是否引用同一个对象,后者可以通过为对象打印唯一标识符(通常是内存位置)来进行调试。)