Python函数全面总结

作者:技术拾光时间:2026-06-07 08:14:02浏览:323

Python函数系统与JavaScript存在对应关系:def等同function,return返回None类似undefined,*args和**kwargs对应剩余参数与对象参数。参数系统支持位置、关键字、默认参数及强制规则。闭包、装饰器、生成器、异步函数等特性与JS本质相通。函数作为对象可回调、lambda只能写单表达式,生成器实现惰性数据产出。

这篇文章的目标读者很明确——你已经有了前端开发的基础,现在正在转向学习 Python。如果之前写过 JavaScript 或 TypeScript,那么恭喜你,你已经拿到了理解 Python 函数系统的“钥匙”。这篇不是那种干巴巴的语法指南,而是要把 Python 函数当成一个完整的系统来拆解。

Python函数全面总结

先别急着写代码,不妨先把下面这张“前端 → Python 映射表”过一遍,建立起最直观的直觉:

  • def:就是 JS 的 function
  • return:就是 JS 的 return
  • None:相当于 JS 中函数没有返回值时的 undefined
  • *args:像 JS 的剩余参数 ...args
  • **kwargs:类似 JS 常用的对象参数,但由 Python 自动帮你收集起来
  • lambda:像 JS 箭头函数,不过只能写一个表达式
  • 闭包:和 JS 闭包本质一样,都是内部函数记住外部变量
  • 装饰器:可以理解为“函数外面套一层函数”,常见于日志、权限、缓存、路由注册等场景
  • 生成器:像可以暂停的函数,用来惰性地产出数据
  • async 函数:和 JS 的 async function 一样,返回一个可等待对象

在学习函数时,重点盯住这 7 个方面就够了:如何定义和调用、参数怎么传、返回值怎么拿、变量作用域怎么找、函数本身也是对象、闭包和装饰器如何理解,以及生成器和异步函数在什么场景下登场。

Python 函数系统  定义调用
     |
     +-- def / return / None / docstring
     |
  参数系统
     |
     +-- 位置参数 / 关键字参数 / 默认参数
     +-- *args / **kwargs / 解包传参
     +-- / 位置-only / * 关键字-only
     |
  返回值
     |
     +-- 单返回值 / 多返回值 / 提前 return
     |
  作用域
     |
     +-- LEGB / global / nonlocal
     |
  函数对象
     |
     +-- 回调 / lambda / 高阶函数 / 闭包 / 装饰器
     |
  特殊函数
     |
     +-- 生成器 / async 函数
     |
  工程写法
     |
     +-- 文档字符串 / 命名 / 坑点

一、先建立总地图

知识点前端脑内映射一句话记住
deffunction定义一个函数
returnreturn把结果交回调用方
不写 return返回 undefinedPython 返回 None
位置参数普通实参按顺序传
关键字参数对象参数的一部分能力按名字传
默认参数默认值参数不传就用默认值
*args...args收集多余的位置参数
**kwargs对象参数收集多余的关键字参数
*list展开数组把列表拆成多个位置参数
**dict展开对象把字典拆成多个关键字参数
lambda箭头函数只能写一个表达式的小函数
闭包JS 闭包内层函数记住外层变量
装饰器包装函数在不改原函数的情况下增强能力
生成器iterator / generator一次产出一个值
async 函数async function定义可等待的异步逻辑
def add(a, b): # 定义函数,接收 a 和 b
    return a + b # 返回两个数字之和result = add(1, 2) # 调用函数
print(result) # 输出 3

二、def:Python 用缩进定义函数体

Python 的 def 对应 JS 的 function。最核心的区别在于:JS 用花括号 {} 包裹函数体,而 Python 完全依赖缩进来界定代码归属。这个设计直接影响你写代码的习惯。

def format_user(name, age): # 定义函数
    text = f'{name} is {age} years old' # 拼接字符串
    return text # 返回结果message = format_user('Alice', 18) # 调用函数
print(message) # 输出 Alice is 18 years old

如果函数体暂时不想写具体逻辑,可以用 pass 来占位,避免语法错误。

def todo():
    pass # 先占位,避免语法报错

对比一下 JS 和 Python 的写法,感受差异:

function formatUser(name, age) {
  const text = `${name} is ${age} years old`;
  return text;
}
def format_user(name, age):
    text = f'{name} is {age} years old'
    return text

需要特别注意的坑是,缩进本身就是语法,不是单纯为了好看。下面这段代码中,print('hello') 属于函数内部,而 print('done') 已经跑到了函数外部:

def say_hello():
    print('hello') # 在函数里面print('done') # 不在函数里面

