Python 基础语法(三):函数、参数、作用域与递归

一、为什么要学习函数

在 Python 入门阶段,我们通常先学习变量、输入输出、条件判断、循环等基础语法。这些内容能让程序"跑起来",但如果代码稍微变复杂,就会遇到三个问题:

  • 重复代码太多:同样的计算逻辑反复复制粘贴,后期修改非常麻烦。
  • 数据不好管理:如果有几十个、几百个数据,不可能每个数据都单独创建一个变量。
  • 程序关闭后数据丢失:变量保存在内存中,程序结束后就不存在了,需要文件来保存数据。

因此,Python 基础语法的下一步重点就是:

  • 函数 封装重复逻辑;
  • 列表、元组、字典 批量组织数据;
  • 文件操作 让数据保存到硬盘中。

这三部分内容是后续学习爬虫、数据分析、自动化办公、Web 开发、人工智能等方向的基础。


二、函数:把重复代码封装起来

1. 函数是什么

编程中的函数,可以理解为"一段可以被重复使用的代码"。

当我们发现某几段代码结构高度相似,只是处理的数据不同,就可以把它们提取成函数。

例如:分别求 1~100300~4001~1000 的整数和,如果不使用函数,代码会重复很多次:

python 复制代码
total = 0
for i in range(1, 101):
    total += i
print(total)

total = 0
for i in range(300, 401):
    total += i
print(total)

total = 0
for i in range(1, 1001):
    total += i
print(total)

这些代码本质上都是"从某个开始值累加到某个结束值"。此时就可以定义一个函数:

python 复制代码
def calc_sum(begin, end):
    total = 0
    for i in range(begin, end + 1):
        total += i
    return total

print(calc_sum(1, 100))
print(calc_sum(300, 400))
print(calc_sum(1, 1000))

2. 函数的基本语法

python 复制代码
def 函数名(形参列表):
    函数体
    return 返回值

说明:

  • def 是定义函数的关键字。
  • 函数名 是自己给函数起的名字,建议使用有意义的英文或拼音。
  • 形参列表 用来接收调用者传进来的数据。
  • 函数体 是函数真正要执行的代码。
  • return 用于把结果返回给调用者。

示例:

python 复制代码
def say_hello():
    print("hello Python")

say_hello()

3. 函数必须先定义,再调用

Python 代码是从上到下执行的。函数虽然可以先写在那里,但只有调用时才会执行函数体。

错误示例:

python 复制代码
test()

def test():
    print("hello")

这段代码会报错,因为执行到 test() 时,解释器还不知道 test 是什么。

正确写法:

python 复制代码
def test():
    print("hello")

test()

注意事项

  1. 函数定义不会自动执行,只有调用函数才会执行函数体。
  2. 函数名不要和 Python 内置函数重名 ,例如不要把变量或函数命名为 sumlistdict
  3. 函数名要体现功能,例如 calc_sumtest1 更容易理解。

三、函数参数:让一份代码处理多种数据

1. 形参与实参

函数定义时写在括号里的变量叫 形参 ;函数调用时传入的具体值叫 实参

python 复制代码
def greet(name):
    print(f"你好,{name}")

greet("张三")
greet("李四")

在这个例子中:

  • name 是形参;
  • "张三""李四" 是实参。

调用 greet("张三") 时,可以理解为函数内部临时执行了:

python 复制代码
name = "张三"

2. 多个参数

函数可以有多个参数:

python 复制代码
def introduce(name, age, city):
    print(f"我叫{name},今年{age}岁,来自{city}")

introduce("小明", 18, "郑州")

调用函数时,实参个数通常要和形参个数一致。

错误示例:

python 复制代码
def add(x, y):
    return x + y

print(add(10))       # 少传了一个参数
print(add(10, 20, 30))  # 多传了一个参数

3. Python 参数不需要指定类型

Python 是动态类型语言,定义函数参数时不需要写参数类型。

python 复制代码
def show(value):
    print(value)

show(100)
show("hello")
show(True)

同一个函数可以接收不同类型的数据。但这也意味着我们要自己注意参数类型是否适合当前操作。

例如:

python 复制代码
def double(value):
    return value * 2

print(double(10))       # 20
print(double("hi"))     # hihi

同样是 * 2,数字和字符串的效果完全不同。

注意事项

  1. 形参只是函数内部使用的变量名,实参才是调用时传入的真实数据。
  2. 传参个数要匹配,否则会报错。
  3. Python 不强制参数类型,但逻辑上要保证传入的数据合理。

