2404-169-Python的默认值陷阱

133 阅读2分钟

摘要

Python类的初始化函数里面,如果有参数指定了一个默认值,并且这个默认值还是引用类型的,那么这个默认值的直接量其实是一个隐式对象,每个调用初始化函数的对象都会共用这么一个默认值,从而造成不容易发现的Bug。针对此问题的解决办法就是,如果这个初始化函数的参数的类型不是字符或者数字等按值拷贝的元素,而是按引用传递的类型,那么不要轻易使用非None默认值,以防造成不必要的麻烦。

默认值问题

假如有这么一个类

class GraphNode:
    def __init__(self, val, neighbors=[]):
        self.val = val
        self.neighbors = neighbors

我们创建两个对象

node3 = GraphNode(3)
node4 = GraphNode(4)

这个时候,node3 和 node4 的 neighbors都是空值。

如果我们给node3的neighbors 增加一个元素 1, 给node4的neighors增加一个元素2,请问现在node3和node4分别的neighbor内部是什么样的?

实际上,node3.neighbors = [1,2] , node4.neighbors = [1,2]

是不是很意外?!!

原因

GraphNode的初始化函数中,neighbors参数默认值如果指定了[], 那么这个参数不会是新建的,而是只有一个对象。

如果你认为它每次都会新建一个数组对象,那么你就会无法理解接下来程序的执行情况。

你可以试一下这段程序, checkDefaultValue.py

class GraphNode:
    def __init__(self, val, neighbors=[]):
        self.val = val
        self.neighbors = neighbors
        
​
if __name__ == '__main__':
    node3 = GraphNode(3)
    node4 = GraphNode(4)
​
    node3.neighbors.append(1)
    node4.neighbors.append(2)
​
    print(node3.neighbors)
    print(node4.neighbors)
       

正确的解决办法是, 初始化函数的按引用传递的参数,默认值一律声明为 None, 具体创建对象的时候再指明。

class GraphNode:
    def __init__(self, val, neighbors=None): // 默认值其实也是一个对象,隐式的对象
        self.val = val
        self.neighbors = neighbors
        
​
if __name__ == '__main__':
    node3 = GraphNode(3,[]) // 每次调用构造方法都会创建一个新的数组作为值
    node4 = GraphNode(4,[]) 
​
    node3.neighbors.append(1)
    node4.neighbors.append(2)
​
    print(node3.neighbors)
    print(node4.neighbors)