跳转到正文

第三章:常用内置函数与函数调用

更新时间: 2026-09-08 19:20 · 中文版
暂无英文版
目录 (10)

章节导航上一章:第二章 函数

第二章中,我们学习了如何使用 def 定义自己的函数。通过把固定的计算或处理逻辑封装为函数,我们可以用不同的输入数据重复调用,避免手写重复代码。

在日常编程与数据分析中,许多基础数据处理任务是普遍存在的。例如:测量文本长度、进行数值类型转换、找出最大最小值、四舍五入,以及对字符串进行切分与拼接。如果每个基础功能都需要我们从零编写,开发效率会非常低。

Python 在安装完成后,就已经自带了一批高度优化、开箱即用的工具,主要分为两类:

  1. 内置函数(Built-in Functions):全局通用的独立函数,可以直接调用,格式为 函数名(数据),例如 len(s)abs(x)round(n) 等;
  2. 内置对象方法(Built-in Methods):绑定在特定数据类型(如字符串)上的专用操作,格式为 数据.方法名(...),例如 text.split(",")text.find("@") 等。

此外,现实业务中的数据处理任务往往包含多个环节,通常需要将多个函数组合使用:有时把一个函数的返回值直接作为另一个函数的输入(复合调用),有时在一个大函数内部调用几个小函数来分工协作(函数嵌套调用)。

本章核心主线介绍函数调用的求值顺序、复合调用与函数间相互调用的机制,以及最常用的数值内置函数和字符串处理方法。扩展阅读介绍对象的内存标识 id()is、控制台交互输入 input() 以及链式方法调用。


第一部分:核心主线

3.1 函数调用(Function Call)的深入理解

3.1.1 参数求值与调用顺序

第一章第二章中,我们多次执行过类似 amount = calculate_subtotal(28.5, 4) 的函数调用。

当括号内的实参不是简单的数字,而是包含运算的表达式时,计算机遵循一条清晰的执行规则:先计算括号内部的表达式,得到具体数值后,再将数值传递给函数

案例:计算商品加价后的总金额,观察函数调用前实参表达式的求值过程。

python
def calculate_subtotal(unit_price, quantity):
    return unit_price * quantity

# 基础价格 20 元,加价 5 元;购买 2 套,每套 2 件
total = calculate_subtotal(20 + 5, 2 * 2)
print(total)

输出:

text
100

计算机执行 calculate_subtotal(20 + 5, 2 * 2) 的具体步骤为:

  1. 从左向右依次计算括号内的表达式:先计算 20 + 5 得到 25,再计算 2 * 2 得到 4
  2. 将计算结果 254 作为实参传递给形参 unit_pricequantity
  3. 进入函数体,执行 25 * 4,返回计算结果 100
  4. 将返回值赋给外部变量 total

3.1.2 复合调用:一个函数的输出作为另一个函数的输入

在初等数学中,我们学过复合函数 (g \circ f)(x) = g(f(x)),即先计算内层函数 f(x),再把结果作为自变量传入外层函数 g

在 Python 中,只要内层函数有返回值,我们就可以把这个返回值直接作为外层函数的实参传入。这种写法称为复合调用(也称嵌套调用):

案例:计算整数订单编号在转换为文本后的字符位数。

python
order_id = 10086315

# 分步书写:先转换为文本,再测量长度
order_str = str(order_id)
digit_count = len(order_str)
print(digit_count)

# 复合调用:将内层 str() 的返回值直接传给外层 len()
print(len(str(order_id)))

输出:

text
8
8

在执行 len(str(order_id)) 时:

  1. 计算机首先执行内层调用 str(order_id),得到字符串 "10086315"
  2. 随后执行外层调用 len("10086315"),得到长度整数 8
  3. 最后由 print() 将数字 8 显示在终端上。

复合调用可以减少临时变量的使用,使代码更加紧凑。但如果单行内嵌套的函数过多(例如超过三层),会降低代码的可读性,此时建议拆分为两到三行书写。

3.1.3 函数嵌套调用:在函数内部调用另一个函数

在实际软件开发和业务系统设计中,大型任务往往被拆分成若干个小任务。在 Python 中,一个函数内部可以调用另一个函数。这种模块化设计让每个函数职责单一、便于复用和测试。

