8 - 函数

函数是带名字的代码块,用于完成具体的工作。

8.1 定义函数

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

def greet_user():            #定义函数
    """输出简单的问候语"""    #文档字符串,Python使用它们来生成有关程序中函数的文档。
    print("你好")            #函数体
greet_user()                 #调用函数

8.1.1 向函数传递信息

def greet_user(username):
    """输出简单的问候语"""
    print(f"你好,{username.title()}")
greet_user('孙悟空')                    #向函数传递信息

8.1.2 实参和形参

变量username是一个形参(parameter),即函数完成工作所需的信息。

在代码greet_user('孙悟空')中,值'孙悟空'是一个实参(argument),即调用函数时传递给函数的信息。

动手试一试

练习8-1:消息

编写一个名为display_message()的函数,它打印一个句子,指出你在本章学的是什么。调用这个函数,确认显示的消息正确无误。

练习8-2:喜欢的图书

编写一个名为favorite_book()的函数,其中包含一个名为title的形参。这个函数打印一条消息,下面是一个例子。One of my favorite books is Alice in Wonderland.调用这个函数,并将一本图书的名称作为实参传递给它。

8.2 传递实参

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

8.2.1 位置实参

位置实参:实参与形参的关联方式是基于实参的顺序。

def describe_pet(animal_type,pet_name):
    """显示宠物信息"""
    print(f"\n我有一只{animal_type}.")
    print(f"我的{animal_type}叫{pet_name.title()}.")
    
describe_pet('狗','旺旺')

这个函数的定义表明,它需要一种动物类型和一个名字。调用describe_pet()时,需要按顺序提供一种动物类型和一个名字。 

注意:

        可以多次调用函数。

        位置实参的顺序很重要。

8.2.2 关键字实参

关键字实参是传递给函数的名称值对。关键字实参让你无须考虑函数调用中的实参顺序,还清楚地指出了函数调用中各个值的用途。

def describe_pet(animal_type,pet_name):
    """显示宠物信息"""
    print(f"\n我有一只{animal_type}.")
    print(f"我的{animal_type}叫{pet_name.title()}.")
    
describe_pet(animal_type = '狗',pet_name = '旺旺')
describe_pet(pet_name = '旺旺',animal_type = '狗')

函数describe_pet()还和之前一样,但调用这个函数时,向Python明确地指出了各个实参对应的形参。 

注意 使用关键字实参时,务必准确指定函数定义中的形参名。

8.2.3 默认值

编写函数时,可给每个形参指定默认值。

使用默认值可简化函数调用,还可清楚地指出函数的典型用法。

def describe_pet(pet_name, animal_type='狗'):
    """显示宠物信息"""
    print(f"\n我有一只{animal_type}.")
    print(f"我的{animal_type}叫{pet_name.title()}.")
    
describe_pet(pet_name='旺旺')
describe_pet('旺旺')

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

8.2.4 等效的函数调用

鉴于可混合使用位置实参、关键字实参和默认值,通常有多种等效的函数调用方式。

#都是输出“我的狗叫旺旺”
describe_pet('旺旺')
describe_pet(pet_name='旺旺')

#都是输出“我的猫叫喵喵”
describe_pet('喵喵','猫')
describe_pet(pet_name = '喵喵',animal_type = '猫')
describe_pet(animal_type = '猫',pet_name = '喵喵')

8.2.5 避免实参错误

你提供的实参多于或少于函数完成工作所需的信息时,将出现实参不匹配错误。例如,如果调用函数describe_pet()时没有指定任何实参,结果将如何呢?

def describe_pet(pet_name, animal_type='狗'):
    """显示宠物信息"""
    print(f"\n我有一只{animal_type}.")
    print(f"我的{animal_type}叫{pet_name.title()}.")
    
describe_pet()
Traceback (most recent call last):
  File "E:\。。。\pets.py", line 6, in <module>
    describe_pet()
TypeError: describe_pet() missing 1 required positional argument: 'pet_name'

