zoukankan      html  css  js  c++  java
  • 使用可变对象作为python函数默认参数引发的问题

    写python的都知道,python函数或者方法可以使用默认参数,比如

    1 def foo(arg=None):
    2     print(arg)
    3  
    4 foo()
    5   
    6 foo("hello world")

    一个很简单的函数,参数arg默认使用None,当调用foo函数时,可以传入一个参数,也可以不传入参数,运行结果如下

    None
    hello world

    这很好理解。默认参数是python一个很好的特性。

    但是如果使用可变对象作为默认参数,就会引发问题。之前写过一个脚本,bug不断,后来终于找到了原因。下面引用Fluent python中的一个例子加以说明。

    废话不多说,直接上代码

     1 """可变对象作为默认参数传入到函数引发的问题"""
     2 
     3 
     4 class Bus:
     5     def __init__(self, passengers=[]):
     6         self.passengers = passengers
     7 
     8     def pick(self, a_passenger):
     9         self.passengers.append(a_passenger)
    10 
    11     def drop(self, a_passenger):
    12         self.passengers.remove(a_passenger)
    13 
    14 
    15 if __name__ == '__main__':
    16     bus1 = Bus(['Alice', 'Jeff', 'ethan'])
    17     print(bus1.passengers)
    18     bus1.pick("Simon")
    19     print(bus1.passengers)  # 到此为止并没什么问题
    20 
    21     # 接下来问题来了, 创建了两个Bus对象,并进行操作。
    22     bus2 = Bus()
    23     print(bus2.passengers)
    24     bus2.pick("Alice")   # "Alice"上了bus2
    25     print(bus2.passengers)  # 打印bus2的乘客列表
    26     bus3 = Bus()   # 创建另一个Bus实例,同样不传入参数(使用默认[]参数)
    27     print(bus3.passengers)  # 打印bus3的乘客列表
    28 
    29     bus3.pick("alex")
    30     print(bus3.passengers)
    31     print(bus2.passengers)
    32     print(bus3.passengers is bus2.passengers)
    33 
    34     print(bus3.passengers is bus1.passengers)

    下面来看运行结果

    ['Alice', 'Jeff', 'ethan']
    ['Alice', 'Jeff', 'ethan', 'Simon']
    []
    ['Alice']
    ['Alice']
    ['Alice', 'alex']
    ['Alice', 'alex']
    True
    False

    从结果可以看出,bus1这个对象似乎没什么问题,而bus2和bus3对象的passengers属性似乎是同一个。为什么会这样呢?

    我们先来分析下Bus这个类

    定义了一个Bus类,有一个__init__方法,pick方法和drop方法。__init__方法有一个默认参数,即passengers=[]。

    大家应该都知道在python中列表对象([])是可变对象。 

    当创建bus1对象的时候,传入了实实在在的passengers参数(一个非空列表)

    当创建bus2和bus3对象的时候,没有传入参数,所以会使用默认参数([])

    然后我对这几个Bus实例进行操作(调用pick或drop方法,即插入或者删除passengers列表中的乘客)

    从结果可以很直观的看出来,bus1.passengers 并没什么问题,一切按照我们的预期进行。但是bus2的乘客"Alice"为什么会出现在bus3上? bus3的乘客“alex”为什么又会出现在bus2上???

    其实这就是用可变对象作为默认参数引发的后果。因为当使用默认参数定义了一个函数/方法之后,加载模块的时候已经初始化了这个可变对象,所以当你调用这个函数/方法的时候,如果你未传入参数(即使用了默认参数), 则对函数/方法的不同调用,其实传入的是同一个可变对象(上面例子中是一个空列表)。 

    下面请看

     1 class Bus:
     2     def __init__(self, passengers=[]):
     3         self.passengers = passengers
     4  
     5     def pick(self, a_passenger):
     6         self.passengers.append(a_passenger)
     7  
     8     def drop(self, a_passenger):
     9         self.passengers.remove(a_passenger) 
    10 
    11 if __name__ == '__main__':
    12     print(Bus.__init__.__defaults__)

    运行结果

    ([],)
    在这个例子中,并没有初始化Bus类, 而__init__方法的__defaults__属性,是一个空列表, 也就是说,在完成__init__方法定义的时候,就已经初始化了默认参数(一个空列表), 因为列表是可变对象,所以后续对Bus类的引用就会引发问题。

    至此,问题原因很明确了,就是可变对象导致了这个问题,因此千万不要使用可变对象作为python函数/方法的默认参数, 一般建议使用None作为默认参数,即
    1 def foo(arg=None):
    2      pass


  • 相关阅读:
    RNA velocity | RNA速率
    Dynamic networks | 动态网络
    Scale Free Network | 无标度网络
    GO | KEGG的注释是怎么来的?
    Nearest neighbor graph | 近邻图
    L0 Regularization
    Median absolute deviation | Singular Value Decomposition奇异值分解 | cumulative sums |
    Multivariate normal distribution | 多元正态分布
    相似性 similarity | Pearson | Spearman | p-value | 相关性 correlation | 距离 distance | distance measure
    Type I and type II errors | 第一类错误和第二类错误
  • 原文地址:https://www.cnblogs.com/hzpythoner/p/9000091.html
Copyright © 2011-2022 走看看