Skip to content

函数

写到第三个"把文章标题和作者拼成一行"的逻辑时,重复就明显了——同样的 f"{title} —— {author}" 在好几个地方出现,改一处忘改另一处,bug 就来了。函数就是给这种重复逻辑起个名字,之后调用名字就行,逻辑只写一遍。

一、定义函数

python
def format_article(title, author):
    """把文章标题和作者拼成一行展示文本。"""
    return f"{title} —— {author}"


line = format_article("Python 入门", "张三")
print(line)  # Python 入门 —— 张三

def 后面是函数名,括号里 titleauthor 这些是占位的参数,调用时填进去真实值。return 把结果交回给调用方。

函数名和变量名一样,小写加下划线——format_articlefmtArt 多打几个字,但过几个月再打开,前者一眼就懂,后者得猜半天。

二、参数

1 默认参数

有些参数大部分时候取同一个值,可以设默认,调用时就不必每次都写:

python
def format_article(title, author, date="未知"):
    return f"{title} —— {author}{date})"


print(format_article("Python 入门", "张三"))
# Python 入门 —— 张三(未知)

print(format_article("Go 并发", "李四", "2024-06-01"))
# Go 并发 —— 李四(2024-06-01)

date 不传就用默认值。默认参数要放在参数列表末尾——先写必填的,再写带默认的,顺序反了 Python 直接报错。

2 关键字参数

参数一多,光靠位置传值容易记错顺序。调用时显式写参数名,顺序就无所谓了:

python
def create_contact(name, phone, email="未填写"):
    return {"name": name, "phone": phone, "email": email}


contact = create_contact(phone="13800138000", name="王五")
print(contact)
# {'name': '王五', 'phone': '13800138000', 'email': '未填写'}

phonename 的传入顺序跟定义里相反也没事,因为带了名字。这种写法在参数四五个的时候特别值——加一个新参数不会把老调用的位置全打乱。

3 可变参数

不确定会传多少个值时,*args 把它们收成一个元组:

python
def join_tags(*tags):
    """把多个标签拼成一个字符串,逗号分隔。"""
    return ", ".join(tags)


print(join_tags("Python", "入门", "编程"))
# Python, 入门, 编程

*tags 把三个字符串收进元组 ("Python", "入门", "编程"),函数里当普通元组用。

**kwargs 收的是关键字参数,成字典:

python
def build_filter(**conditions):
    """把多个筛选条件拼成一行描述。"""
    parts = [f"{key}={value}" for key, value in conditions.items()]
    return "筛选:" + " AND ".join(parts)


print(build_filter(status="已发布", category="技术"))
# 筛选:status=已发布 AND category=技术

status="已发布"category="技术" 被收进字典 {"status": "已发布", "category": "技术"}*args**kwargs 这两个名字是约定俗成,*** 才是关键——叫 *items**opts 也行。

三、返回值

函数执行完可以返回结果。没写 return,或者只写 return 不带值,返回的就是 None:

python
def log_message(msg):
    print(f"[LOG] {msg}")
    # 没有 return,返回 None


result = log_message("保存成功")
print(result)  # None

返回多个值其实就是返回一个元组,调用方拿多个变量接,Python 自动拆开:

python
def parse_date(date_str):
    """把 '2024-06-01' 拆成年、月、日三个整数。"""
    year, month, day = date_str.split("-")
    return int(year), int(month), int(day)


y, m, d = parse_date("2024-06-01")
print(y, m, d)  # 2024 6 1

"返回多个值"这个说法容易让人以为 Python 有什么特殊机制——其实没有,就是返回元组 + 自动拆包。知道这点,后面遇到"函数返回三个值我只接了一个"的情况就不会懵:接到的其实是一个三元组。

四、作用域

函数里定义的变量,外面拿不到。这层隔离就是作用域:

python
def process():
    count = 10  # 局部变量
    print(f"函数内 count={count}")


process()
print(count)  # 报错:NameError: name 'count' is not defined

函数里能读外面的全局变量,但改不了——要改得先 global 声明:

python
total = 0


def add(amount):
    global total  # 声明 total 是外面那个
    total += amount


add(5)
add(3)
print(total)  # 8

global 能不用就别用。函数本来是为了把逻辑隔开,结果又靠全局变量串起来,后面排查"这个变量到底被谁改了"会非常痛苦——几十个函数都摸同一个全局变量,改一个地方不知道影响多大。

五、lambda 表达式

有些逻辑就一行,专门起个函数名又嫌重。lambda 写匿名函数:

python
# 普通 def
def get_title(article):
    return article["title"]


# lambda 等价写法
get_title = lambda article: article["title"]

articles = [
    {"title": "Python 入门", "author": "张三"},
    {"title": "Go 并发", "author": "李四"},
]

titles = [get_title(a) for a in articles]
print(titles)  # ['Python 入门', 'Go 并发']

lambda article: article["title"] 就是 def get_title(article): return article["title"] 的简写,左边参数、右边返回值。

lambda 适合真正只有一行的简单逻辑。逻辑一复杂——要多个步骤、要赋值、要循环——还是老老实实写 def。lambda 拉长之后,排查的人盯着那一行看半天,反而比 def 更难读。

六、文档字符串

函数开头用三引号写的说明,叫文档字符串(docstring),help() 能读出来:

python
def filter_by_status(articles, status):
    """筛选指定状态的文章。

    Args:
        articles: 文章列表,每个元素是字典。
        status: 目标状态,如 '已发布'。

    Returns:
        状态匹配的文章列表。
    """
    return [a for a in articles if a.get("status") == status]


help(filter_by_status)

写法上覆盖三件事就够了:这个函数做什么、参数各是什么、返回什么。不用套复杂模板,几句话把关键信息说清楚——过几个月回来改这个函数,这几句话比重新读一遍函数体省事得多。