动手试一试

练习8-3:T恤

编写一个名为make_shirt()的函数,它接受一个尺码以及要印到T恤上的字样。这个函数应打印一个句子,概要地说明T恤的尺码和字样。使用位置实参调用该函数来制作一件T恤,再使用关键字实参来调用这个函数。

练习8-4:大号T恤

修改函数make_shirt(),使其在默认情况下制作一件印有“I lovePython”字样的大号T恤。调用这个函数来制作:一件印有默认字样的大号T恤,一件印有默认字样的中号T恤,以及一件印有其他字样的T恤(尺码无关紧要)。

练习8-5:城市

编写一个名为describe_city()的函数,它接受一座城市的名字以及该城市所属的国家。这个函数应打印一个简单的句子,下面是一个例子。Reykjavik is in Iceland.给用于存储国家的形参指定默认值。为三座不同的城市调用这个函数,且其中至少有一座城市不属于默认国家。

8.3 返回值

函数返回的值称为返回值。在函数中,可使用return语句将值返回到调用函数的代码行。

8.3.1 返回简单值

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

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)

8.3.2 让实参变成可选的

可使用默认值来让实参变成可选的。

例如,假设要扩展函数get_formatted_name(),使其同时处理中间名,但不是每个人都有中间名:

def get_formatted_name(first_name, last_name, middle_name=''):
    """Return a full name, neatly formatted."""
    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('jimi', 'hendrix')
print(musician)

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

Python将非空字符串解读为True,因此如果函数调用中提供了中间名,if middle_name将为True 

8.3.3 返回字典

例如,下面的函数接受姓名的组成部分,并返回一个表示人的字典:

def build_person(first_name, last_name):
    """Return a dictionary of information about a person."""
    person = {'first': first_name, 'last': last_name}
    return person

musician = build_person('jimi', 'hendrix')
print(musician)

这个函数接受简单的文本信息,并将其放在一个更合适的数据结构中,让你不仅能打印这些信息,还能以其他方式处理它们。

你可以轻松地扩展这个函数,使其接受可选值,如中间名、年龄、职业或其他任何要存储的信息。例如,下面的修改让你能存储年龄:

def build_person(first_name, last_name, age=None):
    """Return a dictionary of information about a person."""
    person = {'first': first_name, 'last': last_name}
    if age:
        person['age'] = age
    return person

musician = build_person('jimi', 'hendrix', age=27)
print(musician)

 在函数定义中,新增了一个可选形参age,并将其默认值设置为特殊值None(表示变量没有值)。可将None视为占位值。

8.3.4 结合使用函数和while循环

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

def get_formatted_name(first_name, last_name):
    """Return a full name, neatly formatted."""
    full_name = f"{first_name} {last_name}"
    return full_name.title()

# This is an infinite loop!
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}!")

动手试一试

练习8-6:城市名

编写一个名为city_country()的函数,它接受城市的名称及其所属的国家。

这个函数应返回一个格式类似于下面的字符串:

”绍兴,中国“

至少使用三个城市国家对来调用这个函数,并打印它返回的值。

练习8-7:专辑

编写一个名为make_album()的函数,它创建一个描述音乐专辑的字典。这个函数应接受歌手的名字和专辑名,并返回一个包含这两项信息的字典。使用这个函数创建三个表示不同专辑的字典,并打印每个返回的值,以核实字典正确地存储了专辑的信息。给函数make_album()添加一个默认值为None的可选形参,以便存储专辑包含的歌曲数。如果调用这个函数时指定了歌曲数,就将该值添加到表示专辑的字典中。调用这个函数,并至少在一次调用中指定专辑包含的歌曲数。

练习8-8:用户的专辑

在为完成练习8-7编写的程序中,编写一个while循环,让用户输入专辑的歌手和名称。获取这些信息后,使用它们来调用函数make_album()并将创建的字典打印出来。在这个while循环中,务必提供退出途径。

8.4 传递列表

