1. 定义
函数是一段可以重复调用的代码。把一组有明确职责的操作放进函数后,其他地方只需要调用函数名,不必重复编写内部细节。
使用 def 定义函数,函数体通过缩进表示:
1def say_hello():2print("你好,Python")34say_hello()
def say_hello(): 只是创建函数,下面的 say_hello() 才是真正调用它。调用时必须保留括号,即使函数没有参数也一样。
函数可以接收参数,让同一段逻辑处理不同的数据:
1def greet(name):2print(f"你好,{name}")34greet("小明")5greet("小红")
函数名应该表达动作或结果,例如 calculate_total()、load_user()、format_message()。一个函数最好只负责一类事情,函数太长或同时做很多不相关的事情时,可以继续拆分。
可以使用文档字符串说明函数用途,方便自己和其他人阅读:
1def add(left, right):2"""返回两个数字的和。"""3return left + right45print(add.__doc__)
2. 参数
参数写在函数定义的括号中,调用时传入的值叫作实参:
1def introduce(name, age):2print(f"{name} 今年 {age} 岁")34introduce("小明", 18)
上面的 name 和 age 是参数,"小明" 和 18 是实参。默认情况下,Python 按照位置把实参依次传给参数。
可以为参数设置默认值。调用时不传这个参数,就使用默认值:
1def greet(name, greeting="你好"):2print(f"{greeting},{name}")34greet("小明")5greet("小红", "早上好")
有默认值的参数必须放在没有默认值的参数后面:
1def connect(host, port=8000):2print(f"连接 {host}:{port}")34connect("localhost")5connect("localhost", 9000)
不要把列表、字典等可变对象直接作为默认值。默认值只会在函数定义时创建一次,多次调用可能会共享同一个对象:
1def add_topic(topic, topics=None):2if topics is None:3topics = []45topics.append(topic)6return topics78print(add_topic("变量"))9print(add_topic("列表"))
使用 None 作为默认值,再在函数内部创建新列表,是更安全的写法。
3. 返回
函数可以使用 return 把结果交给调用方:
1def add(left, right):2return left + right34result = add(2, 3)5print(result) # 5
return 会立即结束当前函数。它后面的代码不会继续执行:
1def check_age(age):2if age < 18:3return "未成年"45return "已成年"67print(check_age(16))
如果函数没有写 return,或者只写了没有返回值的 return,函数结果就是 None:
1def print_message(message):2print(message)34result = print_message("处理完成")5print(result) # None
函数可以返回多个值。Python 会把它们组合成一个元组,调用方可以直接解包:
1def calculate(numbers):2total = sum(numbers)3count = len(numbers)4return total, total / count56total, average = calculate([80, 90, 70])7print(total) # 2408print(average) # 80.0
函数应该返回结果,而不是只依赖 print() 输出。print() 适合展示信息,return 才能让调用方继续使用这个值:
1def wrong_add(left, right):2print(left + right)34def correct_add(left, right):5return left + right67wrong_result = wrong_add(1, 2) # 打印 3,但结果是 None8correct_result = correct_add(1, 2) # 结果是 39print(correct_result)
4. 调用
调用函数时,可以按位置传参,也可以按参数名传参。关键字参数更容易表达每个值的含义:
1def create_user(name, age, city):2return {"name": name, "age": age, "city": city}34user = create_user(age=18, city="上海", name="小明")5print(user)
位置参数必须放在关键字参数前面,关键字参数之间的顺序可以调整:
1def connect(host, port, timeout=30):2print(host, port, timeout)34connect("localhost", port=8000, timeout=10)
在参数前加 *,可以把多个位置参数收集成一个元组:
1def total(*numbers):2return sum(numbers)34print(total(1, 2, 3))5print(total(10, 20, 30, 40))
在参数前加 **,可以把多个关键字参数收集成一个字典:
1def show_options(**options):2for key, value in options.items():3print(f"{key}:{value}")45show_options(model="deepseek-chat", temperature=0.2)
如果希望某些参数只能使用关键字传入,可以在它们前面使用单独的 *:
1def request(path, *, timeout=30):2print(path, timeout)34request("/users", timeout=10)5# request("/users", 10) # TypeError
*args、**kwargs 很灵活,但会降低函数签名的清晰度。只有确实需要接收不定数量参数时再使用它们,普通函数优先写出明确的参数名。
5. 作用域
函数内部创建的变量通常是局部变量,只能在函数内部使用:
1def calculate():2result = 2 + 33return result45print(calculate())6# print(result) # NameError
函数外部的变量属于外部作用域。函数可以读取外部变量,但如果要在函数内部修改外部变量,最好通过参数和返回值传递,而不是依赖全局变量:
1default_timeout = 3023def get_timeout():4return default_timeout56print(get_timeout())
参数和局部变量可以遮蔽同名的外部变量:
1name = "外部名字"23def show_name(name):4print(name)56show_name("函数参数")7print(name) # 外部名字
类型提示可以写在参数和返回值旁边,帮助编辑器提示和读者理解数据类型。它不会自动阻止错误类型传入,运行时校验仍然需要 Pydantic 等工具:
1def add(left: int, right: int) -> int:2return left + right34print(add(2, 3))
6. 组合
函数可以组合使用,一个函数调用另一个函数。把清理、校验和组装分别放进小函数,代码更容易测试和复用:
01def clean_question(question: str) -> str:02return question.strip()0304def is_valid_question(question: str) -> bool:05return len(question) > 00607def prepare_question(question: str) -> str | None:08cleaned = clean_question(question)0910if not is_valid_question(cleaned):11return None1213return cleaned1415print(prepare_question(" 介绍一下 Python "))16print(prepare_question(" "))
在 FastAPI 和 LangChain 项目中,可以把请求数据组装为独立函数:
01def build_chat_request(question: str, model: str = "deepseek-chat") -> dict:02return {03"model": model,04"messages": [05{"role": "user", "content": question},06],07}0809request = build_chat_request("请介绍一下 Python。")10print(request)
这样,接口入口只需要接收请求并调用 build_chat_request(),模型名称和消息结构的组装逻辑集中在一个地方。以后更换模型字段或增加参数时,只需要修改这个函数。
函数也可以接收另一个函数作为参数。Python 中函数也是对象,因此可以把行为传给通用函数:
1def apply_twice(value, action):2return action(action(value))34def add_exclamation(text):5return text + "!"67print(apply_twice("你好", add_exclamation)) # 你好!!
初学时不必急着大量使用高阶函数,但理解「函数可以被传递」后,会更容易读懂排序、异步回调和 LangChain 链式组合中的代码。