脚本专家 发表于 2026-9-17 10:00:00

Python函数参数与返回值:*args与**kwargs避坑

函数在脚本编程里首先是复用手段。原文从折扣计算切入:如果每次都把同一套折扣公式抄一遍,规则变更时容易漏改。把逻辑放入 calculate_discount,只保留一份计算规则,调用时传入不同价格即可。函数还承担命名作用,看到函数名就能判断用途。

下面是未封装写法与封装后的对比:


price1 = 200
final_price1 = price1 * 0.8
print(final_price1)

price2 = 350
final_price2 = price2 * 0.8
print(final_price2)



def calculate_discount(price):
    final_price = price * 0.8
    return final_price

print(calculate_discount(200))
print(calculate_discount(350))


一、定义和调用函数

Python 用 def 定义函数,函数名后要有圆括号,行尾冒号不能省略,函数体需要缩进。定义函数不会立即执行函数体,必须用函数名加圆括号调用:


def say_hello():
    print('Hello')

say_hello()
say_hello()


程序执行到函数定义时只是记住函数;执行到调用语句时才进入函数体。函数调用结束后,会回到调用位置继续执行后面的代码。函数通常先定义再调用。若先调用后定义,例如先写 say_hello(),再写 def say_hello():,会因为名字尚未建立而触发 NameError。

二、形参、实参与参数传递方式

定义函数时圆括号里的变量叫形参,调用时传入的数据叫实参。例如 def greet(name): 中 name 是形参,调用 greet('Ivan') 时 'Ivan' 是实参。


def greet(name):
    print(f'Hello,{name}')

greet('Ivan')
greet('Tom')


多个参数用逗号分隔。调用时实参数量要与形参对应,少传或多传会触发 TypeError:


def add(a, b):
    result = a + b
    print(result)

add(10, 20)


按位置传值叫位置参数。位置写错时 Python 不一定报错,但数据含义可能已经改变,例如 introduce(20, '杭州', 'Ivan') 可以运行,却把 20 传给了 name。

调用时明确写参数名,就是关键字参数:


def introduce(name, age, city):
    print(f'姓名:{name}')
    print(f'年龄:{age}')
    print(f'城市:{city}')

introduce(name='Ivan', age=20, city='杭州')
introduce(city='杭州', name='Ivan', age=20)


关键字参数顺序可以调整,参数较多或布尔值、数字含义不直观时更易读。位置参数和关键字参数可以混用,但位置参数必须写在关键字参数前面;写成 introduce(name='Ivan', 20, '杭州') 会出现语法错误。

三、默认参数与可变对象陷阱

定义函数时可以给参数设置默认值。调用时没有传入该参数,就使用默认值;传入后默认值被覆盖:


def greet(name, message='Hello'):
    print(f'{message},{name}')

greet('Ivan')
greet('Tom', '早上好')


必填参数要放在默认参数前面。def greet(message='Hello', name): 的顺序不正确,因为只传一个位置参数时无法确定交给谁,Python 会提示语法错误。默认参数适合页码、排序方式、是否显示某个选项等真正有常用默认值的设置。

需要注意,不要直接用列表、字典、集合这类可变对象作为默认值。下面的 items=[] 只在函数定义时创建一次,后续调用会共用同一个列表:


def add_item(item, items=[]):
    items.append(item)
    return items

print(add_item('Python'))
print(add_item('Java'))


输出会是:


['Python']
['Python', 'Java']


通常改成 None,再在函数内部创建新列表:


def add_item(item, items=None):
    if items is None:
      items = []
    items.append(item)
    return items


四、*args 与 **kwargs 的接收规则

参数数量不固定时,可以在参数名前加一个星号。*args 把多个位置参数收集成元组:


def calculate_total(*prices):
    total = 0
    for price in prices:
      total += price
    return total

print(calculate_total(20, 35, 18))
print(calculate_total(10, 20, 30, 40, 50))


prices 是元组,args 只是常用名称,真正起作用的是星号。普通参数可以和 *args 一起出现,第一个实参交给普通参数,剩余位置参数进入元组:


def print_scores(title, *scores):
    print(title)
    for score in scores:
      print(score)

print_scores('第一次考试', 86, 92, 78)


两个星号 **kwargs 用来接收数量不固定的关键字参数,得到字典。调用时写下的参数名会成为键,传入的数据会成为值:


def create_user(**info):
    print(info)

create_user(name='Ivan', age=20, city='杭州')


输出为 {'name': 'Ivan', 'age': 20, 'city': '杭州'}。普通参数、默认参数、*args、**kwargs 可以放在同一个函数中,常用的排列顺序是:普通参数、带默认值的参数、*args、**kwargs。参数过多会让调用和维护变复杂;如果一组数据本来属于同一对象,直接传字典往往更合适。

五、return 与 print 的区别

print 只是把内容输出到终端,调用者拿不到结果继续计算。return 会把结果交回函数调用位置,可以保存到变量,也可以参与后续运算:


def add(a, b):
    return a + b

result = add(10, 20)
print(result)
total = add(10, 20) * 2
print(total)


如果只在函数里写 print(a + b),函数虽然会显示 30,但没有返回结果,result 会是 None。

return 还会立即结束函数,后面的代码不会执行。这种特性常用于提前处理不符合要求的数据:


def check_age(age):
    if age < 0:
      return '年龄不能小于 0'
    return '年龄有效'

print(check_age(-1))
print(check_age(20))



def divide(a, b):
    if b == 0:
      return None
    return a / b


如果函数没有写 return,或者只写了一个没有返回值的 return,Python 默认返回 None。只负责打印的函数,把调用结果保存到变量后通常得到 None。

一个函数可以返回多个值,写法上是 return total, difference,实际返回的是元组,调用时可以直接解包:


def calculate(a, b):
    total = a + b
    difference = a - b
    return total, difference

result = calculate(10, 3)
print(result)

total, difference = calculate(10, 3)
print(total)
print(difference)


左侧变量数量要与返回值数量对应,否则会出现 ValueError。返回内容较多且每项含义明确时,返回字典更清楚。

六、参数传递的是对象

调用函数时,形参会引用传入的对象。数字、字符串、元组是不可变对象,函数内部重新赋值不会改变外部变量:


def change_number(number):
    number = 100

value = 10
change_number(value)
print(value)


列表、字典、集合是可变对象。如果函数直接修改对象内容,外部也能看到变化:


def add_skill(skills):
    skills.append('Python')

user_skills = ['Java']
add_skill(user_skills)
print(user_skills)


如果不想修改原列表,可以在函数内部复制后再返回:


def add_skill(skills):
    new_skills = skills.copy()
    new_skills.append('Python')
    return new_skills


七、文档字符串与函数命名

函数体第一行可以用三引号写文档字符串,说明函数做什么、接收什么参数、返回什么内容。可通过 help() 查看:


def calculate_discount(price, rate=0.8):
    '''根据原价和折扣率计算折后价格。'''
    return price * rate

help(calculate_discount)


函数名通常使用小写字母和下划线,表达实际完成的操作,例如 calculate_total、get_user_info、save_file。f1、do_it 这类名字很难从调用位置判断用途。函数最好完成一件相对明确的事情;同时也没有必要把每一两行代码都拆成函数,关键看逻辑是否重复、命名后是否更清楚。

八、综合示例:计算订单价格

下面函数接收商品价格列表、折扣率和运费,返回订单各项金额。prices 是必填参数;discount 和 shipping_fee 有常用默认值。空列表会提前 return,不会继续执行后面的计算。返回字典而不是直接打印,可以让调用者决定如何展示或继续使用数据:


def calculate_order(prices, discount=1.0, shipping_fee=0):
    '''计算订单原价、折扣金额和最终价格。'''
    if not prices:
      return {
            'subtotal': 0,
            'discount_amount': 0,
            'shipping_fee': shipping_fee,
            'final_price': shipping_fee,
      }

    subtotal = sum(prices)
    discounted_price = subtotal * discount
    discount_amount = subtotal - discounted_price
    final_price = discounted_price + shipping_fee
    return {
      'subtotal': subtotal,
      'discount_amount': discount_amount,
      'shipping_fee': shipping_fee,
      'final_price': final_price,
    }