假设有一个用户列表,我们要问候其中的每位用户:

def greet_users(names):
    """Print a simple greeting to each user in the list."""
    for name in names:
        msg = f"Hello, {name.title()}!"
        print(msg)

usernames = ['hannah', 'ty', 'margot']
greet_users(usernames)

8.4.1 在函数中修改列表

打印模型:

def print_models(unprinted_designs, completed_models):
    """
    Simulate printing each design, until none are left.
    Move each design to completed_models after printing.
    """
    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):
    """Show all the models that were printed."""
    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)

第一个函数负责处理打印设计的工作,第二个概述打印了哪些设计。

该程序还演示了这样一种理念:每个函数都应只负责一项具体的工作。 

8.4.2 禁止函数修改列表

可向函数传递列表的副本而非原件。

要将列表的副本传递给函数,可以像下面这样做:

function_name(list_name[:])

切片表示法[:]创建列表的副本。

虽然向函数传递列表的副本可保留原始列表的内容,但除非有充分的理由,否则还是应该将原始列表传递给函数。这是因为让函数使用现成的列表可避免花时间和内存创建副本,从而提高效率,在处理大型列表时尤其如此。 

动手试一试

练习8-9:消息

创建一个列表,其中包含一系列简短的文本消息。将该列表传递给一个名为show_messages()的函数,这个函数会打印列表中的每条文本消息。

练习8-10:发送消息

在你为完成练习8-9而编写的程序中,编写一个名为send_messages()的函数,将每条消息都打印出来并移到一个名为sent_messages的列表中。调用函数send_messages(),再将两个列表都打印出来,确认正确地移动了消息。

练习8-11:消息归档

修改你为完成练习8-10而编写的程序,在调用函数send_messages()时,向它传递消息列表的副本。调用函数send_messages()后,将两个列表都打印出来,确认保留了原始列表中的消息。

8.5 传递任意数量的实参

让形参带星即可:

def make_pizza(*toppings):
    """输出顾客想要给披萨加哪些配料"""
    print(toppings)

make_pizza('辣椒')
make_pizza('辣椒','香肠','芝士')

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

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

def make_pizza(*toppings):
    """输出顾客想要给披萨加哪些配料"""
    print("\n顾客要的配料如下:")
    for topping in toppings:
        print(f"    - {topping}")

make_pizza('辣椒')
make_pizza('辣椒','香肠','芝士')

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

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

def make_pizza(size,*toppings):
    """输出顾客想要多大的披萨以及加哪些配料"""
    print(f"\n顾客点了{size}英寸的披萨,其想要加的配料如下:")
    for topping in toppings:
        print(f"    - {topping}")

make_pizza(10,'辣椒')
make_pizza(10,'辣椒','香肠','芝士')

注意 你经常会看到通用形参名*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('悟空', '孙',
                             location='花果山',
                             hobby='打妖怪')
print(user_profile)

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

编写函数时,能以各种方式混合使用位置实参、关键字实参和任意数量的实参。

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

动手试一试

练习8-12:三明治

编写一个函数,它接受顾客要在三明治中添加的一系列食材。这个函数只有一个形参(它收集函数调用中提供的所有食材),并打印一条消息,对顾客点的三明治进行概述。调用这个函数三次,每次都提供不同数量的实参。

练习8-13:用户简介

复制前面的程序user_profile.py,在其中调用build_profile()来创建有关你的简介。调用这个函数时,指定你的名和姓,以及三个描述你的键值对。

练习8-14:汽车

编写一个函数,将一辆汽车的信息存储在字典中。这个函数总是接受制造商和型号,还接受任意数量的关键字实参。这样调用该函数:提供必不可少的信息,以及两个名称值对,如颜色和选装配件。这个函数必须能够像下面这样进行调用:

 打印返回的字典,确认正确地处理了所有的信息。

8.6 将函数存储在模块中

使用函数的优点之一是可将代码块与主程序分离。

