【Python基础篇-04】深入理解python的函数、参数与作用域

深入理解python的函数、参数与作用域

  • 一、函数、参数与作用域
    • [1. 定义与调用函数](#1. 定义与调用函数)
      • [1.1 第一个函数](#1.1 第一个函数)
      • [1.2 函数名称](#1.2 函数名称)
      • [1.3 函数调用前,定义必须已经执行](#1.3 函数调用前,定义必须已经执行)
    • [2. 参数与返回值](#2. 参数与返回值)
      • [2.1 使用参数传入数据](#2.1 使用参数传入数据)
      • [2.2 多个参数](#2.2 多个参数)
      • [2.3 使用 return 返回结果](#2.3 使用 return 返回结果)
      • [2.4 print 与 return 的区别](#2.4 print 与 return 的区别)
      • [2.5 return 会结束当前函数调用](#2.5 return 会结束当前函数调用)
      • [2.6 返回多个结果](#2.6 返回多个结果)
    • [3. 常用参数形式](#3. 常用参数形式)
      • [3.1 位置参数](#3.1 位置参数)
      • [3.2 关键字参数](#3.2 关键字参数)
      • [3.3 默认参数](#3.3 默认参数)
      • [3.4 不要使用可变对象作为普通默认值](#3.4 不要使用可变对象作为普通默认值)
      • [3.5 仅限关键字参数](#3.5 仅限关键字参数)
      • [3.6 仅限位置参数](#3.6 仅限位置参数)
    • [4. 可变数量参数与解包](#4. 可变数量参数与解包)
      • [4.1 *args:接收额外的位置参数](#4.1 *args:接收额外的位置参数)
      • [4.2 kwargs:接收额外的关键字参数](#4.2 kwargs:接收额外的关键字参数)
      • [4.3 参数组合示例](#4.3 参数组合示例)
      • [4.4 调用时使用 * 解包](#4.4 调用时使用 * 解包)
      • [4.5 调用时使用 ** 解包](#4.5 调用时使用 ** 解包)
    • [5. 参数传递与可变对象](#5. 参数传递与可变对象)
      • [5.1 形参会绑定到传入的对象](#5.1 形参会绑定到传入的对象)
      • [5.2 修改列表会影响调用方](#5.2 修改列表会影响调用方)
      • [5.3 重新赋值不会替换调用方的变量](#5.3 重新赋值不会替换调用方的变量)
      • [5.4 不可变对象通常通过返回新值更新](#5.4 不可变对象通常通过返回新值更新)
      • [5.5 明确函数是否修改输入](#5.5 明确函数是否修改输入)
    • [6. 变量作用域](#6. 变量作用域)
      • [6.1 局部变量与全局变量](#6.1 局部变量与全局变量)
      • [6.2 名称查找顺序:LEGB](#6.2 名称查找顺序:LEGB)
      • [6.3 函数内赋值可能导致 UnboundLocalError](#6.3 函数内赋值可能导致 UnboundLocalError)
      • [6.4 global:重新绑定全局变量](#6.4 global:重新绑定全局变量)
      • [6.5 nonlocal:重新绑定外层函数变量](#6.5 nonlocal:重新绑定外层函数变量)
    • [7. 函数也是对象](#7. 函数也是对象)
      • [7.1 函数可以赋给变量](#7.1 函数可以赋给变量)
      • [7.2 函数可以作为参数](#7.2 函数可以作为参数)
      • [7.3 lambda:简单的匿名函数](#7.3 lambda:简单的匿名函数)
      • [7.4 使用 key 指定排序规则](#7.4 使用 key 指定排序规则)
      • [7.5 函数可以作为返回值:闭包初步](#7.5 函数可以作为返回值:闭包初步)
    • [8. 文档字符串与类型注解](#8. 文档字符串与类型注解)
      • [8.1 文档字符串](#8.1 文档字符串)
      • [8.2 类型注解](#8.2 类型注解)
      • [8.3 容器与可选返回值](#8.3 容器与可选返回值)

如需转载,请附上链接:https://zhenghuisheng.blog.csdn.net/article/details/166746555

一、函数、参数与作用域

上一篇,我们学习了列表、元组、字典和集合,并使用"列表 + 字典"完成了商品查询、筛选和统计。随着功能增加,代码也会越来越长。例如,查询商品时要遍历列表,统计库存时又要遍历列表。如果同一种操作需要在多个地方使用,直接复制代码会让修改和维护变得困难。

函数可以把一段具有明确用途的代码组织起来,为它起一个名字,然后在需要时调用。本篇将学习:

  • 如何定义和调用函数。
  • 如何传入参数、返回结果。
  • 位置参数、关键字参数和默认参数。
  • *args、**kwargs 与参数解包。
  • 变量作用域与可变对象传参。
  • 函数对象、lambda 和类型注解。
  • 如何把商品管理代码拆分成可复用函数。

1. 定义与调用函数

1.1 第一个函数

使用 def 定义函数:

python 复制代码
def 函数名(参数列表):
    函数体

例如:

python 复制代码
def say_hello():
    print("你好,Python!")
if __name__ == '__main__':
    say_hello()
    say_hello()

运行结果:

复制代码
你好,Python!
你好,Python!

其中:

  • def 表示定义函数。
  • say_hello 是函数名称。
  • () 中可以声明参数,本例没有参数。
  • 冒号后面缩进的代码属于函数体。
  • say_hello() 表示调用函数。

定义函数不会立即执行函数体,调用时才会执行。

python 复制代码
def say_hello():
    print("你好,Python!")
if __name__ == '__main__':
    print("程序开始")

这段代码只输出:

复制代码
程序开始

1.2 函数名称

函数名遵循标识符规则,通常使用小写字母和下划线:

复制代码
find_product
calculate_total
get_user_name

函数名应表达它负责的操作。例如:

复制代码
def calculate_total():
    ...

比:

复制代码
def handle():
    ...

更容易理解。也要避免使用内置函数名称作为自己的函数名或变量名,例如 list、sum、max,否则会遮蔽原来的内置名称。

1.3 函数调用前,定义必须已经执行

下面的顺序可以正常运行:

python 复制代码
def greet():
    print("你好")
greet()

反过来则会出现 NameError:

python 复制代码
greet()
def greet():
    print("你好")

这里的关键是运行顺序:执行 greet() 时,名称 greet 还没有绑定到函数。因此,常见的程序组织方式是:

python 复制代码
def function_a():
    ...
def function_b():
if __name__ == '__main__':
    function_a()
    function_b()

2. 参数与返回值

2.1 使用参数传入数据

如果每次调用都要处理不同的数据,可以给函数定义参数:

python 复制代码
def greet(name):
    print(f"你好,{name}!")
if __name__ == '__main__':
    greet("小林")
    greet("小陈")

运行结果:

复制代码
你好,小林!
你好,小陈!

定义中的 name 称为形参 ,调用时传入的 "小林"、"小陈" 称为实参。可以把形参理解为函数内部用于接收输入数据的名字。

2.2 多个参数

python 复制代码
def show_product(name, price, stock):
    print(f"{name}:¥{price:.2f},库存 {stock}")
if __name__ == '__main__':
    show_product("矿泉水", 2, 100)

运行结果:

复制代码
矿泉水:¥2.00,库存 100

按位置传参时,对应关系是:

实参 形参
"矿泉水" name
2 price
100 stock

2.3 使用 return 返回结果

如果函数计算的结果需要交给其他代码继续使用,应当使用 return:

python 复制代码
def calculate_total(price, quantity):
    return price * quantity
if __name__ == '__main__':
    total = calculate_total(8, 3)
    print(f"商品金额:{total}")
    print(f"加上 5 后:{total + 5}")

运行结果:

复制代码
商品金额:24
加上 5 后:29

return 将结果交给调用方,调用方可以保存、显示或继续计算。

2.4 print 与 return 的区别

print() 用于输出信息,return 用于返回结果。

python 复制代码
def add_and_print(a, b):
    print(a + b)
def add_and_return(a, b):
    return a + b
if __name__ == '__main__':
    first = add_and_print(2, 3)
    print(f"first = {first}")
    second = add_and_return(2, 3)
    print(f"second = {second}")

运行结果:

复制代码
5
first = None
second = 5

第一个函数虽然输出了 5,但没有通过 return 返回这个值,因此调用结果是 None。函数正常执行到末尾而没有显式返回值时,会返回 **None****。**下面两种情况也都会返回 None:

python 复制代码
def do_something():
    pass
def do_something():
    return

2.5 return 会结束当前函数调用

执行 return 后,当前函数调用就结束了,后面的普通语句不再执行。

python 复制代码
def get_level(score):
    if not 0 <= score <= 100:
        return "成绩不合法"

    if score >= 90:
        return "优秀"

    if score >= 80:
        return "良好"

    if score >= 60:
        return "及格"

    return "不及格"
if __name__ == '__main__':
    print(get_level(85))
    print(get_level(101))

运行结果:

复制代码
良好
成绩不合法

这种写法先处理特殊情况,再处理正常逻辑,可以减少不必要的嵌套。注意:如果函数中包含循环,return 会直接结束整个函数调用,而不仅仅是退出循环。只想结束循环时,应使用 break。

2.6 返回多个结果

Python 可以使用逗号返回多个值:

python 复制代码
def get_bounds(numbers):
    return min(numbers), max(numbers)
if __name__ == '__main__':
    result = get_bounds([8, 3, 12, 5])
    print(result)
    minimum, maximum = result
    print(f"最小值:{minimum}")
    print(f"最大值:{maximum}")

运行结果:

复制代码
(3, 12)
最小值:3
最大值:12

实际上:

复制代码
return min(numbers), max(numbers)

返回的是一个元组。调用方可以通过解包取得各个结果

3. 常用参数形式

3.1 位置参数

按照参数声明的顺序传入:

python 复制代码
def introduce(name, age):
    print(f"{name},{age} 岁")
if __name__ == '__main__':
    introduce("小林", 25)

运行结果:

复制代码
小林,25 岁

如果顺序写反,函数通常无法替你判断业务含义:

复制代码
introduce(25, "小林")

结果会变成:

复制代码
25,小林 岁

所以位置参数较多时,需要特别注意顺序。

3.2 关键字参数

调用时写出参数名称,可以让含义更明确:

python 复制代码
def introduce(name, age):
    print(f"{name},{age} 岁")
if __name__ == '__main__':
    introduce(name="小林", age=25)
    introduce(age=25, name="小林")

运行结果:

复制代码
小林,25 岁
小林,25 岁

关键字参数按名称匹配,因此可以调整书写顺序。普通的位置参数和关键字参数可以混用,但直接书写时,应把位置参数放在关键字参数之前:

复制代码
introduce("小林", age=25)

不要给同一个参数重复赋值:

复制代码
introduce("小林", name="小陈", age=25)

这里 name 同时收到了位置实参和关键字实参,会抛出 TypeError。

3.3 默认参数

定义函数时,可以为参数设置默认值:

python 复制代码
def greet(name, greeting="你好"):
    return f"{greeting},{name}!"
if __name__ == '__main__':
    print(greet("小林"))
    print(greet("小陈", "早上好"))
    print(greet("小周", greeting="欢迎"))

运行结果:

复制代码
你好,小林!
早上好,小陈!
欢迎,小周!

没有传入 greeting 时,使用默认值 "你好";显式传入时,使用传入值。对于普通的位置或关键字参数,必填参数需要放在有默认值的参数前面:

复制代码
def greet(name, greeting="你好"):
    ...

下面这种定义不合法:

复制代码
def greet(greeting="你好", name):
    ...

3.4 不要使用可变对象作为普通默认值

下面的函数存在隐患:

python 复制代码
def add_tag(tag, tags=[]):
    tags.append(tag)
    return tags
if __name__ == '__main__':
    print(add_tag("Python"))
    print(add_tag("SQL"))

运行结果:

复制代码
['Python']
['Python', 'SQL']

第二次调用没有得到新的空列表,而是继续使用上一次的列表。原因是:

默认参数表达式在执行函数定义时求值,并不是每次调用时重新求值。

因此,这个默认列表会被多次调用共享。通常应使用 None,再在函数内部创建列表:

python 复制代码
def add_tag(tag, tags=None):
    if tags is None:
        tags = []
    tags.append(tag)
    return tags
if __name__ == '__main__':
    print(add_tag("Python"))
    print(add_tag("SQL"))

运行结果:

复制代码
['Python']
['SQL']

这里应使用:

复制代码
if tags is None:

而不是:

复制代码
if not tags:

后者还会把调用方主动传入的空列表当成需要替换的情况。

3.5 仅限关键字参数

在参数列表中使用单独的 *,可以要求后面的参数只能按名称传入:

python 复制代码
def create_user(name, *, active=True):
    return {
        "name": name,
        "active": active
    }

if __name__ == '__main__':
    print(create_user("小林", active=False))

运行结果:

复制代码
{'name': '小林', 'active': False}

下面的调用不允许:

复制代码
create_user("小林", False)

因为 active 被声明为仅限关键字参数。

这种方式适合含义不够明显的开关:

复制代码
create_user("小林", active=False)

比单独传入一个 False 更容易理解。

仅限关键字参数也可以是必填项:

复制代码
def send_message(content, *, recipient):
    print(f"发送给 {recipient}:{content}")

调用时必须显式提供 recipient。

3.6 仅限位置参数

Python 3.8 及以上支持使用 /,将它前面的参数设为仅限位置参数:

python 复制代码
def divide(a, b, /):
    return a / b

if __name__ == '__main__':
    print(divide(10, 2))

运行结果:

复制代码
5.0

不能这样调用:

复制代码
divide(a=10, b=2)

将两种限制放在一起:

python 复制代码
def example(a, /, b, *, c):
    return a + b + c

对应关系:

参数 允许的传参方式
a 只能按位置
b 按位置或按关键字
c 只能按关键字

合法调用:

复制代码
example(1, 2, c=3)
example(1, b=2, c=3)

入门阶段不必在所有函数中使用这些限制,但阅读第三方函数签名时需要认识它们。

4. 可变数量参数与解包

4.1 *args:接收额外的位置参数

如果参数数量不固定,可以使用 *args:

python 复制代码
def add_all(*numbers):
    print(numbers)
    return sum(numbers)

if __name__ == '__main__':
    print(add_all(10, 20, 30))
    print(add_all())

运行结果:

复制代码
(10, 20, 30)
60
()
0

numbers 在函数内部是元组。

args 不是关键字,只是常见名称。真正起作用的是前面的 *,因此也可以写成:

复制代码
def add_all(*values):
    ...

它可以与普通参数组合:

python 复制代码
def show_scores(name, *scores):
    print(f"{name}:{scores}")

if __name__ == '__main__':
    show_scores("小林", 85, 92, 78)

运行结果:

复制代码
小林:(85, 92, 78)

第一个实参交给 name,剩余的位置实参收集到 scores。

4.2 kwargs:接收额外的关键字参数

python 复制代码
def show_options(**options):
    print(options)

if __name__ == '__main__':
    show_options(theme="dark", page_size=20)

运行结果:

复制代码
{'theme': 'dark', 'page_size': 20}

options 在函数内部是字典。

与 args 一样,kwargs 也是命名惯例,不是固定关键字。** 才表示收集额外的关键字参数。

4.3 参数组合示例

python 复制代码
def describe(name, *tags, active=True, **extra):
    print(f"名称:{name}")
    print(f"标签:{tags}")
    print(f"启用:{active}")
    print(f"其他:{extra}")

if __name__ == '__main__':
    describe(
        "小林",
        "Python",
        "SQL",
        active=False,
        city="深圳"
    )

运行结果:

复制代码
名称:小林
标签:('Python', 'SQL')
启用:False
其他:{'city': '深圳'}

参数对应关系:

  • name 接收第一个位置实参。
  • tags 收集剩余位置实参。
  • active 是 *tags 后面的仅限关键字参数。
  • extra 收集尚未匹配的关键字实参。

不需要为了"灵活"而让所有函数都接收 *args 和 **kwargs。参数明确时,直接写出名称更容易理解,也更容易发现调用错误。

4.4 调用时使用 * 解包

定义函数时,* 可以收集参数;调用函数时,* 可以展开参数。

python 复制代码
def add(a, b, c):
    return a + b + c

if __name__ == '__main__':
    numbers = [10, 20, 30]

    print(add(*numbers))

运行结果:

复制代码
60

这里:

复制代码
add(*numbers)

相当于:

复制代码
add(10, 20, 30)

如果直接写:

复制代码
add(numbers)

则只传入了一个列表,并不会自动分配给三个参数。

4.5 调用时使用 ** 解包

python 复制代码
def introduce(name, age):
    return f"{name},{age} 岁"

if __name__ == '__main__':
    user = {
        "name": "小林",
        "age": 25
    }
    print(introduce(**user))

运行结果:

复制代码
小林,25 岁

相当于:

复制代码
introduce(name="小林", age=25)

通过 ** 展开为关键字参数时,键必须是字符串,参数名称也需要符合函数定义;多余参数需要有相应的 **kwargs 接收。同一个参数不能通过不同方式重复传入,否则会抛出 TypeError。

5. 参数传递与可变对象

5.1 形参会绑定到传入的对象

Python 调用函数时,会让形参名称绑定到实参所引用的对象。可以先记住两条规则:

  • 修改共享的可变对象,调用方能够看到变化。
  • 给形参重新赋值,不会改变调用方变量的绑定。

5.2 修改列表会影响调用方

python 复制代码
def add_product(products, name):
    products.append(name)

if __name__ == '__main__':
    items = ["矿泉水"]
    add_product(items, "可乐")
    print(items)

运行结果:

复制代码
['矿泉水', '可乐']

函数中的 products 与外部的 items 引用同一个列表,append() 修改了这个列表。

5.3 重新赋值不会替换调用方的变量

python 复制代码
def replace_products(products):
    products = ["饼干"]
    print(f"函数内部:{products}")
if __name__ == '__main__':
    items = ["矿泉水"]
    replace_products(items)
    print(f"函数外部:{items}")

运行结果:

复制代码
函数内部:['饼干']
函数外部:['矿泉水']

products = ["饼干"] 只是让局部名称 products 引用一个新列表,不会让外部的 items 跟着改变。

5.4 不可变对象通常通过返回新值更新

python 复制代码
def increase(number):
    number += 1
    return number

if __name__ == '__main__':
    count = 10
    result = increase(count)
    print(count)
    print(result)

运行结果:

复制代码
10
11

整数不可变,number += 1 使局部名称绑定到计算后的整数,不会修改原来的整数对象。如果希望更新外部变量,需要接收返回值:

复制代码
count = increase(count)

5.5 明确函数是否修改输入

函数设计时,应尽量让调用者容易判断它是否会修改传入数据。

例如,返回一个新增标签后的列表,同时保留原列表:

python 复制代码
def with_tag(tags, tag):
    result = tags.copy()
    result.append(tag)
    return result

if __name__ == '__main__':
    original = ["Python"]
    updated = with_tag(original, "SQL")

    print(original)
    print(updated)

运行结果:

复制代码
['Python']
['Python', 'SQL']

这里使用浅拷贝已经足够,因为只是向外层列表增加字符串。

如果还要修改内部嵌套的可变对象,就需要进一步考虑是否深拷贝。

6. 变量作用域

6.1 局部变量与全局变量

函数内部创建的变量通常是局部变量,只能在对应函数调用中使用。

python 复制代码
message = "全局消息"
def show_message():
    message = "局部消息"
    print(message)
if __name__ == '__main__':
    show_message()
    print(message)

运行结果:

复制代码
局部消息
全局消息

函数内部的 message 与外部的 message 是不同作用域中的名称。

6.2 名称查找顺序:LEGB

对于普通函数中的名称查找,可以按 LEGB 理解:

层级 含义
Local 当前函数的局部作用域
Enclosing 外层函数的作用域
Global 当前模块的全局作用域
Built-in 内置名称,例如 len、sum

从近到远查找,找到后就使用对应的绑定。

python 复制代码
label = "全局"
def outer():
    label = "外层"
    def inner():
        print(label)
    inner()
if __name__ == '__main__':
    outer()

运行结果:

复制代码
外层

inner() 内部没有定义 label,于是找到外层函数中的 label。

6.3 函数内赋值可能导致 UnboundLocalError

下面的代码会报错:

python 复制代码
count = 10
def increase():
    count += 1
if __name__ == '__main__':
    increase()

原因是函数内存在对 count 的赋值,Python 因此将它视为局部名称。

复制代码
count += 1

需要先读取局部 count,而它还没有被赋值,所以抛出 UnboundLocalError。

通常可以通过参数和返回值解决:

python 复制代码
def increase(count):
    return count + 1
if __name__ == '__main__':
    count = 10
    count = increase(count)
    print(count)

运行结果:

复制代码
11

6.4 global:重新绑定全局变量

如果确实需要在函数中给模块级变量赋值,可以声明 global:

python 复制代码
count = 0
def increase():
    global count
    count += 1
if __name__ == '__main__':
    increase()
    increase()
    print(count)

运行结果:

复制代码
2

global 表示这里的名称指向当前模块的全局绑定。不过,大量函数共同修改全局变量,会让数据变化难以追踪。普通计算逻辑通常更适合使用参数和返回值。

另外,读取全局变量不需要 **global**。仅修改全局变量引用的可变对象,也不需要它:

python 复制代码
logs = []
def add_log(message):
    logs.append(message)

这里没有重新绑定名称 logs,而是在修改它引用的列表。

6.5 nonlocal:重新绑定外层函数变量

嵌套函数中,如果需要修改外层函数的变量,可以使用 nonlocal:

python 复制代码
def outer():
    count = 0
    def increase():
        nonlocal count
        count += 1
        return count
    print(increase())
    print(increase())
if __name__ == '__main__':
    outer()

运行结果:

复制代码
1
2

区别是:

  • global 指向模块级作用域。
  • nonlocal 指向某个外层函数中已经存在的绑定。

nonlocal 不用于声明一个新的全局变量。

7. 函数也是对象

7.1 函数可以赋给变量

python 复制代码
def square(number):
    return number ** 2
if __name__ == '__main__':
    operation = square
    print(operation(5))

运行结果:

复制代码
25

这里要区分:

复制代码
operation = square

表示把函数对象交给变量。

复制代码
result = square(5)

表示调用函数,把返回结果交给变量。有没有圆括号,含义不同。

7.2 函数可以作为参数

python 复制代码
def square(number):
    return number ** 2
def apply_to_all(numbers, operation):
    return [operation(number) for number in numbers]
if __name__ == '__main__':
    result = apply_to_all([1, 2, 3], square)
    print(result)

运行结果:

复制代码
[1, 4, 9]

apply_to_all() 不需要知道具体计算规则,只负责遍历数据,再调用传入的函数。这种写法让"遍历过程"和"处理规则"可以分别复用。

7.3 lambda:简单的匿名函数

lambda 可以定义一个只包含单个表达式的函数。

基本语法:

复制代码
lambda 参数: 表达式
lambda number: number ** 2

相当于一个接收数字并返回平方的函数。它常用于临时传入简单规则:

python 复制代码
def apply_to_all(numbers, operation):
    return [operation(number) for number in numbers]
if __name__ == '__main__':
    result = apply_to_all([1, 2, 3],lambda number: number ** 2)
    print(result)

运行结果:

复制代码
[1, 4, 9]

lambda 不写 return,表达式的结果就是返回值。需要多条语句、复杂分支或清晰的文档说明时,应使用 def。

7.4 使用 key 指定排序规则

上一篇出现过:

复制代码
sorted(products, key=lambda product: product["price"])

现在可以把它完整理解为:将一个函数交给 sorted(),让它为每个商品提取价格作为排序依据。

python 复制代码
products = [
    {"name": "饼干", "price": 8},
    {"name": "矿泉水", "price": 2},
    {"name": "果汁", "price": 5}
]

def product_price(product):
    return product["price"]

if __name__ == '__main__':
    a = sorted(products, key=lambda product: product["price"])
    # a = sorted(products, key=product_price)
    for product in a:
        print(f"{product['name']}:{product['price']}")

运行结果:

复制代码
矿泉水:2
果汁:5
饼干:8

也可以使用普通函数:

复制代码
def get_price(product):
    return product["price"]

然后:

复制代码
ordered = sorted(products, key=get_price)

这里传入的是 get_price,不是 get_price()。真正的调用由 sorted() 完成。

7.5 函数可以作为返回值:闭包初步

函数内部还可以定义并返回另一个函数:

python 复制代码
def make_multiplier(factor):
    def multiply(number):
        return number * factor
    return multiply
if __name__ == '__main__':
    double = make_multiplier(2)
    triple = make_multiplier(3)
    print(double(5))
    print(triple(5))

运行结果:

复制代码
10
15

make_multiplier(2) 返回的函数,仍然能够访问创建它时外层作用域中的 factor。这种内部函数携带外层变量绑定的现象称为闭包。

8. 文档字符串与类型注解

8.1 文档字符串

函数体的第一条语句可以是一个字符串,用来描述函数用途:

python 复制代码
def calculate_total(price, quantity):
    """根据单价和数量计算总金额。"""
    return price * quantity

这称为文档字符串,也就是 docstring。可以通过下面的方式查看:

复制代码
print(calculate_total.__doc__)

运行结果:

复制代码
根据单价和数量计算总金额。

也可以使用:

复制代码
help(calculate_total)

查看函数帮助。

文档应优先说明调用者需要知道的内容:

  • 函数做什么。
  • 参数有什么要求。
  • 返回什么结果。
  • 是否修改输入数据。
  • 什么情况下可能失败。

8.2 类型注解

类型注解可以说明预期的参数类型和返回类型:

python 复制代码
def add(a: int, b: int) -> int:
    return a + b
if __name__ == '__main__':
    print(add(2, 3))

运行结果:

复制代码
5

其中:

  • a: int 表示预期 a 为整数。
  • b: int 表示预期 b 为整数。
  • -> int 表示预期返回整数。

类型注解能够帮助编辑器和类型检查工具理解代码,但Python 本身不会仅因为这些注解就自动执行类型校验或转换。

python 复制代码
def add(a: int, b: int) -> int:
    return a + b

if __name__ == '__main__':
    print(add("A", "B"))

这段代码仍然会输出:

复制代码
AB

虽然调用违背了注解表达的约定,但字符串支持 +,因此运行时完成了拼接。

8.3 容器与可选返回值

Python 3.9 及以上可以直接写:

复制代码
list[int]
dict[str, int]

例如:

复制代码
def total_scores(scores: list[int]) -> int:
    return sum(scores)

Python 3.10 及以上可以使用 | 表示多种可能类型:

python 复制代码
def find_index(items: list[str], target: str) -> int | None:
    for index, item in enumerate(items):
        if item == target:
            return index
    return None
if __name__ == '__main__':
    print(find_index(["苹果", "香蕉"], "香蕉"))
    print(find_index(["苹果", "香蕉"], "橙子"))

运行结果:

复制代码
1
None

int | None 表示返回整数或 None。接收这种结果时,应明确判断:

python 复制代码
index = find_index(["苹果", "香蕉"], "苹果")
if index is not None:
    print(f"找到位置:{index}")

不要写成:

复制代码
if index:

因为合法索引 0 的真假值也是假。

相关推荐
宸津-代码粉碎机5 小时前
OpenAI 连夜迎战 Grok Bot 和 Muse:AI 智能体从 “会聊天” 到 “能办事”,现在入场还来得及吗
java·大数据·人工智能·分布式·python
Y3815326626 小时前
SERP API 计费口径:credits_charged、执行失败扣费与退款边界
python·搜索引擎
长谷深风1117 小时前
Tool与Skill:AI能力设计的分水岭
java·人工智能·ai·大模型·aiagent
蒸鱼Yuzheng7 小时前
设备端性能工件可靠导出:断点续传、哈希、manifest 与失败恢复
android·自动化测试·python·adb·数据完整性
Harzerr7 小时前
AI行业日报|2026-09-28:5个热点事件
ai·行业动态·技术趋势
2601_956743688 小时前
2026年上海GEO优化公司交付力测评:从AI信息链路看可核验、可量化、可归属的能力分野
人工智能·ai·上海·行业观察
圆圆讲门店8 小时前
挑选同城获客服务机构时需要考量的核心因素都有哪些?
大数据·网络·人工智能·python
傻啦嘿哟8 小时前
Python的默认参数把我坑惨了,原来写[]和写None的区别这么大
开发语言·python·机器学习
微小冷8 小时前
Python凸优化cvxpy初步
python·机器人·凸优化·数学规划·cvxpy