美文网首页
Python函数和函数式编程

Python函数和函数式编程

作者: VIVAFT | 来源:发表于2018-03-06 15:49 被阅读0次

    第5章 函数和函数式编程

    5.1 引言
    函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。函数能提高应用的模块性,和代码的重复利用率。在Python中有很多内建函数,比如print()。当然随着学习的深入,你也可以学会创建对自己有用的函数(用户自定义函数)。简单的理解下函数的概念,就是用户编写了一些语句,为了方便使用这些语句,把这些语句组合在一起,给它起一个名字。使用的时候只要调用这个名字,就可以实现语句组的功能了。
    在没用过函数之前,我们要计算一个数的幂时会用到**,方法是这样的:

    >>>2**3
    8 #此处为[**python 函数返回值**](http://www.iplaypy.com/jinjie/return.html "python 返回值")
    

    现在知道了函数,就可以用内建函数pow来计算乘方了:

    >>>pow(2,3)
    8
    

    5.2 调用函数
    python系统中自带的一些函数就叫做内建函数,比如:dir()type()等等,不需要我们自己编写。还有一种是第三方函数,就是其它程序员编好的一些函数,共享给大家使用。这两种函数都是拿来就可以直接使用的。最后就是我们自己编些的方便自己工作学习用的函数,就叫做自定义函数了。
    Python内置了很多有用的函数,我们可以直接调用。要调用一个函数,需要知道函数的名称和参数,比如求绝对值的函数abs,只有一个参数。可以直接从Python的官方网站查看文档:
    http://docs.python.org/3/library/functions.html#abs
    也可以在交互式命令行通过help(abs)查看abs函数的帮助信息。
    调用abs函数:

    >>> abs(100)
    100
    >>> abs(-10)
    10
    

    比如max函数可以接收任意多个参数,并返回最大的那个:

    >>> max(1, 2)
    2
    >>> max(-2, 0, 1, 4)
    4
    

    同时在调用函数的时候,需要注意传入的参数数量是否符合要求。例如当调用abs函数时,若传入的参数数量不对,会报出TypeError的错误,并且Python会明确地告诉你:abs()有且仅有1个参数,但给出了两个:

    >>> abs(1, 2)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: abs() takes exactly one argument (2 given)
    

    如果传入的参数数量是对的,但参数类型不能被函数所接受,也会报TypeError的错误,并且给出错误信息:str是错误的参数类型:

    >>> abs('a')
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: bad operand type for abs(): 'str'
    

    数据类型转换
    Python内置的常用函数还包括数据类型转换函数,比如int()函数可以把其他数据类型转换为整数:

    >>> int('12')
    12
    >>> int(12.3)
    12
    >>> float('12.3')
    12.3
    >>> str(1.23)
    '1.23'
    >>> str(10)
    '10'
    >>> bool(1)
    True
    >>> bool('')
    False
    

    也可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”:

    >>> a = abs # 变量a指向abs函数
    >>> a(-1) # 通过a调用abs函数
    1
    

    定义一个函数:给了函数一个名称,指定了函数里包含的参数,和代码块结构。
    这个函数的基本结构完成以后,用户可以通过另一个函数调用执行,也可以直接从 Python 命令提示符执行。
    如下实例调用了 printme() 函数:

    #!/usr/bin/python3
     
    # 定义函数
    def printme( str ):
       "打印传入的字符串"
       print (str);
       return;
     
    # 调用函数
    printme("调用用户自定义函数!");
    printme("再次调用同一函数");
    
    调用用户自定义函数!
    再次调用同一函数
    

    小结
    调用Python的函数,需要根据函数定义,传入正确的参数。
    如果函数调用出错,一定要注意看错误信息!

    5.3 定义函数
    在Python中,定义一个具有特定功能的函数需要符合一定规则:

    1. 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号 ()。
    2. 任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。
    3. 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
      函数内容以冒号起始,并且缩进。
    4. return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None。
      一般格式如下:
    def 函数名(参数列表):    
        函数体
    

    默认情况下,参数值和参数名称是按函数声明中定义的的顺序匹配起来的。
    我们以自定义一个输出"Hello World!"的函数为例:

    >>> def hello() :
       print("Hello World!")
     
    >>> hello()
    Hello World!
    

    在更复杂的应用中,函数中带上参数变量:

    #!/usr/bin/python3
    
    # 计算面积函数
    def area(width, height):
        return width * height
     
    def print_welcome(name):
        print("Welcome", name)
    
    print_welcome("Runoob")
    w = 4
    h = 5
    print("width =", w, " height =", h, " area =", area(w, h))
    
    Welcome Runoob
    width = 4  height = 5  area = 20
    

    注意:函数体内部语句执行时,一旦执行到return时,函数就执行完毕,并将结果返回。因此,函数内部通过条件判断和循环可以实现非常复杂的逻辑。
    注意:如果没有return语句,函数执行完毕后也会返回结果,只是结果为None。(return None可以简写为return)

    如果用户已经把hello()的函数定义保存为test.py文件了,那么,可以在该文件的当前目录下启动Python解释器,用from test import hello来导入hello()函数,注意test是文件名(不含.py扩展名):

    >>> from test import hello                     
    >>> hello()
    Hello World!
    

    import的用法在后续“模块”一节中会详细介绍。

    空函数
    如果想定义一个什么事也不做的空函数,可以用pass语句:

    def nop():
        pass
    

    pass可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来。

    参数传递
    在 python 中,类型属于对象,变量是没有类型的:

    a=[1,2,3]
    
    a="Runoob"
    

    以上代码中,[1,2,3] 是 List 类型,"Runoob" 是 String 类型,而变量 a 是没有类型,她仅仅是一个对象的引用(一个指针),可以是指向 List 类型对象,也可以是指向 String 类型对象。

    可更改(mutable)与不可更改(immutable)对象
    在 python 中,strings, tuples, 和 numbers 是不可更改的对象,而 list,dict 等则是可以修改的对象。

    1. 不可变类型:变量赋值 a=5 后再赋值 a=10,这里实际是新生成一个 int 值对象 10,再让 a 指向它,而 5 被丢弃,不是改变a的值,相当于新生成了a。
    2. 可变类型:变量赋值 la=[1,2,3,4] 后再赋值 la[2]=5 则是将 list la 的第三个元素值更改,本身la没有动,只是其内部的一部分值被修改了。

    python 函数的参数传递:

    1. 不可变类型:类似 c++ 的值传递,如 整数、字符串、元组。如fun(a),传递的只是a的值,没有影响a对象本身。比如在 fun(a)内部修改 a 的值,只是修改另一个复制的对象,不会影响 a 本身。
    2. 可变类型:类似 c++ 的引用传递,如 列表,字典。如 fun(la),则是将 la 真正的传过去,修改后fun外部的la也会受影响。
      python 中一切都是对象,严格意义我们不能说值传递还是引用传递,我们应该说传不可变对象和传可变对象。
      python 传不可变对象实例
    #!/usr/bin/python3
     
    def ChangeInt( a ):
        a = 10
    b = 2
    ChangeInt(b)
    print( b ) # 结果是 2
    

    实例中有 int 对象 2,指向它的变量是 b,在传递给 ChangeInt 函数时,按传值的方式复制了变量 b,a 和 b 都指向了同一个 Int 对象,在 a=10 时,则新生成一个 int 值对象 10,并让 a 指向它。
    传可变对象实例
    可变对象在函数里修改了参数,那么在调用这个函数的函数里,原始的参数也被改变了。例如:

    #!/usr/bin/python3
     
    # 可写函数说明
    def changeme( mylist ):
       "修改传入的列表"
       mylist.append([1,2,3,4]);
       print ("函数内取值: ", mylist)
       return
     
    # 调用changeme函数
    mylist = [10,20,30];
    changeme( mylist );
    print ("函数外取值: ", mylist)
    

    传入函数的和在末尾添加新内容的对象用的是同一个引用。故输出结果如下:

    函数内取值:  [10, 20, 30, [1, 2, 3, 4]]
    函数外取值:  [10, 20, 30, [1, 2, 3, 4]]
    

    小结
    定义函数时,需要确定函数名和参数个数;
    如果有必要,可以先对参数的数据类型做检查;
    函数体内部可以用return随时返回函数结果;
    函数执行完毕也没有return语句时,自动return None。

    5.4 函数的参数
    在定义函数时,我们把参数的名字和位置确定后,函数的接口定义就完成了。对于函数的调用者来说,只需知道如何传递正确的参数,以及函数的返回值即可,函数内部被封装起来,调用者无需了解。
    Python的函数定义非常简单,但灵活度却非常大。除了正常定义的必选参数外,还可以使用默认参数、可变参数和关键字参数,使得函数定义出来的接口,不但能处理复杂的参数,还可以简化调用者的代码。

    鉴于函数定义中可能包含多个形参,因此函数调用中也可能包含多个实参。想函数传递实参的方式很多,可使用位置实参,和要求实参的顺序相同;也可使用关键字实参,其中每个实参都由变量名和值组成,等等。以下是调用函数时可使用的正式参数类型:
    位置参数

    5.4.1位置实参
    你调用函数时,Python必须将函数调用中的每个实参都关联到函数定义的一个形参。为此,最简单的关联方式是基于实参的顺序,这种关联方式被称为位置实参,可以多次调用。
    为明白其中的工作原理,来看一个显示学生信息的函数。这个函数指出一名学生的名字以及年龄,如下所示:

    ❶ def describe_student(person_name, student_age):
          """显示学生的信息"""
          print("\nMy name is " + person_name + ".")     
          print(person_name + " is " + student_age+ " years old.")
    
    ❷ describe_student('Jack', '18')
    

    这个函数的定义表明,它需要一名学生的姓名和一个年龄数字(见❶)。调用describe_student() 时,需要按顺序提供一名学生的姓名和一个年龄数字。例如,在前面的函数调用中,实参'Jack' 存储在形参person_name中,而实参'18' 存储在形参student_age 中(见❷)。在函数体内,使用了这两个形参来显示学生的信息。输出描述了一名18岁的学生Jack:

    My name is Jack.
    Jack is 18 years old.
    
    1. 调用函数多次
      用户可以根据需要调用函数任意次。要再描述一名学生,只需再次调用describe_student() 即可:
    def describe_student(person_name, student_age):
        """显示学生的信息"""
        print("\nMy name is " + person_name + ".")     
        print(person_name + " is " + student_age+ " years old.")
    
    describe_student('Jack', '18')
    describe_student('Bob', '17')
    

    第二次调用describe_student() 函数时,我们向它传递了实参'Bob' 和'17' 。与第一次调用时一样,Python将实参'Bob' 关联到形参person_name,并将实参'17' 关联到形参student_age 。与前面一样,这个函数完成其任务,但打印的是一名17岁的学生Bob的信息。至此,我们描述了一名18岁的学生Jack和17岁的学生Bob.

    My name is Jack.
    Jack is 18 years old.
    My name is Bob.
    Jack is 17 years old.
    

    调用函数多次是一种效率极高的工作方式。我们只需在函数中编写描述学生的代码一次,然后每当需要描述新学生时,都可调用这个函数,并向它提供新学生的信息。即便描述全校的学生,用户依然只需使用一行调用函数的代码,就可描述一名新学生。
    在函数中,可根据需要使用任意数量的位置实参,Python将按顺序将函数调用中的实参关联到函数定义中相应的形参。

    1. 位置实参的顺序很重要
      使用位置实参来调用函数时,如果实参的顺序不正确,结果可能出乎意料:
    def describe_student(person_name, student_age):
        """显示学生的信息"""
        print("\nMy name is " + person_name + ".")     
        print(person_name + " is " + student_age+ " years old.")
    
    describe_student('18', 'Jack')
    

    在这个函数调用中,我们先指定名字,再指定学生年龄。由于实参'18' 在前,这个值将存储到形参person_name中;同理,'Jack' 将存储到形参student_age中。结果是我们得到了一名年龄为Jack的18:

    My name is 18.
    Jack is Jack years old.
    

    如果结果像上面一样荒谬,请确认函数调用中实参的顺序与函数定义中形参的顺序一致。

    5.4.2 默认参数
    编写函数时,可给每个形参指定默认值 。在调用函数中给形参提供了实参时,Python将使用指定的实参值;否则,将使用形参的默认值。因此,给形参指定默认值后,可在函数调用中省略相应的实参。使用默认值可简化函数调用,还可清楚地指出函数的典型用法。
    例如describe_student函数定义没有问题,但若如下调用会出现错误:

    >>> describe_student('Jack')
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: describe_student() missing 1 required positional argument: 'student_age'
    

    Python的错误信息很明确:调用函数describe_student()缺少了一个位置参数student_age。这个时候,默认参数就排上用场了。若大部分学生的年龄为18岁,我们可以把第二个参数student_age的默认值设定为18:

    def describe_student(person_name, student_age=18):
        """显示学生的信息"""
        print("\nMy name is " + person_name + ".")     
        print(person_name + " is " + student_age+ " years old.")
    

    这样,当我们调用describe_student(Jack)时,相当于调用describe_student(Jack,18):

    >>> describe_student('Jack')
    My name is Jack.
    Jack is 18 years old.
    
    >>> describe_student('Jack','18')
    My name is Jack.
    Jack is 18 years old.
    

    而对于student > 18的其他情况,就必须明确地传入student_age,比如describe_student(Herbie,19)。
    从上面的例子可以看出,默认参数可以简化函数的调用。
    注意:
    设置默认参数时,必选参数在前,默认参数在后,否则Python的解释器会报错(思考一下为什么默认参数不能放在必选参数前面)

    当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面。变化小的参数就可以作为默认参数。
    使用默认参数有什么好处?最大的好处是能降低调用函数的难度。
    举个例子,我们编写一个学生注册的函数,需要传入name和gender两个参数:

    def enroll(name, gender):
        """注册学生的信息"""
        print("name: ",name)     
        print("gender: ",gender)
    

    这样,调用enroll()函数只需要传入两个参数:

    >>> enroll('Jack', 'F')
    name: Jack
    gender: F
    

    如果要继续传入年龄、城市等信息怎么办?这样会使得调用函数的复杂度大大增加。
    我们可以把年龄和城市设为默认参数:

    def enroll(name, gender,age=18, city='Beijing'):
        print('name: ', name)
        print('gender: ', gender)
        print('age: ', age)
        print('city:', city)
    

    这样,大多数学生注册时不需要填写年龄和城市,只提供必须的两个参数:

    >>> enroll('Sarah', 'F')
    name: Sarah
    gender: F
    age: 18
    city: Beijing
    

    只有与默认参数不符的学生才需要提供额外的信息:

    enroll('Bob', 'M', 17)
    enroll('Adam', 'M', city='Tianjin')
    

    可见,默认参数降低了函数调用的难度,而一旦需要更复杂的调用时,又可以传递更多的参数来实现。无论是简单调用还是复杂调用,函数只需要定义一个。
    有多个默认参数时,调用的时候,既可以按顺序提供默认参数,比如调用enroll('Bob', 'M', 17),意思是,除了name,gender这两个参数外,最后一个参数应用在参数age上,city参数由于没有提供,仍然使用默认值。也可以不按顺序提供部分默认参数。当不按顺序提供部分默认参数时,需要把参数名写上。比如调用enroll('Adam', 'M', city='Tianjin'),意思是,city参数用传进去的值,其他默认参数继续使用默认值。
    默认参数很有用,但定义默认参数时要牢记一点:默认参数必须指向不变对象!否则会出现重大错误,例如:
    先定义一个函数,传入一个list,添加一个END再返回:

    def test_add(H=[]):
        H.append('END')
        return H
    

    当正常调用时,结果似乎不错:

    >>> test_add([1, 2, 3])
    [1, 2, 3, 'END']
    >>> add_end(['a', 'b', 'c'])
    ['a', 'b', 'c', 'END']
    

    当初次使用默认参数调用时,结果也是对的:

    >>> test_add()
    ['END']
    

    但是当再次调用test_add()时,结果就出现错误了:

    >>> test_add()
    ['END', 'END']
    >>> test_add()
    ['END', 'END', 'END']
    

    我们会发现,默认参数是[],但是函数test_add()似乎每次都“记住了”上次添加了'END'后的list。这是为什么呢?原因如下:
    Python函数在定义的时候,默认参数H的值就被计算出来了,即[],因为默认参数H也是一个变量,它指向对象[],每次调用该函数,如果改变了H的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。
    注意:定义默认参数时,默认参数必须指向不变对象!
    我们可以用None这个不变对象来解决此问题:

    def test_add(H=None):
        if H is None:
            H = []
        H.append('END')
        return H
    

    现在,无论调用多少次,都不会有问题:

    >>> test_add()
    ['END']
    >>> test_add()
    ['END']
    

    为什么要设计str、None这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。我们在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象。

    5.4.3 不定长参数
    在Python函数中,还可以定义不定长参数,也叫可变参数。顾名思义,不定长参数就是传入的参数个数是可变的。
    我们以数学题为例子,给定一组数字a,b,c……,请计算a+b+c+ ……要定义出这个函数,我们必须确定输入的参数。由于参数个数不确定,我们首先想到可以把a,b,c……作为一个list或tuple传进来,这样,函数可以定义如下:

    def calc(numbers):
        sum = 0
        for n in numbers:
            sum = sum + n 
        return sum
    

    但是调用的时候,需要先组装出一个list或tuple:

    >>> calc([1, 2, 3])
    6
    >>> calc((1, 2, 3, 4))
    10
    

    但若把函数的参数改为不定长参数:

    def calc(*numbers):
        sum = 0
        for n in numbers:
            sum = sum + n 
        return sum
    

    则调用函数的方式可以简化成这样:

    >>> calc(1, 2, 3)
    6
    >>> calc(1, 2, 3, 4)
    10
    

    定义不定长参数和定义一个list或tuple参数相比,仅仅在参数前面加了一个*号。在函数内部,参数numbers接收到的是一个tuple,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数:

    >>> calc()
    0
    

    如果已经有一个list或者tuple,要调用一个不定长参数可如下:

    >>> nums = [1, 2, 3]
    >>> calc(*nums)
    6
    

    *nums表示把nums这个list的所有元素作为可变参数传进去。这种写法相当有用,而且很常见。

    5.4.4 关键字参数
    关键字实参是传递给函数的名称—值对。你直接在实参中将名称和值关联起来了,因此向函数传递实参时不会混淆(不会得到名为18的Jack这样的结果)。关键字实参让用户无需考虑函数调用中的实参顺序,还清楚地指出了函数调用中各个值的用途。
    下面重新编写describe_student()函数,在其中使用关键字实参来调用describe_student():

    def describe_student(person_name, student_age):
        """显示学生的信息"""
        print("\nMy name is " + person_name + ".")     
        print(person_name + " is " + student_age+ " years old.")
    
    describe_student(person_name='Jack', student_age='18')
    

    函数describe_pet() 还是原来那样,但调用这个函数时,我们向Python明确地指出了各个实参对应的形参。看到这个函数调用时,Python知道应该将实参'Jack' 和'18' 分别存储在形参person_name和 student_age中。输出正确无误,它指出我们有一名名叫Jack,年龄为18岁的学生。关键字实参的顺序无关紧要,因为Python知道各个值该存储到哪个形参中。下面两个函数调用是等效的:

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

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

    不定长参数允许传入0个或任意个参数,这些不定长参数在函数调用时自动组装为一个元组(tuple)。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个字典(dict)。如下,函数person除了必选参数name和age外,还接受关键字参数kw。在调用该函数时,可以只传入必选参数:

    def enroll(name, age,**kw)):
        print('name: ', name, 'age: ', age, 'other: ', kw)
    
    >>> enroll('Michael', 18)
    name: Michael age: 18 other: {}
    

    也可以传入任意个数的关键字参数:

    >>> enroll('Bob', 17, city='Beijing')
    name: Bob age: 17 other: {'city': 'Beijing'}
    >>> enroll('Adam', 19, gender='M', job='Engineer')
    name: Adam age: 19 other: {'gender': 'M', 'job': 'Engineer'}
    

    关键字参数有扩展函数的功能。比如,在enroll函数里,我们保证能接收到name和age这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。试想你正在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。
    和不定长参数类似,也可以先组装出一个dict,然后,把该dict转换为关键字参数传进去:

    >>> extra = {'gender':'M','city': 'Beijing'}
    >>> enroll('Jack', 18, gender=extra['M'], city=extra['city'])
    name: Jack age: 18 other: {'gender':'M','city': 'Beijing'}
    

    当然,上面复杂的调用可以用简化的写法:

    >>> extra = {'gender':'M','city': 'Beijing'}
    >>> enroll('Jack', 18, **extra)
    name: Jack age: 18 other: {'gender':'M','city': 'Beijing'}
    

    注意:**extra表示把extra这个dict的所有key-value用关键字参数传入到函数的**kw参数,kw将获得一个dict,注意kw获得的dict是extra的一份拷贝,对kw的改动不会影响到函数外的extra。

    5.4.5 命名关键字参数
    如果要限制关键字参数的名字,就可以用命名关键字参数。
    和关键字参数**kw不同,如果没有可变参数,命名关键字参数就必须加一个*作为特殊分隔符。如果缺少*,Python解释器将无法识别位置参数和命名关键字参数。例如,若只接收age和city作为关键字参数。这种方式定义的函数如下:

    def enroll(name, gender, *, age, city):
        print(name, gender, age, city)
    
    >>> enroll('Jack', ' M', age='18', city='Beijing')
    Jack M 18 Beijing
    

    如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了:

    def enroll(name, gender, *grade, age, city):
        print(name, gender, grader, age, city)
    

    注意:和位置参数不同,命名关键字参数必须传入参数名。
    如果没有传入参数名,调用将报错:

    >>> enroll(' Jack',' M',' 18',' Beijing')
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: enroll() takes 2 positional arguments but 4 were given
    

    由报错信息可知,由于调用时缺少参数名age和city,Python解释器把这4个参数均视为位置参数,但enrroll()函数仅接受2个位置参数。
    注意:命名关键字参数可以有缺省值。
    由于命名关键字参数city具有默认值,调用时可不传入city参数:

    def enroll(name, gender, *, age='18', city):
        print(name, gender, age, city)
    
    >>> enroll('Jack','M',city='Beijing')
    Jack M 18 Beijing
    

    5.4.6 参数组合
    现在我们知道python定义函数的参数类型有:
    必选参数、默认参数、可变参数、关键字参数和命名关键字参数

    在Python中定义函数,我们是可以组合使用这些参数的。但要注意的是,参数定义是有顺序的,定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
    比如定义一个函数,包含上述若干种参数:

    def func(a, b, c=0, *args, **kw):
        print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
    

    在函数调用的时候,Python解释器自动按照参数位置和参数名把对应的参数传进去。

    >>> func(1, 2)
    a = 1 b = 2 c = 0 args = () kw = {}
    >>> func(1, 2, c=3)
    a = 1 b = 2 c = 3 args = () kw = {}
    >>> func(1, 2, 3, 'a', 'b')
    a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
    >>> func(1, 2, 3, 'a', 'b', x=4)
    a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 4}
    

    另外对于任意函数,都可以通过类似 func(*args, **kw) 的形式调用它,无论它的参数是如何定义的。以一个元组(tuple)和字典(dict)为例:

    >>> args = (1, 2, 3, 4)
    >>> kw = {'x': 5}
    >>> func(*args, **kw)
    a = 1 b = 2 c = 3 args = (4,) kw = {'x': 5}
    

    小结
    Python的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。
    默认参数一定要用不可变对象,如果是可变对象,运行会有逻辑错误!
    要注意定义可变参数和关键字参数的语法:
    args 是可变参数,args接收的是一个tuple;
    kw 是关键字参数,kw接收的是一个dict。
    以及调用函数时如何传入可变参数和关键字参数的语法:
    可变参数既可以直接传入: func(1, 2, 3) ,又可以先组装list或tuple,再通过*args 传入: func(
    (1, 2, 3)) ;
    关键字参数既可以直接传入: func(a=1, b=2) ,又可以先组装dict,再通过 **kw 传入: func(
    {'a': 1, 'b': 2}) 。
    使用 *args**kw 是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法

    5.6 返回值
    函数并非总是直接显示输出,相反,它可以处理一些数据,并返回一个或一组值。函数返回的值被称为返回值 。在函数中,可使用return 语句将值返回到调用函数的代码行。返回值让你能够将程序的大部分繁重工作移到函数中去完成,从而简化主程序。
    return [表达式] 语句用于退出函数,选择性地向调用方返回一个表达式。不带参数值的return语句返回None。以下实例演示了 return 语句的用法:

    #!/usr/bin/python3
    
    def sum( arg1, arg2 ):
       total = arg1 + arg2       # 返回2个参数的和
       print ("函数内 : ", total)
       return total;
    
    total = sum( 10, 20 );
    print ("函数外 : ", total)
    
    函数内 :  30
    函数外 :  30
    

    5.7 函数式编程
    函数式编程是一种编程范式,我们常见的编程范式有命令式编程、函数式编程、逻辑式编程,常见的面向对象编程也是一种命令式编程。命令式编程是面向计算机硬件的抽象,在计算机的层次上,CPU执行的是加减乘除的指令代码,以及各种条件判断和跳转指令。而函数式编程是面向数学的抽象,将计算描述为一种表达式求值,越是抽象的计算,离计算机硬件越远。
    值得注意的是,函数式编程中的“函数”不是指计算机中的函数,而是指数学中的函数,即自变量的映射,一个函数的值仅决定于函数参数的值,不依赖其它状态。在编程式语言中,“函数”可以在任何地方定义,在函数内或函数外,可以作为函数的参数或返回值,可以对函数进行组合。纯函数式编程中的变量也不是命令式编程语言中的变量,而是代数中的变量,即一个值的名称,变量的值是不可变的,比如命令式编程中的"x = x+1"这种依赖可变状态的事实此时被认为为假。
    例:通过别的名称使用函数,再把函数作为参数传递

    >>> fact = factorial
    >>> fact
    <function factorial at  >
    >>> func(*args, **kw)
    a = 1 b = 2 c = 3 args = (4,) kw = {'x': 5}
    

    函数式编程最主要的好处主要是不可变性带来的,函数是“引用透明”的,并可避免“副作用”,它不会依赖也不会改变当前函数以外的数据。

    高阶函数
    接受函数为参数,或者把函数作为结果返回的函数是高阶函数(higher-
    order function)。map 函数就是一例,此外,内置函数 sorted 也是:可选的 key 参数用于提供一个函数,它会应用到各个元素上进行排序.
    例,若想根据单词的长度排序,只需把 len 函数传给 key 参数,如下:
    根据单词长度给一个列表排序

    >>> fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
    >>> sorted(fruits, key=len)
    ['fig', 'apple', 'cherry', 'banana', 'raspberry', 'strawberry'] 
    

    任何单参数函数都能作为 key 参数的值。例如,为了创建押韵词典,可以把各个单词反过来拼写,然后排序。注意,示例 5-4 中列表里的单词没有变,我们只是把反向拼写当作排序条件,因此各种浆果(berry)都排在一起。
    示例:根据反向拼写给一个单词列表排序

    >>> def reverse(word):
    ...     return word[::-1]
    >>> reverse('testing')
    'gnitset'
    >>> sorted(fruits, key=reverse)
    ['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry'] 
    

    在函数式编程范式中,最为人熟知的高阶函数有map、filter、reduce 和 apply。apply 函数在 Python 2.3 中标记为过时,在 Python 3 中移除了,因为不再需要它了。如果想使用不定量的参数调用函数,可以编写 fn(*args, **keywords),不用再编写 apply(fn, args, kwargs)。

    5.5 匿名函数
    所谓匿名,意即不再使用 def 语句这样标准的形式定义一个函数。python 使用 lambda 来创建匿名函数。
    lambda 只是一个表达式,函数体比 def 简单很多。lambda 函数的语法如下:

    lambda [arg1 [,arg2,.....argn]]:expression
    

    lambda的主体是一个表达式,而不是一个代码块,仅能在lambda表达式中封装有限的逻辑进去。
    注意:lambda 函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数。
    注意:虽然lambda函数看起来只能写一行,却不等同于C或C++的内联函数,后者的目的是调用小函数时不占用栈内存从而增加运行效率。
    如下实例:

    #!/usr/bin/python3
     
    sum = lambda arg1, arg2: arg1 + arg2;
     
    # 调用sum函数
    print ("相加后的值为 : ", sum( 1, 2 ))
    print ("相加后的值为 : ", sum( 2, 2 ))
    
    相加后的值为 :  3
    相加后的值为 :  4
    

    变量作用域
    Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。
    变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。Python的作用域一共有4种,分别是:
    L (Local) 局部作用域
    E (Enclosing) 闭包函数外的函数中
    G (Global) 全局作用域
    B (Built-in) 内建作用域
    以 L –> E –> G –>B 的规则查找,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内建中找。

    x = int(2.9)  # 内建作用域
     
    g_count = 0  # 全局作用域
    def outer():
        o_count = 1  # 闭包函数外的函数中
        def inner():
            i_count = 2  # 局部作用域
    

    Python 中只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,其它的代码块(如 if/elif/else/、try/except、for/while等)是不会引入新的作用域的,也就是说这这些语句内定义的变量,外部也可以访问,如下代码:

    >>> if True:
    ...  msg = 'I am from Runoob'
    ... 
    >>> msg
    'I am from Runoob'
    

    实例中 msg 变量定义在 if 语句块中,但外部还是可以访问的。
    如果将 msg 定义在函数中,则它就是局部变量,外部不能访问:

    >>> def test():
    ...     msg_inner = 'I am from Runoob'
    ... 
    >>> msg_inner
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    NameError: name 'msg_inner' is not defined
    

    从报错的信息上看,说明了 msg_inner 未定义,无法使用,因为它是局部变量,只有在函数内可以使用。
    全局变量和局部变量
    定义在函数内部的变量拥有一个局部作用域,定义在函数外的拥有全局作用域。
    局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。如下实例:

    #!/usr/bin/python3
    
    total = 0; # 这是一个全局变量
    # 可写函数说明
    def sum( arg1, arg2 ):
        #返回2个参数的和."
        total = arg1 + arg2; # total在这里是局部变量.
        print ("函数内是局部变量 : ", total)
        return total;
    
    #调用sum函数
    sum( 10, 20 );
    print ("函数外是全局变量 : ", total)
    
    函数内是局部变量 :  30
    函数外是全局变量 :  0
    

    global 和 nonlocal关键字
    当内部作用域想修改外部作用域的变量时,就要用到global和nonlocal关键字了。
    以下实例修改全局变量 num:

    #!/usr/bin/python3
    
    num = 1
    def fun1():
        global num  # 需要使用 global 关键字声明
        print(num) 
        num = 123
        print(num)
    fun1()
    
    1
    123
    

    如果要修改嵌套作用域(enclosing 作用域,外层非全局作用域)中的变量则需要 nonlocal 关键字了,如下实例:

    #!/usr/bin/python3
     
    def outer():
        num = 10
        def inner():
            nonlocal num   # nonlocal关键字声明
            num = 100
            print(num)
        inner()
        print(num)
    outer()
    
    100
    100
    

    另外有一种特殊情况,假设下面这段代码被运行:

    #!/usr/bin/python3
     
    a = 10
    def test():
        a = a + 1
        print(a)
    test()
    

    以上程序执行,报错信息如下:

    Traceback (most recent call last):
      File "test.py", line 7, in <module>
        test()
      File "test.py", line 5, in test
        a = a + 1
    UnboundLocalError: local variable 'a' referenced before assignment
    

    错误信息为局部作用域引用错误,因为 test 函数中的 a 使用的是局部,未定义,无法修改。

    全局变量和局部变量
    定义在函数内部的变量拥有一个局部作用域,定义在函数外的拥有全局作用域。
    局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。如下实例:

    #!/usr/bin/python3
    
    total = 0 # 这是一个全局变量
    # 可写函数说明
    def sum( arg1, arg2 ):
        #返回2个参数的和."
        total = arg1 + arg2 # total在这里是局部变量.
        print ("函数内是局部变量 : ", total)
        return total
    
    #调用sum函数
    sum( 10, 20 )
    print ("函数外是全局变量 : ", total)
    以上实例输出结果:
    函数内是局部变量 :  30
    函数外是全局变量 :  0
    

    相关文章

      网友评论

          本文标题:Python函数和函数式编程

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