函数

函数基本概念

为什么要函数?

在写程序时,我们经常会遇到 重复的逻辑。比如:

# 打印 1 到 5 的平方
print(1 * 1)
print(2 * 2)
print(3 * 3)
print(4 * 4)
print(5 * 5)

# 打印 6 到 10 的平方
print(6 * 6)
print(7 * 7)
print(8 * 8)
print(9 * 9)
print(10 * 10)

这样写太 重复、冗余、难维护。比如哪天要改成“立方”,就得一个个手动修改。

解决方法:函数
把“打印平方”的逻辑封装成函数,哪里需要就调用。

def print_square(start, end):
    for i in range(start, end + 1):
        print(i, "的平方是", i * i)

# 调用函数
print_square(1, 5)
print_square(6, 10)

优点:

  1. 复用性:写一次,用多次
  2. 可读性:逻辑被封装成“模块”
  3. 可维护性:修改时只改函数内部,不用全局改

Python 提供了三大类函数

  1. 内置函数(Built-in Functions)

    • Python 解释器启动时自动加载,随时可用。
    • 示例:print(), len(), sum(), int()
    print(len([1, 2, 3]))   # 3
    print(sum([1, 2, 3]))   # 6
    
  2. 内置模块中的函数

    • Python 标准库包含大量模块,每个模块定义了一组函数。
    • 这些函数不是自动可用的,需要先 import 才能使用。
    import math
    
    print(math.sqrt(16))   # 4.0
    print(math.factorial(5))  # 120
    
  3. 用户自定义函数

    • 开发者根据需求编写的函数,灵活封装逻辑。
    def greet(name):
        return f"Hello, {name}!"
    
    print(greet("Alice"))   # Hello, Alice!
    

函数定义与调用

函数定义的基本语法

def 函数名(参数列表):
    """文档字符串(可选)"""
    函数体
    return 返回值(可选)

解析:

  • def:关键字,表示“我要定义一个函数”。
  • 函数名:符合变量命名规则。推荐小写+下划线(PEP8规范)。
  • 参数列表:形式参数,可以是0个或多个,用,隔开。
  • docstring:函数的说明文档,推荐写清楚输入输出。
  • 函数体:必须缩进,通常4个空格。
  • return:用于返回结果。若省略,默认返回 None

最简单的函数

def say_hello():
    print("Hello Python!")

say_hello()

运行结果:

Hello Python!

这里函数没有参数、没有返回值,只是执行打印。

带参数的函数

def greet(name):
    """接收一个名字,返回一段问候语"""
    return f"Hello,{name}!"

msg = greet("Alice")
print(msg)

结果:

Hello,Alice!
  • name 是形参(在定义函数时起的名字)。
  • "Alice" 是实参(调用时传入的具体值)。

带返回值的函数

def add(a, b):
    return a + b

print(add(3, 5))   # 8

如果没有 return,函数默认返回 None

函数调用的原理

foo 为例:

def foo(x):
    return x * 2

print(foo(5))

执行步骤:

  1. 解释器读到 def foo(x): ... → 在内存里创建一个函数对象,并把它绑定到名字 foo 上。
  2. 调用 foo(5) 时,Python:
    • 创建一个“调用栈帧”保存运行信息。
    • 5 传递给形参 x
    • 执行函数体,算出结果 10。
  3. 执行 return 10,栈帧销毁,结果返回给调用点。

函数名就是变量

def say_hi():
    print("hi")

a = say_hi   # 把函数对象赋给变量 a
a()          # 调用

输出:

hi

函数名只是一个“指针”,指向函数对象。
因此:

  • 可以把函数作为参数传递。
  • 可以把函数作为返回值返回。
  • 可以存到数据结构里。

这就是高阶函数、回调函数、装饰器的基础。

文档字符串(docstring)

def greet(name):
    """接收一个名字,返回问候语。
    
    参数:
        name (str): 姓名
    
    返回:
        str: 问候语
    """
    return f"你好,{name}!"

print(greet.__doc__)
help(greet)

docstring 是专业程序员的习惯,方便 自动生成文档团队协作

注意事项

  1. 必须先定义再调用

    say_hello()   # 报错,未定义
    def say_hello():
        print("hi")
    
  2. 函数名可被覆盖

    def hi():
        print("hi")
    hi = 123
    hi()   # 报错,int 不可调用
    
  3. return 后的语句不会执行

    def f():
        return 1
        print("不会执行")
    

函数传参

位置参数(Positional Arguments)

最常见,按顺序传递

def add(x, y):
    return x + y

print(add(3, 5))  # 8

特点:

  • 调用时必须传够参数,否则报错。
  • 参数顺序必须和定义一致。
  • 多传或少传都会报错。

错误示例:

add(1)        # ❌ TypeError: missing 1 required positional argument
add(1, 2, 3)  # ❌ TypeError: takes 2 positional arguments but 3 were given