三、return:返回值和提前结束

Python 的 return 和 JS 的 return 基本一致,都是用来返回结果并立即结束函数。先看一个基础例子:

def get_total(price, count):
    return price * count # 返回总价total = get_total(10, 3)
print(total) # 输出 30

提前返回是一种常用技巧,尤其在参数校验时:

def get_discount(amount):
    if amount <= 0:
        return 0 # 提前结束函数    if amount >= 100:
        return 20    return 5print(get_discount(120)) # 输出 20

一个值得牢记的差异:JS 函数不写 return 时返回 undefined;Python 函数不写 return 时则返回 None。

def log_user(name):
    print(f'user: {name}') # 只打印,不返回result = log_user('Alice')
print(result) # 输出 None

在处理简单的二选一逻辑时,Python 有一种类似 JS 三元表达式的写法,但顺序不同。它叫“条件表达式”:

age = 18
text = '成年' if age >= 18 else '未成年'print(text)

它非常适合非常简单的 if-else 场景:

def get_level(score):
    return 'pass' if score >= 60 else 'fail'

不过一定要注意:如果条件分支复杂,或者每个分支里要做多件事情,老老实实用普通的 if-else 结构,可读性远比一行式重要。

四、参数系统:函数最重要的部分

Python 的参数系统比 JS 更精细,因为它同时支持按顺序传、按名字传、收集参数和强制规则。调用函数时,参数大致可以分为两类:位置参数(如 add(1, 2))和关键字参数(如 create_user(name='Alice', age=18))。

位置参数和关键字参数

位置参数就像 JS 的普通实参,完全靠顺序来匹配;关键字参数则像传入一个对象,靠名字来匹配。看个例子:

def create_user(name, age):
    return {'name': name, 'age': age}user1 = create_user('Alice', 18) # 位置参数,按顺序传
user2 = create_user(age=20, name='Bob') # 关键字参数,按名字传

这里有两个容易踩的坑。第一,位置参数必须写在关键字参数前面,比如 create_user('Alice', age=18) 是正确的,但 create_user(name='Alice', 18) 会报错。第二,同一个参数不能传两次,比如 create_user('Alice', name='Bob') 会触发类型错误。

默认参数

默认参数和 JS/TS 的默认参数用法类似,通常放在必填参数后面:

def greet(name='Guest'):
    return f'Hello {name}'print(greet()) # 输出 Hello Guest
print(greet('Alice')) # 输出 Hello Alice
def create_product(name, price, category='default'):
    return {
        'name': name,
        'price': price,
        'category': category
    }

一个经典陷阱:不要用可变对象(如列表或字典)做默认值。下面这个错误写法中,默认参数 [] 只在函数定义时创建一次,导致多次调用时意外共享了同一个列表:

