我正在使用 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={}
为黄色。将鼠标移到它上面会显示一个警告。
默认参数值是可变的 此检查检测何时在参数的默认值中检测到作为列表或字典的可变值。默认参数值仅在函数定义时计算一次,这意味着修改参数的默认值将影响函数的所有后续调用。
这是什么意思,我该如何解决?
如果您不更改“可变默认参数”或将其传递到可以更改的任何地方,请忽略该消息,因为没有什么需要“修复”。
在您的情况下,您只需解压缩(执行隐式复制)“可变默认参数” - 所以您是安全的。
如果您想“删除该警告消息”,您可以使用 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 中的某些类型是不可变的(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}
PyCharm 可能会显示此警告,因为它很容易被意外弄错(参见例如 Why do mutable default arguments remember mutations between function calls? 和所有链接的问题)。但是,如果您是故意这样做的 (Good uses for mutable function argument default values?),警告可能会很烦人。
您可以用 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
attachment = attachment or {}
这是来自解释器的警告,因为您的默认参数是可变的,如果您就地修改它,您最终可能会更改默认值,这在某些情况下可能会导致意外结果。默认参数实际上只是对您指示的对象的引用,就像您将列表别名为两个不同的标识符时一样,例如,
>>> a={}
>>> b=a
>>> b['foo']='bar'
>>> a
{'foo': 'bar'}
如果通过任何引用更改对象,无论是在调用函数期间、单独调用期间,还是在函数之外,都会影响将来调用该函数。如果您不希望函数的行为在运行时发生变化,这可能是导致错误的原因。每次调用该函数时,都会将同一个名称绑定到同一个对象。 (实际上,我不确定它是否每次都经历了整个名称绑定过程?我认为它只是获得了另一个参考。)
(可能不需要的)行为
您可以通过声明以下内容并多次调用它来查看其效果:
>>> 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"的原因。除此之外,隐式行为往往会隐藏正在发生的事情,这可能会导致您或其他程序员移除胶带。
...使用简单(永久)解决方案
正如其他人所建议的那样,您可以完全避免这个错误磁铁并满足警告,使用不可变类型(例如 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)
由于不可变类型强制您替换它们(从技术上讲,您将新对象绑定到相同的名称。在上面,当附件重新绑定到 new 空字典时,对 None 的引用被覆盖)在更新它们时,您知道 attachment
将始终以 None
开头,除非在调用参数中指定,从而避免意外更改默认值的风险。
(顺便说一句,当不确定一个对象是否与另一个对象相同时,将它们与 is
进行比较或检查 id(object)
。前者可以检查两个引用是否引用同一个对象,后者可以用于通过打印对象的唯一标识符(通常是内存位置)进行调试。)
改写警告:每次调用此函数,如果它使用默认值,将使用相同的对象。只要您从不更改该对象,它是可变的这一事实就无关紧要。但是,如果您确实更改了它,那么后续调用将以修改后的值开始,这可能不是您想要的。
避免此问题的一种解决方案是将默认值设置为不可变类型,例如 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)
def ptest( n, arg = {} ): print(n, arg); arg[n] = len(arg)
ptest('a'); ptest('b'); ptest('c')
将产生 a {}
b {'a':0}
c {'a':0, 'b':1}
列表是可变的,并且在编译时用 def 声明默认值将在某个地址为变量分配一个可变列表 def abc(a=[]): a.append(2) print(a) abc() #prints [ 2] abc() #prints [2, 2] as mutable 因此将 func delaration 点的相同分配列表更改为相同的地址并附加在末尾 abc([4]) #prints [4, 2] 因为传递了新列表在新地址 abc() #prints [2, 2, 2] 采用相同的分配列表并附加在末尾
要更正此问题: def abc(a=None): if not a: a=[] a.append(2) print(a) 这适用于每次创建新列表并且始终不将旧列表作为值引用null 因此在新地址分配新列表
这适用于每次创建新列表并且不将旧列表作为值引用时始终为 null 从而在新地址分配新列表
不定期副业成功案例分享
__init__
中定义的变量吗?例如:ElementTree.Element(tag, attrib={}, **extra)
copy
ing it。这样他们就可以使用副本并且不会冒险改变默认参数。attachment = attachment or {}
而不是if attachment is None: attachment = {}
is None
方法不会默默地将 false-y 值转换为空字典(例如,如果有人传入False
)。我也会采用or {}
方法,但还添加一些文档或类型提示,以便清楚应该传入什么。请注意,我还在另一个答案中提出了该方法({1 }) - 但两者实际上是等价的。