函数定义:def、参数、返回值
一句话概述
函数是 Python 中最核心的代码组织方式——用 def 关键字定义一个可重复使用的代码块,通过参数接收输入数据,通过返回值输出计算结果。你可以把函数想象成一台"加工机器":把原料(参数)从进料口放进去,机器内部按固定流程处理,最后从出料口得到成品(返回值)。写好函数是写好程序的基石——它能消除重复代码、降低复杂度、让程序像乐高积木一样可组合。在 AI 开发中,从损失函数到激活函数,从数据加载到模型训练,一切逻辑都围绕函数构建。
💡 核心要点:①def 函数名(参数):定义函数,函数体必须缩进(4 个空格)②参数是函数的输入接口,写在括号里,可以有零个或多个 ③return语句是函数的输出出口,可以返回任意类型的数据,无 return 时默认返回 None ④函数调用时按位置(或按关键字)传参,执行函数体中的代码 ⑤好的函数遵循"单一职责"原则——一个函数只做一件事
教学与演示
一、函数的定义与调用——制造一台可复用的机器
是什么:函数是用 def 关键字创建的一个命名代码块。基本语法是 def 函数名(参数列表): 加缩进的函数体。函数不会自动执行——只有被"调用"(call)时,它才会运行。
大白话 写函数就像给机器人编程一个固定动作。"def 打招呼(): 说你好"——你教会它之后,每次想让机器人打招呼,只需要喊一声"打招呼!",不用每次都重新教。参数就是给机器人的额外指令——"打招呼(张三)" 会喊 "你好张三!","打招呼(李四)" 会喊 "你好李四!"。一个定义,无限复用。
为什么:没有函数的程序就像没有章节的书——所有内容堆在一起,难以理解和修改。函数的三大核心价值:①消除重复——同样的逻辑只写一次,需要时调用;②降低复杂度——把大问题拆成小函数,逐个击破;③提升可测试性——每个函数可以独立验证是否正确。在 AI 中,损失函数、激活函数、数据预处理流水线等都是用函数构建的。
大白话 假设你要统计 10 组数据中每组的平均值。没有函数你得把"求和、除以个数、输出"这些步骤重复写 10 遍——60 行代码。有函数只需要写一遍def avg(data),然后avg(第一组)、avg(第二组)……——一共 10 行。哪天想要中位数而不是平均值,改一处就行,不用翻遍 10 处。
怎么做:
import numpy as np
# ====== 1. 最简单的函数:无参数、无返回值 ======
def say_hello(): # def 关键字 + 函数名 + 括号 + 冒号
"""打印一句问候语——这是文档字符串(docstring)""" # 三引号是函数的使用说明
print("你好,欢迎学习 Python 函数!") # 函数体:缩进 4 个空格
# 调用函数
say_hello() # 输出:你好,欢迎学习 Python 函数!
print("say_hello 的类型:", type(say_hello)) # <class 'function'> — 函数本身也是对象!
# ====== 2. 带参数的函数——传入数据 ======
def greet(name): # name 是形式参数(形参),占位用
"""向指定的人打招呼"""
print(f"你好,{name}!") # f-string 把变量嵌入字符串
# name 就像一个填空题的空格,调用时填入具体值
greet("张三") # '张三' 是实际参数(实参)
greet("李四") # 函数现在被调用了两次,每次不同输入
# ====== 3. 带返回值的函数——传出结果 ======
def add(a, b): # 两个参数:a 和 b
"""计算两个数的和并返回"""
result = a + b # 函数内部计算
return result # return 把结果送回调用方
sum_result = add(3, 5) # 把返回值赋给变量
print(f"\n3 + 5 = {sum_result}") # 8
# 返回值可以直接参与后续计算(链式调用)
print(f"(3 + 5) × 2 = {add(3, 5) * 2}") # 16 — 返回值就是值,可以继续用
# ====== 4. 没有 return 语句的函数返回 None ======
def no_return():
"""这个函数没有 return 语句"""
x = 100 # 只是赋值,不返回
result = no_return() # 没有 return 的函数也是有返回值的
print(f"\nno_return() 的返回值: {result}") # None — Python 的特殊空值
print(f"返回值的类型: {type(result)}") # <class 'NoneType'>
# ====== 5. return 不仅仅返回一个值——可以返回任意类型 ======
def make_dict(a, b):
"""返回一个字典"""
return {"第一个数": a, "第二个数": b, "和": a + b}
d = make_dict(10, 20)
print(f"\n返回的字典: {d}") # {'第一个数': 10, '第二个数': 20, '和': 30}
def get_statistics(data):
"""返回多个值——Python 会自动打包成元组"""
total = sum(data)
count = len(data)
avg = total / count
return total, count, avg # 返回三个值,实际返回的是一个元组
# 解包接收多个返回值
total, count, avg = get_statistics([85, 92, 78, 95, 88])
print(f"\n总数={total}, 个数={count}, 平均={avg:.1f}") # 总数=438, 个数=5, 平均=87.6
# ====== 6. AI 场景:定义损失函数 ======
# 模拟:均方误差损失函数(MSE Loss)
def mse_loss(predictions, targets):
"""计算均方误差:实际值减去预测值的平方的平均值"""
n = len(predictions) # 样本数量
total_error = 0.0
for pred, target in zip(predictions, targets):
error = (pred - target) ** 2 # 平方误差
total_error += error # 累加
return total_error / n # 平均
# 模拟模型预测 vs 真实值
predictions = [0.8, 0.3, 0.9, 0.2, 0.7] # 模型预测的 5 个概率
targets = [1.0, 0.0, 1.0, 0.0, 1.0] # 真实标签(0 或 1)
loss = mse_loss(predictions, targets)
print(f"\n模型 MSE Loss: {loss:.4f}") # 越小越好!什么用:函数在 AI 中无处不在。PyTorch 的 torch.nn.functional 里全是函数——torch.relu(x)、torch.softmax(x)、torch.cross_entropy(pred, target)。数据预处理用函数封装:normalize(X)、tokenize(text)、augment_image(img)。训练循环的核心就是反复调用 loss_fn(pred, label) 和 optimizer.step()。把 AI 的复杂逻辑拆成小函数,代码可读性立即提升一个档次。
二、参数的本质——函数的输入接口
是什么:参数(parameter)是函数定义时在括号里声明的变量名,用于接收外部传入的数据。调用时传入的具体值叫"实参"(argument)。Python 的传参机制是"对象引用传递"——形参和实参指向同一个对象,对于可变对象(列表、字典),函数内部的修改会影响外部的原数据。
大白话 参数就像快递单上的收件地址——函数是寄件人,调用方是收件人。你定义函数时写的参数名(比如name)只是预留的"地址栏",调用时填写的具体值(比如"张三")才是要寄的东西。Python 把传参简化到了极致——你不需要声明参数类型(不像 Java 要写String name),Python 自己会知道传进来的是什么类型。
为什么:参数是函数和外部世界通信的主要方式。没有参数,函数就像没有窗户的房间——它只能靠全局变量(坏习惯)或硬编码的值(不灵活)来工作。有了参数,同一个函数可以处理不同的数据——sum([1,2,3]) 和 sum([100,200,300]) 是一模一样的函数,只是参数不同。理解"可变对象传参会被修改"这个陷阱,能避免很多隐蔽的 bug。
大白话 如果你定义一个函数add_numbers(),里面写死了3 + 5——这个函数永远只能算 3+5,毫无用处。改成add(a, b)——这个函数成了一个"万能加法器",任何人把任意两个数传进来都能得到它们的和。参数就是函数的"通用性开关"。
怎么做:
import numpy as np
# ====== 1. 形参 vs 实参 ======
def multiply(x, y): # x 和 y 是形参(定义时的占位符)
"""乘法函数——x 和 y 只是名字,没实际值"""
return x * y
# 调用时,具体值 5 和 6 是实参
print("5 × 6 =", multiply(5, 6)) # 30 — 5 传给 x,6 传给 y
print("10 × 3 =", multiply(10, 3)) # 30 — 不同的实参,不同的结果
# ====== 2. Python 的传参是"对象引用传递" ======
def modify_list(lst):
"""在函数内部修改列表——注意!会影响到外部"""
lst.append("新元素") # append 修改了原列表
lst[0] = "被改了" # 索引赋值也修改原列表
def modify_number(n):
"""在函数内部修改数字——不会影响外部"""
n = n + 100 # 重新赋值 n 变量,不影响外部
print(f" 函数内部 n = {n}")
# 测试可变对象(列表)
my_list = ["原始", "数据"]
print("\n=== 可变对象(列表)===")
print("调用前:", my_list) # ['原始', '数据']
modify_list(my_list)
print("调用后:", my_list) # ['被改了', '数据', '新元素'] — 被修改了!
# 测试不可变对象(数字)
my_num = 10
print("\n=== 不可变对象(数字)===")
print("调用前:", my_num) # 10
modify_number(my_num)
print("调用后:", my_num) # 10 — 没变!因为数字不可变
# ⚠️ 关键认知:列表传给函数后,函数内部的 append() 会修改原列表!
# 如果想保护原数据,传一份拷贝:modify_list(my_list.copy())
# ====== 3. return vs 修改参数——两种输出方式 ======
def double_by_return(lst):
"""方式一:返回新列表(纯函数,推荐!)"""
return [x * 2 for x in lst] # 创建新列表返回
def double_in_place(lst):
"""方式二:原地修改列表(副作用,谨慎使用)"""
for i in range(len(lst)):
lst[i] = lst[i] * 2 # 直接修改原列表
original = [1, 2, 3]
doubled = double_by_return(original) # 返回新列表
print(f"\n返回新列表: {doubled}, 原列表: {original}") # 原列表不变
double_in_place(original) # 原地修改
print(f"原地修改后: {original}") # [2, 4, 6] — 原列表被改了
# ====== 4. AI 场景:数据标准化函数 ======
def normalize(data):
"""Min-Max 标准化:把数据缩放到 [0, 1] 区间"""
d_min = min(data) # 最小值
d_max = max(data) # 最大值
if d_max == d_min: # 防止除以 0
return [0.0] * len(data)
return [(x - d_min) / (d_max - d_min) for x in data]
# 原始数据(不同量级的特征)
raw = [10, 50, 100, 200, 500]
normalized = normalize(raw)
print(f"\n原始数据: {raw}")
print(f"标准化后: {[round(x, 3) for x in normalized]}") # [0.0, 0.082, 0.184, 0.388, 1.0]什么用:理解 Python 传参机制对 AI 开发至关重要。数据增强函数通常会修改传入的图片数据(numpy 数组是可变对象)——如果不想污染原始数据,就要显式 img.copy()。训练时 model.forward(input) 接收批处理数据作为参数,内部计算不修改输入(纯函数)。损失函数的参数是预测值和真实值,返回值是标量——这是函数式编程在 AI 中的完美体现。
三、return 语句——函数的输出出口
是什么:return 是函数的"出口",它做两件事:①立即终止函数的执行(return 之后的代码不会运行);②把 return 后面表达式的值发送给调用方。函数可以有多个 return 语句(比如条件判断中的不同出口),也可以一个都没有(此时自动返回 None)。
大白话 return 就像做饭的"出锅"动作。菜做完(函数执行完)不等于能上桌——你得把锅里的菜盛到盘子里(return),外面的人才能吃到。如果你做完菜直接关火走人(没有 return),外面的人看到的是一个空盘子(None)。return 还有一个特性——只要一出锅,后面还有没有切完的配菜(return 之后的代码)都不管了。
为什么:return 是函数价值的最终体现——函数计算了半天,如果不把结果送出去,这个计算就毫无意义。设计好的 return 能让函数用起来舒服——返回简单值用于数学计算,返回元组/字典用于多值输出,返回 None 表示操作类函数(如 print、sort)。在 AI 中,损失函数的 return 值直接决定了优化器往哪个方向走,绝对不能马虎。
大白话 想象一群人接力搬砖。每个人(函数)干完自己的活,要把砖递给下一个人(return)。如果你忘了传递(忘记 return),砖就掉地上了(变成 None),整个接力就断了。Python 不会提醒你"嘿,你忘记 return 了",它会默默地给你一个 None——然后后面的代码就悄悄出错了。
怎么做:
import numpy as np
# ====== 1. return 立即终止函数执行 ======
def early_exit(x):
"""演示:return 之后的代码不会执行"""
if x < 0:
return "负数,不处理了" # 如果 x<0,直接从这里返回
# x >= 0 才会走到这里
result = x ** 0.5 # 平方根
return f"平方根为 {result:.3f}" # 第二个返回出口
print(early_exit(-5)) # 负数,不处理了
print(early_exit(16)) # 平方根为 4.000
# ====== 2. 多条件多 return——清晰的分支出口 ======
def classify_number(n):
"""根据数字返回分类字符串(AI 中常见的多分支判断)"""
if not isinstance(n, (int, float)): # 输入验证:不是数字
return "错误:请输入数字!"
if n > 0:
return "正数"
elif n < 0:
return "负数"
else:
return "零"
print(f"\n5 是: {classify_number(5)}") # 正数
print(f"-3 是: {classify_number(-3)}") # 负数
print(f"0 是: {classify_number(0)}") # 零
print(f"'abc' 是: {classify_number('abc')}") # 错误:请输入数字!
# ====== 3. 返回多个值——元组打包/解包 ======
def min_max_avg(data):
"""返回最小值、最大值、平均值(常用于数据探索)"""
if not data: # 空列表处理
return None, None, None
data_min = min(data)
data_max = max(data)
data_avg = sum(data) / len(data)
return data_min, data_max, data_avg # 逗号分隔 = 返回元组
scores = [78, 85, 92, 63, 71, 88]
low, high, mean = min_max_avg(scores) # 解包接收三个值
print(f"\n最低分={low}, 最高分={high}, 平均分={mean:.1f}")
# ====== 4. 返回函数——高阶函数(Python 特色) ======
def make_multiplier(factor):
"""返回一个「乘以 factor」的函数——闭包"""
def multiplier(x):
return x * factor
return multiplier # 注意:返回函数对象,不是调用结果!
double = make_multiplier(2) # double 现在是一个函数:乘以 2
triple = make_multiplier(3) # triple 是另一个函数:乘以 3
print(f"\ndouble(5) = {double(5)}") # 10
print(f"triple(5) = {triple(5)}") # 15
# ====== 5. AI 场景:自定义激活函数 ======
def leaky_relu(x, alpha=0.01):
"""Leaky ReLU 激活函数——改进版的 ReLU"""
# Leaky ReLU: 正数部分原样输出,负数部分乘以一个小斜率
if x >= 0:
return x # 正数不变
else:
return alpha * x # 负数乘以 alpha(如 0.01),保留微弱信号
# 测试激活函数
inputs = [-2.0, -0.5, 0.0, 1.0, 3.0]
outputs = [round(leaky_relu(x), 4) for x in inputs]
print(f"\nLeaky ReLU 输入: {inputs}")
print(f"Leaky ReLU 输出: {outputs}") # [-0.02, -0.005, 0.0, 1.0, 3.0]
# 模拟:计算一个 mini-batch 上的损失
def batch_loss(preds, labels):
"""对批处理数据计算平均交叉熵损失(简化版)"""
epsilon = 1e-8 # 防止 log(0) 导致数学错误
total = 0.0
for p, l in zip(preds, labels):
# 截断防止数值溢出:pred 不能太接近 0 或 1
p_clipped = np.clip(p, epsilon, 1 - epsilon)
# 二元交叉熵:- (y*log(p) + (1-y)*log(1-p))
loss = -(l * np.log(p_clipped) + (1 - l) * np.log(1 - p_clipped))
total += loss
return total / len(preds) # 返回平均损失
pred_batch = [0.9, 0.1, 0.8, 0.3] # 模型预测概率
label_batch = [1.0, 0.0, 1.0, 0.0] # 真实标签
loss = batch_loss(pred_batch, label_batch)
print(f"\nBatch 交叉熵损失: {loss:.4f}")什么用:return 在 AI 代码中承载关键职责。激活函数(ReLU、sigmoid、tanh)return 输入的变换结果;损失函数 return 一个标量用于反向传播;数据加载器 __next__ return 一个 batch;模型 forward return 预测结果。PyTorch 的 model.forward(x) 本质上就是带有大量参数的复杂函数,最终 return 输出张量——说到底,整个神经网络推理就是一次巨大的函数调用!
四、好函数的设计原则——新手也能写出专业代码
是什么:写好函数不在于写得"多",而在于写得"好"。好的函数遵循三大原则:①单一职责(一个函数只做一件事);②命名清晰(函数名要能描述它做什么,动词开头);③输入输出明确(参数和返回值一看就懂)。坏的函数是"上帝函数"——一个函数几百行,什么都干,谁也看不懂。
大白话 好函数像瑞士军刀上的一个工具——剪刀就是剪东西,开瓶器就是开瓶。坏函数像一团揉在一起的橡皮泥——你说它是剪刀也行,开瓶器也行,但事实上什么都做不好。判断函数好坏的标准很简单:你能不能一句话说清楚这个函数干什么?能 → 好函数,不能 → 该拆分了。
为什么:在真实的 AI 项目中,代码量动辄成千上万行。如果每个人都把逻辑写得乱七八糟,项目很快就变得无法维护——这就是"技术债"。遵循好的函数设计原则,代码就像一本有目录的书:想看数据加载找 load_data(),想看模型定义找 build_model(),想看训练逻辑找 train_epoch()。团队协作、代码审查、Bug 修复都变得容易。
怎么做:
import numpy as np
# ====== 反例:糟糕的函数——把所有逻辑塞一起 ======
def bad_function(data):
"""一个做了太多事情的反面教材——不要这样写!"""
# 步骤1:清洗数据
cleaned = [x for x in data if x is not None]
# 步骤2:计算统计量
total = sum(cleaned)
avg = total / len(cleaned)
# 步骤3:标准化
std = (sum((x - avg) ** 2 for x in cleaned) / len(cleaned)) ** 0.5
normalized = [(x - avg) / std if std > 0 else 0 for x in cleaned]
# 步骤4:找异常值
outliers = [x for x in normalized if abs(x) > 2]
# 步骤5:打印报告
print(f"总数: {len(cleaned)}, 平均: {avg:.2f}")
print(f"标准差: {std:.2f}, 异常值: {outliers}")
return normalized
# ====== 正例:把逻辑拆成多个小函数——每个只做一件事 ======
def clean_data(data):
"""[单一职责1] 清洗数据:移除 None 和无效值"""
return [x for x in data if x is not None]
def compute_statistics(data):
"""[单一职责2] 计算均值和标准差"""
avg = sum(data) / len(data)
variance = sum((x - avg) ** 2 for x in data) / len(data)
std = variance ** 0.5
return avg, std
def normalize_data(data, avg, std):
"""[单一职责3] Z-score 标准化"""
if std == 0:
return [0.0] * len(data)
return [(x - avg) / std for x in data]
def find_outliers(data, threshold=2.0):
"""[单一职责4] 找出异常值"""
return [x for x in data if abs(x) > threshold]
# ====== 组合使用——像乐高积木一样拼接 ======
raw_data = [10, None, 15, 25, None, 30, 12, 100, 18, None, 22]
# 流水线处理:每一步调用一个专用函数
cleaned = clean_data(raw_data)
avg, std = compute_statistics(cleaned)
normalized = normalize_data(cleaned, avg, std)
outliers = find_outliers(normalized)
print("=== 数据处理流水线 ===")
print(f"原始: {raw_data}")
print(f"清洗后 ({len(cleaned)}项): {cleaned}")
print(f"平均值: {avg:.2f}, 标准差: {std:.2f}")
print(f"标准化后: {[round(x, 2) for x in normalized]}")
print(f"异常值 (|z|>2): {[round(x, 2) for x in outliers]}")
print("✅ 每个小函数都可以独立测试、独立复用!")
# ====== AI 实践中命名清晰的重要性 ======
# ❌ 模糊的函数名
def proc(x):
return x * 2 # 到底 "proc" 是什么意思?
# ✅ 清晰的函数名(动词开头 + 描述作用)
def double_values(x):
return x * 2
# ❌ 不清晰
def f(n, m):
return n / (1 + np.exp(-m)) # 什么鬼?
# ✅ 清晰——一看就知道是 sigmoid
def sigmoid(x):
return 1 / (1 + np.exp(-x))
# 测试 sigmoid
x_vals = [-2, -1, 0, 1, 2]
sigmoid_vals = [round(sigmoid(x), 3) for x in x_vals]
print(f"\nSigmoid: {sigmoid_vals}") # [0.119, 0.269, 0.5, 0.731, 0.881]什么用:良好函数设计是进入 AI 公司或开源项目的"入场券"。阅读 PyTorch 或 TensorFlow 源码会发现——每个函数职责清晰、命名规范。训练脚本的标准写法就是组合小函数:data = load_data(path) → model = build_model(config) → train(model, data, epochs) → evaluate(model, test_data) → save(model, path)。养成良好的函数习惯,从今天开始。
概念关系图谱
| 概念 | 核心含义 | 与AI的关系 | 关联概念 |
|---|---|---|---|
| def | 定义函数的关键字 | 定义损失函数、激活函数等所有逻辑 | class、lambda |
| 参数(parameter) | 函数接收输入的变量 | 模型 forward 的输入张量、训练超参数 | 实参、形参、传参 |
| return | 函数的输出出口 | 返回损失值、预测结果、激活值 | yield、副作用 |
| None | Python 的空值,无 return 时默认返回 | 判断函数是否有有效输出 | 可选类型、null |
| 调用(call) | 执行函数体代码 | model(x) 前向传播、loss_fn(pred,label) | 执行、触发 |
| 形参/实参 | 定义时的占位符 vs 调用时的实际值 | 定义接口 vs 传入具体数据 | 位置参数、关键字参数 |
| 对象引用 | Python 传参方式:传递对象引用 | 理解数据增强是否修改原图 | 可变/不可变、拷贝 |
| 单一职责 | 一个函数只做一件事 | 数据处理流水线、模块化设计 | 可维护性、可测试性 |
重点答疑
Q1: 函数定义中,def 后面的冒号能省略吗?缩进为什么必须是 4 个空格?
冒号绝对不能省略! 冒号是 Python 语法的强制要求——它告诉解释器"接下来是一个代码块"。如果忘了写冒号,会得到 SyntaxError: invalid syntax。缩进方面,Python 社区约定使用4 个空格(PEP 8 规范)。虽然技术上你可以用 1 个空格或 1 个 tab(不能混用),但 4 个空格是"世界语"——你用 4 空格,全世界的 Python 程序员都能读懂你的代码。IDE(如 VSCode、PyCharm)会自动把 tab 键转换成 4 个空格。
Q2: 一个函数可以没有参数吗?可以没有返回值吗?
都可以。 无参数的函数完全合法——比如 def say_hello(): print("你好"),调用时括号里什么都不写:say_hello()。无 return 语句的函数也完全合法——Python 会在函数末尾隐式地加上 return None。无参无返回值的函数通常用于"产生副作用"——比如打印日志、保存文件、发送 HTTP 请求等。但在 AI 中,绝大多数函数至少会有参数或返回值——因为数据处理和模型计算都需要输入和输出。
Q3: return 能返回多个值吗?Python 底层是怎么实现的?
return 在语法上只能 return 一个值,但是你用逗号分隔多个值时——return a, b, c——Python 会自动把它们打包成一个元组(tuple)。所以 return 1, 2, 3 等价于 return (1, 2, 3),返回的是一个包含三个元素的元组。调用方可以用解包语法接收:x, y, z = my_func()。这个特性让 Python 函数看起来能"返回多个值",省去了在其他语言中需要定义结构体或类的麻烦。
Q4: 函数内部修改变量会影响外部吗?什么是"可变/不可变"的区别?
取决于变量类型。 Python 的数据类型分为两类:不可变类型(int、float、str、tuple、bool、None)——函数内部"修改"它们实际上是创建了新对象并让局部变量指向新对象,不影响外部;可变类型(list、dict、set、自定义对象)——函数内部调用它们的修改方法(如 .append()、.update())会改变对象本身,外部也会看到变化。关键规则:赋值 = 永远不会影响外部(只改变变量指向),方法调用(如 .append())会影响外部。在 AI 中,这解释了为什么 normalize(img) 可能会修改原始图像——numpy 数组是可变对象。
Q5: 函数名可以用中文吗?函数名过长怎么办?
技术上可以用中文——def 打招呼(): print("你好") 完美运行。但强烈不推荐!原因:①国际协作——外国人看不懂你的代码;②某些环境和工具对 Unicode 标识符支持不完善;③输入效率——切换中英文输入法很烦。命名过长时遵循"缩写够明确即可"原则:calculate_mean_squared_error 可以简写为 mse_loss(业内通用),get_index_to_word_mapping → make_idx2word。好的函数名长度通常在 10-20 个字符,动词开头,小写+下划线(snake_case)。
章节单词汇总
| 英文 | 音标 | 术语/释义 |
|---|---|---|
| function | /ˈfʌŋkʃən/ | 函数;可重复调用的代码块 |
| define (def) | /dɪˈfaɪn/ | 定义;用 def 关键字创建函数 |
| parameter | /pəˈræmɪtər/ | 参数;函数定义时的输入变量名 |
| argument | /ˈɑːrɡjumənt/ | 实参;调用函数时传入的具体值 |
| return | /rɪˈtɜːrn/ | 返回;输出计算结果给调用方 |
| call | /kɔːl/ | 调用;执行函数体代码 |
| None | /nʌn/ | 空值;没有 return 时的默认返回值 |
| indentation | /ˌɪndenˈteɪʃən/ | 缩进;用空格表示代码块的层级 |
面试练习
Q1 [单选] 以下哪个是定义函数的正确语法?
- A.
def my_func(x): - B.
def my_func(x) - C.
function my_func(x): - D.
define my_func(x):
解答:A 正确。Python 用def关键字,必须有冒号。B 缺少冒号,C 和 D 用了其他语言的关键字(JavaScript 的function、伪代码的define),Python 只用def。
Q2 [单选] 以下代码输出什么?def f(): pass; print(f())
- A. 0
- B. pass
- C. None
- D. 报错
解答:C 正确。pass是一个空操作占位符,表示"什么都不做"。没有 return 的函数自动返回 None。所以print(f())打印 None。
Q3 [单选] 关于函数参数,以下说法正确的是?
- A. 函数必须有参数
- B. 参数名必须和调用时传入的变量名一致
- C. 函数可以没有参数,调用时括号不能省略
- D. 参数只能是数字类型
解答:C 正确。函数可以无参数(def f():),但调用时f()的括号不能省——括号是"执行函数"的信号。A 错误(可见def f():)。B 错误(参数名是函数内部的变量名,外面怎么传不重要)。D 错误(参数可以是任何类型)。
Q4 [多选] 以下关于 return 的说法正确的是?
- A. return 可以出现在函数的任何位置
- B. 一个函数可以有多个 return 语句
- C. return 之后的代码不会被执行
- D. 没有 return 的函数调用时会报错
解答:A、B、C 都正确。return 可以在任何位置(条件分支、循环中等),可以有多个(不同分支不同出口),return 后函数立即终止。D 错误——没有 return 的函数返回 None,不报错。
Q5 [单选] def add(a, b): return a + b 调用 add(3) 会怎样?
- A. 返回 3
- B. b 默认为 0,返回 3
- C. 报 TypeError
- D. 返回 None
解答:C 正确。函数定义有两个参数,但调用时只传了一个。Python 会报 TypeError: add() missing 1 required positional argument: 'b'。必须传两个参数(或 b 设置默认值)。
Q6 [多选] 以下函数返回多个值的说法正确的是?
- A.
return 1, 2, 3实际返回一个元组(1, 2, 3) - B. 可以用
a, b, c = func()解包接收 - C. Python 函数只能返回一个值,多个值必须用列表
- D. 返回的元组长度必须和解包变量数量一致
解答:A、B、D 正确。C 错误——虽然底层返回元组,但语法上看起来返回多个值,不需要手动装列表。D 正确——解包时变量数量不匹配会报 ValueError。
Q7 [单选] 函数内部修改列表参数,会影响外部吗?
- A. 会,因为列表是可变对象
- B. 不会,函数内部变量和外部无关
- C. 不一定,取决于函数有没有 return
- D. 不会,除非用 global 关键字
解答:A 正确。列表是可变对象,函数内部对列表调用.append()、.remove()等方法会修改原始对象,外部也能看到变化。这和 return 无关——即使没有 return,外部变量也已经被改了。如果想保护原数据,先传.copy()进去。
Q8 [多选] 以下哪些函数定义是合法的?
- A.
def f(): pass - B.
def f(a, b): return a * b - C.
def f(*args): pass - D.
def f() return 42
解答:A、B、C 都合法。A 是最简函数(pass占位),B 是标准带参返值函数,C 使用可变参数*args。D 语法错误——缺少:冒号,正确写法是def f(): return 42(冒号不能少)。
Q9 [单选] def f(): x = 100; return; print(x) 执行 f() 输出什么?
- A. 100
- B. 报错
- C. 什么都不输出
- D. None
解答:C 正确。return语句会立即终止函数,return后面的print(x)永远不会执行。所以f()什么都不打印。如果print(f())则会输出 None,但题目只问执行f()的输出。
Q10 [多选] 关于好函数的设计原则,以下哪些是正确的?
- A. 一个函数只做一件事(单一职责)
- B. 函数名应该用动词开头,描述函数做什么
- C. 函数越长越好,方便在一个地方看完所有逻辑
- D. 函数应该尽量"纯"——同样输入总产生同样输出
解答:A、B、D 正确。C 是典型的反面观点——长函数(超过 30-50 行)应该拆分。D 是函数式编程的"纯函数"概念:add(2,3) 永远返回 5,不修改外部状态,可预测、可测试、可缓存。