Python 从零开始 · 第 6 课:函数——把代码打包,随用随调
前面几课,我们写的代码都是"一条线走到底"。但真实程序往往有大量重复:比如算圆的面积,可能要算十几次。
# 不写函数,每次都重写一遍
area1 = 3.14 * 5 * 5
area2 = 3.14 * 8 * 8
area3 = 3.14 * 12 * 12三行还好,那要是 100 次呢?更糟的是,如果哪天发现"圆的面积公式记错了",得改 100 处。
函数就是来解决这个问题的:把一段代码打包、起个名字,以后喊名字就执行。
一、定义和调用函数
def 面积(半径):
return 3.14 * 半径 * 半径用 def 关键字定义,后面跟函数名、圆括号(放参数)、冒号,然后缩进写函数体。return 是"把结果交出去"。
调用就是喊名字:
def circle_area(r):
return 3.14 * r * r
print(circle_area(5)) # 78.5
print(circle_area(8)) # 200.96
print(circle_area(12)) # 452.16核心思想:写一次,到处用。 要改公式,只改函数里面那一个地方。
二、参数:函数的"输入口"
函数可以接受零个、一个、或多个参数:
def 打招呼():
print("你好") # 无参数
def 加法(a, b):
return a + b # 两个参数
print(加法(3, 5)) # 8参数还可以有默认值,调用时不传就用默认的:
def 打招呼(name="同学"):
print(f"你好,{name}")
打招呼() # 你好,同学
打招呼("张三") # 你好,张三注意:有默认值的参数要放在没默认值的参数后面,否则会报错。
三、返回值:函数的"输出口"
return 把结果交还给调用者。没有 return 的函数,默认返回 None:
def 打印(s):
print(s) # 只打印,没有 return
result = 打印("hi") # result 是 None
print(result) # None一个函数可以返回多个值(本质是返回一个元组):
def 求最值(nums):
return min(nums), max(nums)
a, b = 求最值([3, 1, 2])
print(a, b) # 1 3四、作用域:变量"管哪一片"
函数内部的变量是"私有的",外面看不到;外面定义的变量,函数里也只能读、不能随便改:
x = 10 # 全局变量
def 改一下():
y = 5 # 局部变量,只在函数内部有效
print(x) # 能读全局变量 x
改一下() # 10
print(y) # 报错!y 是函数内部的,外面看不到核心:函数内部的变量,出了函数就消失了。这是"封装"的基础——函数自己管自己的事,不污染外面的世界。
还有一个高频坑:在函数里给全局变量赋值,会报错——Python 会把它当成新的局部变量:
count = 0
def 加一():
count = count + 1 # 报错 UnboundLocalError!Python 以为 count 是局部变量
加一()真想改全局变量,得先用 global 声明(但尽量少用,函数最好靠参数和返回值沟通):
count = 0
def 加一():
global count # 声明"我要改外面的 count"
count = count + 1
加一()
print(count) # 1五、lambda:一行的小函数
有些函数特别短,比如"取平方",用 lambda 可以一行写完:
square = lambda x: x * x
print(square(5)) # 25
add = lambda a, b: a + b
print(add(3, 4)) # 7lambda 参数: 表达式,适合写一次性用的小函数,常配合 sorted、map、filter 使用(第 11 课细讲)。
六、把列表、字典传给函数
函数参数可以是任何类型,包括列表、字典:
def 总分(scores):
return sum(scores)
def 平均分(scores):
return sum(scores) / len(scores)
scores = [90, 85, 78]
print(总分(scores)) # 253
print(平均分(scores)) # 84.33...注意一个坑:列表是"可变"的,函数里改它,外面也会跟着变:
def 加一个(lst):
lst.append(99) # 直接改了传入的列表
nums = [1, 2, 3]
加一个(nums)
print(nums) # [1, 2, 3, 99] 外面也被改了!七、类型注解:给函数"上户口"
Python 是"动态类型"——变量是什么类型,运行前不强制声明。这让代码写得快,但也有代价:函数多了之后,你很难一眼看出某个参数该传什么、返回什么。比如 平均分(scores),scores 是列表还是字典?返回值是小数还是整数?只能靠猜或者看注释。
类型注解(typing) 就是在函数签名上"写明"参数和返回值的类型,让人和工具(编辑器、IDE)都能看懂:
def 加法(a: int, b: int) -> int:
return a + b
def 平均分(scores: list) -> float:
return sum(scores) / len(scores)
def 打招呼(name: str = "同学") -> None:
print(f"你好,{name}")语法很简单:
- 参数后面加
: 类型,如a: int - 返回值类型写在括号后面
-> 类型,没有返回就写-> None - 常用类型:
int、float、str、bool、list、dict、None
一个重要事实:注解只是"提示",Python 不会强制检查它——你传错类型,代码照样能跑(然后可能报别的错)。它的价值在于:
- 给人看:函数签名就是说明书,一眼看清输入输出。
- 给工具看:编辑器(VS Code、PyCharm)会根据注解自动补全、提示错误。
- 给未来的自己看:三个月后回来看代码,不用再猜参数类型。
更复杂的类型(比如"列表里装的是 int")可以用 typing 模块:
from typing import List, Dict, Optional
def 求和(nums: List[int]) -> int: # 整数列表
return sum(nums)
def 查分(成绩表: Dict[str, int], 名字: str) -> Optional[int]:
# Optional[int] 表示"要么 int,要么 None"
return 成绩表.get(名字)小结:类型注解不是给 Python 看的(它不看),是给人看的。团队协作、写库、写大项目时尤其重要,是从"能跑"走向"工程化"的第一步。
八、动手时间 🎯
实验 1:判断素数
def is_prime(n):
if n < 2:
return False
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
return False
return True
for i in range(1, 31):
if is_prime(i):
print(i, end=" ")
# 2 3 5 7 11 13 17 19 23 29实验 2:摄氏华氏转换器
def to_fahrenheit(c):
return c * 9 / 5 + 32
def to_celsius(f):
return (f - 32) * 5 / 9
print(to_fahrenheit(100)) # 212.0
print(to_celsius(212)) # 100.0实验 3:把前面的代码重构成函数
把第 3 课的"FizzBuzz"改写成函数版:
def fizzbuzz(n):
if n % 3 == 0 and n % 5 == 0:
return "FizzBuzz"
elif n % 3 == 0:
return "Fizz"
elif n % 5 == 0:
return "Buzz"
return str(n)
for i in range(1, 16):
print(fizzbuzz(i))九、小结
- 函数 = 打包代码 + 起名字,
def定义、喊名字调用,return交结果。 - 参数是输入、返回值是输出,默认参数要有默认值且放后面;函数内变量是私有的。
- lambda 写短函数;用类型注解(
def f(a: int) -> int)给函数"上户口",人和编辑器都看得懂;注意列表这种可变参数,函数里改它会连累外面。
下一课,我们学 字符串处理与文件读写——让程序能读文件、写文件,不再只能对着屏幕打印。
先把「素数判断」和「华氏转换器」敲熟,函数的手感就长在你脑子里了。