四、返回值:让函数把结果交还给调用者

1. 为什么需要返回值

函数的参数可以看作"输入",返回值可以看作"输出"。

不推荐把所有结果都直接在函数内部打印:

python 复制代码
def calc_sum(begin, end):
    total = 0
    for i in range(begin, end + 1):
        total += i
    print(total)

calc_sum(1, 100)

这种写法的问题是:函数既负责计算,又负责输出。

如果以后想把结果保存到文件、显示到网页、继续参与计算,就不够灵活。

更推荐使用 return

python 复制代码
def calc_sum(begin, end):
    total = 0
    for i in range(begin, end + 1):
        total += i
    return total

result = calc_sum(1, 100)
print(result)

这种写法把"计算逻辑"和"用户交互"分开,函数本身只负责返回结果。

2. return 会结束函数

函数执行到 return 后,会立即结束。

python 复制代码
def is_odd(num):
    if num % 2 == 0:
        return False
    return True

print(is_odd(10))
print(is_odd(9))

num 是偶数时,执行 return False 后函数直接结束,不会继续执行下面的 return True

3. 一个函数可以返回多个值

Python 支持一次返回多个值:

python 复制代码
def get_point():
    x = 10
    y = 20
    return x, y

a, b = get_point()
print(a)
print(b)

其实多个返回值本质上会被打包成一个元组。

python 复制代码
def get_point():
    return 10, 20

result = get_point()
print(result)
print(type(result))

输出:

python 复制代码
(10, 20)
<class 'tuple'>

4. 使用 _ 忽略不关心的返回值

python 复制代码
def get_point():
    return 10, 20

_, y = get_point()
print(y)

_ 是一种习惯写法,表示这个值我不关心。

注意事项

  1. return 后面的代码通常不会再执行。
  2. 建议函数优先返回结果,而不是直接 print
  3. 多返回值适合返回一组相关数据,例如坐标、统计结果等。

五、变量作用域:局部变量与全局变量

1. 什么是作用域

作用域指的是变量生效的范围。

在函数内部定义的变量叫 局部变量,只能在函数内部使用。

python 复制代码
def test():
    x = 10
    print(f"函数内部 x = {x}")

test()
print(x)  # 这里会报错

原因是 x 定义在函数内部,函数外部无法访问。

2. 局部变量和全局变量可以同名

python 复制代码
x = 20

def test():
    x = 10
    print(f"函数内部 x = {x}")

test()
print(f"函数外部 x = {x}")

输出:

python 复制代码
函数内部 x = 10
函数外部 x = 20

虽然都叫 x,但它们不是同一个变量。

3. 函数内部可以读取全局变量

如果函数内部没有局部变量,就会去全局作用域查找:

python 复制代码
x = 20

def test():
    print(x)

test()

4. 修改全局变量需要 global

如果想在函数内部修改全局变量,需要使用 global

python 复制代码
x = 20

def change_x():
    global x
    x = 100

change_x()
print(x)

如果不写 global,函数内部的 x = 100 会被认为是在创建局部变量。

5. if、for、while 不会形成新的作用域

Python 中,ifforwhile 代码块不会创建新的函数作用域。

python 复制代码
for i in range(3):
    value = i

print(i)
print(value)

这和一些其他语言不同,初学者要特别注意。

注意事项

  1. 函数内部定义的变量默认是局部变量。
  2. 尽量少使用 global,过多全局变量会让代码难维护。
  3. 如果函数需要改变某个值,更推荐通过参数和返回值完成。

六、函数调用过程、链式调用与嵌套调用

1. 函数调用过程

函数只有被调用时才会执行:

python 复制代码
def test():
    print("执行函数内部代码 1")
    print("执行函数内部代码 2")

print("开始")
test()
print("结束")

执行顺序是:

python 复制代码
开始
执行函数内部代码 1
执行函数内部代码 2
结束

2. 链式调用

把一个函数的返回值直接作为另一个函数的参数,称为链式调用。

普通写法:

python 复制代码
def is_odd(num):
    return num % 2 != 0

result = is_odd(9)
print(result)

链式写法:

python 复制代码
print(is_odd(9))

再看一个例子:

python 复制代码
def add(x, y):
    return x + y

def square(num):
    return num * num

print(square(add(2, 3)))

这里先执行 add(2, 3) 得到 5,再执行 square(5) 得到 25

3. 嵌套调用

函数内部也可以调用其他函数:

python 复制代码
def a():
    print("函数 a")

def b():
    print("函数 b 开始")
    a()
    print("函数 b 结束")