关键字参数(Keyword Arguments)

调用时用 参数名=值 的方式,不必按照顺序。

def printinfo(name, age):
    print("Name:", name)
    print("Age:", age)

printinfo("Naveen", 29)                # 按位置
printinfo(age=30, name="Miki")         # 按关键字(顺序可变)

输出:

Name: Naveen
Age: 29
Name: Miki
Age: 30

注意:
混合使用时,必须 位置参数在前,关键字参数在后

def division(num, den):
    print(num / den)

division(10, den=5)  # ✅ 正确
division(num=5, 10)  # ❌ SyntaxError

默认参数(Default Arguments)

给参数一个默认值,调用时可省略。

def showinfo(name, city="Hyderabad"):
    print("Name:", name)
    print("City:", city)

showinfo("Ansh", "Delhi")
showinfo("Shrey")  # 使用默认值

输出:

Name: Ansh
City: Delhi
Name: Shrey
City: Hyderabad

另一个例子:

def percent(phy, maths, maxmarks=200):
    return (phy + maths) * 100 / maxmarks

print(percent(60, 70))      # 默认 maxmarks=200 → 65.0
print(percent(40, 46, 100)) # 覆盖默认值 → 86.0

坑点:默认参数只计算一次
如果用可变对象(list/dict)作为默认值,会“共享”。

def fcn(nums, numericlist=[]):
    numericlist.append(nums + 1)
    print(numericlist)

fcn(66)  # [67]
fcn(68)  # [67, 69]  意外共享

正确写法:

def fcn(nums, numericlist=None):
    if numericlist is None:
        numericlist = []
    numericlist.append(nums + 1)
    print(numericlist)

仅关键字参数(Keyword-only Arguments)

在定义时用 * 来限制,之后的参数必须用关键字传递。

def intr(amt, *, rate):
    return amt * rate / 100

print(intr(1000, rate=10))  # ✅ 正确
print(intr(1000, 10))       # ❌ TypeError

内置 print()sep 就是 keyword-only:

print("Hello", "World", sep="-")  # Hello-World

仅位置参数(Positional-only Arguments)

在定义时用 / 来限制,之前的参数只能按位置传。

def intr(amt, rate, /):
    return amt * rate / 100

print(intr(1000, 10))           # ✅
print(intr(amt=1000, rate=10))  # ❌ TypeError

混合示例(位置 + 关键字 + 限制):

def myfunction(x, /, y, *, z):
    print(x, y, z)

myfunction(10, 20, z=30)  # ✅

任意参数(可变参数)

  1. *args:任意位置参数

    接收任意数量的位置参数,形成一个 元组

    def add(*args):
        return sum(args)
    
    print(add(10, 20, 30))  # 60
    print(add(1, 2, 3, 4))  # 10
    
  2. **kwargs:任意关键字参数

    👉 接收任意数量的关键字参数,形成一个 字典

    def addr(**kwargs):
        for k, v in kwargs.items():
            print(f"{k}:{v}")
    
    addr(Name="John", City="Mumbai")
    

    输出:

    Name: John
    City: Mumbai
    
  3. 混合使用

    参数顺序必须是:

    位置参数 → 默认参数 → *args → 仅关键字参数 → **kwargs
    

    示例:

    def func(a, b=2, *args, c=5, **kwargs):
        print(a, b, args, c, kwargs)
    
    func(1, 3, 4, 5, x=10, y=20)
    

    输出:

    1 3 (4, 5) 5 {'x': 10, 'y': 20}
    

参数解包传参

  1. * 解包序列

    def add(a, b, c):
        return a + b + c
    
    nums = [1, 2, 3]
    print(add(*nums))  # 相当于 add(1, 2, 3)
    
  2. ** 解包字典

    def introduce(name, age):
        print(f"我是{name}{age}岁")
    
    info = {"name": "小红", "age": 20}
    introduce(**info)   # 相当于 introduce(name="小红", age=20)
    

参数传递的本质

在 Python 中:

  • 传参不是传值,也不是传引用
  • 而是 “传对象引用”

规则:

  • 如果传的是 不可变对象(int, str, tuple) → 在函数内修改不会影响外部。
  • 如果传的是 可变对象(list, dict, set) → 在函数内修改会影响外部。

示例 1(不可变):

def change(x):
    x = 100
    print("函数内:", x)

a = 10
change(a)
print("函数外:", a)

输出:

函数内: 100
函数外: 10

示例 2(可变):

def modify(lst):
    lst.append(999)

nums = [1, 2, 3]
modify(nums)
print(nums)

输出:

[1, 2, 3, 999]

这就是 Python 参数传递的核心特性。

函数返回值

在 Python 里,函数的返回值通过 return 实现。

如果没有写 return,函数会默认返回 None

无返回值 → None

def say_hi():
    print("Hi!")

res = say_hi()
print(res)   # None

返回一个值

