函数中可变参数的应用

背景

在函数或类定义中传入的参数是可变参数,常见的是字典、列表、数组(ndarray),函数内容如果仅仅是引用该这些对象没有什么大问题。但是如果涉及增、删操作,将会发生非常诡异的事情。

下面以《流畅的Python》中定义的一个案例进行介绍:

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)


passengers = ['Alice', 'Bill', 'Carrie', "Dave"]
bus1 = HauntedBus(passengers)
bus1.pick("Charlie")
bus1.drop("Alice")  # Alice下车
print(f"bus1.passengers={bus1.passengers}")  #
print(f"passengers={passengers}")
"""
bus1.passengers=['Bill', 'Carrie', 'Dave', 'Charlie']
passengers=['Bill', 'Carrie', 'Dave', 'Charlie']
"""

类HauntedBus违反了涉及接口的最佳实践,即“最少惊讶原则”。学生从校车中下车后,他的名字就从篮球队消失了。

简单解释就是:实例的修改不能影响其它外部变量。

解决问题

通过python内置的数据结构可知,列表、字典是可变的数据对象.而且赋值是浅拷贝,所在在该模式下修改赋值后的变量也会影响源变量。针对上述问题解决方案就是将对应的变量进行copy。

class HauntedBus:
    def __init__(self, passengers=[]):
        self.passengers = passengers.copy()
        

    def pick(self, name):
        self.passengers.append(name)

    def drop(self, name):
        self.passengers.remove(name)


passengers = ['Alice', 'Bill', 'Carrie', "Dave"]
bus1 = HauntedBus(passengers)
bus1.pick("Charlie")
bus1.drop("Alice")  # Alice下车
print(f"bus1.passengers={bus1.passengers}")  #
print(f"passengers={passengers}")
"""
bus1.passengers=['Bill', 'Carrie', 'Dave', 'Charlie']
passengers=['Alice', 'Bill', 'Carrie', 'Dave']
"""

然而copy并不总是有效,如果当前这个数据对象非常大的情况下,copy将比较耗时,此时比较便捷的方式就是基于目标数据结构进行输出数据重构。

这种情况在输入的参数是字典时(且嵌套层级较多时),尤为明显。

ndarray中类似的问题

import numpy as np

a = np.arange(10)


def func(a):
    """测试,将a的前2个值修改为0"""
    a[:2] = 0
    return True


print(f"before a={a}")
func(a)
print(f"after a={a}")
"""
before a=[0 1 2 3 4 5 6 7 8 9]
after a=[0 0 2 3 4 5 6 7 8 9]
"""

def func2(a):
    """测试,将a的前2个值修改为0"""
    b=a.copy()
    b[:2] = 0
    print(f"b={b}")
    return True

c=np.arange(10)
print(f"before c={c}")
func2(c)
print(f"after c={c}")
"""
before c=[0 1 2 3 4 5 6 7 8 9]
b=[0 0 2 3 4 5 6 7 8 9]
after c=[0 1 2 3 4 5 6 7 8 9]
"""

小结

  • 输入的参数如果是可变对象,若涉及对象修改问题需慎重考虑:
    • 单通道串行:正常操作即可
    • 多通道并行(存在多个对象同时或部分同时应用的情况):
      • 若数据体量较小,可以采用深拷贝或浅拷贝的方式
      • 若数据体量较大,且浅拷贝不足以解决问题时,但一般情况不建议深拷贝。通常基于目标数据格式进行重组。
posted @ 2023-04-11 09:43  LgRun  阅读(142)  评论(0编辑  收藏  举报