前面几课,我们写的代码都是"一条线走到底"。但真实程序往往有大量重复:比如算圆的面积,可能要算十几次。

# 不写函数,每次都重写一遍
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))    # 7

lambda 参数: 表达式,适合写一次性用的小函数,常配合 sortedmapfilter 使用(第 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
  • 常用类型:intfloatstrboollistdictNone

一个重要事实:注解只是"提示",Python 不会强制检查它——你传错类型,代码照样能跑(然后可能报别的错)。它的价值在于:

  1. 给人看:函数签名就是说明书,一眼看清输入输出。
  2. 给工具看:编辑器(VS Code、PyCharm)会根据注解自动补全、提示错误。
  3. 给未来的自己看:三个月后回来看代码,不用再猜参数类型。

更复杂的类型(比如"列表里装的是 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))

九、小结

  1. 函数 = 打包代码 + 起名字def 定义、喊名字调用,return 交结果。
  2. 参数是输入、返回值是输出,默认参数要有默认值且放后面;函数内变量是私有的。
  3. lambda 写短函数;用类型注解def f(a: int) -> int)给函数"上户口",人和编辑器都看得懂;注意列表这种可变参数,函数里改它会连累外面。

下一课,我们学 字符串处理与文件读写——让程序能读文件、写文件,不再只能对着屏幕打印。

先把「素数判断」和「华氏转换器」敲熟,函数的手感就长在你脑子里了。

标签: none

添加新评论