def square(x):
    return x * x

print(square(4))  # 16

返回多个值(打包成元组)

def calc(a, b):
    return a+b, a-b, a*b

res = calc(6, 3)
print(res)      # (9, 3, 18)

x, y, z = calc(6, 3)  # 拆包
print(x, y, z)        # 9 3 18

提前结束函数

return 不仅能返回值,还能 立即结束函数执行

def check(num):
    if num < 0:
        return "负数"
    return "非负数"

print(check(-5))   # "负数"

变量作用域

什么是作用域(Scope)?

变量作用域就是“变量能被访问到的范围”。
Python 的作用域由 变量定义的位置命名空间(namespace) 决定。

  • 局部变量(local):函数/代码块内定义,函数运行完就销毁。
  • 全局变量(global):模块级别定义,整个文件都能用。
  • 非局部变量(nonlocal):嵌套函数里,修改外层函数变量。
  • 内置变量(built-in):Python 自带,比如 lensum

局部变量(Local Variable)

定义在函数内部,只在函数体内有效。

def foo():
    x = 10   # 局部变量
    print("函数内:", x)

foo()
print("函数外:", x)   # ❌ NameError: name 'x' is not defined

特点:

  • 生命周期 = 函数调用期。
  • 离开函数,变量就不存在。

全局变量(Global Variable)

定义在函数外部,整个模块内都能访问。

x = 100  # 全局变量

def bar():
    print("函数内:", x)

bar()
print("函数外:", x)

局部变量覆盖全局变量(同名冲突)

如果函数内部定义了同名变量,它会屏蔽全局变量。

x = 100

def test():
    x = 50   # 局部变量
    print("函数内:", x)

test()
print("函数外:", x)

输出:

函数内: 50
函数外: 100

global 关键字(修改全局变量)

默认情况下,函数内部无法直接修改全局变量。
如果要修改,必须用 global 声明。

x = 10

def change():
    global x
    x = 99   # 修改全局变量

change()
print(x)   # 99

注意:

  • 滥用 global 会让程序难以维护。
  • 更推荐 函数返回值,而不是直接改全局变量。

nonlocal 关键字(修改外层函数变量)

在嵌套函数里,用 nonlocal 修改外层函数的变量。

def outer():
    a = 10
    def inner():
        nonlocal a
        a = 20
        print("inner:", a)
    inner()
    print("outer:", a)

outer()

输出:

inner: 20
outer: 20

如果不用 nonlocalinner 里的 a 就只是一个新的局部变量。

作用域规则:LEGB 原则

