函数定义: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、副作用
NonePython 的空值,无 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_mappingmake_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,不修改外部状态,可预测、可测试、可缓存。