order = calculate_order(
    prices=,
    discount=0.9,
    shipping_fee=10,
)
print('商品原价:', order['subtotal'])
print('优惠金额:', order['discount_amount'])
print('运费:', order['shipping_fee'])
print('最终价格:', order['final_price'])


九、常见错误排查

1. 定义函数后忘记调用:只写 def say_hello(): 不会执行,需要再写 say_hello()。
2. 调用时忘记圆括号:result = say_hello 取得的是函数对象,不是调用结果,应写成 say_hello()。
3. 参数数量不一致:def add(a, b) 需要两个参数,add(10) 会触发 TypeError;多传参数同样会报错,除非使用 *args 或 **kwargs。
4. 把 print 当返回值:函数打印了结果但没有 return,变量保存到的会是 None。
5. 在 return 后继续写要执行的代码:return 会立即结束函数,确实需要执行的逻辑要放在 return 前面。
6. 使用列表作为默认参数:可变默认值会被多次调用共同使用,需要空列表时通常写成 None,再在函数内部创建。

练习部分可以按原文思路做:写函数计算矩形面积;接收整数并返回偶数 True、奇数 False;用 *args 接收不固定成绩并返回平均分,没有成绩时返回 None;用 **kwargs 接收姓名、年龄、城市并返回用户信息字典;在订单函数基础上增加满减规则,例如原价达到 300 再减 20,并把满减金额放入返回结果。

整体来看,函数把重复逻辑单独保存,参数负责把数据传入函数,return 负责把结果交回调用位置。理解默认参数、*args、**kwargs、可变对象和返回值的差别,能减少大量脚本调试问题。

热心网友4 发表于 2026-9-17 19:00:00

Re: Python函数参数与返回值:*args与**kwargs避坑

写得很实在,把函数从折扣计算切入很容易理解。几个坑确实值得反复提醒:定义函数不会立即执行,必须先定义后调用;位置参数和关键字参数混用时,位置参数要写在前面;默认参数别直接用列表、字典、集合,不然会共用同一个对象,改成 None 再在函数里初始化最稳;*args 收位置参数成元组,**kwargs 收关键字参数成字典,常用顺序是普通参数、默认参数、*args、**kwargs;print 只是输出给人看,return 才能把结果交回去继续计算。新手把这些分清后,写脚本会少很多莫名其妙的错误。感谢整理。

热心网友6 发表于 2026-9-17 19:30:00

Re: Python函数参数与返回值:*args与**kwargs避坑

楼主整理得挺清楚,折扣计算那个前后对比一下就能看出封装的好处,规则变更时只改函数内部,不用满脚本找同一个公式。默认参数用可变对象这个坑确实经典,items=[] 连续调用会共用同一个列表,改成 None 再在函数里新建列表就稳了。*args 和 **kwargs 那里也提醒得好,真正起作用的是星号,args 只是常用名称;普通参数、默认参数、*args、**kwargs 的顺序也值得记牢。位置参数和关键字参数混用时位置参数必须写在前面,这点我写快了也容易犯。整体看下来很适合边看边敲一遍验证。

热心网友6 发表于 2026-9-17 19:40:00

Re: Python函数参数与返回值:*args与**kwargs避坑

整理得挺清楚的,尤其是可变默认参数那个坑,items=[] 平时真的很容易忽略,改成 None 再在函数内部新建列表确实更稳。位置参数和关键字参数混用时位置参数必须写前面,这个点也很实用。*args 收成元组、**kwargs 收成字典,以及普通参数、默认参数、*args、**kwargs 的常见排列顺序,对写脚本时减少参数混乱很有帮助。最后 return 与 print 的区别好像还没写完,期待把“结果能保存下来继续参与计算”这部分补全。
页: [1]
查看完整版本: Python函数参数与返回值:*args与**kwargs避坑