Python 查找变量的顺序遵循 LEGB

  1. L(Local)局部作用域 —— 函数内部
  2. E(Enclosing)外部嵌套函数作用域 —— 闭包里的变量
  3. G(Global)全局作用域 —— 模块级变量
  4. B(Built-in)内置作用域 —— Python 内置的变量(如 lensum

例子:

x = "global"   # 全局变量

def outer():
    x = "enclosing"   # 外层函数变量
    def inner():
        x = "local"   # 局部变量
        print(x)
    inner()

outer()

输出:

local

如果 inner 里没定义 x,就会逐层往上找(E → G → B),直到全局或内置。

命名空间(Namespace)

命名空间就是“名字到对象的映射表”。
Python 有三类命名空间:

  • 内置命名空间:加载 Python 解释器时就存在(如 lenprint)。
  • 全局命名空间:模块级的变量、函数。
  • 局部命名空间:函数内部的变量。

命名空间是 嵌套关系:内置 ⊂ ⊂ 全局 ⊂ ⊂ 局部

globals()locals()

Python 提供了两个内置函数来查看命名空间。

globals():返回全局命名空间的字典

x = 100
def foo():
    pass

print(globals().keys())  # 包含 x, foo 等
print(globals()['x'])    # 100

locals():返回当前局部命名空间的字典

def myfunction():
    a = 10
    b = 20
    print(locals())

myfunction()

输出:

{'a': 10, 'b': 20}

命名空间冲突与错误

  1. 同名变量:局部 > 全局

    marks = 50
    def myfunction():
        marks = 70
        print(marks)
    
    myfunction()
    print(marks)
    

    输出:

    70
    50
    
  2. 未声明就修改全局变量 → 报错

    marks = 50
    def myfunction():
        marks = marks + 20   # ❌ UnboundLocalError
        print(marks)
    
    myfunction()
    

    报错:

    UnboundLocalError: cannot access local variable 'marks'
    

    解决办法:

    • global
    marks = 50
    def myfunction():
        global marks
        marks = marks + 20
        print(marks)
    
    myfunction()
    
    • 或者通过 globals()['marks']
    marks = 50
    def myfunction():
        globals()['marks'] = globals()['marks'] + 20
    myfunction()
    print(marks)  # 70
    
  3. 局部变量在函数外访问 → 报错

    def calc(x, y):
        total = x + y
        print("函数内:", total)
    
    calc(5, 6)
    print(total)  # ❌ NameError: total 未定义
    

函数高级用法

高阶函数

高阶函数(higher-order function, HOF)是函数式编程里非常核心的概念。简单定义把函数当作值来用 —— 高阶函数就是接受函数作为参数返回函数的函数。Python 支持函数一等公民(first-class objects),所以实现 HOF 非常自然。

在 Python 中函数是对象,可以赋值、作为参数传递、作为返回值、放入容器、有属性等。

def greet(name):
    return f"Hello, {name}"

f = greet          # 把函数赋给变量 f
print(f("Alice"))  # Hello, Alice

funcs = [abs, str, greet]
print(funcs[2]("Bob"))  # Hello, Bob

什么是高阶函数?

  • 接受函数作为参数 的函数,例如 map, filter, sorted(..., key=...)
  • 返回函数 的函数,例如工厂函数、闭包、装饰器。

例:函数作为参数

def apply(func, value):
    return func(value)

print(apply(lambda x: x*2, 10))  # 20

例:返回函数(工厂)

def make_multiplier(n):
    def multiplier(x):
        return x * n
    return multiplier

double = make_multiplier(2)
print(double(5))  # 10

常见内置高阶函数与用法

  1. map(func, iterable)

    对每个元素应用 func,返回迭代器(Python3)。

    nums = [1,2,3]
    squares = list(map(lambda x: x*x, nums))  # [1,4,9]
    # 等价的 Pythonic 写法(通常更推荐):
    squares = [x*x for x in nums]
    
  2. filter(func, iterable)

    保留 func(item)True 的元素,返回迭代器。

    evens = list(filter(lambda x: x%2==0, range(10)))  # [0,2,4,6,8]
    # 等价写法:
    evens = [x for x in range(10) if x%2==0]
    
  3. functools.reduce(func, iterable, initializer?)

    把序列归约为单个值(需 from functools import reduce)。

    from functools import reduce
    sum_all = reduce(lambda a,b: a+b, [1,2,3,4])  # 10
    

    注:reduce 在可读性上通常不如显式循环或 sum,在某些场景仍有用。

  4. sorted(iterable, key=..., reverse=...)

    key 是接受每个元素、返回用于比较的函数(高阶用法)。

    people = [{"name":"A","age":30},{"name":"B","age":25}]
    sorted_by_age = sorted(people, key=lambda p: p["age"])
    

    也可用 operator.itemgetter('age') 替代 lambda(更快更清晰):

    from operator import itemgetter
    sorted(people, key=itemgetter('age'))
    
  5. 其他实用函数

    • any(func(item) for item in ...) / all(...)(短路求值)

    • max(iterable, key=...) / min(..., key=...)

    • map, filter, zip, enumerate(返回迭代器)

    • itertools 模块提供大量高阶工具:accumulate, chain, groupby, starmap

闭包

在编程中,我们经常希望一个函数能够“记住”它创建时的环境。比如:

  • 有时我们希望某个函数能带着一些配置参数返回,而不必每次都重新传入;
  • 或者希望函数内部的数据不会被外部随便修改。

闭包就是为了解决这些问题。

闭包的定义

在 Python 中,闭包是指一个函数嵌套在另一个函数中,并且内部函数引用了外部函数的变量

  • 外层函数称为“外部函数”;
  • 内层函数称为“内部函数”;
  • 当外部函数返回内部函数时,这个内部函数 + 外部函数的变量环境 就构成了 闭包

闭包的基本结构

def outer(x):       # 外部函数
    def inner(y):   # 内部函数
        return x + y  # 内部函数引用外部函数变量 x
    return inner     # 返回内部函数(不是调用)

add_five = outer(5)   # 生成一个闭包,记住了 x = 5
print(add_five(10))   # 输出 15
print(add_five(20))   # 输出 25

解释:

  • outer(5) 执行时,返回了 inner 这个函数。
  • 但是 inner 并没有丢掉 outer 的变量 x,它“记住”了 x = 5
  • 所以以后不管在什么地方调用 add_five,它都会把 x 当作 5 来计算。

闭包的特性

  1. 内部函数引用外部函数的变量
    这让函数更灵活,不必每次都传入重复参数。
  2. 外部函数执行结束后,变量依然存在
    一般情况下,函数执行完后,局部变量会被销毁。
    但闭包“抓住”了它们,使得变量得以保留。
  3. 可以形成工厂函数(函数返回函数)
    常用于生成带特定配置的函数。

闭包的应用场景

  1. 创建带参数的函数工厂

    def power(exp):
        def calc(base):
            return base ** exp
        return calc
    
    square = power(2)   # 创建平方函数
    cube = power(3)     # 创建立方函数
    
    print(square(4))   # 16
    print(cube(4))     # 64
    
  2. 数据隐藏(保护变量)

    def counter():
        count = 0
        def add_one():
            nonlocal count   # 声明使用外部函数的变量
            count += 1
            return count
        return add_one
    
    c = counter()
    print(c())  # 1
    print(c())  # 2
    print(c())  # 3
    

    这里 count 对外部是隐藏的,外部不能直接改,只能通过 c() 调用来修改。

注意事项

  1. 如果内部函数需要修改外部函数的变量,必须用 nonlocal 关键字声明。
  2. 闭包可能会导致 内存占用,因为它会保存外部变量。

闭包的本质就是:

  1. 外部函数执行时,创建了一个内部函数
  2. 外部函数并不直接返回结果,而是返回内部函数这个“函数对象”
  3. 内部函数对象带着它用到的外部函数变量(形成“自由变量环境”)一起返回给外部。
  4. 外部接收到的其实就是一个“变量”,只不过这个变量是一个函数,可以继续调用。

形象比喻:

  • 外部函数就像一个工厂,工厂里生产了一个“定制的小机器”(内部函数)。
  • 这个小机器需要用到工厂里的某些零件(外部变量)。
  • 工厂停工了(外部函数执行结束),但是小机器被送了出来,零件也一起打包带出来了。
  • 所以后面你在别的地方用小机器,它还能正常运作。

匿名函数 lambda

lambda 就是匿名函数表达式,用来定义一个单行、返回表达式值的函数对象。等价于用 def 定义一个很短的小函数,但没有名字(或名字会是 <lambda>)。

语法

lambda 参数1, 参数2, ...: 表达式

该表达式的计算结果就是函数的返回值(隐式 return)。

代码示例

# 普通函数
def square(x):
    return x * x

# lambda 写法(功能相同)
square2 = lambda x: x * x

print(square(5))   # 25
print(square2(5))  # 25

# 直接调用
print((lambda x, y: x + y)(3, 4))  # 7

lambda 常见应用场景

  1. map / filter / reduce 中的短小函数:

    nums = [1, 2, 3, 4]
    list(map(lambda x: x**2, nums))        # [1,4,9,16]
    list(filter(lambda x: x % 2 == 0, nums))  # [2,4]
    from functools import reduce
    reduce(lambda a, b: a + b, nums)       # 10
    
  2. sorted / list.sortkey

    people = [{"name":"a","age":30},{"name":"b","age":25}]
    sorted(people, key=lambda p: p["age"])  # 按 age 升序
    
  3. 简短回调(GUI、线程、事件)或字典映射操作:

    ops = {'+': lambda a,b: a+b, '-': lambda a,b: a-b}
    ops['+'](3,4)  # 7
    
  4. 生成闭包 / 立即返回一个函数(和 def 相同):

    def make_adder(n):
        return lambda x: x + n
    
    add5 = make_adder(5)
    add5(10)  # 15
    

语法/能力与限制(重要)

  • 只能是一个表达式(不能有语句)。因此不能出现 if/for/while/try/def/return 等语句。但可以使用条件表达式a if cond else b)和生成器/推导式

    lambda x: x+1          # 合法
    lambda x: (x+1 if x>0 else 0)  # 合法:条件表达式
    lambda x: print(x)    # 合法(print 在 Python3 是函数,算表达式调用),但 print 返回 None
    
  • 不能写多行逻辑。当逻辑复杂时,应使用 def

  • lambda 会返回函数对象,函数对象像普通函数一样可以有闭包、捕获外层变量等。

  • lambda__name__"<lambda>"(不利于调试),也没有 docstring(或不方便写文档)。

