第八章:函数


2017年12月5日

8.1 定义函数

8.1.1 简单的函数实例:

def greet_user():
    """显示简单的问候语"""
    print("Hello!")

greet_user()

8.1.2 实参与形参:

  • 形参(parameters):函数完成其工作所需的一项信息
  • 实参(arguments):调用函数时传递给函数的信息,调用函数时,实参的值被存储在形参中

8.2 传递实参

鉴于函数定义中可能包含多个形参,因此函数调用中也可能包含多个实参。向函数传递实参的方式很多,包括位置实参关键字实参以及列表和字典

8.2.1 位置实参

我们调用函数,将每个实参关联到一个实参时,最简单的关联方式是基于实参的顺序,这种方式被称为位置实参(positional arguments)*

注意:位置实参的顺序很重要

def describe_pet(animal_type, pet_name):
    """显示宠物的信息"""
    print("\nI have a " + animal_type + ".")
    print(" ".join(["My", animal_type + "'s name is",\
        pet_name.title()+"."]))

describe_pet('hamster', 'harry')    # 位置实参

8.2.2 关键字实参

关键字实参(keyword argument)是传递给函数的名称-值对,你直接在实参中将名称和值关联起来。

def describe_pet(animal_type, pet_name):
    """显示宠物的信息"""
    print("\nI have a " + animal_type + ".")
    print(" ".join(["My", animal_type + "'s name is",\
        pet_name.title()+"."]))

describe_pet(animal_type='hamster', pet_name='harry')    # 关键字实参

调用函数时,我们向Python明确地指出了各个实参对应的形参。

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

8.2.3 默认值

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

def describe_pet(pet_name, animal_type='dog'):
    """显示宠物的信息"""
    print("\nI have a " + animal_type + ".")
    print(" ".join(["My", animal_type + "'s name is",\
        pet_name.title()+"."]))

describe_pet('harry')    # 位置实参

如果要描述的动物不是小狗,可使用类似于下面的函数调用:

describe_pet(pet_name='harry', animal_type='Hamster')

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

8.2.5 避免实参错误

提供的实参多于或少于函数完成其工作所需的信息时,将出现实参不匹配错误。
应该给变量和函数指定描述性名称,这将使Python的traceback提供的错误信息更有帮助。

8.3 返回值

函数返回的值被称为返回值。在函数中,可使用return语句将值返回到调用函数的代码行。
函数可返回任何类型的值,包括列表和字典等较复杂的数据结构,例如下面的函数接受姓名的组成部分,并且增加了作为可选形参的年龄:

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

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

8.4 传递列表

8.4.1 在函数中修改列表

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

def confirm_users(unconfirmed_users, confirmed_users):
    """
    模拟验证每个用户,直到没有未验证的用户为止
    验证每个用户后,都将其移动到列表confirmed_users中
    """
    while unconfirmed_users:
        current_user = unconfirmed_users.pop()    # 使用pop来提取列表中元素
        confirmed_users.append(current_user)
        print(current_user.title() + " has been confirmed.")

def show_confirmed_users(confirmed_users):
    """显示所有已验证的用户"""
    print("\nThe following users have been confirmed:")
    for confirmed_user in confirmed_users:
        print(confirmed_user)

unconfirmed_users = ['alice', 'brian', 'candace']
confirmed_users = []

confirm_users(unconfirmed_users, confirmed_users)
show_confirmed_users(confirmed_users)

相比于没有使用函数的版本,这个程序更容易扩展和维护。
此外,这个程序还演示了这样一种理念,即每个函数都应只负责一项具体的工作。

8.4.2 禁止函数修改列表

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

function_name(list_name[:])

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

8.5 传递任意数量的实参

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

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

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

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

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

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

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

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

基于上述函数定义。Python将收到的第一个值存储在形参size中,并将其他的所有值都存储在元组toppings中。

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

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

8.6 将函数存储在模块中

可将函数存储在被称为模块的独立文件中,再将模块导入到主程序中。import语句允许在当前运行的程序文件中使用模块中的代码。

8.6.1 导入整个模块
import module_name    # 导入模块

module_name.function_name()    # 使用模块中的函数

这就是一种导入方法:只需要编写一条import语句并在其中指定模块名,就可在程序中使用该模块中的所有函数。

8.6.2 导入特定的函数

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

from module_name import function_name

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

from module_name import function_0, function_1, function_2

若使用这种语法,调用函数时就无需使用.句点。

8.6.3 使用as给函数指定别名

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

from module_name import function_name as fn
8.6.4 使用as给模块指定别名

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

import module_name as mn    # 给模块制定别名

mn.function_name()    # 使用函数中的模块
8.6.5 导入模块中的所有函数

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

from pizza import *

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

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

8.7 函数编写指南

编写函数时,需要牢记几个细节。应给函数指定描述性名称,且只在其中使用小写字母和下划线。描述性名称可帮助你和别人明白代码想要做什么。给模块命名时也应遵循上述约定。
每个函数都应包含简要地描述其功能的注释,该注释应紧跟在函数定义后面,并采用文档字符串格式。加入说明文档以后,使用funcion_name.__doc__(注意:函数名后不包含空格)就可以查看函数function_name的说明文档。
文档良好的函数让其他程序员只需阅读文档字符串中的描述就能够使用它:他们完全相信代码如描述的那样运行;只要知道函数的名称、需要的实参以及返回值的类型,就可以在自己的程序中使用它。
给实参指定默认值时,等号两边不要有空格:

def function_name(parameter_0, parameter_1='default value')

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

function_name(value_0, parameter_1='value')

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

def function_name(
        parameter_0, parameter_1, parameter_2,
        parameter_3, parameter_4, parameter_5):
    function body...

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


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容