1. 函数的概念

数学上的函数通常形如 y = f(x) 或者 z = g(x, y) 这样的形式。在 y = f(x) 中,f 是函数的名字,x 是函数的自变量,y 是函数的因变量。Python 中的函数跟这个结构是一致的。

我们通常把 Python 函数的自变量称为函数的参数,而因变量称为函数的返回值

1.1 使用 def 关键字定义函数

函数名命名规则跟变量一样。在函数名后面的圆括号中可以设置函数的参数。函数执行完成后,通过 return 关键字返回执行结果。如果函数中没有 return 语句,那么函数会返回代表空值的 None。函数也可以没有参数,但圆括号必须要有。

python
# 定义一个实现两数减法的函数
def sub(x, y):      # 定义一个函数,函数名是sub,参数是x和y
    return x - y    # 函数返回值是 x-y

print(sub(1, 2))    # -1
print(sub(4, 6))    # -2
函数定义时设置的参数叫做形式参数(形参),函数在实际使用时传入的参数叫做实际参数(实参)。

1.2 无参数、无返回值的函数

python
def list1():          # 定义一个无参数的函数
    for i in range(10):
        print(f" {i} ", end="")
    print()
    # 没有return,返回值默认是None

list1()               # 输出数字序列
print(list1())        # 输出None

2. 函数的参数

2.1 位置参数与关键字参数

上面 sub 函数的 x 和 y 就是位置参数,调用时按从左到右顺序传入,数量必须匹配。如果不想按顺序给出参数值,可以使用关键字参数(参数名=参数值)。

python
print(sub(1, 2))         # 位置参数
print(sub(y=1, x=2))     # 关键字参数
# print(sub(3))          # 报错:少了一个参数
# print(sub(2, 3, 4))    # 报错:多了一个参数

2.2 强制位置参数(/)与命名关键字参数(*)

在参数列表中用 / 设置强制位置参数(只能按位置传参),用 * 设置命名关键字参数(只能通过"参数名=参数值"传参)。强制位置参数是 Python 3.8 引入的特性。

python
def sub1(x, y, /):       # 强制位置参数
    return x - y

print(sub1(2, 1))        # 正确
# print(sub1(x=1, y=2))  # 报错!不能使用关键字参数

def sub2(*, x, y):       # 命名关键字参数
    return x - y

print(sub2(x=2, y=1))    # 正确
# print(sub2(2, 1))      # 报错!不能使用位置参数

2.3 带默认值的参数

Python 中允许函数的参数拥有默认值。带默认值的参数必须放在不带默认值的参数之后

python
def sub3(x=20, y=10):    # 定义时给参数赋值(默认值)
    return x - y

print(sub3())            # 10 — 使用默认值
print(sub3(3, 3))        # 0 — 传入新值覆盖默认值

2.4 可变参数 *args

通过 * 星号表达式语法让函数支持可变参数,可以向函数传入 0 个或任意多个参数。传入的参数会组装成一个元组

python
def add(*x):            # 可变长度参数
    return sum(x)

print(add(1, 2))           # 3
print(add(1, 2, 3, 4, 5))  # 15

2.5 可变关键字参数 **kwargs

通过 **kwargs 可以传入 0 个或任意多个关键字参数。传入的关键字参数会组装成一个字典

python
def foo(*args, **kwargs):
    print(args)      # 元组
    print(kwargs)    # 字典

foo(3, 2.1, True, name='骆昊', age=43, gpa=4.95)
# 输出: (3, 2.1, True)
# 输出: {'name':'骆昊', 'age':43, 'gpa':4.95}

3. 内置函数

Python 提供了 68 个内置函数,可以直接使用而无需导入任何模块。

python
# 常用内置函数分类
# 类型转换:int() float() str() bool() list() tuple() set() dict()
# 数学:abs() pow() round() sum() min() max() divmod()
# 序列:len() range() enumerate() zip() sorted() reversed() filter() map()
# 字符:ord() chr() bin() oct() hex() repr() format()
# 输入输出:print() input() open()
# 判断:isinstance() issubclass() hasattr() callable()
# 其他:type() dir() id() help() eval() exec() locals() globals()

需要注意的是,不建议将内置函数名称用作变量名。虽然程序不会报错,但原先的函数功能会丧失。

除了内置函数,Python 还有内置关键字(keyword)和软关键字(soft keyword)。关键字是核心保留字(if、else、for、while、def、class 等),软关键字只在特定上下文中特殊(如 match、case)。

4. 变量的作用域

Python 中变量作用域分为四层(LEGB 规则):

  • Local —— 局部作用域(函数内部)
  • Enclosing —— 嵌套作用域(外层函数)
  • Global —— 全局作用域(模块顶层)
  • Built-in —— 内置作用域(Python 内置)

4.1 局部作用域(Local Scope)

在函数内部定义的变量,只能在函数内部访问,函数执行结束后变量被销毁。

