Python 函数

函数是带名字的代码块,用于完成具体的工作。要执行函数定义的特定任务,可调用该函数。需要在程序中多次执行同一项任务时,无需反复编写完成该任务的代码,只需要调用执行该任务的函数,让Python运行其中的代码即可。通过使用函数,程序编写、阅读、测试和修复起来都更加容易。

1. 定义函数

下面是一个打印问候语的简单函数:

python 复制代码
def greet_user():   # 函数定义
    """显示简单的问候语"""   # 文档字符串,用于注释函数的作用
    print("hello")
greet_user()      # 函数调用

1.1 向函数传递信息

只需要稍作修改,就可以让上面的函数不仅向用户显示文本,还可以将用户的名字作为开头,为此调用该函数时需要将一个名字传递给它,如下所示:

python 复制代码
def greet_user(username):   # 函数定义
    """显示简单的问候语"""   # 文档字符串,用于注释函数的作用
    print(f"hello, {username.title()}")
greet_user('jesse')      # 函数调用

可根据需要调用函数任意次,调用时无论传入什么名字都将生成相应的输出。

1.2 实参和形参

在上面的函数中变量 username 是一个形参,即函数完成工作所需的信息。在代码 greet_user('jesse') 中,值 jesse 是一个实参,即调用函数时传递给函数的信息。调用函数时,将实参 jesse 传递给了形参 username。

2. 传递实参

函数定义中可能包含多个形参,因此函数调用中也可能包含多个实参。向函数传递实参的方式很多:可使用位置实参 ,这要求实参的顺序与形参的顺序相同;也可使用关键字实参,其中每个实参都由变量名和值组成,还可使用列表和字典。

2.1 位置实参

调用函数时,python必须将函数调用中的每个实参都关联到函数定义中的一个形参。为此,最简单的关联方式是基于实参的顺序。这种关联方式称为位置实参。如下所示:

python 复制代码
def describe_pet(animal_type, pet_name):
    """显示宠物的信息"""
    print(f"\nI have a {animal_type}")
    print(f"My {animal_type}'s name is {pet_name.title()}")
describe_pet('hanster', 'harry')

这个函数的定义表明,它需要一种动物类型和一个名字。实参 hamster 被赋给形参 animal_type,而实参 harry 被赋给形参 pet_name。
1.多次调用函数

可以根据需要调用函数任意次,要再描述一个宠物,只需要再次调用即可:

python 复制代码
def describe_pet(animal_type, pet_name):
    """显示宠物的信息"""
    print(f"\nI have a {animal_type}")
    print(f"My {animal_type}'s name is {pet_name.title()}")
describe_pet('hanster', 'harry')
describe_pet('dog', 'willie')

多次调用函数是一种效率极高的工作方式。在函数中,可根据需要使用任意数量的位置实参,python 将按顺序将函数调用中的实参关联到函数定义中相应的形参。
2.位置实参的顺序很重要

使用位置实参来调用函数时,如果实参的顺序不正确,结果可能出乎意料:

python 复制代码
def describe_pet(animal_type, pet_name):
    """显示宠物的信息"""
    print(f"\nI have a {animal_type}")
    print(f"My {animal_type}'s name is {pet_name.title()}")
describe_pet('hanster', 'harry')
describe_pet('harry', 'hanster')

上面两个函数调用的结果不同。

2.2 关键字实参

关键字实参是传递给函数的名称值对。因为直接在实参中将名称和值关联起来,所以向函数传递实参时不会混淆。关键字实参让你无需考虑函数调用中的实参顺序,还清楚地指出了函数调用中各个值的用途。如下所示:

python 复制代码
def describe_pet(animal_type, pet_name):
    """显示宠物的信息"""
    print(f"\nI have a {animal_type}")
    print(f"My {animal_type}'s name is {pet_name.title()}")
describe_pet(animal_type = 'hamster', pet_name = 'harry')

调用这个函数时,向 python 明确的指出了各个实参对应的形参。关键字实参的顺序无关紧要,因为 python 知道各个值该赋给那个形参。
注意: 使用关键字实参时,务必准确指定函数定义中的形参名。

2.3 默认值

编写函数时,可给每个形参指定默认值。在调用函数中给形参提供了实参时,python 将使用指定的实参值,否则将使用形参的默认值。因此,给形参指定默认值后,可在函数调用中省略相应的实参。使用默认值可简化函数调用,还可清楚的指出函数的典型用法。例如:

