Python函数参数传递与可变参数的处理

Python函数参数传递与可变参数处理

Python函数的共享传参模式

Python 采用共享传参(call by sharing)的参数传递模式,这也是多数面向对象语言如 Ruby、Smalltalk 和 Java(Java 的引用类型)所采用的模式。在共享传参中,函数的形式参数获得实参中各个引用的副本,即函数内部的形参是实参的别名。

这种模式下,函数可能会修改作为参数传入的可变对象,但无法修改对象的标识。例如:

def f(a, b): 
    a += b 
    return a 
 
x = 1 
y = 2 
f(x, y)  # 返回 3 
print(x, y)  # 输出 (1, 2),数字 x 未改变 
 
a = [1, 2] 
b = [3, 4] 
f(a, b)  # 返回 [1, 2, 3, 4] 
print(a, b)  # 输出 ([1, 2, 3, 4], [3, 4]),列表 a 被改变 
 
t = (10, 20) 
u = (30, 40) 
f(t, u)  # 返回 (10, 20, 30, 40) 
print(t, u)  # 输出 ((10, 20), (30, 40)),元组 t 未改变 

这表明数字和元组等不可变对象不会被函数修改,而列表等可变对象可能会被修改。

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

Python 函数的可选参数可以有默认值,这是一个很棒的特性,但应避免使用可变对象作为参数的默认值。

以 HauntedBus 类为例:

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)  

当实例化 HauntedBus 时,如果不传入乘客参数,多个实例会共享同一个默认列表。例如:

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

这是因为默认值在定义函数时计算,成为函数对象的属性。如果默认值是可变对象,修改它会影响后续的函数调用。通常应使用 None 作为接收可变值的参数的默认值。

防御可变参数

如果函数接收可变参数,要谨慎考虑调用方是否期望修改传入的参数。

以 TwilightBus 类为例:

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')  
print(basketball_team)  # 输出 ['Sue', 'Maya', 'Diana'] 

这违反了“最少惊讶原则”,因为学生从校车下车后,名字从篮球队名单中消失了。问题在于校车为传入的列表创建了别名。

正确的做法是在__init__ 中创建参数值的副本:

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

这样处理既不会影响初始化时传入的参数,又更灵活,能接受各种可迭代对象作为参数。

总之,在类中直接把参数赋值给实例变量之前要谨慎,不确定时应创建副本,以减少客户的麻烦。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

钢铁男儿

赛博功德充值,BUG退散

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值