python
def ex1():
    n = 10
    print("函数内部:", n)

ex1()
# print(n)  # NameError!n超出了作用域

def ex2(a, b):
    result = a + b
    return result

s = ex2(3, 5)
print("返回结果:", s)     # 只能访问返回值
# print(result)           # NameError!

4.2 全局作用域(Global Scope)

在模块顶层(整个 .py 文件)定义的变量,整个模块都可以访问。要在函数内部修改全局变量,必须使用 global 关键字声明。

python
n = 50   # 全局变量

def ex1():
    print("函数内部访问全局变量:", n)   # 可以读

n = 100  # 全局变量

def ex2():
    global n           # 声明要使用外部的全局变量n
    n = n * 2
    print("函数内部修改后:", n)

ex2()                  # 输出: 200
print("函数外部修改后:", n)  # 输出: 200

4.3 嵌套作用域(Enclosing Scope)

存在于嵌套函数中,内层函数可以访问外层函数的变量。要在内层函数中修改外层变量,使用 nonlocal 关键字。

python
def t1():
    t = 20
    def t2():
        print("内层访问外层变量:", t)  # 可以读
    t2()

t1()

def t2():
    num = 10
    def t3():
        nonlocal num     # 声明来自外层函数
        num = num + 5
        print("内层修改后:", num)
    t3()
    print("外层函数中:", num)

t2()  # 内层修改后: 15 / 外层函数中: 15

4.4 内置作用域(Built-in Scope)

包含了 Python 内置的函数、类型和变量(print、len、int 等),在整个程序中都可以直接访问。不建议覆盖内置名称。

5. 递归函数

5.1 什么是递归函数

递归函数就是一个函数在它的函数体内调用它自身。执行递归函数将反复调用其自身,每调用一次就进入新的一层。

设计递归函数三要素:

  1. 明确函数要干什么
  2. 寻找递归终止条件(没有终止条件会无限递归导致栈溢出)
  3. 找出函数的等价关系式(问题的规模逐渐减小直到满足终止条件)
但凡循环能够解决的问题,递归都可以;但是递归能解决的问题,循环不一定。递归函数层数过多容易导致栈内存溢出,一般用来解决层数较少的问题。

5.2 例1:递归输出 1 → 10

python
# 写法1:输出在递归前(正序)
def num(n):
    if n <= 10:
        print(n)
        num(n + 1)

num(1)   # 输出: 1 2 3 4 5 6 7 8 9 10

# 写法2:输出在递归后(倒序)
def num(n):
    if n >= 1:
        num(n - 1)
        print(n)

num(10)  # 输出: 1 2 3 4 5 6 7 8 9 10

5.3 例2:阶乘 n!

n! = n × (n-1) × (n-2) × ... × 1,规定 0! = 1。

python
def ni(n):
    if n == 0 or n == 1:   # 终止条件
        return 1
    else:
        return n * ni(n - 1)  # 等价关系式: n! = n * (n-1)!

print(ni(5))  # 120

5.4 例3:理解递归的调用过程

python
def p(n):
    if n == 0:
        return
    print('递归前->', n)
    p(n - 1)
    print('递归后->', n)

p(5)
# 输出:
# 递归前-> 5
# 递归前-> 4
# 递归前-> 3
# 递归前-> 2
# 递归前-> 1
# 递归后-> 1
# 递归后-> 2
# 递归后-> 3
# 递归后-> 4
# 递归后-> 5

理解:递归调用是"先深入再返回"的过程。"递归前"打印是在进入下一层之前执行(正序),"递归后"打印是在从下一层返回之后执行(逆序)。这类似于栈的先入后出(FILO)特性。

6. lambda 匿名函数

6.1 基本语法

lambda 函数是 Python 中一种特殊的匿名函数,提供了一种简洁的方式来创建简单的函数。

语法:lambda 参数: 表达式

  • lambda 是关键字,用于定义匿名函数
  • 参数可以是零个或多个,用逗号分隔
  • 表达式是函数的返回值,只能有一个表达式(不能有多行语句,不能用 return)
python
# lambda函数 vs 普通函数
add = lambda x, y: x + y
print(add(1, 3))    # 4

def substract(x, y):
    return x - y
print(substract(1, 3))  # -2

# 直接调用(不赋值)
print((lambda x, y: x ** y)(5, 2))  # 25

# 搭配三元运算符
is_odd = lambda n: "奇数" if n % 2 != 0 else "偶数"
print(is_odd(7))   # 奇数

6.2 lambda 函数的参数形式

python
# 无参数
func = lambda: "Hello World!"
# 一个参数
func = lambda x: x ** 2
# 多个参数
func = lambda x, y, z: x * y * z
# 默认参数
func = lambda x, y=2: x ** y
# 可变参数
func = lambda *args: [x for x in args if x % 2 == 1]
# 关键字可变参数
func = lambda **kwargs: [f'{k}:{v}' for k, v in kwargs.items()]