python 复制代码
def describe_pet(pet_name, animal_type='dog'):
    """显示宠物的信息"""
    print(f"\nI have a {animal_type}")
    print(f"My {animal_type}'s name is {pet_name.title()}")
describe_pet(pet_name='willie')
# 在调用这个函数时,如果没给 animal_type 指定值,python 就将这个形参设置为 dog

注意: 使用默认值时,必须先在形参列表中列出没有默认值的形参,再列出有默认值的实参。这让 python 依然能够正确的解读位置实参。

2.4 等效的函数调用

鉴于可混合使用位置实参、关键字实参和默认值,通常有多种等效的函数调用方式,请看下面对函数的定义,其中给一个形参提供了默认值:

python 复制代码
def describe_pet(pet_name,animal_type='dog'):

下面对这个函数的所有调用都可行:

python 复制代码
# 一条名为willie的小狗
describe_pet('willie')
describe_pet(pet_name='willie')
# 一条名为Harry的仓鼠
describe_pet('harry','hamster')
describe_pet(pet_name='harry', animal_type='hamster')
describe_pet(animal_type='hamster', pet_name='harry')

注意: 使用哪种调用方式无关紧要,只要函数调用能生成你期望的输出就行。

2.5 避免实参错误

等你开始使用函数后,如果遇到实参不匹配的错误,不要大惊小怪。你提供的实参多于或少于函数完成工作所需的信息时,将出现实参不匹配错误。如下所示:

python 复制代码
def describe_pet(animal_type, pet_name):
    """显示宠物的信息"""
    print(f"\nI have a {animal_type}")
    print(f"My {animal_type}'s name is {pet_name.title()}")
describe_pet( )

python 会发现该函数调用缺少必要的信息,发生错误。如果提供的实参太多,也会引发错误。

3. 返回值

函数并非总是直接显示输出,它还可以处理一些数据,并返回一个或一组值。函数返回的值称为返回值。在函数中,可使用 return 语句将值返回到调用函数的代码行。返回值让你能够将程序的大部分繁重工作移到函数中去完成,从而简化主程序。

3.1 返回简单值

下面来看一个函数,它接受名和姓并返回整洁的姓名:

python 复制代码
def get_formatted_name(first_name, last_name):
    """返回整洁的姓名"""
    full_name = f"{first_name} {last_name}"
    return full_name.title()
musician = get_formatted_name('jimi','hendrix')
print(musician)

3.2 让实参变成可选的

有时候需要让实参变成可选的,这样使用函数的人就能只在必要时提供额外的信息。可使用默认值来让实参变为可选的。假设要拓展函数 get_formatted_name(),使其同时处理中间名,如下所示:

python 复制代码
def get_formatted_name(first_name, middle_name, last_name):
    """返回整洁的姓名"""
    full_name = f"{first_name} {middle_name} {last_name}"
    return full_name.title()
musician = get_formatted_name('john', 'lee', 'hooker')
print(musician)

只要同时提供名、中间名和姓,这个函数就能正确运行。但并非所有人都有中间名,为了让中间名变成可选的,可给形参 middle_name 指定一个空的默认值,并在用户没有提供中间名时不使用这个形参。如下所示:

python 复制代码
def get_formatted_name(first_name, last_name, middle_name=' '):
    """返回整洁的姓名"""
    if middle_name:
        full_name = f"{first_name} {middle_name} {last_name}"
    else:
        full_name = f"{first_name} {last_name}"
    return full_name.title()
musician = get_formatted_name('john', 'lee', 'hooker')
print(musician)

musician = get_formatted_name('john', 'hooker')
print(musician)

在函数体中,检查是否提供了中间名。python 将非空字符串解读为 True,因此如果函数调用中提供了中间名,if 判断将为 True,执行if 条件判断下的语句,否则将执行 else 后的语句。

3.3 返回字典

函数可返回任何类型的值,包括列表和字典等较复杂的数据结构。如下所示:

python 复制代码
def build_person(first_name, last_name):
    """返回一个字典,其中包含有关一个人的信息"""
    person = {'first': first_name, 'last': last_name}
    return person
musician = build_perso('jimi','hendrix')
print(musician)

