zoukankan      html  css  js  c++  java
  • 函数笔记整理

    一,函数基础

    1、函数是什么

    在学习函数之前,一直遵循面向过程编程,即根据业务逻辑从上到下实现功能,其往往用一长段代码来实现指定功能,开发过程中最常见的操作就是粘贴复制,也就是将之前实现的代码块复制到现需功能处。这样最后的程序会变得特别冗长,有没有什么办法把这些相同的代码块做一些处理?让程序的代码显得简便明朗,这就引出了今天要学习的内容:函数。

    定义: 函数是指将一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需调用其函数名即可。

    特性:减少重复代码、使程序变的可扩展、使程序变得易维护

    2、定义函数

    def 函数名(参数):
        
        ....
        函数体
        ....
        return 返回值
    
    函数名()
    

    函数组成:函数名、参数、函数体、返回值,接下来进行详细介绍

    3、普通参数

    学习函数参数前,我们先认识下形参和实参的区别:

    • 形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只在函数内部有效。函数调用结束返回主调用函数后则不能再使用该形参变量
    • 实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。因此应预先用赋值,输入等办法使参数获得确定值

    通过下面代码来认识下普通参数、形参、实参

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    #-Author-Lian
    #函数的普通参数
    # name 叫做函数func的形式参数,简称:形参
    def func(name):                     #定义函数 函数名func
        print(name)
    
    #  'lzl' 叫做函数func的实际参数,简称:实参
    func("lzl")                         #执行函数
    
    ########打印输出########
    #lzl  

     4、默认参数

    默认参数的应用环境

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    #-Author-Lian
    #函数默认参数
    def stu_register(name, age, country, course):       #定义函数
        print("----注册学生信息------")
        print("姓名:", name)
        print("age:", age)
        print("国籍:", country)
        print("课程:", course)
    
    stu_register("张三", 22, "CN", "Python")          #执行函数
    stu_register("赵四", 21, "CN", "Linux")           
    stu_register("王五", 25, "CN", "JAVA")
    
    ###########打印###############
    #----注册学生信息------
    #姓名: 张三
    #age: 22
    #国籍: CN
    #课程: Python
    #----注册学生信息------
    #姓名: 赵四
    #age: 21
    #国籍: CN
    #课程: Linux
    #----注册学生信息------
    #姓名: 王五
    #age: 25
    #国籍: CN
    #课程: JAVA
    

    通过上面代码发现country 这个参数 基本都 是"CN", 就像我们在网站上注册用户,像国籍这种信息,你不填写,默认就会是 中国, 这就是通过默认参数实现的,把country变成默认参数

    def stu_register(name,age,course,country="CN"):
    

    这样,这个参数在调用时不指定,那默认就是CN,指定了的话,就用你指定的值。 

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    #-Author-Lian
    #指定默认参数
    def stu_register(name, age,course,country="CN"):       #定义函数 
        print("----注册学生信息------")
        print("姓名:", name)
        print("age:", age)
        print("国籍:", country)
        print("课程:", course)
    
    stu_register("张三", 22, "Python")                    #不指定country参数值
    stu_register("尼古拉斯.赵四", 21, "Linux", "US")     #指定country参数值
    
    ##############打印################
    #----注册学生信息------
    #姓名: 张三
    #age: 22
    #国籍: CN
    #课程: Python
    #----注册学生信息------
    #姓名: 尼古拉斯.赵四
    #age: 21
    #国籍: US
    #课程: Linux
    

    另外,你可能注意到了,在把country变成默认参数后,我同时把它的位置移到了最后面,为什么呢? 

    5、关键参数  

     正常情况下,给函数传参数要按顺序,不想按顺序就可以用关键参数,只需指定参数名即可,但记住一个要求就是,关键参数必须放在位置参数之后

    #关键参数
    stu_register(age=22,name='lzl',course="python")
    
    ##############打印################
    #----注册学生信息------
    #姓名: lzl
    #age: 22
    #国籍: CN
    #课程: python
    

    6、动态参数  

     ①  非固定参数 *args 

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    #-Author-Lian
    
    def stu_register(name,*args):     # *args 会把多传入的参数变成一个元组形式
        print(name,args,type(args))       #打印传入args的类型
    
    stu_register("lzl","18","CN","Python")
    ##############打印################
    #lzl ('18', 'CN', 'Python') <class 'tuple'>
    
    
    def stu_register(name,*args):     #测试*args传入list参数
        print(name,args,type(args))       #打印传入args的类型
        
    info_list = ["18","CN","Python"]
    stu_register("lzl",info_list)
    ##############打印################
    #lzl (['18', 'CN', 'Python'],) <class 'tuple'>
    

    总结:由上面代码可以看出,*args会把多传入的实参变成一个元组的类型;即使传入的是list类型也会变成元组,成为元组中的一个元素;另函数中有*args与其他形参的时候,*args一定要写到其他形参的后面,否则传入的实参都会被传入到*args当中打印成元组;还有如果没有多出传入的实参即*args没有值的时候,*args为空,不会报错。

     ② 非固定参数 **kwargs 

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    #-Author-Lian
    
    def stu_register(name, *args, **kwargs):  # **kwargs 会把多传入的参数变成一个dict形式
        print(name, args, type(args))        # 打印args的类型
        print(kwargs, type(kwargs))          #打印kwargs的类型
    
    stu_register("lzl", 18, "CN", "Python", sex="Male", province="HeBei")
    ##############打印################
    #lzl (18, 'CN', 'Python') <class 'tuple'>
    #{'sex': 'Male', 'province': 'HeBei'} <class 'dict'>
    
    def stu_register(name, *args, **kwargs):  # **kwargs 会把多传入的参数变成一个dict形式
        print(name, args, type(args))        # 打印args的类型
        print(kwargs, type(kwargs))          #打印kwargs的类型
    
    info_list=[18, "CN", "Python",]         #定义列表
    info_dict={"sex":"Male","province":"HeBei"} #定义字典
    stu_register("lzl",info_list,info_dict)     #传入列表和字典
    ##############打印################
    #lzl ([18, 'CN', 'Python'], {'sex': 'Male', 'province': 'HeBei'}) <class 'tuple'>
    #{} <class 'dict'>

    看完上面的第一段代码,**kwargs会把多出的a=b这种类型的实参打印成字典的类型(要区分开与关键参数的区别,关键参数的实参有对应的形参),大家都能理解;但是第二段代码为毛下面把字典传入函数后,打印的**kwargs为空值呢?!  是这样的,传入的字典会被当成一个元素传入函数,所有被当成多余的实参传入到了*args里面,所以**kwargs的值才为空;那么有什么办法可以把字典传入到**kwargs呢?

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    #-Author-Lian
    
    def stu_register(name, *args, **kwargs):  # **kwargs 会把多传入的参数变成一个dict形式
        print(name, args, type(args))        # 打印args的类型
        print(kwargs, type(kwargs))          #打印kwargs的类型
    
    info_list=[18, "CN", "Python",]         #定义列表
    info_dict={"sex":"Male","province":"HeBei"} #定义字典
    stu_register("lzl",*info_list,**info_dict)     #传入列表和字典
    ##############打印################
    #lzl (18, 'CN', 'Python') <class 'tuple'>
    #{'sex': 'Male', 'province': 'HeBei'} <class 'dict'>
    

    把定义好列表info_list、info_dict,分别用*inf_list和**info_dict的方式传入到*args、**kwargs当中,解决了第二块代码中的问题

    总结:*args必须放到**kwargs前面(规定);位置参数一定要放到关键参数之前(规定);默认参数不能跟*args、**kwargs一块存在(会报错)。

    7、return 返回值

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    #-Author-Lian
    
    #ruturn 返回值
    def func(name,age):
        print(name)
        print(age)
    
    result = func("lzl",18)     #赋值变量result函数func执行的返回值
    print(result)                #函数不声明return,返回值为None
                                 #函数在赋值变量result的时候也进行了打印
    ##############打印################
    #lzl
    #18
    #None
    
    def func(name,age):
        print(name)
        return "best"          #执行return返回值
        print(age)
    
    result = func("lzl",18)     #赋值变量result函数func执行的返回值
    print(result)                #函数返回值为best
                                 #age没有打印,表明函数执行到return后,函数结束
    ##############打印################
    #lzl
    #best

    结论:如果不执行return,函数的默认返回值为None;当函数执行到return时,函数结束执行 

    8、局部变量

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    #-Author-Lian
    
    name = "Alex Li"                #定义变量name
    
    def change_name(name):
        print("before change:", name)   
        name = "金角大王,一个有Tesla的男人"   #函数内部更改变量
        print("after change", name)          #打印更改后的变量name
    
    change_name(name)
    print("在外面看看name改了么?", name)      #现在看看name变量有没有被更改
    
    ##############打印################
    #before change: Alex Li
    #after change 金角大王,一个有Tesla的男人
    #在外面看看name改了么? Alex Li
    

    结论:函数内部对变量进行更改后,生效范围仅限于函数内部,对外部变量没有影响,这种变量称为局部变量;函数内部也可以让变量全局生效,需要加参数global,这种情况很少用。

    9、递归函数

    定义:如果一个函数在内部调用自身本身,这个函数就是递归函数

    递归特性:

    •  必须有一个明确的结束条件
    •  每次进入更深一层递归时,问题规模相比上次递归都应有所减少
    •  递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)

    利用函数编写一个斐波那契数列

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    #-Author-Lian
    
    # def func(n1,n2):        #获取斐波那契数列100之前的数字
    #     if n1 > 100:
    #         return
    #     print(n1)
    #     n3 = n1 + n2
    #     func(n2,n3)
    # func(0,1)
    #######打印#######
    #0
    #1
    #1
    #2
    #3
    #5
    #8
    #13
    #21
    #34
    #55
    #89
    
    def func(count,n1,n2):        #获取斐波那契数列第200个数字并返回给调用者
        if count == 200:
            return n1
        n3 = n1 + n2
        r = func(count + 1,n2,n3)
        return r
    ret = func(1,0,1)
    print(ret)
    
    #######打印#######
    #173402521172797813159685037284371942044301
    

    10、函数式编程介绍 

    函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。而函数式编程(请注意多了一个“式”字)——Functional Programming,虽然也可以归结到面向过程的程序设计,但其思想更接近数学计算。我们首先要搞明白计算机(Computer)和计算(Compute)的概念。在计算机的层次上,CPU执行的是加减乘除的指令代码,以及各种条件判断和跳转指令,所以,汇编语言是最贴近计算机的语言。而计算则指数学意义上的计算,越是抽象的计算,离计算机硬件越远。对应到编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如C语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如Lisp语言。

    函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。

    函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。

    简单说,"函数式编程"是一种"编程范式"(programming paradigm),也就是如何编写程序的方法论。主要思想是把运算过程尽量写成一系列嵌套的函数调用。举例来说,现在有这样一个数学表达式:

     (1 + 2) * 3 - 4

    传统的过程式编程,可能这样写:

        var a = 1 + 2;
    
      var b = a * 3;
    
      var c = b - 4;

    函数式编程要求使用函数,我们可以把运算过程定义为不同的函数,然后写成下面这样:  

    var result = subtract(multiply(add(1,2), 3), 4);

    这就是函数式编程。  

     

    11、匿名函数

    匿名函数就是不需要显式的指定函数

    #这段代码
    def calc(n):
        return n**n
    print(calc(10))
     
    #换成匿名函数
    calc = lambda n:n**n
    print(calc(10))
    

    你也许会说,用上这个东西没感觉有毛方便呀, 。。。。呵呵,如果是这么用,确实没毛线改进,不过匿名函数主要是和其它函数搭配使用的呢,如下

    res = map(lambda x:x**2,[1,5,7,4,8])
    for i in res:
        print(i)
    
    #####打印#####
    1
    25
    49
    16
    64
    
    calc = map(lambda n,x,y:n*10000+x*100+y,[1,5,7,4,8],[2,2,2,2,2],[3,3,3,3,3])
    for i in calc:
        print(i)
    
    #####打印#####
    # 10203
    # 50203
    # 70203
    # 40203
    # 80203

     

    12、高阶函数

    变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。

    def add(x,y,f):
        return f(x) + f(y)
     
     
    res = add(3,-6,abs)
    print(res
    

      

    13、内置函数

    二、函数练习

    1、做个累加程序(类似1+2+3+.......+99+100的和)

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    #-Author-Lian
    def cumulative(a=1,b=100):
        n = 0
        while a <=b:
                n += a
                a +=1
        print(n)
     
    cumulative()        #默认为1加到100
    #5050
    cumulative(1,1000)
    #500500
    cumulative(50)      #50加到100
    #3825
    

    三,函数剖析

      

    1、函数的调用顺序

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    #-Author-Lian
    
    #函数错误的调用方式
    def func():                     #定义函数func()
        print("in the func")
        foo()                       #调用函数foo()
    func()                          #执行函数func()
    def foo():                      #定义函数foo()
        print("in the foo")
    
    ###########打印输出###########              #报错:函数foo没有定义
    #NameError: name 'foo' is not defined
    
    
    #函数正确的调用方式
    def func():                     #定义函数func()
        print("in the func")
        foo()                       #调用函数foo()
    def foo():                      #定义函数foo()
        print("in the foo")
    func()                          #执行函数func()
    
    ###########打印输出###########              
    #in the func
    #in the foo

    总结:被调用函数要在执行之前被定义 

    2、高阶函数

    满足下列条件之一就可成函数为高阶函数

    • 某一函数当做参数传入另一个函数中

    • 函数的返回值包含一个或多个函数

    刚才调用顺序中的函数稍作修改就是一个高阶函数

    #高阶函数
    def func():                     #定义函数func()
        print("in the func")
        return foo()                #调用函数foo()
    def foo():                      #定义函数foo()
        print("in the foo")
        return 100
    
    res = func()                     #执行函数func()
    print(res)                       #打印函数返回值
    
    ###########打印输出###########
    #in the func
    #in the foo
    #100

    从上面的程序得知函数func的返回值为函数foo的返回值,如果foo不定义返回值的话,func的返回值默认为None;

    下面我来看看更复杂的高阶函数:

    #更复杂的高阶函数
    import time                 #调用模块time
    def bar():
        time.sleep(1)
        print("in the bar")
    def foo(func):
        start_time=time.time()
        func()
        end_time=time.time()
        print("func runing time is %s"%(end_time-start_time))
        
    foo(bar)
    ###########打印输出###########
    #in the bar
    #func runing time is 1.0000572204589844
    

    其实上面这段代码已经实现了装饰器一些功能,即在不修改bar()代码的情况下,给bar()添加了功能;但是改变了bar()调用方式

    下面我们对上面的code进行下修改,不改变bar()调用方式的情况下进行功能添加

    #更复杂的高阶函数,不改变调用方式
    import time                 #调用模块time
    def bar():
        time.sleep(1)
        print("in the bar")
    def foo(func):
        start_time=time.time()
        print("in the foo")
        return func            #返回bar函数的内存地址
        end_time=time.time()
        print("func runing time is %s"%(end_time-start_time))
    
    bar = foo(bar)              #bar重新赋值
    bar()
    ###########打印输出###########
    #in the foo
    #in the bar
    

    我们没有对bar()源码进行过修改,也没有改变bar()的调用方式,当执行bar()函数时,多加了一些功能,装饰器的一些雏形已经呈现;但是我们又发现之前添加的计算bar()执行时间的功能没有打印出来,return执行后函数就结束了。

    3、内嵌函数和作用域

    定义:在一个函数体内创建另外一个函数,这种函数就叫内嵌函数(基于python支持静态嵌套域)

    #内嵌函数示例
    def foo():
        print("in the foo")
        def bar():
            print("in the bar")
        bar()
    
    foo()
    ###########打印输出###########
    #in the foo
    #in the bar
    

    嵌套函数有什么用呢?我们暂时先记住这个内容

    局部作用域和全局作用域的访问顺序

    #嵌套函数变量与全部变量
    x = 0
    def grandpa():
        x=1
        def dad():
            x=2
            def son():
                x=3
                print(x)
            son()
        dad()
    
    grandpa()
    print(x)
    ###########打印输出###########
    # 3
    # 0 

    注:内嵌函数中定义的函数在全局中是无法直接执行的

    4、装饰器

    定义:本质是函数(装饰其他函数),为其他函数添加附加功能的。

    遵循原则:①不能修改被装饰函数的源代码

         ②不能修改被装饰函数的调用方式

    组成:装饰器由高阶函数+内嵌函数组成

    之前说了那么多其实都是了给装饰器做铺垫,回到刚才高阶函数中最后一个示例,能不能给函数加上运算时间计算?

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    #-Author-Lian
    
    #装饰器
    import time
    def timer(func):
        def deco():
            start_time=time.time()
            func()                    #执行形参func()
            end_time=time.time()
            print("func runing time is %s"%(end_time-start_time))
        return deco                #返回函数deco的内存地址
    def test1():
        print("in the test1")
        time.sleep(1)
    
    test1 = timer(test1)            #重新赋值test1  此时test1=deco的内存地址
    test1()                         #执行test1
    ###########打印输出###########
    #in the test1
    #func runing time is 1.0000572204589844
    

    现在我们已经实现了装饰器的功能,但是如果test1有形参的话,上面的代码就会报错了,下面我们对代码做下修改

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    #-Author-Lian
    
    #装饰器
    import time
    def timer(func):
        def deco(*args,**kwargs):
            start_time=time.time()
            func(*args,**kwargs)                    #执行形参func()
            end_time=time.time()
            print("func runing time is %s"%(end_time-start_time))
        return deco                #返回函数deco的内存地址
    @timer                          #test1 = timer(test1)  test1=deco
    def test1(name):
        print("in the test1 name %s"%name)
        time.sleep(1)
    
    test1("lzl")                         #执行test1
    ###########打印输出###########
    #in the test1
    #func runing time is 1.0000572204589844
    

     上面的代码是不是觉得很完美了,呵呵,假如test1()有return返回值怎么办?你会发现最后执行test1返回值丢失了,所以要对上面的代码再完善一下了。

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    #-Author-Lian
    
    #装饰器
    import time
    def timer(func):
        def deco(*args,**kwargs):
            start_time=time.time()
            res = func(*args,**kwargs)                    #执行形参func()
            end_time=time.time()
            print("func runing time is %s"%(end_time-start_time))
            return res
        return deco                #返回函数deco的内存地址
    @timer                          #test1 = timer(test1)  test1=deco
    def test1(name):
        print("in the test1 name %s"%name)
        time.sleep(1)
        return "return form test1"
    
    print(test1("lzl"))                  #执行test1
    ###########打印输出###########
    #in the test1
    #func runing time is 1.0000572204589844
    #return form test1

     好了现在我们探讨一个问题,函数可以被多个装饰器装饰吗?!

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    #-Author-Lian
    
    #函数用多个装饰器
    def w1(func):
        def inner(*args, **kwargs):
            print("in the w1")
            return func(*args, **kwargs)
        return inner
    def w2(func):
        def inner(*args, **kwargs):
            print("in the w2")
            return func(*args, **kwargs)
        return inner
    @w1
    @w2
    def f1(*args, **kwargs):
        print("in the f1")
    
    f1()
    ###########打印输出###########
    #in the w1
    #in the w2
    #in the f1
    

    终极版装饰器来了......

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    #-Author-Lian
    
    #终极版装饰器
    def Before(*args, **kwargs):
        print("before")
    def After(*args, **kwargs):
        print("after")
    
    def Filter(before_func, after_func):
        def outer(main_func):
            def wrapper(*args, **kwargs):
                before_result = before_func(*args, **kwargs)
                if (before_result != None):
                    return before_result
                main_result = main_func(*args, **kwargs)
                if (main_result != None):
                    return main_result
                after_result = after_func(*args, **kwargs)
                if (after_result != None):
                    return after_result
            return wrapper
        return outer
    
    @Filter(Before, After)      #Filter(Before,After)=outer  Index=outer(Index)=wrapper
    def Index(*args, **kwargs):
        print("index")
    
    Index()                     #Index() = wrapper()
    ###########打印输出###########
    #before
    #index
    #after
    

      

     5、生成器

     学习生成器之前,我们先来看看什么是列表生成式

    #列表生成式
    b = [ i*2 for i in range(10)]
    print(b)
    
    ###########打印输出###########
    #[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
    

    通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,还需要花费很长时间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种调用时才会生成相应数据的机制,称为生成器:generator

    要创建一个generator,有很多种方法,第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个生成器

    #生成器
    l = [ i*2 for i in range(10)]
    print(l)
    
    g = (i*2 for i in range(10))
    print(g)
    
    ###########打印输出###########
    #[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
    #<generator object <genexpr> at 0x0064AAE0>

    print(g) 打印出来的信息显示g是一个生成器,创建lg的区别仅在于最外层的[]()l是一个list,而g是一个generator;我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值

    #生成器next打印
    print(next(g))
    #.........                 不断next 打印10次
    #..........
    print(next(g))
    
    ###########打印输出###########
    #0
    #........
    #18
    #Traceback (most recent call last):
    #  File "<stdin>", line 1, in <module>
    #StopIteration
    

    我们讲过,generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。

    上面这种不断调用next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象,所以,我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误

    #生成器for调用
    g = (i*2 for i in range(10))        #不用担心出现StopIteration错误
    for i in g:
        print(i)
    
    ###########打印输出###########
    # 0
    # 2
    # 4
    # 6
    # 8
    # 10
    # 12
    # 14
    # 16
    # 18
    

    generator非常强大。如果推算的算法比较复杂,用列表生成式转换的生成器无法去实现时,我们还可以用函数来实现。比如,著名的斐波拉契数列(Fibonacci)

    #函数表示斐波拉契数列
    def fib(max):
        n, a, b = 0, 0, 1
        while n < max:
            print(b)
            a, b = b, a + b
            n += 1
        return 'done'
    
    fib(5)
    ###########打印输出###########
    # 1
    # 1
    # 2
    # 3
    # 5
    

    仔细观察,可以看出,fib函数实际上是定义了斐波那契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator;也就是说,上面的函数和generator仅一步之遥,那我们能不能把上面的函数变成一个生成器呢?

    #斐波拉契数列转换为generator
    def fib(max):
        n, a, b = 0, 0, 1
        while n < max:
            #print(b)
            yield b
            a, b = b, a + b
            n += 1
        return 'done'
    
    print(type(fib(5)))     #打印fib(5)的类型
    for i in fib(5):        #for循环去调用
        print(i)
    ###########打印输出###########
    # <class 'generator'>
    # 1
    # 1
    # 2
    # 3
    # 5
    

    要把fib函数变成generator,只需要把print(b)改为yield b就可以了,这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator

    但是用for循环调用generator时,会发现拿不到generator的return语句的返回值,也就是return的值没有打印出来,现在我们来看看怎么去打印generator的返回值

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    #-Author-Lian
    
    #获取generator的返回值
    def fib(max):
        n, a, b = 0, 0, 1
        while n < max:
            #print(b)
            yield b
            a, b = b, a + b
            n += 1
        return 'done'
    
    g = fib(5)
    while True:
        try:
            x = next(g)
            print( x)
        except StopIteration as e:
            print(e.value)
            break
    ###########打印输出###########
    # 1
    # 1
    # 2
    # 3
    # 5
    # done
    

    如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIterationvalue中,关于如何捕获错误,后面的错误处理还会详细讲解。 

    还可通过yield实现在单线程的情况下实现并发运算的效果

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    #-Author-Lian
    
    import time
    def consumer(name):
        print("%s 准备吃包子啦!" %name)
        while True:
           baozi = yield
    
           print("包子[%s]来了,被[%s]吃了!" %(baozi,name))
    
    
    def producer(name):
        c = consumer('A')
        c2 = consumer('B')
        c.__next__()        #c.__next__()等同于next(c)
        c2.__next__()
        print("老子开始准备做包子啦!")
        for i in range(10):
            time.sleep(1)
            print("%s做了2个包子!"%(name))
            c.send(i)
            c2.send(i)
    
    producer("lzl")
    

    6、迭代器

    我们已经知道,可以直接作用于for循环的数据类型有以下2种:

    • 集合数据类型,如listtupledictsetstr等;

    • 生成器,包括generator和带yield的generator function;

    定义:这些可以直接作用于for循环的对象统称为可迭代对象:Iterable  

    我们可以使用isinstance()去判断一个对象是否是Iterable对象

    #可迭代对象
    from collections import Iterable
    
    print(isinstance([], Iterable))
    # True
    print(isinstance("abc", Iterable))
    # True
    print(isinstance((x for x in range(10)), Iterable))
    # True
    print(isinstance(100, Iterable))
    # False
    

    我们知道生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了

    重点来了....*可以被next()函数调用并不断返回下一个值的对象称为迭代器Iterator

    可以使用isinstance()判断一个对象是否是Iterator对象

    #迭代器对象
    from collections import Iterator
    
    print(isinstance([], Iterator))
    # True
    print(isinstance("abc", Iterator))
    # False
    print(isinstance((x for x in range(10)), Iterator))
    # True
    print(isinstance(100, Iterator))
    # False
    

    由上面可知,生成器都是Iterator对象,但listdictstr虽然是Iterable,却不是Iterator;把listdictstrIterable变成Iterator可以使用iter()函数

    #可迭代对象转迭代器对象
    print(isinstance(iter([]), Iterator))
    # True
    print(isinstance(iter("abc"), Iterator))
    # True

    你可能会问,为什么listdictstr等数据类型是Iterable但不是Iterator

    这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

    小结:

    • 凡是可作用于for循环的对象都是Iterable类型;
    • 凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
    • 集合数据类型如listdictstr等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象;

    Python的for循环本质上就是通过不断调用next()函数实现的,例如:

    for x in [1, 2, 3, 4, 5]:
        pass

    实际上完全等价于:

    # 首先获得Iterator对象:
    it = iter([1, 2, 3, 4, 5])
    # 循环:
    while True:
        try:
            # 获得下一个值:
            x = next(it)
        except StopIteration:
            # 遇到StopIteration就退出循环
            break
    
  • 相关阅读:
    【线段树合并】联通分量计数
    莫队算法
    Ubuntu实用软件安装[转]
    装系统·折腾记
    Qt环境配置 + Qt使用教程
    Google题解
    隐含马尔科夫模型
    Pythonの坑
    C++11并发编程个人小结
    微软2017年预科生计划在线编程笔试
  • 原文地址:https://www.cnblogs.com/lixiaomingpython/p/6802403.html
Copyright © 2011-2022 走看看