第8章 函数

发布于 2022-03-23  102 次阅读


函数是带名字的代码块,用于完成具体的工作,要执行函数定义的特定任务,可调用该函数。需要在程序中多次执行同一项任务时,无需编写完成该任务的代码,只需要调用执行该任务的函数,让Python运行其中的代码即可。
最后,学习如何将函数存储在成为模块的独立文件中,让主程序文件的组织更有序。

8.1 定义函数

下面是一个打印问候语的简单函数,名为greet_user():

def greet_user():
    """这里是注释"""
    print("Hello!")
greet_user()

def告诉Python定义一个函数,这是函数定义。文档字符串(docstring)的注释,描述了函数是做什么的。文档字符串用三引号括起来,Python使用它们来生成有关城西中函数的文档。

8.1.1 向函数传递信息

# def.py
def greet_user(username):
    """显示简单的问候语句"""
    print(f"Hello!{username}")
greet_user('guoan')

file

8.1.2 实参和形参

username是形参
'guoan'是实参

8.2 传递实参

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

8.2.1 位置实参

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

# pets2.py
def describe_pet(animal_type,pet_name):
    """显示宠物信息"""
    print(f"\n I have a {animal_type}.")
    print(f"My {animal_type}'s name is {pet_name.title()}.")

describe_pet('hamster','harry')

file

1.多次调用函数

2.位置实参的顺序很重要

8.2.2 关键字实参

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

...
describe_pet(animal_type = 'hamster',pet_name = 'harry')
# 与 describe_pet(pet_name = 'harry',animal_type = 'hamster')等效

使用关键字实参时,无比准确指定函数定义中的形参名。

8.2.3 默认值

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

def describe_pet(pet_name,animal_type = 'dog'):
    """显示宠物信息"""
    print(f"\n I have a {animal_type}.")
    print(f"My {animal_type}'s name is {pet_name.title()}.")

describe_pet(pet_name = 'willie')

调整函数中形参的位置,是因为,将其设置为默认参数后,Python仍然将此位置的实参视为位置实参,因此如果函数调用中只包含宠物的名字,这个参数将关联到函数定义中的第一个形参。

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

8.2.4 等效的函数调用

三种方式皆可。。

8.2.5 避免实参错误

例如没有输入实参
file

8.3 返回值

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

8.3.1 返回简单值

# formatted_name.py
def get_formatted_name(first_name,last_name):
    """返回整洁的姓名"""
    full_name = f"{first_name} {last_name}"
    return full_name.title()

musician = get_formatted_name('Guo','an')
print(musician)

file

8.3.2 让实参变成可选的

有时候,需要让实参变成可选的,这样使用函数的人就只能再必要时提供额外的信息。可使用默认值来让实参变成可选的。

# formatted_name.py
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('Guo','xiao','an')
print(musician)

file

但是实际情况是,并非所有的人都有中间名,但如果调用这个函数时,只提供了名和姓,它将不能正常运行。为了让中间名变成可选的,可以给middle_name指定一个空的默认值,并在用户没有提到中间名时不适用这个形参。为让get_formatted_name()在没有提供中间名时依然可行,可将形参middle_name的默认设置为空字符串,并将其移到形参列表的末尾:

# formatted_name.py
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('Guo','an',middle_name = 'xiao')
computer = get_formatted_name('Guo','an')
print(musician)
print(computer)

file
可选值让函数能够处理各种不同的情形,同时确保函数调用尽可能简单。

8.3.3 返回字典

函数可返回任何类型的值,包括列表和字典等较为复杂的数据结构。下面的函数接受姓名的组成部分,并返回一个表示人名的字典:

def build_person(first_name,last_name):
    """返回一个字典,其中包含有关一个人的信息"""
    person = {'first':first_name,'last':last_name}
    return person

musician = build_person('Guo','an')
print(musician)

file
函数build_person()接受名和姓,并将这些,并将这些值放到字典中。存储first_name的值时,使用的键为'last'。最后,返回表示人的整个字典。

