十七、函数

作者: 焰火青春 | 来源:发表于2017-11-25 09:52 被阅读22次

    函数可以将程序拆分成独立的几部分,使程序趋于简单化。

    1.1、定义和调用函数

    使用关键字def 来告诉python定义一个函数,向python指出了函数名,也还可以在括号内指出函数为完成任务需要什么样的信息。

    而调用一个函数,只需让python指定函数名以及括号内的必要信息即可。

    def display_message():         # 函数定义
        """display a message about what I am learning."""  # 文档字符串的注释,描述函数是做什么的,由三引号组成,python用来生成有关程序中函数的文档
        msg = "I'm learning to store code in functions."
        print(msg)
    
    
    display_message()     # 函数调用
    
    ----------
    I'm learning to store code in functions.
    

    1.1.1、向函数参数传递信息

    函数在定义时,可在括号内指定任意参数,在调用时再给其指定一个值,调用函数时,可将值传递给参数:

    def great_user(username):  # 指定参数(形参)
        print('Hello ' + username.title() + '!')
    
    
    great_user('edward') # 指定值并传递给参数(实参)
    -------
    Hello Edward!
    

    1.1.2、形参和实参

    在1.1.1中,变量username是一个形参,它是函数定义时括号内的参数,是函数完成其工作所需的一项信息。

    值'edward' 是一个实参,它是函数调用时括号内的参数,是调用函数时传递给函数的信息。

    在great_user('edward')中,将参数edward传递给函数great_user(),并存储在形参user_name中。

    1.2、传递实参

    函数定义可能包含多个形参,调用时也可以有多个实参,传递实参的方式很多,有位置参数法,(要求形参实参的顺序一致)意义,关键字参数。

    1.2.1、位置实参

    位置实参要求实参与形参的顺序一一对应,切记不能对应错误,另一方面,一个函数可以多次调用,只需再次指定实参即可。

    def describe_pet(animal_type, pet_name):
        """显示宠物信息"""
        print('\nI have a ' + animal_type + '.')
        print("My " + animal_type + "'s name is " + pet_name.title() + '.')
    
    
    describe_pet('hamster', 'harry')  # 实参与形参顺序对应,宠物种类对应animal_type、名字对应pet_name
    describe_pet('dog', 'baby')  # 二次调用
    

    1.2.2、关键字实参

    位置参数固然有其优点,但只适合形参比较少的程序,若形参有很多,传递实参时就容易显得杂乱无章,关键字实参将名称与值关联起来,向形参传递实参时就不会混淆。

    def describe_pet(animal_type, pet_name):   # 形参
        """显示宠物信息"""
        print('\nI have a ' + animal_type + '.')
        print("My " + animal_type + "'s name is " + pet_name.title() + '.')
    
    
    describe_pet(animal_type= 'hamster', pet_name= 'harry')  # 将实参直接存储到形参变量中,不管顺序如何变化都不影响结果
    describe_pet(pet_name= 'baby', animal_type= 'dog')
    

    1.2.3、默认值

    在函数定义时可以给形参直接指定个默认值,如果与其关联的实参给有默认值的形参提供了值,则用提供的值,否则用默认值。

    需要注意的是,在调用函数时,只有一个实参,且是位置实参,那么没有指定默认值的形参要在有默认值的形参前面,否则实参将会以位置参数的形式传递给第一个形参:

    def describe_pet(pet_name, animal_type='dogs'):     # 有默认值在没有默认值的形参后面
        """显示宠物信息"""
        print('\nI have a ' + animal_type + '.')
        print("My " + animal_type + "'s name is " + pet_name.title() + '.')
    
    
    describe_pet('willie')           # 这个实参将传递给第一个形参,第二个形参使用默认值
    describe_pet(animal_type='dog', pet_name='baby')  # 有默认值的形参使用实参
    -------------------
    
    I have a dogs.
    My dogs's name is Willie.
    
    I have a dog.
    My dog's name is Baby.
    

    1.2.4、等效的函数调用

    鉴于可混合位置实参、关键字实参以及默认值,通常有多种调用方式,以下便是等效的函数调用方式,其结果一致:

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

    1.2.5、避免实参错误

    在使用函数时,若遇到实参不匹配时,python会提示我们需要为哪些提供实参,如果这个函数存储在一个独立的文件中,那么需要打开这个文件查看函数的代码:

    def describe_pet(pet_name, animal_type):
        print('My ' + animal_type + "'s name is " + pet_name.title() + '.')
    
    
    describe_pet()             # 这个函数缺少实参,python会提示我们需要为哪个形参提供实参
    
    Traceback (most recent call last):
      File "C:/Users/hj/PycharmProjects/untitled1/function_1.py", line 55, in <module>
        describe_pet()
    TypeError: describe_pet() missing 2 required positional arguments: 'pet_name' and 'animal_type'
    

    1.3、返回值

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

    1.3.1、返回简单值

    def get_formatted_name(first_name, last_name):
      """返回完整姓名"""
      full_name = first_name + ' ' + last_name # 将完整姓名存储到变量full_name中
      return full_name.title()  # 将full_name的值返回到函数调用行
    musician = get_formatted_name('jimi', 'hendrix') # 在调用返回值的函数时,需要提供一个变量,用于存储返回值
    print(musician)  # 打印变量
    ------
    Jimi Hendrix
    

    1.3.2、让实参变得可选

    有时需要让实参变得可选,这样就能存储额外的信息,可使用默认值来使实参可选:

    # 拓展get_formatted_name(),使其还处理中间名
    def get_formatted_name(first_name, last_name, middle_name=''):  # 指定middle_name的默认值为空
      """返回完整姓名"""
      if middle_name:     # 判断调用函数时是否给middle_name指定了实参,python将非空字符串认为True
        full_name = first_name + ' ' + middle_name + ' ' + last_name
      else:                  
        full_name = first_name + ' ' + last_name
      return full_name.title()
    
    musician = get_formatted_name('jimi', 'hooker', 'lee')
    print(musician)
    
    musician = get_formatted_name('jimi', 'hooker') # 调用函数时,如果没有middle_name的实参就会导致程序错误,因此可以指定形参middle_name的默认值为空,当有实参时则用实参的值,没有的时候就是空字符串,这样不会奔溃程序
    print(musician)
    -----
    
    Jimi Lee Hooker
    Jimi Hooker
    

    1.3.3、返回字典

    函数可以返回任何类型的值,包括列表、元组以及字典等较复杂的数据结构:

    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', 'hendrix': 'hendrix'}
    

    返回字典,可以轻易拓展这个函数,使其接受可选值,如年龄、中间名、职业等你想存储的其他信息:

    def buile_person(first_name, last_name, age=''): # 新增了age可选项
      """返回一个字典,其中包含人名、年龄"""
      person = {'first': first_name, 'last': last_name}
      if age:
        person['age'] = age  # 将键-值对添加到字典person中
      return person
    
    musician = build_person('jimi', 'hendrix', age=27)
    print(musician)
    -------
    {'first': 'jimi', 'last': 'hendrix', 'age': 27}
      
    

    1.3.4、结合使用函数和 while 循环

    def get_formatted_name(first_name, last_name):
      """返回完整姓名"""
      full_name = first_name + ' ' + last_name
      return full_name.title()
    
    while True:
      print("\nTell me what's your name: ")
      print("(enter 'q' to quit)")        # 设置循环退出条件
      f_n = input('First_name: ')         # 接收用户输入信息
      if f_n == 'q':
        break
        
      l_n = input('Last_name: ')
      if l_n == 'q':
        break
        
      formatted = get_formatted_name(f_n, l_n)
      print('\nHello: ' + formatted + '!')
    --------------------
    
    Please tell me your name: 
    (Enter 'q' to quit.)
    First_name: Li
    Last_name: La
    
    Hello Li La!
    

    1.4、传递列表

    向函数传递列表,这种列表包含的可能是名字、数字或更复杂的对象(字典),将列表传递给函数后,函数就能直接访问其内容,可以提高处理列表的效率。

    1.4.1、在函数中修改列表

    将列表传递给函数后,可以对其进行修改,修改后是永久性的,可以高效第处理大量数据。

    一家为用户提交设计制作的3D打印模型公司,需要打印的设计存储在一个列表中,打印后移到另一个列表中:

    def printed_models(unprinted_designs, completed_models): # 定义一个函数,2个形参,一个为未打印的设计,一个为完成的模型
      """
      模拟打印每个设计,直到没有未打印的为止
      打印每个设计后,将其移动completed_models中
      """
      while unprinted_designs:     # 循环(只要unprinted_designs不为空循环继续)
        current_design = unprited_designs.pop()   # 将列表中所有元素弹出
        # 模拟根据设计制作3D打印模型的过程
        print('Printing model: ' + current_design)
        completed_models.append(current_design)     # 将弹出的元素添加到空列表completed_models中
        
    def show_completed_models(completed_models):   # 定义一个函数,用以处理所有打印好的模型
      """显示打印好的模型"""
      print('\nThe folloing models have been printed:')
      for completed_model in completed_models:    # 遍历打印好的模型列表completed_models
        print(completed_model)
        
        
    unprinted_designs = ['iphone case', 'robot pendant', 'dodecahedron']   # 定义未打印的设计的列表
    completed_models = []         # 定义已经打印好的模型为空列表
    
    printed_models(unprinted_designs, completed_models)     # 调用函数,将列表传递给主函数
    show_printed_models(completed_models)
    
    --------------
    Printing model: dodecahedron
    Printing model: robot pendant
    Printing model: iphone case
    
    The following models have been printed: 
    dodecahedron
    robot pendant
    iphone case
    

    1.4.2、禁止函数修改列表

    有时需要禁止修改列表,因为修改是永久的,如上个列子,修改未打印的设计中元素,将其中的元素全部移到已经打印的模型列表中,原来的列表就变为空,为解决这个问题,可以在调用函数时,只传递列表的副本(切片)而不是原列表:

    # 在上个列子中,可以在调用函数,传递列表时,只传递列表的切片
    # 格式为:function_name(list_name[:])
    printed_models(unprinted_design[:], completed_models)
    

    但是除非有充分理由需要保留原列表,否则尽量避免使用,因为可以使函数避免花时间去和内存去创建副本,从而提高效率,在处理大型列表尤其如此。

    1.5、传递任意数量的实参

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

    def make_pizza(*topping):      # 形参名*topping的星号让python创建一个名为tooping的空元组,并将传递来的实参全部封装在这个元组中 
      """打印顾客点的所有配料"""
      print(topping)
    
    make_pizza('pepperoni')
    make_pizza('mushrooms', 'green peppers', 'extra cheese')
    
    ----
    ('pepperoni')     # 封装成一个元组
    ('mushrooms', 'green peppers', 'extra cheese')
    

    1.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(16, 'pepperoni')
    make_pizza(20, 'mushrooms', 'green peppers', 'extra cheese')
    
    ------------
    Making a 16-inch pizza with the following toppings: 
    - pepperoni
    
    Making a 20-inch pizza with the following toppings: 
    - mushrooms
    - green peppers
    - extra cheese
    

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

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

    下面列子,你将接受用户信息,但不确定什么信息,函数build_profile()接受名和姓,同时还接受任意数量的关键字实参:

    def build_profile(first, last, **user_info):  # **user_info两个星号让python创建一个名为user_info的空字典,并将接受到所有名称-值对都封装在其中
        """创建一个字典,其中包含我们知道的有关用户的一切"""
        profile = {}     # 首先创建一个空字典,用以存储用户的名和姓
        profile['first_name'] = first  # 将名和姓添加到字典profile中
        profile['last_name'] = last
        for key, value in user_info.items():  # 遍历user_info这个字典
            profile[key] = value          # 将每个键-值对添加到字典profile中
        return profile         # 返回字典profile到函数调用行,并把它存储到变量user_profile中
    
    
    user_profile = build_profile('albert', 'einstein',
                                 location='princeton',
                                 field='physics')  # 传入实参(调用函数)
    print(user_profile)
    
    

    在上面列子中,返回的字典包含了用户的名和姓,已经求学的地方和所学专业,调用这个函数时,不管额外提供了多少键-值对,它都能正确处理。

    练习

    编写一个函数,将一辆汽车的信息存储在一个字典中,这个函数始终接受制造商和型号,还接受关键字实参,调用这个函数,提供必不可少的信息以及两个名称-值对,如颜色、选择配件等:

    def make_car(manufacturer, model, **options):   # **options接受任意数量的关键字实参,创建一个字典并将信息存储在其中
        """做一个字典代表一辆车"""
        car_dict = {                           # 创建一个字典能代表一辆车,其中有必不可少的 制造商型号等信息
            'manufacturer': manufacturer.title(),
            'model': model.title(),
        }
    
        for option, value in options.items():   # 遍历字典options
            car_dict[option] = value            # 将其他信息诸如颜色等添加到字典car_dict中
        return car_dict                   # 返回car_dict字典
     
    
    car = make_car('subaru', 'outback', color='blue', tow_package=True)
    print(car)
    
    my_car = make_car('honda', 'accord', year=1991, color='white', headlights='popup')
    print(my_car)
    
    

    1.6、将函数存储在模块中

    函数可以将代码块与主程序分离,这样使程序更容易理解,还可以进一步将函数存储到独立的文件中(模块),再将模块带入主程序中,这样就可以将重点放在程序的高层逻辑上,可以与其他程序员共享这个文件而不是程序。

    1.6.1、导入整个模块

    模块的拓展名为.py的文件,包含要导入到程序中的代码,下面来演示再另一个程序中调用第一个模块里的函数。

    创建一个名为pizza.py的模块,里面包含make_pizza() 的函数,再在模块pizza.py所在目录创建一个名为making_pizza.py的文件,在这个文件中导入刚创建的模块,再调用make_pizza() 函数,调用模块中函数格式:(模块名.函数名,如pizza.make_pizza() ):

    # pizza.py 模块
    def make_piza(size, *topping):   # make_pizza() 函数
      """概述要制作的披萨"""
        print('\nMaking a ' + str(size) + '-inch pizza with the following topping:')
        for topping in toppings:
            print('- ' + topping)
    
    # making_pizza.py 文件
    import pizza        # 导入pizza模块
    
    pizza.make_pizza(16, 'fish')    # 调用pizza.py模块中的make_pizza()函数
    pizza.make_pizza(20, 'egg', 'meat', 'fish')   
    ----------------
    
    Making a 12-inch pizza with the following topping:
    - meat
    - fish
    - egg
    

    1.6.2、导入特定的函数:

    import 只是一种导入方式,还可以导入模块中的特定函数,其格式为:

    from modul_name import function_name  # from 模块名 import 函数名
    from modul_name import function_0, function_1, function_2  # 导入任意数量的函数
    

    对于1.6.1的列子也可以使用这种导入方式:

    from pizza import make_pizza
    
    make_pizza(16, 'fish')  # 因为import语句显示地导入了函数名,所以调用时不需要指定其名称
    

    1.6.3、使用 as 给函数指定别名

    如果要导入的函数与现有函数名有冲突,或者函数名过长,可指定别名(用以代替函数名,类似于外号),只需在导入模块时,(from modul_name import funtion_name as fn):

    from pizza import make_pizza as mp  # 给函数指定别名
    
    mp(16, 'fish')  # 调用时可以使用别名
    

    1.6.4、给模块指定别名

    也可以给模块指定别名,其格式为:( import module_name as mn )

    import pizza as p
    
    p.make_pizza(16, 'fish')
    

    1.6.5、导入模块中所有函数

    使用星号(*)可以让python导入模块中的所有函数,其格式为:

    from modul_name import *

    在大型模块中,最好不要采用这种导入方式,如果模块中有函数的名称与你的项目有相同的,可能导致意想不到的结果,python处置名称相同的函数或者变量时,会进行覆盖,最佳做法是,只导入需要的函数,要么整个模块并使用句点表示法(即modul_name.function_name() )。

    1.7、函数编写指南

    • 函数名,应取具有描述性的名称(即与实现函数功能相关的名称),使用小写字母与下划线,模块名亦如此,这样便于理解。
    • 每个函数应有注释,紧跟函数定义后面,采用文档字符串格式
    • 给形参指定默认值时,等号两边不用有空格,实参亦如此。
    • PEP 8 建议代码行长度不超过79个字符,如果形参很多,超过79个字符,可以在函数定义时输入左括号后按回车键,并在下一行按两次Tab键,实质上多数编辑器都能自动实现参数对齐,如下:
    def function_name(
            parameter_0, parameter_1, parameter_2,
            parameter_3, parameter_4, parameter_5
        
    ):
    
    • 如果程序或模块包含多个函数,可使用两个空行将相邻的函数分开,这样将更容易知道前一个函数在哪结束,下一个函数在哪开始。
    • 所有的import 语句都应在开头,唯一的列外的情形时,在文件开头使用了注释来描述整个程序。

    相关文章

      网友评论

        本文标题:十七、函数

        本文链接:https://www.haomeiwen.com/subject/kpgqbxtx.html