def add_item(item, items=[]):
    items.append(item)
    return itemsprint(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

*args:可变位置参数

*args 类似 JS 的剩余参数 ...args,用来收集所有多余的、按位置传入的参数。这些参数会被打包成一个元组:

def sum_all(*args):
    total = 0    for number in args:
        total += number    return totalprint(sum_all(1, 2, 3)) # 输出 6
def show_args(*args):
    print(args)
    print(type(args))show_args('a', 'b') # 输出 ('a', 'b') 和 

**kwargs:可变关键字参数

**kwargs 可以理解为 Python 自动把多余的命名参数收集成一个字典。它的本质就是一个 dict 对象。

def create_user(**kwargs):
    print(kwargs)
    return kwargsuser = create_user(name='Alice', age=18, city='Shanghai')

参数完整顺序

当一个函数需要混合各种参数时,推荐的书写顺序如下:

def demo(pos1, pos2, default='x', *args, keyword_only=True, **kwargs):
    print(pos1)
    print(pos2)
    print(default)
    print(args)
    print(keyword_only)
    print(kwargs)

对应的调用及含义如下:

demo(1, 2, 'value', 3, 4, keyword_only=False, name='Alice')
  • pos1, pos2:接收前两个位置参数
  • default:有默认值,也可以被位置参数覆盖
  • *args:收集剩下的所有位置参数
  • keyword_only:位于 *args 之后,只能用关键字传
  • **kwargs:收集剩下的所有关键字参数

强制关键字参数:*

如果你希望某个参数在调用时必须写参数名,可以在参数列表中使用 * 来分隔。* 后面的所有参数都必须使用关键字方式传入:

def list_users(page, *, page_size=10, keyword=''):
    return {
        'page': page,
        'page_size': page_size,
        'keyword': keyword
    }print(list_users(1, page_size=20)) # 正确# list_users(1, 20) # 错误,page_size 必须写参数名

这个模式非常适合配置类参数,能显著提升代码的可读性。比如封装一个网络请求函数时:

def request(url, *, method='GET', timeout=10, headers=None):
    return {
        'url': url,
        'method': method,
        'timeout': timeout,
        'headers': headers or {}
    }request('/api/users', method='POST', timeout=30)

强制位置参数:/

与 * 相反,/ 符号用于强制其前面的参数只能按位置传入,不能写参数名。这个语法在写业务代码时不常用,但阅读内置库或第三方库的源码时经常会遇到:

def add(a, b, /):
    return a + bprint(add(1, 2)) # 正确# add(a=1, b=2) # 错误,a 和 b 只能按位置传

解包传参:*list 和 **dict

这个功能与 JS 的展开运算符很像。当你有一个列表时,可以用 * 把它拆成多个位置参数:

def add(a, b):
    return a + bnumbers = [1, 2]
print(add(*numbers)) # 相当于 add(1, 2)

字典则可以用 ** 拆成关键字参数,注意字典的 key 必须与函数形参名对应:

def create_user(name, age):
    return {'name': name, 'age': age}data = {'name': 'Alice', 'age': 18}
user = create_user(**data)

函数传参和可变对象

Python 函数传参时,并不是把对象复制一份传进去,而是让形参这个名字绑定到同一个对象。理解这一点对避免意外修改至关重要:

def add_user(users):
    users.append('Alice')names = []
add_user(names)print(names) # 输出 ['Alice']

但如果你只是让形参重新绑定到一个新对象,则不会影响外面的变量:

def reset_users(users):
    users = [] # 只是让局部变量 users 指向新列表
    users.append('Bob')names = ['Alice']
reset_users(names)print(names) # 输出 ['Alice']

简单记一下:修改可变对象本身(如 append),外部可能受影响;重新给形参赋值,只改函数内部这个局部名字。如果不希望影响外部数据,可以在传入前复制,或在函数内部先复制一份再操作。

五、多返回值:本质是元组

JS 中要返回多个值通常用数组或对象,而 Python 可以直接写多个返回值,本质上是返回一个元组:

def get_position():
    return 10, 20x, y = get_position()

等价于显式返回一个元组:

def get_position():
    return (10, 20)

如果返回值数量少且含义明确,用多返回值很简洁。但如果字段很多,还是推荐返回字典、数据类或 Pydantic 模型,语义更清晰。

六、作用域:LEGB 查找规则

Python 查找变量时,遵循一个清晰的顺序:

L - Local       函数内部
E - Enclosing   外层函数
G - Global      当前模块
B - Built-in    内置作用域

你可以把它想象成一个“变量查找链”:先找当前函数内部,找不到就去外层函数找,再找当前 .py 文件里的全局变量,最后才去找 print、len、range 这类内置名字。

name = 'global'def outer():
    name = 'outer'    def inner():
        name = 'inner'
        print(name)    inner()outer() # 输出 inner

局部变量

函数内部定义的变量,默认只在函数内部可用,外部无法访问:

def create_name():
    name = 'Alice'
    return nameprint(create_name()) # 输出 Alice
# print(name) # 错误,函数外不能访问函数内部变量

global:修改全局变量

如果要在函数里修改全局变量,必须使用 global 关键字声明。注意,只是读取全局变量时不需要 global,只有要给它重新赋值时才需要:

count = 0def add_count():
    global count
    count += 1add_count()
print(count) # 输出 1

业务代码里不要滥用 global,它会让数据来源变得不清晰,追查问题时很头疼。

nonlocal:修改外层函数变量

在闭包中,如果要修改外层函数的变量,则需要使用 nonlocal 关键字:

def create_counter():
    count = 0    def counter():
        nonlocal count
        count += 1
        return count    return countercounter = create_counter()
print(counter()) # 输出 1
print(counter()) # 输出 2

同样,读取外层变量不需要 nonlocal,只有要重新赋值时才需要。

七、函数也是对象

这一点和 JS 一致:Python 的函数也是一等公民,可以赋值、作为参数传入、作为返回值返回。

def add(a, b):
    return a + bhandler = add
print(handler(1, 2)) # 输出 3
def double(number):
    return number * 2def apply(value, handler):
    return handler(value)print(apply(10, double)) # 输出 20

作为返回值返回时,就构成了高阶函数的基础:

def create_multiplier(rate):
    def multiplier(value):
        return value * rate    return multiplierdouble = create_multiplier(2)
print(double(10)) # 输出 20

八、lambda:小型匿名函数

lambda 就像 JS 的箭头函数,但更严格——它只能写一个表达式。这个表达式的结果会自动作为返回值,所以不需要写 return:

add = lambda a, b: a + b
print(add(1, 2)) # 输出 3

它最常用的场合是作为 sorted、map、filter 等函数的参数:

users = [
    {'name': 'Alice', 'age': 18},
    {'name': 'Bob', 'age': 20},
    {'name': 'Tom', 'age': 16}
]result = sorted(users, key=lambda user: user['age'])

需要注意:lambda 里不能写 if、for、while 等语句块。如果需要条件判断,只能用条件表达式:

get_text = lambda age: '成年' if age >= 18 else '未成年'

如果逻辑复杂,建议直接用 def 定义普通函数,可读性远胜于强行写成一行的 lambda。

九、常用内置高阶函数

所谓高阶函数,就是能接收另一个函数作为参数,或者返回另一个函数的函数。Python 自带了好几个非常实用的高阶函数。

sorted:排序

users = [
    {'name': 'Alice', 'age': 18},
    {'name': 'Bob', 'age': 20},
    {'name': 'Tom', 'age': 16}
]result = sorted(users, key=lambda user: user['age'])

map:映射

map 会对可迭代对象里的每个元素执行同一个处理函数。它返回的是一个迭代器,需要用 list() 转换为列表:

numbers = [1, 2, 3]
result = list(map(lambda number: number * 2, numbers))print(result) # 输出 [2, 4, 6]

不过日常写简单映射逻辑时,列表推导式通常更直观、更 Pythonic:

result = [number * 2 for number in numbers]

filter:过滤

filter 会保留判断结果为真的元素,同样返回迭代器:

numbers = [1, 2, 3, 4]
result = list(filter(lambda number: number % 2 == 0, numbers))print(result) # 输出 [2, 4]

同样,列表推导式也适用于此场景:

result = [number for number in numbers if number % 2 == 0]

一个小技巧:如果 filter 的第一个参数传 None,它会自动过滤掉所有假值:

values = [0, 1, '', 'hello', None, [], [1]]
result = list(filter(None, values))print(result) # 输出 [1, 'hello', [1]]

reduce:合并

reduce 用于把一组数据不断合并,最终得到一个结果。它需要从 functools 模块导入:

from functools import reducenumbers = [1, 2, 3, 4, 5]result = reduce(lambda total, number: total + number, numbers)print(result) # 输出 15

如果不传初始值,reduce 会把第一个元素当成初始的 total。如果需要指定初始值,把它作为第三个参数传入:

result = reduce(lambda total, number: total + number, numbers, 100)print(result) # 输出 106

日常编码中,求和用 sum() 就够了,reduce 更适合“把多个对象合并成一个对象”这类更复杂的场景。

all / any:判断一组条件

scores = [90, 80, 70]print(all(score >= 60 for score in scores)) # 是否全部及格
print(any(score >= 90 for score in scores)) # 是否有人大于等于 90

十、闭包:内部函数记住外部变量

Python 闭包和 JS 闭包本质完全一样,就是“函数 + 它记住的外层变量”。当外层函数执行结束后,内层函数仍然能访问当时的环境变量:

def create_prefixer(prefix):
    def format_text(text):
        return f'{prefix}-{text}'    return format_textapi_prefixer = create_prefixer('/api')
print(api_prefixer('users')) # 输出 /api-users

每调用一次外层函数,都会创建一份新的闭包状态,它们彼此独立:

def create_counter():
    count = 0    def counter():
        nonlocal count
        count += 1
        return count    return countercounter1 = create_counter()
counter2 = create_counter()print(counter1()) # 1
print(counter1()) # 2
print(counter2()) # 1

闭包的应用场景很广:保存配置、创建计数器、生成定制函数、装饰器,以及延迟执行逻辑,都会用到它。

十一、装饰器:函数外面套函数

从本质上讲,装饰器就是一个高阶函数:它接收一个函数,返回一个经过增强的新函数。如果从前端的角度理解,它很像下面这个模式:

原函数
  |
  +-- 传给装饰器函数
        |
        +-- 装饰器内部创建 wrapper
              |
              +-- wrapper 里可以在原函数前后加逻辑
                    |
                    +-- 最后返回 wrapper

先不用 @ 语法,手动写一个装饰器来理解它的运作方式:

def logger(func):
    def wrapper():
        print('before')        result = func()        print('after')
        return result    return wrapperdef say_hello():
    print('hello')say_hello = logger(say_hello)say_hello()

这一行 say_hello = logger(say_hello) 是理解装饰器的核心:右边的 say_hello 是原函数,logger 把它传进去并返回 wrapper,左边的 say_hello 重新指向了 wrapper。所以后面再调用 say_hello(),其实是在调用 wrapper()。

而 @logger 语法糖所做的,就是把这个过程简化成了:

@logger
def say_hello():
    print('hello')

在处理带参数的函数时,wrapper 需要能接收任意参数:

def logger(func):
    def wrapper(*args, **kwargs):
        print(f'call {func.__name__}')
        return func(*args, **kwargs)    return wrapper@logger
def add(a, b):
    return a + b

实际写装饰器时,强烈建议使用 functools.wraps 来保留原函数的元信息(如函数名、文档字符串等):

from functools import wrapsdef logger(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f'call {func.__name__}')
        return func(*args, **kwargs)    return wrapper

如果装饰器本身也需要参数,就需要再多套一层函数结构:

from functools import wrapsdef retry(times):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            last_error = None            for _ in range(times):
                try:
                    return func(*args, **kwargs)
                except Exception as error:
                    last_error = error            raise last_error        return wrapper    return decorator@retry(3)
def request_data():
    print('request')
    return 'ok'

装饰器的常用场景包括:Web 框架路由注册、登录校验、权限校验、日志记录、统计耗时、缓存结果和重试逻辑等。

十二、生成器函数:yield 一次产出一个值

生成器像 JS 的 function*。普通函数用 return 一次性返回结果,而生成器函数用 yield 分批产出结果:

def create_numbers():
    yield 1
    yield 2
    yield 3numbers = create_numbers()print(next(numbers)) # 1
print(next(numbers)) # 2

更常见的用法是配合 for 循环使用:

def range_numbers(max_number):
    current = 0    while current < max_number:
        yield current
        current += 1for number in range_numbers(3):
    print(number)

生成器最适合处理大文件、分批读取数据、惰性计算、流式处理以及自定义迭代器。

yield from 是一个很实用的语法,它可以把另一个可迭代对象里的值一个一个地产出来:

def create_all():
    yield from [1, 2, 3]
    yield from ['a', 'b']for item in create_all():
    print(item)

输出结果是依次产出的 1、2、3、a、b,而不是一次性输出两个列表。

十三、async 函数:异步逻辑

Python 的 async def 类似 JS 的 async function,定义异步函数,里面通常配合 await 等待异步结果:

import asyncioasync def fetch_user():
    await asyncio.sleep(1)
    return {'name': 'Alice'}async def main():
    user = await fetch_user()
    print(user)asyncio.run(main())

一个容易踩的坑:调用异步函数并不会立刻拿到结果,而是得到一个 coroutine 对象,必须加上 await 才能拿到真正的值。

十四、文档字符串:函数自己的说明书

类似 JSDoc,但 Python 约定把说明写在函数体的第一行字符串中:

def calculate_total(price, count):
    """计算商品总价。"""
    return price * count

多行文档字符串一般写作:

def create_user(name, age):
    """
    创建用户字典。    name: 用户名
    age: 年龄
    """
    return {'name': name, 'age': age}

通过 __doc__ 可以随时访问函数的文档字符串。

十五、函数对象的常见属性

函数本身是对象,自然也带有一些常用属性:

def say_hello(name):
    """返回问候语。"""
    return f'Hello {name}'print(say_hello.__name__) # say_hello
print(say_hello.__doc__) # 文档字符串

这些信息在装饰器、框架和调试工具中经常被读取。

十六、常见坑点汇总

1. 不写 return 返回 None

def add(a, b):
    a + bprint(add(1, 2)) # None

正确写法一定要有 return。

2. 可变默认参数会共享

def append_item(item, items=[]):
    items.append(item)
    return items

推荐改用 None 作为默认值,内部重新初始化。

3. 修改外层变量忘记 nonlocal

def create_counter():
    count = 0    def counter():
        nonlocal count
        count += 1
        return count    return counter

4. async 函数忘记 await

async def get_user():
    return {'name': 'Alice'}# user = get_user() # 只是 coroutine

5. lambda 写复杂逻辑

逻辑复杂时直接用 def 定义函数,不要为了短而牺牲可读性。

6. 函数名覆盖内置函数

list = [1, 2, 3]
dict = {'name': 'Alice'}

这些名字会覆盖 Python 内置函数,导致后续调用 list()、dict() 等内置方法时报错,务必避免。