参数形式支持(和 def 类似)

  • 普通位置参数、默认参数:

    lambda x, y=2: x*y
    
  • 可变位置参数 *args / 可变关键字 **kwargs 也可以:

    lambda *args: sum(args)
    lambda **kwargs: kwargs.get('a')
    
  • 条件表达式、元组、列表推导等都可以在右侧表达式里使用。

lambda 与 def 的差别(什么时候用 / 不用)

  • 何时用 lambda
    • 短小、一次性、在表达式中直接使用的函数(例如 sorted(..., key=...)mapfilter、简短回调)。
    • 想要把简单功能内联写在一行里。
  • 何时不用 lambda(建议用 def
    • 功能复杂、需要多行逻辑、需要注释或文档时;
    • 需要在栈跟踪中看到可读函数名时(调试友好性);
    • 需要写类型注解或更复杂签名时(def 更灵活、易读)。

PEP8 的建议:对于复杂行为或多行逻辑,用 deflambda 适用于非常短的匿名函数。

装饰器

在开发中,我们经常会遇到这种情况:

  • 想在函数执行前后 增加一些额外功能,比如:打印日志、统计执行时间、检查权限、缓存结果……
  • 但是又不想修改原函数的代码(保证原逻辑独立、可复用)。

装饰器就是为了解决这个问题:它允许你在 不改变函数本身代码 的前提下,给函数 动态增加功能

装饰器的本质就是:

  • 函数作为参数传递
  • 返回一个新的函数(闭包)

基本形式:

def 装饰器函数(func):
    def wrapper(*args, **kwargs):
        # 在调用前做点事情
        print("函数开始执行了...")
        result = func(*args, **kwargs)  # 调用原函数
        # 在调用后做点事情
        print("函数执行完毕")
        return result
    return wrapper

装饰器的使用方法

Python 提供了 语法糖 @装饰器名,写在函数定义前,就等价于:

@装饰器函数
def my_func():
    print("这是我的函数")

等价于:

def my_func():
    print("这是我的函数")

my_func = 装饰器函数(my_func)  # 手动装饰

装饰器示例

  1. 打印日志

    def log(func):
        def wrapper(*args, **kwargs):
            print(f"[LOG] 正在执行 {func.__name__} 函数")
            return func(*args, **kwargs)
        return wrapper
    
    @log
    def say_hello():
        print("Hello, Python!")
    
    say_hello()
    

    输出:

    [LOG] 正在执行 say_hello 函数
    Hello, Python!
    
  2. 统计执行时间

    import time
    
    def timer(func):
        def wrapper(*args, **kwargs):
            start = time.time()
            result = func(*args, **kwargs)
            end = time.time()
            print(f"{func.__name__} 执行耗时 {end - start:.4f} 秒")
            return result
        return wrapper
    
    @timer
    def slow_func():
        time.sleep(1)
        print("函数执行完毕")
    
    slow_func()
    

    输出:

    函数执行完毕
    slow_func 执行耗时 1.0001
  3. 带参数的装饰器

    如果装饰器本身也需要参数,就需要再套一层函数:

    def repeat(n):  # 外层函数:接收参数
        def decorator(func):  # 中层函数:接收原函数
            def wrapper(*args, **kwargs):  # 内层函数:增强功能
                for i in range(n):
                    func(*args, **kwargs)
            return wrapper
        return decorator
    
    @repeat(3)
    def greet():
        print("你好!")
    
    greet()
    

    输出:

    你好!
    你好!
    你好!
    

装饰器的典型应用

  1. 日志:跟踪函数调用
  2. 权限验证:判断用户是否有权限执行
  3. 缓存:保存计算结果,避免重复计算
  4. 性能分析:统计函数执行时间
  5. 框架应用:Flask、Django 路由就是用装饰器实现的

Flask 示例:

from flask import Flask
app = Flask(__name__)

@app.route("/")
def index():
    return "Hello Flask!"

这里 @app.route("/") 就是一个装饰器,用来把函数和 URL 绑定。

注意事项

原函数的元信息会丢失:因为装饰器返回的是 wrapper,原函数的名字、文档字符串都会变成 wrapper 的。
解决方法:用 functools.wraps 保留原信息。

from functools import wraps

def log(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"调用 {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

装饰器工作原理图

假设我们写了一个装饰器:

def log(func):
    def wrapper(*args, **kwargs):
        print("函数开始执行")
        result = func(*args, **kwargs)
        print("函数执行结束")
        return result
    return wrapper

@log
def hello():
    print("Hello Python!")

等价于:

hello = log(hello)

调用流程图

调用 hello()
      │
      ▼
 ┌─────────────┐
 │ wrapper()   │   ← log 返回的闭包函数
 └─────────────┘
      │
      ├── 打印 "函数开始执行"
      │
      ├── 调用原函数 func → hello()
      │         │
      │         └── 打印 "Hello Python!"
      │
      ├── 打印 "函数执行结束"
      │
      └── 返回 result

图解关键点

  1. @log 语法糖:相当于 hello = log(hello),所以真正被调用的不是原来的 hello,而是 wrapper
  2. 闭包wrapper 内部保存了对 func(原函数)的引用,所以还能调用原函数。
  3. 增强功能:在调用前后加了打印,就是装饰器的扩展逻辑。

函数说明文档

什么是函数的说明文档(docstring)

  • 定义:函数的说明文档是一种用来说明函数用途、参数、返回值等信息的特殊字符串,写在函数内部开头,用 三重引号"""''')包裹。
  • 作用
    • 方便自己或他人阅读函数作用。
    • 可以通过 Python 内置函数 help().__doc__ 获取。
    • 很重要的文档实践,尤其在团队开发或库开发中。

基本写法

def add(a, b):
    """计算两个数的和并返回结果"""
    return a + b

这里的 """计算两个数的和并返回结果""" 就是函数的说明文档。

访问方式:

print(add.__doc__)
help(add)

输出:

计算两个数的和并返回结果

推荐格式(Google 风格 / NumPy 风格)

Google 风格

def add(a, b):
    """
    计算两个数的和

    Args:
        a (int or float): 第一个加数
        b (int or float): 第二个加数

    Returns:
        int or float: 返回 a 与 b 的和
    """
    return a + b

NumPy 风格

def add(a, b):
    """
    计算两个数的和

    Parameters
    ----------
    a : int or float
        第一个加数
    b : int or float
        第二个加数

    Returns
    -------
    int or float
        a 与 b 的和
    """
    return a + b

多行说明文档注意事项

  • 三引号可以跨多行书写。
  • 首行最好简洁说明函数用途。
  • 第二行留空行,然后详细描述参数、返回值、异常等。
  • 可以在文档中加示例:
def add(a, b):
    """
    计算两个数的和

    Args:
        a (int or float): 第一个加数
        b (int or float): 第二个加数

    Returns:
        int or float: 返回 a 与 b 的和

    Example:
        >>> add(3, 5)
        8
    """
    return a + b

访问说明文档

print(add.__doc__)  # 获取 docstring
help(add)           # 查看完整帮助信息

输出将包含你写的文档内容,help() 会自动格式化。

函数注释

函数注释的基本概念

函数注释(Function Annotations)是 PEP 3107 引入的功能,它允许我们在函数的 参数返回值 上附加 元数据(metadata)

语法形式

def func(参数名: 注释表达式, ...) -> 返回值注释:
    函数体

要点:

  1. 注释是任意 Python 表达式(不仅限于类型)。
  2. 注释不会影响函数执行(解释器不会检查类型),只是存储在函数对象的 __annotations__ 属性中。
  3. 注释主要用于:
    • 文档用途(让代码更易懂)。
    • IDE / 编辑器提示(如 PyCharm 会提示输入类型)。
    • 静态类型检查工具(如 mypy, pyright 会根据注释做检查)。

参数注释

最常见的用法是为参数指定类型:

def add(a: int, b: int):
    return a + b

print(add(10, 20))        # 30
print(add("Hello ", "Python"))  # Hello Python
print(add.__annotations__)

输出:

30
Hello Python
{'a': <class 'int'>, 'b': <class 'int'>}

解释:

  • a: int 说明希望 aint
  • 但 Python 不会强制检查,所以你依然能传字符串。

返回值注释

使用 -> 语法指定函数的返回类型:

def add(a: int, b: int) -> int:
    return a + b

print(add(56, 88))              # 144
print(add.__annotations__)

输出:

144
{'a': <class 'int'>, 'b': <class 'int'>, 'return': <class 'int'>}

任意表达式作为注释

注释不仅限于类型,还可以写成任意表达式(字符串最常见):

def total(x: "marks in Physics", y: "marks in Chemistry"):
    return x + y

print(total(86, 88))                # 174
print(total.__annotations__)

输出:

174
{'x': 'marks in Physics', 'y': 'marks in Chemistry'}

这种写法常用于 文档说明

默认参数 + 注释

注释和默认值可以同时出现,顺序是:注释在前,默认值在后

def myfunction(a: "physics", b: "maths" = 20) -> int:
    return a + b

print(myfunction(10))               
print(myfunction.__annotations__)

输出:

30
{'a': 'physics', 'b': 'maths', 'return': <class 'int'>}

可变参数的注释

def myfunction(*args: "arbitrary args", **kwargs: "arbitrary keyword args") -> int:
    pass

print(myfunction.__annotations__)

输出:

{'args': 'arbitrary args', 'kwargs': 'arbitrary keyword args', 'return': <class 'int'>}

复杂表达式作为注释

注释甚至可以是 字典对象 或其他复杂表达式:

def division(num: dict(type=float, msg="numerator"),
             den: dict(type=float, msg="denominator")) -> float:
    return num / den

print(division.__annotations__)

输出:

{'num': {'type': <class 'float'>, 'msg': 'numerator'},
 'den': {'type': <class 'float'>, 'msg': 'denominator'},
 'return': <class 'float'>}

函数对象的 __annotations__

函数是对象,__annotations__ 就是一个 字典,存储了所有参数和返回值的注释:

def f(x: int, y: str = "hi") -> bool:
    return str(x) == y

print(f.__annotations__)

输出:

{'x': <class 'int'>, 'y': <class 'str'>, 'return': <class 'bool'>}

可以用 dir(f) 查看函数的所有属性(包括 __annotations__)。

注释与 IDE / 类型检查工具

虽然 Python 本身不会强制类型,但注释结合工具就能实现 静态类型检查

pip install mypy

代码:

def add(a: int, b: int) -> int:
    return a + b

add("hi", "hello")   # 运行时 OK

执行:

mypy script.py

输出:

error: Argument 1 to "add" has incompatible type "str"; expected "int"

这样可以提前发现类型错误。

函数注释 vs Docstring

特性函数注释 (Annotations)Docstring (文档字符串)
语法位置参数和返回值函数体第一行
存储位置__annotations__ 属性__doc__ 属性
内容任意表达式纯字符串说明
是否强制类型检查
常见用途IDE / 静态类型检查工具函数说明、帮助文档

内置函数

什么是内置函数?

  • 定义:Python 内置函数(Built-in Functions)是 Python 解释器启动时就加载好的函数,不需要 import 就能直接使用。

  • 作用:简化开发,比如数学计算(sum()max())、类型转换(int()str())、迭代器操作(len()zip())等等。

  • 查看所有内置函数

    import builtins
    print(dir(builtins))
    

    输出一个列表,里面就是所有内置函数。

最常用的内置函数

text = "Python"
print(len(text))     # 6, 求长度
print(type(text))    # <class 'str'>, 查看类型
print(str(123))      # "123", 转字符串
print(int("456"))    # 456, 转整数
print(abs(-10))      # 10, 绝对值
print(max(1, 5, 3))  # 5, 最大值
print(min(1, 5, 3))  # 1, 最小值
print(sum([1, 2, 3, 4]))  # 10, 求和

类型转换类

  • int():转整数
  • float():转浮点数
  • complex():创建复数
  • str():转字符串
  • bool():转布尔值
  • list():转列表
  • tuple():转元组
  • set():转集合
  • frozenset():不可变集合
  • dict():字典
  • bytes():字节对象
  • bytearray():可变字节对象
  • memoryview():内存视图
  • object():基础对象
  • type():获取对象类型 / 动态创建类

示例:

print(int("10"))           # 10
print(float("3.14"))       # 3.14
print(complex(2, 3))       # (2+3j)
print(set([1, 2, 2, 3]))   # {1, 2, 3}

数学运算类

  • abs(x):绝对值
  • pow(x, y[, z]):指数(带取模)
  • round(x, n):四舍五入
  • sum(iterable):求和
  • min(iterable):最小值
  • max(iterable):最大值
  • divmod(a, b):返回 (商, 余数)

示例:

print(abs(-7))            # 7
print(pow(2, 3))          # 8
print(pow(2, 3, 5))       # 3
print(round(3.14159, 2))  # 3.14
print(divmod(17, 5))      # (3, 2)

迭代器 / 序列操作

  • len():长度
  • range():生成序列
  • enumerate():带索引迭代
  • zip():打包迭代
  • map():函数映射
  • filter():条件筛选
  • iter():迭代器
  • next():取迭代器下一个值
  • reversed():反向迭代
  • sorted():排序
  • slice():切片对象
  • all():全为真
  • any():任一为真

示例:

nums = [1, 2, 3, 4]
print(list(enumerate(nums)))      # [(0,1), (1,2), (2,3), (3,4)]
print(list(zip([1,2], ['a','b']))) # [(1,'a'), (2,'b')]
print(list(map(lambda x: x*2, nums))) # [2, 4, 6, 8]
print(list(filter(lambda x: x>2, nums))) # [3, 4]
print(all([True, 1, "hi"]))       # True
print(any([0, "", False, 5]))     # True

输入输出 / 格式化

  • print():打印
  • input():输入
  • format():格式化
  • repr():对象的字符串表示
  • ascii():ASCII 安全的字符串
  • chr():整数 → 字符
  • ord():字符 → Unicode 编码
  • bin():转二进制字符串
  • oct():转八进制字符串
  • hex():转十六进制字符串

示例:

print(chr(65))    # 'A'
print(ord('A'))   # 65
print(bin(10))    # '0b1010'
print(hex(255))   # '0xff'

反射 / 对象相关

  • dir():对象的所有属性
  • vars():返回对象 __dict__
  • getattr():获取属性
  • setattr():设置属性
  • delattr():删除属性
  • hasattr():检查属性
  • isinstance():是否某类实例
  • issubclass():是否子类
  • callable():是否可调用
  • id():对象唯一标识
  • hash():哈希值
  • globals():全局变量字典
  • locals():局部变量字典
  • super():父类代理对象
  • staticmethod():定义静态方法
  • classmethod():定义类方法
  • property():定义属性方法

示例:

class Student:
    def __init__(self, name): self.name = name

s = Student("Tom")
print(getattr(s, "name"))     # Tom
setattr(s, "age", 20)
print(s.age)                  # 20
print(isinstance(s, Student)) # True
print(callable(len))          # True

动态执行与调试

  • eval(expr):执行表达式
  • exec(code):执行代码
  • compile(source, filename, mode):编译代码
  • __import__(name):导入模块
  • help():帮助文档
  • breakpoint():进入调试器

示例:

expr = "3 + 5"
print(eval(expr))    # 8

code = "for i in range(3): print(i)"
exec(code)

内置函数的优势

  1. 减少代码量:比如 sum([1,2,3]) 比自己写循环加法简洁很多。
  2. 性能优化:内置函数是 C 语言实现的,比自己写的 Python 代码快。
  3. 可读性好max() 一看就懂,不需要解释。
  4. 跨平台稳定:由 Python 官方维护,跨版本稳定。

更多推荐