函数接受名和姓,并将这些值放到字典中,最后返回表示人的整个字典。

拓展这个函数,使其接受可选值,如中间名、年龄、职业等。如下所示:

python 复制代码
def build_person(first_name, last_name, age=None):
    """返回一个字典,其中包含有关一个人的信息"""
    person = {'first':first_name, 'last':last_name}
    if age:
        person['age'] = age
    return person
musician = build_perso('jimi','hendrix', age=27)
print(musician)

在函数定义中新增了一个可选形参 age,并将其默认值设置为特殊值 None,表示变量没有值,在条件测试中,None 相当于 False。

如果函数调用中包含形参 age 的值,这个值将被储存到字典中。

3.4 结合使用函数和while循环

可将函数同任何 python 结构结合起来使用。如下所示:

python 复制代码
def get_formatted_name(first_name, last_name):
    """返回整洁的姓名"""
    full_name = f"{first_name} {last_name}"
    return full.name.title()
# 这是一个无限循环
while True:
    print("\nPlease tell me your name:")
    f_name = input("First name: ")
    l_name = input("Last name: ")
    formatted_name = get_formatted_name(f_name, l_name)
    print(f"\nHello,{formatted_name}!!")

while 循环让用户输入姓名:依次提示用户输入名和姓。

但这个 while 循环存在一个问题:没有定义退出条件。因此每次提示用户输入时都应提供退出途径,每次提示用户输入时,都使用 break 语句提供退出循环的简单途径:

python 复制代码
def get_formatted_name(first_name, last_name):
    """返回整洁的姓名"""
    full_name = f"{first_name} {last_name}"
    return full.name.title()
while True:
    print("\nPlease tell me your name:")
    print("(enter 'q' at any time to quit)")
    f_name = input("First name: ")
    if f_name == 'q':
        break
    l_name = input("Last name: ")
    if l_name == 'q':
        break
    formatted_name = get_formatted_name(f_name, l_name)
    print(f"\nHello,{formatted_name}!!")

我们添加了一条消息来告诉用户如何退出,然后在每次提示用户输入时,都检查他输入的是否是退出值。如果是,就退出循环。

4.传递列表

你经常会发现,向函数传递列表很有用,其中包含的可能是名字、数或更复杂的对象,如字典。将列表传递给函数后,函数就能直接访问其内容。下面使用函数来提高处理列表的效率。如下所示:

python 复制代码
def greet_users(names):
    """向列表中的每位用户发出简单的问候"""
    for name in names:
        msg = f"Hello, {name.title()}"
        print(msg)
username = ['hannah', 'ty', 'margot']
greet_users(usernames)

我们将函数定义为接受一个名字列表,并将其赋给形参 names,这个函数遍历收到的列表,并对其中的每位用户打印一条问候语。

4.1 在函数中修改列表

将列表传递给函数后,函数就可以对其进行修改。在函数中对这个列表所做的任何修改都是永久性的,这让你能够高效的处理大量数据。

来看一家为用户提交的设计制作3D打印模型的公司,需要打印的设计储存在一个列表中,打印后将移到另一个列表中。下面是在不使用函数的情况下模拟这个过程:

python 复制代码
unprinted_designs = ['phone case', 'robot pendant', 'dodecahedron']
completed_models = [ ]
# 模拟打印每个设计,直到没有未打印的设计为止
# 打印每个设计后,都将其移到列表completed_models中
while unprint_designs:
    current_design = unprinted_designs.pop()
    print(f"Printing model: {current_design}")
    completed_models.append(currented_design)
# 显示打印好的所有模型:
print("\nThe following modelshave been printed:")
for completed_model in completed_models:
    print(completed_model)

为重新组织这些代码,可编写两个函数,每个都做一件具体的工作。如下所示:

python 复制代码
def print_models(unprinted_designs,completed_models):
    """模拟打印每个设计,直到没有未打印的设计为止"""
    """打印每个设计后,都将其移到列表completed_models中"""
    while unprinted_design:
        current_design = unprinted_designs.pop()
        print(f"Printing model:{current_design}")
        completed_models.append(current_design)

def show_completed_models(completed_models):
    """显示打印好的所有模型"""
    print("\nThe following models have been printed:")
    for completed_model in completed_models:
        print(completed_model)
        
