Python基础之函数

Posted davieyang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python基础之函数相关的知识,希望对你有一定的参考价值。

一、摘要

要执行函数定义的特定任务,可调用该函数。需要在程序中多次执行同一项任务时,你无需反复编写完成该任务的代码,而只需调用执行该任务的函数,让Python运行其中的代码。你将发现,通过使用函数,程序的编写、阅读、测试和修复都将更容易。

二、函数实操

定义函数:

def greet_user():
    """显示简单的问候语"""
    print("Hello!")
greet_user()
  • 使用关键字def 来告诉Python你要定义一个函数,它向Python指出了函数名,还可能在括号内指出函数为完成其任务需要什么样的信息。在这里,函数名为greet_user() ,它不需要任何信息就能完成其工作,因此括号是空的(即便如此,括号也必不可少)。最后,定义以冒号结尾。
  • 紧跟在def greet_user(): 后面的所有缩进行构成了函数体,由3对双引号包含的文本是被称为文档字符串 (docstring)的注释,描述了函数是做什么的,Python使用它们来生成有关程序中函数的文档。
  • 代码行print("Hello!") 是函数体内的唯一一行代码,greet_user() 只做一项工作:打印Hello!
  • 要使用这个函数,可调用它。函数调用 让Python执行函数的代码。要调用 函数,可依次指定函数名以及用括号括起的必要信息,由于这个函数不需要任何信息,因此调用它时只需输入greet_user() 即可

向函数传递信息:

def greet_user(username):
    """显示简单的问候语"""
    print("Hello, " + username.title() + "!")
greet_user(jesse)

实参和形参:

在函数greet_user() 的定义中,变量username 是一个形参 ——函数完成其工作所需的一项信息。在代码greet_user(‘jesse‘) 中,值‘jesse‘ 是一个实参 。实参是调用函数时传递给函数的信息。我们调用函数时,将要让函数使用的信息放在括号内。在greet_user(‘jesse‘) 中,将实参‘jesse‘ 传递给了函数greet_user() ,这个值被存储在形参username 中

传递实参:

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

位置实参:

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