案例:设计电商订单结算流程。由辅助函数负责核算单个商品的小计金额,主函数负责整体订单的应付核算(调用辅助函数核算小计,再减去优惠券金额并加上运费)。

python
def calculate_subtotal(unit_price, quantity):
    """辅助函数:计算单品小计。"""
    return unit_price * quantity


def calculate_order_payment(unit_price, quantity, coupon_discount, shipping_fee):
    """主函数:核算订单实付金额。"""
    # 1. 调用辅助函数,获得商品小计
    subtotal = calculate_subtotal(unit_price, quantity)
    # 2. 结合优惠与运费核算最终金额
    final_amount = subtotal - coupon_discount + shipping_fee
    return final_amount


# 调用主函数核算订单:单价 80 元,购买 2 件,优惠 20 元,运费 6 元
bill = calculate_order_payment(80.0, 2, 20.0, 6.0)
print(bill)

输出:

text
146.0

这段代码的执行控制流如下:

  1. 程序从最外层的 bill = calculate_order_payment(80.0, 2, 20.0, 6.0) 开始;
  2. 计算机跳转进入 calculate_order_payment 函数体内部;
  3. 执行到第一行语句时,遇到了对 calculate_subtotal(unit_price, quantity) 的调用;
  4. 主函数的执行在此处暂停等待,计算机跳转进入辅助函数 calculate_subtotal
  5. 辅助函数执行乘法计算 80.0 * 2,并通过 return160.0 交还;
  6. 辅助函数执行结束退出;主函数恢复执行,将拿到的 160.0 赋给局部变量 subtotal
  7. 主函数继续执行后续的减法与加法计算:160.0 - 20.0 + 6.0 = 146.0
  8. 主函数执行 return,将 146.0 作为最终结果交还给外部变量 bill

通过这种方式,辅助函数 calculate_subtotal 既可以在主函数内部作为零件使用,也可以在其他业务场景中被单独调用。


试一试(必做):修改主函数调用的实参,将购买数量改为 3,先预测应付金额,再运行核对(结果应为 226.0)。


3.2 常用数值处理内置函数

3.2.1 统计长度:len()

内置函数 len()(length 的简写)用于返回一个对象包含的元素个数。在处理文本数据时,它返回字符串中的字符总数(包括中英文字符、数字、空格和标点符号):

案例:校验用户注册时输入的手机号是否符合 11 位标准,以及登录密码是否达到 8 位安全长度。

python
phone = "13812345678"
password = "safe_password_99"

phone_length = len(phone)
password_length = len(password)

print("手机号位数:", phone_length)
print("手机号是否合法:", phone_length == 11)
print("密码长度是否达标:", password_length >= 8)

输出:

text
手机号位数: 11
手机号是否合法: True
密码长度是否达标: True

len() 只能测量具有“长度”概念的对象(如字符串,以及后续章节将学习的列表、元组、字典)。如果对整数或浮点数直接调用 len(123),Python 会抛出 TypeError,因为单个数字没有长度属性;如需测量整数的位数,应先用 str() 转换为文本。

3.2.2 绝对值与数值舍入:abs() 与 round()

绝对值函数:abs()

abs()(absolute value 的简写)用于计算数值的绝对值:正数和零保持不变,负数转换为对应的正数。

案例:核算某分店当月实际营业额与预算指标之间的偏离绝对值。

python
target_budget = 50000.0   # 预算指标:50,000 元
actual_revenue = 46200.0  # 实际完成:46,200 元

# 计算偏离差额的绝对值
gap = abs(actual_revenue - target_budget)
print(f"营业额偏离绝对值: {gap} 元")

输出:

text
营业额偏离绝对值: 3800.0 元

无论实际业绩是超出预算还是未达预算,使用 abs() 都能准确衡量出偏差的客观幅度。

数值舍入函数:round()

round(number, ndigits) 用于将浮点数四舍五入到指定的小数位数:

  • 第一个参数 number 是待舍入的数值;
  • 第二个参数 ndigits 表示保留的小数位数(若省略,则默认舍入为最接近的整数)。

