【Python】函数
函数
函数基本概念
为什么要函数?
在写程序时,我们经常会遇到 重复的逻辑。比如:
# 打印 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)
优点:
- 复用性:写一次,用多次
- 可读性:逻辑被封装成“模块”
- 可维护性:修改时只改函数内部,不用全局改
Python 提供了三大类函数:
-
内置函数(Built-in Functions)
- Python 解释器启动时自动加载,随时可用。
- 示例:
print(),len(),sum(),int()
print(len([1, 2, 3])) # 3 print(sum([1, 2, 3])) # 6 -
内置模块中的函数
- Python 标准库包含大量模块,每个模块定义了一组函数。
- 这些函数不是自动可用的,需要先
import才能使用。
import math print(math.sqrt(16)) # 4.0 print(math.factorial(5)) # 120 -
用户自定义函数
- 开发者根据需求编写的函数,灵活封装逻辑。
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))
执行步骤:
- 解释器读到
def foo(x): ...→ 在内存里创建一个函数对象,并把它绑定到名字foo上。 - 调用
foo(5)时,Python:- 创建一个“调用栈帧”保存运行信息。
- 把
5传递给形参x。 - 执行函数体,算出结果 10。
- 执行
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 是专业程序员的习惯,方便 自动生成文档、团队协作。
注意事项
-
必须先定义再调用
say_hello() # 报错,未定义 def say_hello(): print("hi") -
函数名可被覆盖
def hi(): print("hi") hi = 123 hi() # 报错,int 不可调用 -
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) # ✅
任意参数(可变参数)
-
*args:任意位置参数接收任意数量的位置参数,形成一个 元组。
def add(*args): return sum(args) print(add(10, 20, 30)) # 60 print(add(1, 2, 3, 4)) # 10 -
**kwargs:任意关键字参数👉 接收任意数量的关键字参数,形成一个 字典。
def addr(**kwargs): for k, v in kwargs.items(): print(f"{k}:{v}") addr(Name="John", City="Mumbai")输出:
Name: John City: Mumbai -
混合使用
参数顺序必须是:
位置参数 → 默认参数 → *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}
参数解包传参
-
用
*解包序列def add(a, b, c): return a + b + c nums = [1, 2, 3] print(add(*nums)) # 相当于 add(1, 2, 3) -
用
**解包字典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 自带,比如
len、sum。
局部变量(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
如果不用 nonlocal,inner 里的 a 就只是一个新的局部变量。
作用域规则:LEGB 原则
Python 查找变量的顺序遵循 LEGB:
- L(Local)局部作用域 —— 函数内部
- E(Enclosing)外部嵌套函数作用域 —— 闭包里的变量
- G(Global)全局作用域 —— 模块级变量
- B(Built-in)内置作用域 —— Python 内置的变量(如
len、sum)
例子:
x = "global" # 全局变量
def outer():
x = "enclosing" # 外层函数变量
def inner():
x = "local" # 局部变量
print(x)
inner()
outer()
输出:
local
如果 inner 里没定义 x,就会逐层往上找(E → G → B),直到全局或内置。
命名空间(Namespace)
命名空间就是“名字到对象的映射表”。
Python 有三类命名空间:
- 内置命名空间:加载 Python 解释器时就存在(如
len、print)。 - 全局命名空间:模块级的变量、函数。
- 局部命名空间:函数内部的变量。
命名空间是 嵌套关系:内置 ⊂ ⊂ ⊂ 全局 ⊂ ⊂ ⊂ 局部
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}
命名空间冲突与错误
-
同名变量:局部 > 全局
marks = 50 def myfunction(): marks = 70 print(marks) myfunction() print(marks)输出:
70 50 -
未声明就修改全局变量 → 报错
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 - 用
-
局部变量在函数外访问 → 报错
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
常见内置高阶函数与用法
-
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] -
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] -
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,在某些场景仍有用。 -
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')) -
其他实用函数
-
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 来计算。
闭包的特性
- 内部函数引用外部函数的变量
这让函数更灵活,不必每次都传入重复参数。 - 外部函数执行结束后,变量依然存在
一般情况下,函数执行完后,局部变量会被销毁。
但闭包“抓住”了它们,使得变量得以保留。 - 可以形成工厂函数(函数返回函数)
常用于生成带特定配置的函数。
闭包的应用场景
-
创建带参数的函数工厂
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 -
数据隐藏(保护变量)
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()调用来修改。
注意事项
- 如果内部函数需要修改外部函数的变量,必须用
nonlocal关键字声明。 - 闭包可能会导致 内存占用,因为它会保存外部变量。
闭包的本质就是:
- 外部函数执行时,创建了一个内部函数。
- 外部函数并不直接返回结果,而是返回内部函数这个“函数对象”。
- 内部函数对象带着它用到的外部函数变量(形成“自由变量环境”)一起返回给外部。
- 外部接收到的其实就是一个“变量”,只不过这个变量是一个函数,可以继续调用。
形象比喻:
- 外部函数就像一个工厂,工厂里生产了一个“定制的小机器”(内部函数)。
- 这个小机器需要用到工厂里的某些零件(外部变量)。
- 工厂停工了(外部函数执行结束),但是小机器被送了出来,零件也一起打包带出来了。
- 所以后面你在别的地方用小机器,它还能正常运作。
匿名函数 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 常见应用场景
-
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 -
sorted/list.sort的key:people = [{"name":"a","age":30},{"name":"b","age":25}] sorted(people, key=lambda p: p["age"]) # 按 age 升序 -
简短回调(GUI、线程、事件)或字典映射操作:
ops = {'+': lambda a,b: a+b, '-': lambda a,b: a-b} ops['+'](3,4) # 7 -
生成闭包 / 立即返回一个函数(和
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=...)、map、filter、简短回调)。 - 想要把简单功能内联写在一行里。
- 短小、一次性、在表达式中直接使用的函数(例如
- 何时不用
lambda(建议用def):- 功能复杂、需要多行逻辑、需要注释或文档时;
- 需要在栈跟踪中看到可读函数名时(调试友好性);
- 需要写类型注解或更复杂签名时(
def更灵活、易读)。
PEP8 的建议:对于复杂行为或多行逻辑,用
def;lambda适用于非常短的匿名函数。
装饰器
在开发中,我们经常会遇到这种情况:
- 想在函数执行前后 增加一些额外功能,比如:打印日志、统计执行时间、检查权限、缓存结果……
- 但是又不想修改原函数的代码(保证原逻辑独立、可复用)。
装饰器就是为了解决这个问题:它允许你在 不改变函数本身代码 的前提下,给函数 动态增加功能。
装饰器的本质就是:
- 函数作为参数传递
- 返回一个新的函数(闭包)
基本形式:
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) # 手动装饰
装饰器示例
-
打印日志
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! -
统计执行时间
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 秒 -
带参数的装饰器
如果装饰器本身也需要参数,就需要再套一层函数:
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()输出:
你好! 你好! 你好!
装饰器的典型应用
- 日志:跟踪函数调用
- 权限验证:判断用户是否有权限执行
- 缓存:保存计算结果,避免重复计算
- 性能分析:统计函数执行时间
- 框架应用: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
图解关键点
@log语法糖:相当于hello = log(hello),所以真正被调用的不是原来的hello,而是wrapper。- 闭包:
wrapper内部保存了对func(原函数)的引用,所以还能调用原函数。 - 增强功能:在调用前后加了打印,就是装饰器的扩展逻辑。
函数说明文档
什么是函数的说明文档(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(参数名: 注释表达式, ...) -> 返回值注释:
函数体
要点:
- 注释是任意 Python 表达式(不仅限于类型)。
- 注释不会影响函数执行(解释器不会检查类型),只是存储在函数对象的
__annotations__属性中。 - 注释主要用于:
- 文档用途(让代码更易懂)。
- 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说明希望a是int。- 但 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)
内置函数的优势
- 减少代码量:比如
sum([1,2,3])比自己写循环加法简洁很多。 - 性能优化:内置函数是 C 语言实现的,比自己写的 Python 代码快。
- 可读性好:
max()一看就懂,不需要解释。 - 跨平台稳定:由 Python 官方维护,跨版本稳定。
更多推荐
所有评论(0)