def describe_pet(animal_type, pet_name):
    """显示宠物的信息"""
    print("
I have a " + animal_type + ".")
    print("My " + animal_type + "‘s name is " + pet_name.title() + ".")
describe_pet(hamster, harry)

我们看到这个函数的定义,接受两个参数,第一个参数animal_type表示动物类型,第二个参数pet_name表示宠物名称,然后再形参的位置和意义已知的情况下,我们后边的调用describe_pet(‘hamster‘, ‘harry‘)的时候传给参数的实际参数正确的对应了函数定义时候的位置及其意义

执行结果为:

I have a hamster.
My hamsters name is Harry.

而如果我们的实际参数传递顺序和形参的位置及意义不一致,执行结果可能会让人很迷茫

函数定义好,我们可以多次调用它,如下代码所示:

def describe_pet(animal_type, pet_name):
    """显示宠物的信息"""
    print("
I have a " + animal_type + ".")
    print("My " + animal_type + "‘s name is " + pet_name.title() + ".")
describe_pet(hamster, harry)
describe_pet(dog, willie)

调用函数多次是一种效率极高的工作方式

关键字实参:

关键字实参 是传递给函数的名称=值对。你直接在实参中将名称和值关联起来了,因此向函数传递实参时不会出现实际参数和函数定义时的形参位置和意义不相符的情况。关键字实参让你无需考虑函数调用中的实参顺序,还清楚地指出了函数调用中各个值的用途。

def describe_pet(animal_type, pet_name):
    """显示宠物的信息"""
    print("
I have a " + animal_type + ".")
    print("My " + animal_type + "‘s name is " + pet_name.title() + ".")
describe_pet(animal_type=hamster, pet_name=harry)

函数describe_pet() 还是原来那样,但调用这个函数时,我们向Python明确地指出了各个实参对应的形参。看到这个函数调用时,Python知道应该将实参‘hamster‘和‘harry‘ 分别存储在形参animal_type 和pet_name 中。输出正确无误,它指出我们有一只名为Harry的仓鼠。关键字实参的顺序无关紧要,因为Python知道各个值该存储到哪个形参中。下面两个函数调用是等效的:

describe_pet(animal_type=hamster, pet_name=harry)
describe_pet(pet_name=harry, animal_type=hamster)

注意:使用关键字实参时,务必准确地指定函数定义中的形参名,如果形参名敲错,那么调用会报异常,如下所示

>>> describe_pet(animal = aaa, pet_name = bbb)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: describe_pet() got an unexpected keyword argument animal
>>>

参数默认值:

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

def describe_pet(pet_name, animal_type=dog):
    """显示宠物的信息"""
    print("
I have a " + animal_type + ".")
    print("My " + animal_type + "‘s name is " + pet_name.title() + ".")
describe_pet(pet_name=willie)

执行结果为:

I have a dog.
My dogs name is Willie.

我们在调用函数时,只给了一个实参,但另一个参数有默认值,因此调用是成功的,并且Python使用了默认值

请注意,在这个函数的定义中,修改了形参的排列顺序。由于给animal_type 指定了默认值,无需通过实参来指定动物类型,因此在函数调用中只包含一个实参——宠物的名字。然而,Python依然将这个实参视为位置实参,因此如果函数调用中只包含宠物的名字,这个实参将关联到函数定义中的第一个形参。这就是需要将pet_name 放在形参列表开头的原因所在。因此入选两种调用就是等效的

describe_pet(willie)
describe_pet(pet_name=willie)

虽然我们给函数的形参指定了默认值,但它只是默认值,当我们没给该形参传递实参时,Python会使用默认值,如果我们调用函数的时候显示的给拥有默认值的形参传递一个新的实参,Python则会忽略原有默认值使用新传递的实参

describe_pet(pet_name=harry, animal_type=hamster)

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

等效的函数调用:

对于函数:

def describe_pet(pet_name, animal_type=dog):

如下调用均是可以成功的

# 一条名为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)

避免实参错误:

def describe_pet(animal_type, pet_name):
    """显示宠物的信息"""
    print("
I have a " + animal_type + ".")
    print("My " + animal_type + "‘s name is " + pet_name.title() + ".")

describe_pet()

等你开始使用函数后,如果遇到实参不匹配错误,当你提供的实参多于或少于函数完成其工作所需的信息时,将出现实参不匹配错误

Traceback (most recent call last):
    File "pets.py", line 6, in <module>
        describe_pet()
TypeError: describe_pet() missing 2 required positional arguments: animal_type and pet_name    

如上异常信息所示,Python读取函数的代码,并指出我们需要为哪些形参提供实参,这提供了极大的帮助。这也是应该给变量和函数指定描述性名称的另一个原因;如果你这样做了,那么无论对于你,还是可能使用你编写的代码的其他任何人来说,Python提供的错误消息都将更有帮助。

返回值:

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

def get_formatted_name(first_name, last_name):
    """返回整洁的姓名"""
    full_name = first_name +   + last_name
    return full_name.title()

musician = get_formatted_name(jimi, hendrix)
print(musician)

函数get_formatted_name() 的定义通过形参接受名和姓。它将姓和名合而为一,在它们之间加上一个空格,并将结果存储在变量full_name 中。然后,将full_name 的值转换为首字母大写格式,并将结果返回到函数调用行。调用返回值的函数时,需要提供一个变量,用于存储返回的值。在这里,将返回值存储在了变量musician 中。输出为整洁的姓名:

Jimi Hendrix

让实参变成可选的:

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

def get_formatted_name(first_name, last_name, middle_name=‘‘):
    """返回整洁的姓名"""
    if middle_name:
        full_name = first_name +   + middle_name +   + last_name
    else:
        full_name = 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,如果提供了中间名,就将名、中间名和姓合并为姓名,然后将其修改为首字母大写格式,并返回到函数调用行。在函数调用行,将返回的值存储在变量musician 中;然后将这个变量的值打印出来。如果没有提供中间名,middle_name 将为空字符串,导致if 测试未通过,进而执行else 代码块只使用名和姓来生成姓名,并将设置好格式的姓名返回给函数调用行。在函数调用行,将返回的值存储在变量musician 中;然后将这个变量的值打印出来。调用这个函数时,如果只想指定名和姓,调用起来将非常简单。如果还要指定中间名,就必须确保它是最后一个实参,这样Python才能正确地将位置实参关联到形参,这个修改后的版本适用于只有名和姓的人,也适用于还有中间名的人:

Jimi Hendrix
John Lee Hooker

返回字典:

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

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

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

执行结果为:

{first: jimi, last: hendrix}

再看一个例子:

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

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

结合使用函数和while 循环:

def get_formatted_name(first_name, last_name):
    """返回整洁的姓名"""
    full_name = first_name +   + last_name
    return full_name.title()

# 这是一个无限循环!
while True:
    print("
Please tell me your name:")
    f_name = input("First name: ")
    l_name = input("Last name: ")
    formatted_name = get_formatted_name(f_name, l_name)
    print("
Hello, " + formatted_name + "!")
def get_formatted_name(first_name, last_name):
    """返回整洁的姓名"""
    full_name = first_name +   + last_name
    return full_name.title()
while True:
    print("
Please 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("
Hello, " + formatted_name + "!")

传递列表:

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

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

我们将greet_users() 定义成接受一个名字列表,并将其存储在形参names 中。这个函数遍历收到的列表,并对其中的每位用户都打印一条问候语。我们定义了一个用户列表——usernames ,然后调用greet_users() ,并将这个列表传递给它:

执行结果为:

Hello, Hannah!
Hello, Ty!
Hello, Margot!

在函数中修改列表:

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

def print_models(unprinted_designs, completed_models):
    """
    模拟打印每个设计,直到没有未打印的设计为止
    打印每个设计后,都将其移到列表completed_models中
    """
    while unprinted_designs:
        current_design = unprinted_designs.pop()
        # 模拟根据设计制作3D打印模型的过程
        print("Printing model: " + current_design)
        completed_models.append(current_design)

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

unprinted_designs = [iphone case, robot pendant, dodecahedron]
completed_models = []
print_models(unprinted_designs, completed_models)
show_completed_models(completed_models)

定义了函数print_models() ,它包含两个形参:一个需要打印的设计列表和一个打印好的模型列表。给定这两个列表,这个函数模拟打印每个设计的过程:将设计逐个地从未打印的设计列表中取出,并加入到打印好的模型列表中。然后定义了函数show_completed_models() ,它包含一个形参:打印好的模型列表。给定这个列表,函数show_completed_models() 显示打印出来的每个模型的名称。

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

禁止函数修改列表:

有时候,需要禁止函数修改列表。例如,假设像前一个示例那样,你有一个未打印的设计列表,并编写了一个将这些设计移到打印好的模型列表中的函数。你可能会做出这样的决定:即便打印所有设计后,也要保留原来的未打印的设计列表,以供备案。但由于你将所有的设计都移出了unprinted_designs ,这个列表变成了空的,原来的列表没有了。为解决这个问题,可向函数传递列表的副本而不是原件;这样函数所做的任何修改都只影响副本,而丝毫不影响原件。要将列表的副本传递给函数,可以像下面这样做:

function_name(list_name[:])

切片表示法[:] 创建列表的副本,如果不想清空未打印的设计列表,可像下面这样调用print_models() 

print_models(unprinted_designs[:], completed_models)

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

传递任意数量的实参:

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

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

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

形参名*toppings 中的星号让Python创建一个名为toppings 的空元组,并将收到的所有值都封装到这个元组中。函数体内的print 语句通过生成输出来证明Python能够处理使用一个值调用函数的情形,也能处理使用三个值来调用函数的情形。它以类似的方式处理不同的调用,注意,Python将实参封装到一个元组中,即便函数只收到一个值也如此:

执行结果为:

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

再看一个例子:

def make_pizza(*toppings):
    """概述要制作的比萨"""
    print("
Making a pizza with the following toppings:")
    for topping in toppings:
    print("- " + topping)

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

执行结果为:

Making a pizza with the following toppings:
- pepperoni
Making a pizza with the following toppings:
- mushrooms
- green peppers
- extra cheese

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

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

def make_pizza(size, *toppings):
    """概述要制作的比萨"""
    print("
Making a " + str(size) + "-inch pizza with the following toppings:")
    for topping in toppings:
    print("- " + topping)

make_pizza(16, pepperoni)
make_pizza(12, mushrooms, green peppers, extra cheese)

基于上述函数定义,Python将收到的第一个值存储在形参size 中,并将其他的所有值都存储在元组toppings 中。在函数调用中,首先指定表示比萨尺寸的实参,然后根据需要指定任意数量的配料, 执行结果如下:

Making a 16-inch pizza with the following toppings:
- pepperoni
Making a 12-inch pizza with the following toppings:
- mushrooms
- green peppers
- extra cheese

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

有时候,需要接受任意数量的实参,但预先不知道传递给函数的会是什么样的信息。在这种情况下,可将函数编写成能够接受任意数量的键—值对——调用语句提供了多少就接受多少。一个这样的示例是创建用户简介:你知道你将收到有关用户的信息,但不确定会是什么样的信息。在下面的示例中,函数build_profile() 接受名和姓,同时还接受任意数量的关键字实参:

def build_profile(first, last, **user_info):
    """创建一个字典,其中包含我们知道的有关用户的一切"""
    profile = {}
    profile[first_name] = first
    profile[last_name] = last
    for key, value in user_info.items():
        profile[key] = value
    return profile

user_profile = build_profile(albert, einstein,location=princeton, field=physics)
print(user_profile)

函数build_profile() 的定义要求提供名和姓,同时允许用户根据需要提供任意数量的名称—值对。形参**user_info 中的两个星号让Python创建一个名为user_info 的空字典,并将收到的所有名称值对都封装到这个字典中。在这个函数中,可以像访问其他字典那样访问user_info 中的名称值对

执行结果为:

{first_name: albert, last_name: einstein, location: princeton, field: physics}

将函数存储在模块中:

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

导入整个模块:

import module_name

导入模块后,调用模块中的函数:

module_name.function_name()

导入特定的函数:

from module_name import function_name
from module_name import function_0, function_1, function_2

若使用这种语法,调用函数时就无需使用句点,由于我们在import 语句中显式地导入了函数,因此调用它时只需指定其名称。

使用as 给函数指定别名:

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

from module_name import function_name as fn

然后fn就等同于我们导入的function_name

使用as 给模块指定别名:

你还可以给模块指定别名。通过给模块指定简短的别名(如给模块pizza 指定别名p ),让你能够更轻松地调用模块中的函数。相比于pizza.make_pizza(),p.make_pizza() 更为简洁:

import module_name as mn

导入模块中的所有函数:

使用星号(* )运算符可让Python导入模块中的所有函数,import 语句中的星号让Python将模块pizza 中的每个函数都复制到这个程序文件中。由于导入了每个函数,可通过名称来调用每个函数,而无需使用句点表示法。然而,使用并非自己编写的大型模块时,最好不要采用这种导入方法:如果模块中有函数的名称与你的项目中使用的名称相同,可能导致意想不到的结果:Python可能遇到多个名称相同的函数或变量,进而覆盖函数,而不是分别导入所有的函数。最佳的做法是,要么只导入你需要使用的函数,要么导入整个模块并使用句点表示法。这能让代码更清晰,更容易阅读和理解。这里之所以介绍这种导入方法,只是想让你在阅读别人编写的代码时,如果遇到类似于下面的import 语句,能够理解它们

from module_name import *

函数编写规范:

编写函数时,需要牢记几个细节。

  • 应给函数指定描述性名称,且只在其中使用小写字母和下划线。描述性名称可帮助你和别人明白代码想要做什么。
  • 给模块命名时也应遵循上述约定。
  • 每个函数都应包含简要地阐述其功能的注释,该注释应紧跟在函数定义后面,并采用文档字符串格式。文档良好的函数让其他程序员只需阅读文档字符串中的描述就能够使用它:他们完全可以相信代码如描述的那样运行;只要知道函数的名称、需要的实参以及返回值的类型,就能在自己的程序中使用它。
  • 给形参指定默认值时,等号两边不要有空格:
def function_name(parameter_0, parameter_1=default value)
  • 对于函数调用中的关键字实参,也应遵循这种约定:
function_name(value_0, parameter_1=value)
  • PEP 8(https://www.python.org/dev/peps/pep-0008/ )建议代码行的长度不要超过79字符,这样只要编辑器窗口适中,就能看到整行代码。如果形参很多,导致函数定义的长度超过了79字符,可在函数定义中输入左括号后按回车键,并在下一行按两次Tab键,从而将形参列表和只缩进一层的函数体区分开来。大多数编辑器都会自动对齐后续参数列表行,使其缩进程度与你给第一个参数列表行指定的缩进程度相同:
def function_name(
        parameter_0, parameter_1, parameter_2,
        parameter_3, parameter_4, parameter_5):
    function body...    
  • 如果程序或模块包含多个函数,可使用两个空行将相邻的函数分开,这样将更容易知道前一个函数在什么地方结束,下一个函数从什么地方开始。
  • 所有的import 语句都应放在文件开头,唯一例外的情形是,在文件开头使用了注释来描述整个程序,那么就将import语句跟在注释之后

以上是关于Python基础之函数的主要内容,如果未能解决你的问题,请参考以下文章

Python之如何优雅的重试

Python 之 基础知识

python基础之函数

python之函数基础

Python基础之函数

python-之函数基础知识