案例:计算商品折后均摊价格,并保留两位小数。

python
original_price = 199.0
discount_rate = 0.85
item_count = 3

total_discounted = original_price * discount_rate
price_per_item = total_discounted / item_count

print("原始均摊价格:", price_per_item)
print("保留两位小数:", round(price_per_item, 2))

输出:

text
原始均摊价格: 56.38333333333333
保留两位小数: 56.38

注意:Python 的“奇进偶舍”规则: Python 的 round() 在处理恰好处于两数正中间的数值(例如 0.5)时,采用 IEEE 754 标准推荐的**偶数舍入(Banker's Rounding,四舍六入五成双)**机制:舍入到最近的偶数。 例如:round(2.5) 得到 2,而 round(3.5) 得到 4。这种规则在统计学中可以平衡大量数据四舍五入累计产生的正向系统偏差。

3.2.3 极值比较与业务封顶保底:min() 与 max()

内置函数 min()max() 分别用于求出传入参数中的最小值与最大值。它们可以同时接收两个或多个数值:

python
print(min(10, 25, 8, 30))  # 输出: 8
print(max(10, 25, 8, 30))  # 输出: 30

在经济管理与日常业务中,min()max() 最典型的用途是实现**封顶(Capping)保底(Flooring)**逻辑,无需编写复杂的条件判断:

  1. 业务封顶(上限控制):使用 min(计算值, 上限阈值)
    • 例如:某项营销活动提供 15% 的优惠减免,但每笔订单优惠最高不超过 50 元;
  2. 业务保底(下限控制):使用 max(计算值, 下限阈值)
    • 例如:同城货运按里程计费,但不足 15 元时一律按最低起步价 15 元收取。

案例:核算一笔订单的优惠券抵扣金额(封顶 50 元)以及同城配送费(保底 15 元)。

python
# 1. 优惠券抵扣封顶计算
order_amount = 400.0
discount_calculated = order_amount * 0.15  # 理论优惠: 400 * 0.15 = 60 元
max_discount_allowed = 50.0                # 平台规定最高补贴: 50 元

# 使用 min() 确保实际抵扣不突破 50 元
actual_discount = min(discount_calculated, max_discount_allowed)
print("实际优惠金额:", actual_discount)

# 2. 配送费保底计算
distance_km = 3.5
shipping_calculated = distance_km * 3.0    # 理论运费: 3.5 * 3 = 10.5 元
min_shipping_fee = 15.0                    # 基础起步价: 15 元

# 使用 max() 确保实际运费不低于 15 元
actual_shipping = max(shipping_calculated, min_shipping_fee)
print("实际配送运费:", actual_shipping)

输出:

text
实际优惠金额: 50.0
实际配送运费: 15.0

3.3 常用文本处理方法

第一章中,我们接触了字符串的 strip()replace()。这些操作称为字符串方法(String Methods)。方法与普通函数的区别在于:方法必须通过 字符串对象.方法名(...) 的形式发起调用。

3.3.1 文本切分:split()

split() 方法用于将一个长字符串按照指定的分隔符拆开,返回包含各个切分片段的列表(list)

其语法格式为 字符串.split(分隔符)

案例:解析以英文逗号分隔的单行 CSV 交易数据,提取商品名称、单价与购买数量。

python
# 一行标准的逗号分隔交易记录
record = "2026-09-08,无线机械键盘,299.0,2"

fields = record.split(",")
print("拆分后的字段列表:", fields)

# 提取所需数据并做类型转换
product_name = fields[1]
unit_price = float(fields[2])
quantity = int(fields[3])

subtotal = unit_price * quantity
print(f"商品: {product_name},总价: {subtotal} 元")

输出:

text
拆分后的字段列表: ['2026-09-08', '无线机械键盘', '299.0', '2']
商品: 无线机械键盘,总价: 598.0 元

不带参数的默认切分: 如果调用 split() 时不提供任何参数,它会自动按照空白字符(连续的空格、制表符 \t、换行符 \n)进行切分,并且会自动丢弃两端及中间多余的连续空白:

案例:清理并切分排版不规则的搜索关键词文本。

python
raw_query = "  Python    商业数据分析     零基础指南  "
keywords = raw_query.split()
print(keywords)

输出:

text
['Python', '商业数据分析', '零基础指南']

3.3.2 文本合并:join()

join() 方法的作用与 split() 正好相反:它使用指定的连接符,把包含多个字符串片段的序列拼接为一个完整的长字符串。

其语法格式为 连接符.join(字符串序列)

特别注意语法位置: 初学者常误写为 列表.join(连接符)。正确的写法是将连接符写在前面,由连接符调用 join 方法,列表作为括号内的参数。

案例:将年份、月份、日期拼接为标准日期格式,并将商品分类层级拼接为面包屑导航路径。

python
# 1. 拼接日期
date_parts = ["2026", "09", "08"]
formatted_date = "-".join(date_parts)
print("格式化日期:", formatted_date)

# 2. 拼接分类层级
categories = ["数码办公", "电脑外设", "机械键盘"]
breadcrumb = " > ".join(categories)
print("商品分类导航:", breadcrumb)

输出:

text
格式化日期: 2026-09-08
商品分类导航: 数码办公 > 电脑外设 > 机械键盘

join() 要求待拼接序列中的所有元素都必须是字符串类型。如果序列中包含数字,必须先将数字转换为字符串,否则会引发 TypeError

3.3.3 查找与统计:find() 与 count()

查找子串位置:find()

find(sub) 用于检测子串 sub 是否存在于当前文本中:

  • 如果找到了子串,返回它在字符串中首次出现处的起始索引值(从 0 开始计数);
  • 如果没有找到,返回整数 -1

案例:检查用户输入的电子邮箱是否包含 @ 符号,并提取 @ 前面的用户名部分。

python
email = "li_minghui@university.edu.cn"

at_index = email.find("@")
print("@ 符号所在索引:", at_index)

# 使用切片提取 @ 之前的用户名
username = email[:at_index]
print("提取的用户名:", username)

# 查找不存在的子串
print(email.find("#"))

输出:

text
@ 符号所在索引: 10
提取的用户名: li_minghui
-1

find() 即使找不到内容也不会使程序报错,而是安全地返回 -1,因此非常适合用来判断某个标识是否存在。

统计出现频次:count()

count(sub) 用于统计指定子串在文本中出现的总次数:

案例:统计客户留言中包含疑问词“怎么”和“为什么”的次数,用于评估该留言是否属于咨询求助类信息。

python
feedback = "请问这个订单怎么申请发票?还有配送地址怎么修改?"

count_zenme = feedback.count("怎么")
count_weishenme = feedback.count("为什么")

print("出现'怎么'的次数:", count_zenme)
print("出现'为什么'的次数:", count_weishenme)

输出:

text
出现'怎么'的次数: 2
出现'为什么'的次数: 0

3.3.4 前缀与后缀判断:startswith() 与 endswith()

  • startswith(prefix):检查文本是否以指定前缀开头;
  • endswith(suffix):检查文本是否以指定后缀结尾。 两者的返回值均为布尔值(TrueFalse)。

案例:在电商客服系统中自动识别退款类工单编号(前缀为 "REF-"),并核验导入的文件是否为 CSV 报表(后缀为 ".csv")。

python
order_code = "REF-20260908-8801"
filename = "monthly_sales_report.csv"

is_refund = order_code.startswith("REF-")
is_csv_file = filename.endswith(".csv")

print("是否为退款订单:", is_refund)
print("是否为 CSV 报表:", is_csv_file)

输出:

text
是否为退款订单: True
是否为 CSV 报表: True

这两个方法区分大小写。例如,filename.endswith(".CSV") 将返回 False


主线练习(必做)

练习 1:会员卡号合法性校验函数

任务:编写函数 validate_member_card(card_id),核验传入的卡号字符串是否符合发卡规则。

业务规则

  • 会员卡号必须恰好由 10 个字符组成;
  • 会员卡号必须以 "VIP-" 开头;
  • 两个条件必须同时满足,符合返回 True,否则返回 False

编写完成后,核对以下测试调用的返回值:

测试调用 期望返回值 说明
validate_member_card("VIP-100869") True 长度为 10 且以 VIP- 开头
validate_member_card("VIP-88") False 长度不足 10 位
validate_member_card("REG-100869") False 非 VIP- 开头
validate_member_card("VIP-100869001") False 长度超出 10 位

练习 2:电商订单阶梯核算系统(函数间嵌套调用)

任务:编写两个函数,模拟电商订单系统的分工核算。

业务规则

  1. 辅助函数 calculate_subtotal(unit_price, quantity)
    • 计算并返回商品小计:unit_price * quantity
  2. 主结算函数 calculate_order_payment(unit_price, quantity, coupon_rate, max_coupon, shipping_fee)
    • 内部调用 calculate_subtotal 得到商品小计金额;
    • 计算理论优惠金额:subtotal * coupon_rate
    • 使用 min() 对优惠金额进行封顶:实际优惠 = min(理论优惠, max_coupon)
    • 实付金额 = 小计 - 实际优惠 + 配送费;
    • 使用 round() 将实付金额四舍五入保留两位小数后返回。

编写完成后,核对以下测试调用的返回值:

测试调用 期望返回值 计算过程说明
calculate_order_payment(100.0, 3, 0.2, 50.0, 10.0) 260.0 小计 300 元,理论优惠 60 元,封顶 50 元,实付 300 - 50 + 10 = 260.0 元
calculate_order_payment(100.0, 1, 0.2, 50.0, 10.0) 90.0 小计 100 元,理论优惠 20 元,未超 50 元封顶,实付 100 - 20 + 10 = 90.0 元

练习 3:单行交易记录清洗与重新排版

任务:编写函数 format_transaction_record(raw_record),解析排版杂乱的单行记录并重新格式化输出。

输入数据与规则

  • 传入字符串由竖线 | 分隔四个字段:" 2026-09-08 | 李明辉 | 299.0 | 2 "
  • 使用 split("|") 拆分各字段;
  • 去除每个字段两端的多余空格;
  • 计算总金额:float(单价) * int(数量)
  • 最终返回格式化通知:"客户 李明辉 于 2026-09-08 购买 2 件商品,应付总额: ¥598.00"(金额保留两位小数)。

编写完成后,核对测试结果:

  • 调用 format_transaction_record(" 2026-09-08 | 李明辉 | 299.0 | 2 ")
  • 期望返回值:"客户 李明辉 于 2026-09-08 购买 2 件商品,应付总额: ¥598.00"

第二部分:扩展阅读

以下内容供学有余力或需要进行控制台交互与系统调试的读者选读。完成核心主线练习不需要依赖本部分内容。

3.4 对象的内存标识与同一性判断:id() 与 is

3.4.1 查看对象的内存标识:id()

在 Python 中,每一个被创建的对象在计算机内存中都有一个唯一的数字编号,相当于该对象的“身份证号”。内置函数 id() 可以查看这个编号(在标准 CPython 解释器中,这个编号就是对象在内存中的虚拟地址)。

案例:查看不同变量所指向对象的内存标识。

python
name_a = "李明辉"
name_b = "李明辉"
name_c = "赵琳"

print("name_a 的 id:", id(name_a))
print("name_b 的 id:", id(name_b))
print("name_c 的 id:", id(name_c))

在同一行运行中,内容不同的对象通常具有不同的 id

3.4.2 is 与 == 的本质区别

在编写代码时,初学者很容易混淆 ==is 两个运算符:

  • == 运算符(值相等性,Equality):比较两个对象包含的数据内容是否相等;
  • is 运算符(同一性,Identity):比较两个变量是否指向内存中的同一个对象(即 id(a) == id(b))。

案例:观察内容相等但属于不同内存对象的情况。

python
# 创建两个包含相同数字的列表对象(列表将在第四章深入学习)
list_1 = [1, 2, 3]
list_2 = [1, 2, 3]

# 比较内容是否一致
print(list_1 == list_2)

# 比较是否为同一个内存对象
print(list_1 is list_2)
print("list_1 的 id:", id(list_1))
print("list_2 的 id:", id(list_2))

输出:

text
True
False
list_1 的 id: 2548231950400
list_2 的 id: 2548231948864

list_1list_2 各自独立开辟了内存空间,因此它们的 id 不同,is 比较返回 False;但它们存储的内容完全一致,因此 == 比较返回 True

工程准则

  1. 判断两个数值或字符串的内容是否相等,始终使用 ==
  2. is 运算符主要用于检查单例对象,最常见的是判断变量是否为空:if x is None:

3.5 交互式输入:input() 函数

3.5.1 获取键盘输入

在前面的案例中,所有数据都是在代码中预先写好的。内置函数 input(prompt) 可以让程序在终端暂停,等待用户从键盘输入数据,按回车键后程序继续执行:

  • prompt 是显示在终端给用户的提示文字;
  • 用户输入完成后,input() 会将用户输入的整行文本捕获,并作为**字符串(str)**返回。

案例:接收用户输入的姓名,并输出欢迎问候语。

python
user_name = input("请输入您的姓名: ")
print(f"您好,{user_name}!欢迎进入 Python 学习系统。")

若在终端输入 李明辉 并按回车,终端输出:

text
请输入您的姓名: 李明辉
您好,李明辉!欢迎进入 Python 学习系统。

3.5.2 input() 的类型转换配合

核心注意点input() 返回的结果永远是字符串类型(str,即便用户在键盘上输入了数字 100,Python 接收到的也是文本 "100"。 如果直接对 input() 的结果进行算术运算,会引发 TypeError。若要进行数学计算,必须使用 int()float() 进行显式转换。

案例:接收用户输入的商品单价与购买数量,核算应付金额。

python
# 接收输入并立即转换为数值
raw_price = input("请输入商品单价(元): ")
unit_price = float(raw_price)

raw_qty = input("请输入购买数量(件): ")
quantity = int(raw_qty)

total = unit_price * quantity
print(f"本次购物总金额为: {total:.2f} 元")

在熟练后,我们通常使用复合调用将输入与转换合并为一行:

python
unit_price = float(input("请输入商品单价: "))
quantity = int(input("请输入购买数量: "))

3.6 链式方法调用(Method Chaining)

因为字符串的许多方法都会返回一个新的字符串对象,所以我们可以在一次表达式中对返回结果紧接着调用下一个方法。这种将多个方法通过圆点连接的写法称为链式方法调用(Method Chaining)

执行顺序同样是从左至右依次进行。

案例:对用户输入的混乱搜索词进行连续清洗:先去除首尾空格,再转换为小写,最后将下划线统一替换为空格。

python
dirty_input = "   Python_DATA_ANALYSIS_GUIDE   "

# 链式调用:strip() -> lower() -> replace()
clean_text = dirty_input.strip().lower().replace("_", " ")
print(clean_text)

输出:

text
python data analysis guide

执行过程分析:

  1. dirty_input.strip():去除两端空格,得到 "Python_DATA_ANALYSIS_GUIDE"
  2. 紧接着调用 .lower():将大写英文字母全部转为小写,得到 "python_data_analysis_guide"
  3. 紧接着调用 .replace("_", " "):将下划线替换为空格,得到最终字符串 "python data analysis guide"

链式调用避免了定义多个中间过渡变量,使数据管道式的清洗处理非常清晰高效。


扩展练习(选做)

练习 1:命令行交互式汇率折算小程序

任务:编写一个名为 run_currency_converter() 的交互式函数。

业务规则

  • 提示用户输入美元金额:"请输入待折算的美元金额(USD): "
  • 使用 float() 转换输入的文本;
  • 固定汇率按 1 美元 = 7.15 人民币 折算;
  • 使用 round() 保留两位小数;
  • 输出换算结果,例如:"折合人民币(CNY): 715.00 元"

练习 2:探索不可变对象的内存标识变迁

任务:编写一段脚本,观察对变量重新赋值时 id() 的变化情况。

步骤

  1. 定义变量 score = 90,使用 print(id(score)) 打印其内存标识;
  2. 执行 score = score + 5
  3. 再次使用 print(id(score)) 打印其内存标识;
  4. 对比两次输出的数字是否相同,结合第一章字符串不可变性所学的“字符串与数字的不可变性(重新赋值实际上是变量指向了新的对象)”,说明为什么 id 会发生改变。

章节导航上一章:第二章 函数