unprinted_designs = ['phone case', 'robot pendant', 'dodecahedron']
completed_models = [ ] 
print_models(unprinted_designs, completed_models)
show_completed_models(completed_models)

该程序还演示了这样一种理念:每个函数都应只负责一项具体的工作。这优于使用一个函数来完成这两项工作。

4.2 禁止函数修改列表

有时候需要禁止函数修改列表。为解决这个问题可向函数传递列表的副本而非原件。这样函数所做的任何修改都只影响副本,而原件丝毫不受影响。如下所示:

python 复制代码
print_models(unprinted_designs[:], completed_models)
# 切片表示法创建列表的副本

虽然向函数传递列表的副本可保留原始列表的内容,但除非有充分的理由,否则还是应该将原始列表传递给函数,这样可以避免复制列表带来的开销。

5. 传递任意数量的实参

有时候不知道函数需要接受多少个实参,好在 python 允许函数从调用语句中收集任意数量的实参。

例如来看一个制作披萨的函数,它需要接受很多配料,但无法预先确定顾客要多少种配料。如下所示:

python 复制代码
def make_pizza(*toppings):
    """打印顾客点的所有配料"""
    print(toppings)
make_pizza('pepperoni')
make_pizza('mushrooms', 'green peppers', 'extra cheese')

形参名 *toppings 中的星号让 python 创建一个名为 toppings 的空元组,并将收到的所有值都封装到这个元组中。

现在将函数调用 print( ) 替换为一个循环,遍历配料列表并对顾客点的披萨进行描述:

python 复制代码
def make_pizza(*toppings):
    """概述要制作的披萨"""
    print("\nMaking a pizza with the following toppings:")
    for topping in toppings:
        print(f"- {topping}")
make_pizza('pepperoni')
make_pizza('mushrooms', 'green peppers', 'extra cheese')

5.1 结合使用位置实参和任意数量实参

如果要让函数接受不同类型的实参,必须在函数定义中将接纳任意数量实参的形参放在最后。python 先匹配位置实参和关键字实参,再将余下的实参都收集到最后一个形参中。

例如,如果前面的函数还需要一个表示披萨尺寸的形参,必须将其放在形参 *toppings 的前面:

python 复制代码
def make_pizza(size, *toppings):
    """概述要制作的披萨"""
    print(f"\nMaking a {size}-inch pizza with the following toppings:")
    for topping in toppings:
        print(f"- {topping}")
make_pizza(16, 'pepperoni')
make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')

注意: 你会经常看到通用形参名 *args,它也能收集任意数量的位置实参。

5.2 使用任意数量的关键字实参

有时候需要接受任意数量的实参,但预先不知道传递给函数的会是什么样的信息。在这种情况下,可将函数编写成能够接受任意数量的键值对------调用语句提供了多少就接受多少。如下所示:

python 复制代码
def build_profile(first, last, **user_info):
    """创建一个字典,其中包含我们知道的有关用户的一切"""
    user_info['first_name'] = first
    user_info['last_name'] = last
    return user_info
user_profile = build_profile('albert', 'einstein', location = 'princeton',field = 'physics')
print(iser_pofile)

形参 **user_info 中的两个星号让 python 创建一个名为 user_info 的空字典,并将收到的所有名称值对都放到这个字典中。
注意: 你经常会看到形参名 **kwargs,它用于收集任意数量的关键字实参。

6.将函数储存在模块中

使用函数的优点之一是可将代码块与主程序分离。通过给函数指定描述性名称,可让主程序容易理解得多。你可以更进一步,将函数储存在称为模块的独立文件中,再将模块导入到主程序中。import语句允许在当前运行的程序文件中使用模块中的代码。

6.1 导入整个模块

要让函数是导入的,得先创建模块。模块是拓展名为 .py 的文件,包含要导入的代码。下面来创建一个包含函数 make_pizza( ) 的模块。如下所示:

python 复制代码
def make_pizza(size, *toppings):
    """概述要制作的披萨"""
    print(f"\nMaking a {size}-inch pizza with the following toppings:")
    for topping in toppings:
        print(f"- {topping}")

接下来在 pizza.py 所在的目录中创建一个名为 making_pizzas.py 的文件。这个文件导入刚创建的模块,再调用 make_pizza( )两次:

python 复制代码
import pizza
pizza.make_pizza(16, 'pepperoni')
pizza.make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')

python 读取这个文件时,代码行 import pizza 让 python 打开文件 pizza.py,并将其中的所有函数都复制到这个程序中。这就是一种导入方法,只需要编写一条 import 语句并在其中指定模块名,就可在程序中使用该模块中的所有函数。

6.2 导入特定的函数

还可以导入模块中的特定函数,这种导入方法的语法如下:

python 复制代码
from module_name import function_name

通过用逗号分隔函数名,可根据需要从模块中导入任意数量的函数:

python 复制代码
from module_name import function_0, function_1, function_2

对于前面的示例,代码将类似下面这样:

cpp 复制代码
import pizza import make_pizza
make_pizza(16, 'pepperoni')
make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')

使用这种语法时,调用函数时无须使用句点。由于在 import 语句中显式地导入了函数 make_pizza( ),调用时只需指定其名称即可。

6.3 使用as给函数指定别名

如果要导入的函数的名称可能与程序中现有的名称冲突,或者函数的名称太长,可指定简短而独一无二的别名,即函数的另一个名称,类似于外号。要给函数取这种特殊外号,需要在导入它时指定。如下所示:

python 复制代码
# 给函数 make_pizza 指定别名
from pizza import make_pizza as mp
mp(16, 'pepperoni')
mp(12, 'mushrooms', 'green peppers', 'extra cheese')

在这个程序中,每当需要调用 make_pizza( ) 时,都可简写成 mp( )。

6.4 使用as给模块指定别名

还可以给模块指定别名。通过给模块指定简短的别名,让你可以更轻松地调用模块中的函数。

python 复制代码
import pizza as p
p.make_pizza(16, 'pepperoni')
p.make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')

上述 import 语句给模块 pizza 制定了别名 p,但该模块中所有函数的名称都没变。

给模块指定别名的通用语法如下:

python 复制代码
import module_name as mn

6.5 导入模块中的所有函数

使用星号运算符可让 python 导入模块中的所有函数:

python 复制代码
from pizza import *
make_pizza(16, 'pepperoni')
make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')

import 语句中的星号让 python 将模块中的每个函数都复制到这个程序文件中。由于导入了每个函数,所以无须使用句点表示法。

7. 函数编写指南

编写函数时,需要牢记几个细节。应给函数指定描述性名称,且只在其中使用小写字母和下划线。描述性名称可帮助你和别人明白代码想要做什么,给模块命名时也应遵循上述约定。此外每个函数都应包含简要的阐述其功能的注释。该注释应紧跟在函数定义后面,并采用文档字符串的格式。

7.1 等号两边不要有空格

给形参指定默认值时,等号两边不要有空格:

python 复制代码
def function_name(parameter_0, parameter_1='default value')

对于函数调用中的关键字实参,也应遵循这种约定:

python 复制代码
function_name(value_0, parameter_1='value')

7.2 参数对齐

大多数编辑器会自动对齐后续参数列表行,使其缩进程度与你给第一个参数列表行指定的缩进程度相同:

python 复制代码
def function_name(
        parameter_0,parameter_1,parameter_2,
        parameter_3,parameter_4,parameter_5):
function body...

7.3 import语句置于开头

如果程序或模块包含多个函数,可使用两个空行将相邻的函数分开,这样更容易知道前一个函数在什么地方结束,下一个函数从什么地方开始。

所有的 import 语句都应放在文件开头。唯一例外的情形是在文件开头使用了注释来描述整个程序。

相关推荐
湫ccc2 分钟前
《Python基础》之基本数据类型
开发语言·python
吃杠碰小鸡6 分钟前
commitlint校验git提交信息
前端
虾球xz37 分钟前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇43 分钟前
HTML常用表格与标签
前端·html
疯狂的沙粒1 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
drebander1 小时前
使用 Java Stream 优雅实现List 转化为Map<key,Map<key,value>>
java·python·list
小镇程序员1 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐1 小时前
前端图像处理(一)
前端
程序猿阿伟1 小时前
《智能指针频繁创建销毁:程序性能的“隐形杀手”》
java·开发语言·前端
疯狂的沙粒1 小时前
对 TypeScript 中函数如何更好的理解及使用?与 JavaScript 函数有哪些区别?
前端·javascript·typescript