你还可以更进一步,将函数存储在称为模块的独立文件中,再将模块导入到主程序中。

import语句允许在当前运行的程序文件中使用模块中的代码。

通过将函数存储在独立的文件中,可隐藏程序代码的细节,将重点放在程序的高层逻辑上。

8.6.1 创建模块和导入整个模块

模块是扩展名为.py的文件,包含要导入到程序中的代码。

下面来创建一个包含函数make_pizza()的模块。

pizza.py

def make_pizza(size,*toppings):
    """输出顾客想要多大的披萨以及加哪些配料"""
    print(f"\n顾客点了{size}英寸的披萨,其想要加的配料如下:")
    for topping in toppings:
        print(f"    - {topping}")

接下来,在pizza.py所在的目录中创建一个名为making_pizzas.py的文件。

import pizza

pizza.make_pizza(16, '芝士')
pizza.make_pizza(12, '蘑菇', '辣椒', '芝士')

Python读取这个文件时,代码行import pizza让Python打开文件pizza.py,并将其中的所有函数都复制到这个程序中。

要调用被导入模块中的函数,可指定被导入模块的名称pizza和函数名make_pizza(),并用句点分隔。 

8.6.2 导入特定的函数

语法:from module_name import function_0,function_1,......

from pizza import make_pizza

make_pizza(16, '芝士')
make_pizza(12, '蘑菇', '辣椒', '芝士')

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

8.6.3 使用as给函数指定别名

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

from pizza import make_pizza as mp

mp(16, '芝士')
mp(12, '蘑菇', '辣椒', '芝士')

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

 from module_name import function_name as fn

8.6.4 使用as给模块指定别名

通过给模块指定简短的别名(如给模块pizza指定别名p),让你能够更轻松地调用模块中的函数。

给模块指定别名的通用语法如下:import module_name as mn

8.6.5 导入模块中的所有函数

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

from pizza import *

make_pizza(16, '芝士')
make_pizza(12, '蘑菇', '辣椒', '芝士')

import语句中的星号让Python将模块pizza中的每个函数都复制到这个程序文件中。

使用并非自己编写的大型模块时,最好不要采用这种导入方法。

Python遇到多个名称相同的函数或变量,后面覆盖前面。

最佳的做法是,要么只导入需要使用的函数,要么导入整个模块并使用句点表示法。

语法:from module import * 

8.7 函数编写指南

应给函数指定描述性名称,且只在其中使用小写字母和下划线。

每个函数都应包含简要地阐述其功能的注释。该注释应紧跟在函数定义后面,并采用文档字符串格式。

给形参指定默认值时,等号两边不要有空格,对于函数调用中的关键字实参,也应遵循这种约定。

如果形参很多,导致函数定义的长度超过了79字符,可在函数定义中输入左括号后按回车键,并在下一行按两次Tab键,从而将形参列表和只缩进一层的函数体区分开来。

如果程序或模块包含多个函数,可使用两个空行将相邻的函数分开。

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

动手试一试

练习8-15:打印模型

将示例printing_models.py中的函数放在一个名为printing_functions.py的文件中。在printing_models.py的开头编写一条import语句,并修改该文件以使用导入的函数。

练习8-16:导入

选择一个你编写的且只包含一个函数的程序,将该函数放在另一个文件中。在主程序文件中,使用下述各种方法导入这个函数,再调用它:

练习8-17:函数编写指南 选择你在本章中编写的三个程序,确保它们遵循了本节介绍的函数编写指南。

8.8 小结

在本章中,你学习了:如何编写函数,以及如何传递实参,让函数能够访问完成其工作所需的信息;如何使用位置实参和关键字实参,以及如何接受任意数量的实参;显示输出的函数和返回值的函数;如何将函数同列表、字典、if语句和while循环结合起来使用;如何将函数存储在称为模块的独立文件中,让程序文件更简单、更易于理解。最后,你学习了函数编写指南,遵循这些指南可让程序始终结构良好,并对你和其他人来说易于阅读。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值