这个函数接受简单文本信息,并将其放在一个更合适的数结构中,不仅可以打印,还能以其他方式处理它们。
当前,字符串'Guo'和'an'被标记为名和姓。可以轻松地扩展这个函数,使其接受可选值,如中间值、年龄、职业或其他任何要存储的信息。

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_person('Guo','an',age=27)
print(musician)

file
在函数定义中,新增加了一个可选形参age,并将其默认值设置为特殊值None(表示变量没有值)。可将None视为占位值。在条件测试中,None相当于False。如果函数调用中包含形参age的值,这个值将被存储到字典中。在任何情况下,这个函数都会存储人的姓名,但可进行修改,使其同时存储有关人的其他信息。

8.3.4 结合使用函数和while循环

可将函数同之前所有的结构结合起来使用。
例如,下面将结合使用函数get_formatted_name()和while循环,以更正式的方式问候用户。
下面尝试使用名和姓跟用户打招呼:

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"\bHello,{formatted_name}")

file

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("\nEnter 'q' 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"\bHello,{formatted_name}")

file

8.4 传递列表

向函数传递列表很有用,其中包含的可能是名字、数或更复杂的对象(如字典)。
将列表传递给函数后,函数就能直接访问其内容。下面使用函数来提高处理列表的效率。
假设有一个用户列表,我们要问候其中的每位用户。下面的示例将包含名字的列表传递给一个名为greet_users()的函数,这个函数问候列表中的每个人:

def greet_users(names):
    """向列表中的每位用户发出简单的问候。"""
    for name in names:
        msg = f"Hello!,{name.title()}"
        print(msg)

usernames = ['Guoan','xiaoan','knight7z']
greet_users(usernames)

file

8.4.1 在函数中修改列表

在函数中对列表的修改是永久性的。

# 首先创建一个列表,其中包含一些要打印的设计
upprinted_designs = ['phone case','robot pendant','dodecahedron']
completed_models = []

# 模拟打印每个设计,直到没有未打印的设计为止
# 打印每个设计后,都将其移到列表completed_models
while upprinted_designs:
    current_design = upprinted_designs.pop()
    print(f"Printing model:{current_design}")
    completed_models.append(current_design)

# 显示打印好的所有模型
print("\nThe following models have been printed:")
for completed_model in completed_models:
    print(completed_model)

file

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

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

def show_completed_models(completed_models):
    """显示打印好的所有模型"""
    print("\bThe following models have been printed:")
    for completed_model in completed_models:
        print(completed_model)

upprinted_designs = ['phone case','robot pendant','dodecahedron']
completed_models = []

print_models(upprinted_designs,completed_models)
show_completed_models(completed_models)

file
每个函数都应只负责一项具体的工作。第一个函数打印每个设计,第二个显示打印好的模型。这优于使用一个函数来完成来这亮项工作。编写函数时,如果发现它执行的任务太多,请尝试将这些代码划分到两个函数中。总是可以在一个函数中调用另外一个函数,这有助于将复杂的任务划分成一系列步骤。

8.4.2 禁止函数修改列表

为了给函数传递副本而非本体。

function_name(list_name[:])

切片表示法[:]创建列表的副本。在上一个代码中,若是不想清空未打印的设计列表,可像下面这样调用print_models():

print_models(upprinted_designs[:],completed_models)

处理大表格,若非必要,还是直接传递列表较好。

8.5 传递任意数量的实参

有时候预先不知道需要接收多少个参数,Python允许函数从调用语句中收集任意数量的实参。
例如:

def make_pizza(*toppings):
    """打印顾客点的所有配件"""
    print(toppings)

make_pizza('pepperoni')
make_pizza('mushrooms','green peppers','extra cheese')

file
形参中的*让Python创建一个名为toppings的空元组,并将收到的所有制都封装到该元组中。

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')

file
不管函数收到的实参是多少个,这个方法都管用。

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

如果要让函数接受不同类型的实参,必须在函数定义中接纳任意数量实参的形参放在最后。
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')

file
提示:通用形参名*args,它也收集任意数量的位置实参

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

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

一个示例是创建用户简洁:你知道将接受有关用户的信息,但不确定会是什么样的信息。在下面的示例中,函数build_profile()接受名和姓,还接受任意数量的关键字实参:
def build_profile(first,last,**user_info):
    """创建一个字典,其中包含我们直到的有关用户的一切。"""
    user_info['first_name'] = first
    user_info['last_name'] = last
    return user_info

user_profile = build_profile('Guo','an',favorit = 'c',love = 'blue')
print(user_profile)

file

形参**user_info中的两个*让Python创建一个名为user_info的空字典,并将收到的所有名称都放入这个字典中。

调用这个函数时,不管提供了多少额外键值对,他都可以正确处理。

提示:会经常看到形参名字**kwargs,它用于收集任意数量的关键字实参。

8.6 将函数存储在模块中

使用函数的有点之一是可以将代码于主程分离。通过给函数指定描述性名称,可让主程序容易理解得多。还可以更进一步,将函数存储在称为模块的独立文件中,再将模块导入到主程序中。
import 语句允许在当前允许的程序文件中使用模块中的代码,
通过将函数存储在独立的文件中,可隐藏程序代码的细节,将重点放在程序的高层逻辑上。还能让在众多不同的程序中重用函数。将函数存储在独立文件中后,可与其他程序员共享这些文件而不是整个程序

8.6.1 导入整个模块

要让函数是可导入的,首先创建模块。模块的扩展名为.py的文件。包含要导入到程序中的代码。

def make_pizza(size,*toppings):
    """概述要做的比萨"""
    print(f"\nMaking a {size}-inch pizza with the following toppings:")
    for topping in toppings:
        print(f"- {topping}")

file
Python读取这个文件时,代码行import pizza让Python打开文件pizza.py并将其中的所有函数复制到这个文件中。
这就是一种导入方法:只需编写一个import语句并在其中指定模块名,就可以在程序中使用该模块中的所有函数。如果使用这种import语句导入了名为model_name.py的模块,就可以使用下面的语法来使用其中任何一个函数:

model_name.function_name()

8.6.2 导入特定的函数

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

from module_name import function_name
# 通过用逗号分隔函数名,可根据需要从模块中导入任意数量的函数:
from module_name import function_0,function_1,function_2
# 对于前面示例:
from pizza import make_pizza
make_pizza(...)

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

8.6.3 使用as给函数指定别名

若与当前函数名冲突或者太长,可以起一个外号

form pizza import make_pizza as mp

mp(...)

通用:

form model_name import function_name as fn

8.6.4 使用as给模块指定别名

还可以给模块指定别名。

import pizza as p

p.make_pizza(...)

通用:

import module_name as mn

8.6.5 导入模块中的所有函数

使用(*)可让Python导入模块中的所有函数:

form pizza import *

make_pizza(...)

import语句中的星号让Python将模块pizza中的每个函数都复制到这个程序文件中。由于导入了每个函,可通过名称来调用每个函数,而无需使用句点表示法。
使用并非自己编写的大型模块时候,不要采用这种方法,这是因为如果模块中有函数名称相同的函数时候,可能导致错误。
要么只导入使用的代码,要么导入整个模块使用句点表示。

form module_name import *

8.7 函数编写指南

  • 给函数指定描述性名称
  • 阐述功能
  • 给形参指定默认值的时候,=号两边不要有空格。
    def function_name(parameter_0,parameter_1='default value')

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

    function_name(value_0,parameter_1='value')
  • 过长缩进(79字符)
  • 程序包含多个函数,函数与函数间两个空行隔开
  • 除了开头注释外,其余所有import放在文件开头

8.8 小结

学习编写函数,传递参数等。


擦肩而过的概率