不要使用可变类型作为参数的默认值

先看一个例子

class HauntedBus:
       """备受幽灵乘客折磨的校车"""
       def __init__(self, passengers=[]): 
             self.passengers = passengers 
       def pick(self, name):
             self.passengers.append(name) 
       def drop(self, name):
             self.passengers.remove(name)

测试

>>> bus1 = HauntedBus(['Alice', 'Bill'])
>>> bus1.passengers
['Alice', 'Bill']
>>> bus1.pick('Charlie')
>>> bus1.drop('Alice')
>>> bus1.passengers 
['Bill', 'Charlie']
>>> bus2 = HauntedBus() 
>>> bus2.pick('Carrie')
>>> bus2.passengers
['Carrie']
>>> bus3 = HauntedBus() 
>>> bus3.passengers 
['Carrie']
>>> bus3.pick('Dave')
>>> bus2.passengers 
['Carrie', 'Dave']
>>> bus2.passengers is bus3.passengers 
True
>>> bus1.passengers 
['Bill', 'Charlie']

出现这个问题的根源是,默认值在定义函数时计算(通常在加载模块时),因此默认值变成了函数对象的属性。因此,如果默认值是可变对象,而且修改了它的值,那么后续的函数调用都会受到影响.
可变默认值导致的这个问题说明了为什么通常使用 None 作为接收可变值的参数的默认值.

对上面的类进行改进,规避上述的问题:

class TwilightBus:
      """让乘客销声匿迹的校车"""
      def __init__(self, passengers=None):
            if passengers is None:
                  self.passengers = []
            else:
                  self.passengers = passengers
      def pick(self, name):
            self.passengers.append(name)
      def drop(self, name):
            self.passengers.remove(name)

测试

>>> basketball_team = ['Sue', 'Tina', 'Maya', 'Diana', 'Pat']
>>> bus = TwilightBus(basketball_team)
>>> bus.drop('Tina')
>>> bus.drop('Pat')
>>> basketball_team
['Sue', 'Maya', 'Diana']

TwilightBus 违反了设计接口的最佳实践,即“最少惊讶原则”。学生从校车中下车后,她的名字就从篮球队的名单中消失了,这确实让人惊讶。
这里的问题是,校车为传给构造方法的列表创建了别名。正确的做法是,校车自己维护乘客列表。修正的方法很简单:在 init 中,传入 passengers 参数时,应该把参数值的副本赋值给self.passengers
修改初始化函数

def __init__(self, passengers=None):
      if passengers is None:
            self.passengers = []
     else:
            self.passengers = list(passengers)

在内部像这样处理乘客列表,就不会影响初始化校车时传入的参数了。此外,这种处理方式还更灵活:现在,传给 passengers 参数的值可以是元组或任何其他可迭代对象,例如set 对象,甚至数据库查询结果,因为 list 构造方法接受任何可迭代对象。自己创建并管理列表可以确保支持所需的.remove() 和 .append() 操作,这样 .pick() 和.drop() 方法才能正常运作

除非这个方法确实想修改通过参数传入的对象,否则在类中直接把参数赋值给实例变量之前一定要三思,因为这样会为参数对象创建别名。如果不确定,那就创建副本

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容