6.3 lambda 与普通函数的选择

适合用 lambda适合用 def
函数逻辑简单,只有一行表达式函数逻辑复杂,需要多行代码
作为高阶函数的临时参数(sorted、map、filter)函数需要被多次复用
需要在短时间内创建简单的函数对象需要文档字符串解释功能

7. 高阶函数

高阶函数是指可以接收函数作为参数,或者将函数作为返回值的函数。Python 中最常用的三个高阶函数是 map()filter()reduce()

7.1 map() — 批量处理

map(函数, 可迭代对象1, 可迭代对象2, ...):将函数应用到可迭代对象的每一个元素上,返回一个新的可迭代对象。

python
# 将列表中每个元素平方
nums = [1, 2, 3, 4, 5]
result = map(lambda x: x ** 2, nums)
print(list(result))  # [1, 4, 9, 16, 25]

# 处理多个列表(对应位置元素运算)
list1 = [10, 20, 30, 40]
list2 = [5, 15, 25, 35]
result = map(lambda x, y: x + y, list1, list2)
print(list(result))  # [15, 35, 55, 75]

# 处理字符串
result = map(lambda c: ord(c), "python")
print(list(result))  # [112, 121, 116, 104, 111, 110]

7.2 filter() — 批量筛选

filter(函数, 可迭代对象):根据函数返回的 True/False,保留返回 True 的元素,过滤掉返回 False 的元素。

python
# 筛选奇数
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9]
result = filter(lambda x: x % 2 != 0, nums)
print(list(result))  # [1, 3, 5, 7, 9]

# 筛选长度大于3的字符串
strs = ["a", "ab", "abc", "abcd", "abcde"]
result = filter(lambda s: len(s) > 3, strs)
print(list(result))  # ['abcd', 'abcde']

# filter的第一个参数为None时,过滤掉假值
data = [0, 1, "", "hello", None, False, 3.14, []]
result = filter(None, data)
print(list(result))  # [1, 'hello', 3.14]

7.3 reduce() — 累积计算

from functools import reducereduce(函数, 可迭代对象, 初始值):从左到右依次累积,最终得到一个单一的结果。

python
from functools import reduce

# 求列表所有元素的和
nums = [1, 2, 3, 4, 5]
result = reduce(lambda a, b: a + b, nums)
print(result)  # 15(1+2=3,3+3=6,6+4=10,10+5=15)

# 指定初始值
result2 = reduce(lambda a, b: a + b, nums, 10)
print(result2)  # 25(初始值10参与累积)

# 求乘积
result = reduce(lambda a, b: a * b, nums)
print(result)  # 120

# 拼接字符串
strs = ["我", "爱", "Python", "编程"]
result = reduce(lambda a, b: a + b, strs)
print(result)  # 我爱Python编程

map/filter/reduce 对比

函数作用返回值比喻
map()每个元素都处理,不筛选可迭代对象"加工流水线"
filter()只保留符合条件的元素可迭代对象"质量检验员"
reduce()从左到右累积,得到一个值单一值"累加累乘器"

7.4 sorted() 配合 lambda

python
# 按字符串长度排序
arr = ['apple', 'banana', 'cherry', 'date']
print(sorted(arr, key=lambda s: len(s)))
# ['date', 'apple', 'cherry', 'banana']

# 按字典的某个键排序
people = [
    {'name': 'alice', 'age': 15},
    {'name': 'charlie', 'age': 30},
    {'name': 'bob', 'age': 25},
]
print(sorted(people, key=lambda x: x['age']))
# [{'name':'alice','age':15}, {'name':'bob','age':25}, {'name':'charlie','age':30}]

# 按字典的值排序
words = {"apple": 5, "orange": 6, "banana": 4}
print(sorted(words, key=lambda x: words[x], reverse=True))
# ['orange', 'apple', 'banana']

8. if __name__ == '__main__'

8.1 __name__ 变量

在 Python 中,每个模块(.py 文件)都有一个内置的 __name__ 变量:

  • 当脚本直接运行时,__name__ 的值为 '__main__'
  • 当脚本作为模块被导入时,__name__ 的值为模块的名字(文件名去掉 .py)

8.2 作用:代码开关

if __name__ == '__main__': 就像是一个开关:当脚本直接运行时执行 if 块内的代码,被导入时不执行。

python
# example.py
def add(x, y):
    return x + y

def multiply(x, y):
    return x * y

# 下面的代码只在直接运行时执行
if __name__ == '__main__':
    print("测试 add 函数:", add(3, 5))
    print("测试 multiply 函数:", multiply(4, 6))

如果一个模块只是定义函数、类等,没有特殊代码需要在直接运行时执行,那么 if __name__ == '__main__' 不是必须的。但保留它可以让代码结构更清晰,方便测试和调试。