b()

输出:

python 复制代码
函数 b 开始
函数 a
函数 b 结束

4. 函数调用栈

当函数调用另一个函数时,Python 会记录当前函数的执行现场。

这个记录函数调用关系的数据结构叫 函数调用栈

每一次函数调用都会产生一个 栈帧。每个栈帧中保存当前函数的局部变量、执行位置等信息。

理解调用栈有助于分析:

  • 函数执行顺序;
  • 局部变量为什么互不影响;
  • 递归为什么可能栈溢出。

注意事项

  1. 链式调用虽然简洁,但嵌套太深会影响可读性。
  2. 函数嵌套调用时,要能画出调用顺序。
  3. 调试时可以使用 PyCharm 的断点、Step Into 等功能观察调用过程。

七、递归:函数调用自己

1. 什么是递归

递归是一种特殊的嵌套调用:函数在函数体内部调用自己。

经典例子:计算阶乘。

数学上:

python 复制代码
5! = 5 * 4 * 3 * 2 * 1
n! = n * (n - 1)!

Python 代码:

python 复制代码
def factorial(n):
    if n == 1:
        return 1
    return n * factorial(n - 1)

print(factorial(5))

执行过程:

python 复制代码
factorial(5)
= 5 * factorial(4)
= 5 * 4 * factorial(3)
= 5 * 4 * 3 * factorial(2)
= 5 * 4 * 3 * 2 * factorial(1)
= 5 * 4 * 3 * 2 * 1
= 120

2. 结束条件

如果没有结束条件,函数会一直调用自己,最终出现递归深度错误。

错误示例:

python 复制代码
def factorial(n):
    return n * factorial(n - 1)

print(factorial(5))

这段代码永远不会停下来。

即使有结束条件,如果参数没有变化,也会无限递归:

python 复制代码
def countdown(n):
    if n == 0:
        return
    print(n)
    countdown(n)  # 错误:n 没有变小

正确写法:

python 复制代码
def countdown(n):
    if n == 0:
        return
    print(n)
    countdown(n - 1)

countdown(5)

3. 递归与循环的比较

递归版本:

python 复制代码
def factorial(n):
    if n == 1:
        return 1
    return n * factorial(n - 1)

循环版本:

python 复制代码
def factorial(n):
    result = 1
    for i in range(1, n + 1):
        result *= i
    return result

递归优点:

  • 代码短;
  • 和数学递推公式接近;
  • 适合处理树形结构、分治问题等。

递归缺点:

  • 理解难度较高;
  • 调用层数太深可能栈溢出;
  • 性能通常不如循环稳定。

注意事项

  1. 递归必须有明确的结束条件。
  2. 每次递归调用都要逐渐接近结束条件。
  3. 简单循环能解决的问题,不一定非要使用递归。

八、默认参数与关键字参数

1. 默认参数

函数参数可以设置默认值。带默认值的参数,调用时可以不传。

python 复制代码
def add(x, y, debug=False):
    if debug:
        print(f"调试信息:x={x}, y={y}")
    return x + y

print(add(10, 20))
print(add(10, 20, True))

输出:

python 复制代码
30
调试信息:x=10, y=20
30

错误写法:

python 复制代码
def add(x, debug=False, y):
    return x + y

正确写法:

python 复制代码
def add(x, y, debug=False):
    return x + y

原因是:如果默认参数放在前面,Python 很难判断后面的实参到底应该传给谁。

2. 关键字参数

普通传参按照顺序传递:

python 复制代码
def show_info(name, age):
    print(f"name = {name}")
    print(f"age = {age}")

show_info("Tom", 18)

关键字参数可以显式指定传给哪个形参:

python 复制代码
show_info(age=18, name="Tom")

这样即使顺序改变,也不会影响结果。

注意事项:

  1. 默认参数适合给"不经常变化的配置项"提供默认值。
  2. 默认参数应放在普通参数后面。
  3. 参数较多时,使用关键字参数可以提升代码可读性。

总结

函数是 Python 代码组织能力的核心。掌握函数以后,我们就能把重复代码封装起来,通过参数接收外部数据,通过返回值交还处理结果,并借助作用域管理变量的生命周期。

学习函数时建议重点抓住四句话:

  1. 函数定义不会自动执行,必须调用才会运行。
  2. 参数是函数的输入,返回值是函数的输出。
  3. 局部变量只在函数内部有效,全局变量要谨慎修改。
  4. 递归必须有结束条件,并且每次调用都要靠近结束条件。