如果你连函数都写不出来, 那么你根本就没有进入到这门学问的门槛里面去。
函数, 它是编程里面的那个核心的抽象。你现在去写一个函数, 这就等于你去发明了一个新的动词。那其他的人, 包括那个未来的你自己在内, 也只需要去知道它是做什么事情的就行了。至于说它到底是怎么去做的这个问题, 是不需要去知道的。
今天我们将由浅入深, 从最基础的知识逐步过渡到更高级的内容, 把函数这个知识点讲透彻。
一、我们来了解一下关于函数的第一点一内容, 具体来说就是大家经常会问到的问题, 即究竟为什么要使用函数呢?
这段代码不需要用到函数。
# 计算三个圆的面积r1, r2, r3 = 5, 10, 15
area1 = 3.14159 * r1 ** 2
area2 = 3.14159 * r2 ** 2
area3 = 3.14159 * r3 ** 2
# 如果π需要更高精度,你要改3个地方...
使用函数这一类事物的代码内容来展开表述。
def circle_area(radius, pi=3.14159):"""计算圆的面积"""
return pi * radius ** 2
areas = [circle_area(r) for r in [5, 10, 15]]
# 改精度只需改一个地方
函数这种存在, 它的益处在哪里呢?
二、我们来定义和调用, 然后看一看2.1基本语法。
def greet(name):"""向指定的人打招呼""" # 这是文档字符串(docstring)
return f"你好,{name}!"
# 调用
result = greet("小明")
print(result) # 你好,小明!
2.2是关于文档这一方面的字符串内容。
这个内容应当被视为函数的说明书, 它需要去清楚地说明函数究竟是做什么工作的, 其中的参数分别是什么情况, 以及最终的返回值又是什么东西。
def calculate_bmi(height, weight):"""
计算BMI值并给出健康建议
Args:
height: 身高(米)
weight: 体重(公斤)
Returns:
dict: 包含BMI值和健康建议
Example:
>>> calculate_bmi(1.75, 70)
{'bmi': 22.86, 'advice': '正常'}
"""
bmi = weight / (height ** 2)
if bmi < 18.5:
advice = "偏瘦"
elif bmi < 24:
advice = "正常"
elif bmi < 28:
advice = "偏胖"
else:
advice = "肥胖"
return {"bmi": round(bmi, 2), "advice": advice}
好习惯:写 !它可以通过 help(函数名) 查看,配合 IDE 自动提示,是代码可读性的核心。
2.第三部分是关于语句这一内容的介绍。
# 返回一个值def add(a, b):
return a + b
# 返回多个值(自动打包成元组)
def min_max(numbers):
return min(numbers), max(numbers)
result = min_max([3, 1, 4, 1, 5])
print(result) # (1, 5)
mn, mx = result # 解包
print(mn, mx) # 1 5
# 没有 return 返回 None
def print_hello():
print("Hello") # 没有 return
result = print_hello() # Hello
print(result) # None
三、关于3.1这个位置参数的详细解释如下。
def introduce(name, age, city):print(f"我叫{name},今年{age}岁,住在{city}")
# 按位置传参
introduce("小明", 18, "北京")
3.2 关键字参数
# 按名称传参(顺序可以打乱)introduce(city="上海", name="小红", age=20)
# 混合使用:位置参数必须在关键字参数前面
introduce("小刚", city="广州", age=22) #
# introduce(city="广州", "小刚", 22) # SyntaxError
3.3 默认参数
def connect(host="localhost", port=8080, timeout=30):print(f"连接 {host}:{port},超时{timeout}秒")
connect() # 连接 localhost:8080,超时30秒
# 连接 example.com:8080,超时30秒
connect(port=9000) # 连接 localhost:9000,超时30秒
443, 10) # 全部自定义
️ 默认参数的陷阱:
# 大坑!默认参数只会在定义时计算一次
def add_item(item, items=[]):
items.append(item)
return items
print(add_item("a")) # ['a']
print(add_item("b")) # ['a', 'b'] ← 不是预期的 ['b']!
# 正确做法:用 None + 在函数内部创建
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
3.第四个知识点是关于可变参数。
# *args:接收任意数量的位置参数(打包成元组)def sum_all(*args):
print(f"参数:{args}")
return sum(args)
print(sum_all(1, 2, 3, 4, 5)) # 15
# **kwargs:接收任意数量的关键字参数(打包成字典)
def print_kwargs(**kwargs):
for key, value in kwargs.items():
print(f"{key} = {value}")
print_kwargs(name="小明", age=18, city="北京")
# name = 小明
# age = 18
# city = 北京
# 组合使用
def api_request(url, *args, method="GET", **kwargs):
print(f"请求:{method} {url}")
print(f"额外参数:{args}")
print(f"查询参数:{kwargs}")
api_request("/api/data", "extra", method="POST", page=1, size=20)
3.5 关键字仅可用的参数这一内容, 总共包含了 3 个特性。
# * 后面的参数必须用关键字传递def create_user(name, age, *, city, phone):
print(f"创建用户:{name}, {age}岁")
print(f"城市:{city}, 电话:{phone}")
create_user("小明", 18, city="北京", phone="13800138000") #
# create_user("小明", 18, "北京", "13800138000") # TypeError
这种设计强制调用者写清楚参数名, 避免 ("小明", 18"北京""") 这种"天书调用"。
四、作用域与命名空间4.1 LEGB 规则
查找变量的顺序是这样规定的, 先是局部变量, 然后是非局部变量, 接着是全局变量, 最后才会去内置变量里面找。
# 全局作用域x = "global"
def outer():
# 外层函数作用域
x = "outer"
def inner():
# 局部作用域
x = "inner"
print(f"inner: {x}") # inner: inner
inner()
print(f"outer: {x}") # outer: outer
outer()
print(f"global: {x}") # global: global
4.2 和
count = 0def increment():
global count # 告诉 Python:我要改全局的 count
count += 1
increment()
print(count) # 1
# nonlocal:修改外层函数的变量
def outer():
x = 10
def inner():
nonlocal x
x += 5
inner()
print(x) # 15
我们要尽量少用全局变量。函数最好写成独立完整的模样, 也就是让它自己包含自己需要的东西。把输入参数交进去, 然后得到输出的结果。要是用了全局变量的话, 代码就会变得让人不容易理解, 也不太容易去调试排查问题。
五、所谓匿名函数,就是那种没有名字的函数()。
那是一行函数, 是比较合适的, 适用于那种简单的操作内容。
# 普通函数def square(x):
return x ** 2
# lambda 等效
square = lambda x: x ** 2
# lambda 的真正用途是作为参数传递
# 配合 sorted()
students = [
("小明", 85),
("小红", 92),
("小刚", 78),
]
# 按分数排序
sorted_students = sorted(students, key=lambda s: s[1], reverse=True)
print(sorted_students) # [('小红', 92), ('小明', 85), ('小刚', 78)]
# 配合 filter()
numbers = [1, 2, 3, 4, 5, 6]
even = list(filter(lambda x: x % 2 == 0, numbers))
print(even) # [2, 4, 6]
# 配合 map()
squared = list(map(lambda x: x ** 2, numbers))
print(squared) # [1, 4, 9, 16, 25, 36]
这一条经验之谈是这么来的: 它比较适合用来处理那些只有一到两行代码的简单逻辑判断。如果遇到的情况是逻辑非常复杂, 那么还是用普通的 功能命名的函数来写更为合适。
六、高阶函数
这个函数是一个第一等级的公民, 它能够让使用者将普通变量视为等同物来进行相互之间的传递操作。
# 函数作为参数def apply_twice(func, arg):
return func(func(arg))
def add_one(x):
return x + 1
print(apply_twice(add_one, 5)) # 7
# 函数作为返回值(闭包)
def make_power(n):
def power(x):
return x ** n
return power
square = make_power(2)
cube = make_power(3)
print(square(5)) # 25
print(cube(5)) # 125
# 装饰器基础
def timer(func):
"""计算函数执行时间"""
import time
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} 耗时:{elapsed:.4f}秒")
return result
return wrapper
@timer
def slow_function():
import time
time.sleep(0.5)
return "完成"
slow_function() # 输出:slow_function 耗时:0.5003秒
七、实战:工具箱
# utils.py — 你的个人工具箱import time
from functools import wraps
def retry(max_attempts=3, delay=1):
"""
重试装饰器:函数执行失败时自动重试
Args:
max_attempts: 最大重试次数
delay: 重试间隔(秒)
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
last_exception = None
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except Exception as e:
last_exception = e
print(f"第{attempt}次尝试失败:{e}")
if attempt < max_attempts:
time.sleep(delay)
raise last_exception
return wrapper
return decorator
# 数据验证函数
def validate_email(email):
"""简单验证邮箱格式"""
import re
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}#39;
return bool(re.match(pattern, email))
def validate_phone(phone):
"""验证中国大陆手机号"""
import re
# 1开头,第二位3-9,后面9位数字
pattern = r'^1[3-9]\d{9}#39;
return bool(re.match(pattern, phone))
# 数据转换函数
def chunk_list(data, chunk_size):
"""将列表分割成固定大小的块"""
return [data[i:i + chunk_size] for i in range(0, len(data), chunk_size)]
def flatten(nested_list):
"""展平嵌套列表"""
result = []
for item in nested_list:
if isinstance(item, list):
result.extend(flatten(item)) # 递归展平
else:
result.append(item)
return result
# 使用示例
@retry(max_attempts=3, delay=0.5)
def unstable_api_call(url):
"""模拟不稳定的API调用"""
import random
if random.random() < 0.7: # 70%概率失败
raise ConnectionError("网络错误")
return f"数据来自 {url}"
# 测试
result = unstable_api_call("http://api.example.com/data")
print(f"结果:{result}")
# 其他工具测试
data = [1, 2, 3, 4, 5, 6, 7, 8]
print(chunk_list(data, 3)) # [[1, 2, 3], [4, 5, 6], [7, 8]]
nested = [1, [2, [3, 4], 5], 6]
print(flatten(nested)) # [1, 2, 3, 4, 5, 6]
# True
print(validate_phone("13800138000")) # True
总结
概念
要点
定义
定义一个名为函数名的代码块, 并让它在内部接收参数。
返回值
它可以将各种类型的结果给带回来, 要是没有返回的具体东西, 那么就让它去返回那个叫作 None 的状态。
位置参数
按顺序传参
关键字参数
使用 name=value 的传参方式, 其中的顺序是可以被打乱的。
默认参数
这个内容在定义的时候就需要进行指定, 在这里务必要注意到可变对象所带来的陷阱。
*args
接收任意数量位置参数
**
接收任意数量关键字参数
作用域
LEGB规则指的是搜索顺序, 具体来说是先从局部开始进行查找, 接着再找内置名称。
一行匿名函数
装饰器
函数增强器,@语法糖
下一篇预告是关于文件操作的内容, 具体将涉及文件的读取和写入环节, 这一部分我们是十分认真的。
你学会了函数, 这意味着你已经从被称为“脚本小子”的人, 进化成为了真正的程序员。