函数
Python 函数:写了这么多年,这几个参数用法你未必全用过
函数是 Python 里最基础的代码组织方式。把一段逻辑包起来,起个名字,需要的时候调用一下——就这么简单。
但 Python 函数的参数系统比大多数语言都灵活,位置参数、默认参数、关键字参数、*args、\kwargs**,一套下来能覆盖各种调用场景。
最基本的:定义和调用
用 def 定义一个函数,括号里放参数,冒号后面缩进写函数体。
def say_hello():
print("Hello, Python!")
say_hello() # 调用,记得加括号
带参数的:
def print_max(a, b):
if a > b:
print(f"{a} 更大")
else:
print(f"{b} 更大")
print_max(5, 7) # 7 更大
跟 Java 的方法比起来,Python 函数有几个明显不一样的地方:
- 不需要声明返回值类型
- 可以返回多个值(本质是返回元组)
- 参数传递非常灵活(下面重点讲)
参数系统:五种参数,各有各的用途
1. 位置参数:最基础的,按顺序传
最基本的参数类型,调用时必须按定义顺序传递。
def introduce(name, age, city):
print(f"{name}, {age}岁, 来自{city}")
introduce("Alice", 20, "Beijing") # 必须按顺序
没什么好说的,大多数函数都是这么写的。缺点就是调用的时候必须记住顺序,参数多了容易搞混。
2. 默认参数:给参数一个”备用值”
def greet(name, greeting="Hello"):
print(f"{greeting}, {name}!")
greet("Alice") # Hello, Alice!
greet("Bob", "Hi") # Hi, Bob!
这个特性解决的是:大多数情况下用默认值,偶尔需要定制。比如日志函数,默认级别是 INFO,但特殊场景可以传 ERROR。
注意一个坑:默认参数在函数定义时就被创建了,如果用可变对象(列表、字典)当默认值,会出问题。 后面会说怎么解决。
默认参数必须放在位置参数后面:
# 这样不行,语法错误
def func(a=1, b):
pass
3. 关键字参数:调用的时候指名道姓
def describe_person(name, age, city):
print(f"{name}, {age}岁, 来自{city}")
describe_person(age=20, name="Alice", city="Beijing") # 顺序随便
关键字参数的好处是调用的时候清晰,参数多了不容易搞混。而且可以只传一部分参数(配合默认参数用)。
位置参数必须在关键字参数之前:
describe_person("Bob", city="Shanghai", age=22)
# describe_person(city="Shanghai", "Bob", age=22) # 错误, 位置参数不能在关键字后面
4. *args:收拢多余的位置参数
场景:你写一个求和函数,但不确定调用的时候会传几个数。
def sum_numbers(*args):
# args 是一个元组,装着你传进来的所有位置参数
return sum(args)
sum_numbers(1, 2, 3) # 6
sum_numbers(10, 20, 30, 40) # 100
另一个实际场景:日志函数,第一个参数是日志级别,后面跟着任意数量的消息:
def log(level, *messages):
for msg in messages:
print(f"[{level}] {msg}")
log("INFO", "用户登录", "IP: 192.168.1.1")
# [INFO] 用户登录
# [INFO] IP: 192.168.1.1
*args 解决的是:函数参数数量不固定的情况。
5. **kwargs:收拢多余的关键字参数
场景:你要写一个通用的配置函数,调用的时候可能传各种不同的配置项。
def print_user_info(**kwargs):
# kwargs 是一个字典
for key, value in kwargs.items():
print(f"{key}: {value}")
print_user_info(name="Alice", age=20, city="Beijing", hobby="reading")
# name: Alice
# age: 20
# city: Beijing
# hobby: reading
**kwargs 解决的是:函数参数名称和数量都不固定的情况。
这五个参数可以组合使用,顺序是固定的:
def func(位置参数, 默认参数, *args, 关键字参数, **kwargs):
pass
一个完整的例子:
def example(a, b=10, *args, c=20, **kwargs):
print(f"a={a}, b={b}")
print(f"args: {args}")
print(f"c={c}")
print(f"kwargs: {kwargs}")
example(1, 2, 3, 4, 5, c=30, d=40, e=50)
# a=1, b=2
# args: (3, 4, 5)
# c=30
# kwargs: {'d': 40, 'e': 50}
这个顺序记不住也没关系,IDE 会提示,但理解”位置参数先、然后是默认、然后是收尾”的逻辑就够了。
函数的返回值:单个、多个、或者不返回
返回单个值:
def add(a, b):
return a + b
返回多个值:实际上返回的是元组,但 Python 会自动解包
def get_user():
return "Alice", 20, "Beijing" # 等价于 return ("Alice", 20, "Beijing")
name, age, city = get_user() # 解包赋值
这个特性写起来很爽,函数返回一堆相关的值,调用方一行就能拆开。
没有 return,默认返回 None:
def do_nothing():
pass
result = do_nothing()
print(result) # None
作用域:变量在哪儿能用
局部变量:函数里面定义的,外面用不了
def my_func():
local = "我是局部变量"
print(local)
my_func() # 正常
# print(local) # 报错,NameError
全局变量:外面定义的,函数里面可以读
global_var = "我是全局的"
def my_func():
print(global_var) # 可以访问
my_func()
想修改全局变量?用 global 声明
count = 0
def increment():
global count # 告诉 Python:我要改的是全局变量
count += 1
increment()
print(count) # 1
不加 global 的话,你在函数里给 count 赋值,Python 会认为你在创建一个同名的局部变量,全局的 count 不受影响。
模块:把函数放进别的文件里
写一个 my_module.py:
def greet(name):
return f"Hello, {name}!"
def add(a, b):
return a + b
在另一个文件里导入它:
# 方式1:导入整个模块
import my_module
my_module.greet("Alice")
# 方式2:起个别名
import my_module as mm
mm.greet("Alice")
# 方式3:只导入需要的
from my_module import greet, add
greet("Alice")
# 方式4:导入全部(不推荐,容易命名冲突)
from my_module import *
greet("Alice")