zoukankan      html  css  js  c++  java
  • Python之路--Python基础3--函数

    1、函数简介

      函数是重(chong)用的程序段。它们允许你给一个语句块一个名称,然后你用这个名字可以在你的程序的任何地方,任意多次地运行这个语句块。这被称为调用函数。我们已经使用了许多内建的函数,比如 len 和 range 。函数用关键字 def 来定义。def 关键字后跟一个函数的标识符名称,然后跟一对圆括号。圆括号之中可以包括一些变量名,该行以冒号结尾。接下来是一块语句,它们是函数体。下面这个例子将说明这事实上是十分简单的:

    def sayhi(name): #函数名(参数)
        print("Hello,I'm %s" % name)
    
    sayhi("YL")  #调用函数

     

    2、函数参数、局部变量、全局变量

      形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只在函数内部有效。函数调用结束返回主调用函数后则不能再使用该形参变量

      实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。因此应预先用赋值,输入等办法使参数获得确定值

     

    默认参数

    先看下面代码:

    def stu_register(name,age,country,course):
        print("----注册学生信息------")
        print("姓名:",name)
        print("age:",age)
        print("国籍:",country)
        print("课程:",course)
     
    stu_register("Jack",22,"CN","python")
    stu_register("Tom",21,"CN","linux")
    stu_register("Alex",25,"JP","C++")

      country 这个参数基本都是"CN", 就像我们在网站上注册用户,像国籍这种信息,你不填写,默认就会是“CN”,这就是通过默认参数实现的,把country变成默认参数非常简单:

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

    这样,country这个参数在调用时不指定,那默认就是CN,指定了的话,就用你指定的值。注意:默认参数只能放到最后面 。 

     

    关键参数

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

       stu_register(age=22,name='alex',course="python")

     

    非固定参数

    若你的函数在定义时不确定用户想传入多少个参数,就可以使用非固定参数

    def stu_register(name,age,*args):  #*args 会把多传入的参数变成一个元组形式
        print(name,age,args)
    
    stu_register("Tom",22)
    #输出
    #Tom 22 ()               #后面这个()就是args,只是因为没传值,所以为空
     
    stu_register("Jack",32,"CN","Python")
    #输出
    #Jack 32 ('CN', 'Python')

    还可以有一个**kwargs

    def stu_register(name,age,*args,**kwargs):  #*kwargs 会把多传入的参数变成一个dict形式
        print(name,age,args,kwargs)
     
    stu_register("Tom",22)
    #输出
    #Tom 22 () {}                   #后面这个{}就是kwargs,只是因为没传值,所以为空
     
    stu_register("Jack",32,"CN","Python",sex="Male",province="ShanDong")
    #输出
    # Jack 32 ('CN', 'Python') {'province': 'ShanDong', 'sex': 'Male'}

    调用函数时,没有对应上的位置参数会传入*args,变成元组形式;关键参数会传入**kwargs,变成字典形式。

     

    全局变量与局部变量

    在子程序中定义的变量称为局部变量,在程序的一开始定义的变量称为全局变量。
    全局变量作用域是整个程序,局部变量作用域是定义该变量的子程序。
    当全局变量与局部变量同名时:在定义局部变量的子程序内,局部变量起作用;在其它地方全局变量起作用。

     

    3、返回值

    要想获取函数的执行结果,就可以用return语句把结果返回

    注意:

    1. 函数在执行过程中只要遇到return语句,就会停止执行并返回结果,so 也可以理解为 return 语句代表着函数的结束
    2. 如果未在函数中指定return,那这个函数的返回值为None 

     

    4、函数嵌套与函数递归

    啥都别说直接看函数嵌套的代码1:

    name = "YL"
    def change_name():
        name = "YL2"
        def change_name2():
            name = "YL3"
            print("第3层打印", name)
        change_name2()  #调用内层函数
        print("第2层打印", name)
    
    change_name()
    print("最外层打印", name)

    #输出:
    第3层打印 YL3
    第2层打印 YL2
    最外层打印 YL

    代码2:

    #嵌套调用
    def my_max4(a, b, c, d):
        res1 = my_max2(a, b)
        res2 = my_max2(res1, c)
        res3 = my_max2(res2, d)
        return res3
    
    def my_max2(x, y):
        if x > y:
            return x
        else:
            return y
    
    print(my_max4(11,35,34,-5))  #35
    
    
    #嵌套定义(通常2到3层,多了不好看懂)
    x = 3
    def f1():
        x = 1
        def f2():
            x = 2
            print(x)
        return f2
    
    func = f1()
    func()      #2

    递归:如果一个函数在内部调用自己,这个函数就是递归函数

    def calc(n):
        print(n)
        if int(n/2) ==0:
            return n
        return calc(int(n/2))
     
    calc(10)
     
    输出:
    10
    5
    2
    1

    递归特性:

    1. 必须有一个明确的结束条件

    2. 每次进入更深一层递归时,问题规模相比上次递归都应有所减少

    3. 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出

    堆栈扫盲http://www.cnblogs.com/lln7777/archive/2012/03/14/2396164.html 

     递归函数的实际应用:二分查找

    def binary_search(find_str, data_set, count):  #参数说明:要找的数据,数据集合,查找计数器
        mid = int(len(data_set)/2)
        if mid == 0:
            if data_set[mid] == find_str:
                print("找到了->:", find_str, count)
            else:
                print("没找到:", find_str, count)
            return
        if data_set[mid] == find_str:
            print("找到了:", data_set[mid], count)
        elif data_set[mid] > find_str:
            print("Going to search in left:", data_set[mid], data_set[0:mid])
            binary_search(find_str, data_set[0:mid], count+1)
        else:
            print("Going to search in right:", data_set[mid], data_set[mid+1:])
            binary_search(find_str, data_set[mid+1:], count+1)
    
    binary_search(585, data, 0)

     

    5、匿名函数

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

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

    这个看不出啥NB之处,那就看看下面的:

    res = map(lambda x:x**2,[1,5,7,4,8])
    for i in res:
        print(i)
    
    #输出:
    1
    25
    49
    16
    64
    #匿名函数最复杂的函数就是三元运算,不能再复杂了
    calc2 = lambda x, y: x**y
    print(calc2(10, 12))
    for i in map(calc, [1, 2, 3]):
        print(i)
    
    for i in map(lambda x: x*x, [1, 2, 3]):  #逼格高
        print(i)
    
    for i in map(lambda x: x*2 if x>5 else x-1, [1, 2, 3]): 
        print(i)

    map()函数是Python内置函数,第一个参数接受一个函数名,后面的参数接受一个或多个可迭代的序列,返回的是一个集合。(后面的博客中会有详细介绍)

     

    6、高阶函数

    # 高阶函数:1.把一个函数的内存地址当做参数传给另一个函数
    #         2.一个函数把另一个函数当做返回值 返回
    
    
    def add(x, y, f):
        return f(x) + f(y)
    
    
    res = add(3, -6, abs)  #abs绝对值函数
    print(res)

     

    7、闭包函数

    如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包。

    举个栗子:

    def closure():
        x = 5
        def sub():
            return x * x
        return sub

    如上,在内部函数sub中包含了对函数closure中局部变量x的引用,这就是闭包。

    闭包的意义:返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,优先使用自己外层包裹的作用域

    应用领域延迟计算(原来我们是传参,现在我们是包起来)

    再看一个nb一点的栗子:

    from urllib.request import urlopen
    
    def page(url):
        #url = http//:www.baidu.com
        def get():
            return urlopen(url).read()
        return get
    
    baidu = page('http://www.baidu.com') #爬取百度页面
    # python = page('http://www.python.org')
    
    # print("python:", python())
    print("baidu:", baidu().decode("utf-8")) #这里要转码

     

    8、装饰器

    装饰器就是闭包函数的一种应用场景

    装饰器遵循开放封闭原则:对修改封闭,对扩展开放

    装饰器本身可以是任意可调用对象,被装饰者也可以是任意可调用对象。

    装饰器的原则:1、不修改被装饰对象的源代码

           2、不修改被装饰对象的调用方式

    装饰器的目标:在遵循以上两点原则的前提下,为被装饰对象添加上新功能

    #装饰器语法
    #被装饰函数的正上方,单独一行
    @deco1
    @deco2
    @deco3
    def foo():
        pass
    
    #此时调用foo就相当于---> foo=deco1(deco2(deco3(foo)))

    举个栗子:

    #无参装饰器
    import time
    
    def timer(func):
        def wrapper(*arg, **kwargs): #任意参数传入
            strat_time = time.time()
            res = func(*arg, **kwargs)#运行最原始的index
            stop_time = time.time()
            print("run time is %s" % (stop_time-strat_time))
            return res
        return wrapper
    
    @timer      #index = timer(index)
    def index(msg):
        print("in the index:%s" % (msg))
    
    @timer
    def home(user, msg):
        print("in the home:%s,%s" % (user, msg))
        return "home return 1"
    
    index("hello world")
    print(home("jack", msg="123456"))
    
    
    
    #输出-------------------
    in the index:hello world
    run time is 0.0
    in the home:jack,123456
    run time is 0.0
    home return 1

    下面是有参装饰器的栗子:

    #有参装饰器
    accounts = {}
    current_logon_user = None
    
    def auth(auth_type):
        def auth_deco(func):
            def wrapper(*args, **kwargs):
                if current_logon_user not in accounts: #之前没有验证成功过
                    username = input("username:")
                    password = input("password:")
                    if auth_type == "file":
                        if username == "YL" and password == "123":
                            accounts[username] = True
                            global current_logon_user  #修改全局变量
                            current_logon_user = username
                            return func(*args, **kwargs)
                    elif auth_type == "ldap":
                        print("----->ldap")
                        return func(*args, **kwargs)
                else:
                    return func(*args, **kwargs)   #如果验证成功了的直接返回执行最原始的函数
            return wrapper
        return auth_deco
    
    @auth("file")
    def index(msg):
        print("in the index %s" %(msg))
    
    @auth("ldap")
    def home(msg):
        print("in the home %s" %(msg))
    
    index("hello")  #只要第一次验证通过,后面就不需要验证了
    home("hello")

     

    9、生成器

    现在有个需求,将列表[0,1,2,3,4,5,6,7,8,9]里的每一个值都加一,有下面三种方法:

    #普通青年版--->使用enumerate函数
    a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    
    for index, i in enumerate(a):  #将列表中的各项加一
        #print(index, i)       #打印下标和元素值
        a[index] += 1
    
    print(a)
    a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    
    #文艺青年版--->使用匿名函数和map函数
    a = map(lambda x: x+1, a)
    for i in a:
        print(i)
    a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    
    #nb青年版----->使用列表生成
    a = [i+1 for i in a]
    print(a)                       #[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    
    #列表生成式还可以使用三元运算
    #a = [i*i if i>5 else i-1 for i in a]  #大于5的进行平方运算,小于5的进行减1运算
    #print(a)                      #[-1, 0, 1, 2, 3, 4, 36, 49, 64, 81]

      通过上面的列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,如果创建一个包含100万个元素的列表,就会占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

      所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

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

    L = [x * x for x in range(10)]
    print(L)   #[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    
    g = (x * x for x in range(10))
    print(g)   #<generator object <genexpr> at 0x00000133F4CE6FC0>

    创建Lg的区别仅在于最外层的[]()L是一个list,而g是一个generator。

      我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值:

    g = (x * x for x in range(4))
    print(g)   #<generator object <genexpr> at 0x00000133F4CE6FC0>
    
    print(next(g))       #0
    print(g.__next__())  #1
    print(g.__next__())  #4
    print(next(g))       #9
    #print(next(a))      #最后一个元素已经计算出了,再调用next(g)就会报错

    generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。当然,上面这种不断调用next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象:

    for n in g:
        print(n)
    
    #输出:
    0
    1
    4
    9

      所以,我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误。generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

      比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:1, 1, 2, 3, 5, 8, 13, 21, 34, ...

      斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

    def fib(max):
        n, a, b = 0, 0, 1
        while n < max:
            print(b)
            a, b = b, a + b
            n = n + 1
        return 'done'

    注意:

    赋值语句:a, b = b, a + b
    
    相当于: t = (b, a + b)  # t是一个tuple
                a = t[0]
                b = t[1]        

    调用上面的函数可以输出斐波那契数列的前N个数:

    >>> fib(10)
    1
    1
    2
    3
    5
    8
    13
    21
    34
    55
    done

      仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:

    def generator(max):
        n, a, b = 0, 0, 1
        while n<max:
            #print(b)
            yield b             #生成器yield 保存了函数的中断状态
            a, b = b, a + b
            n = n+1
        return "done"
    
    g = generator(5)
    print(next(g))              
    print(g.__next__())
    print("do something else")
    print(g.__next__())
    print(g.__next__())
    print(next(g))
    
    # 输出:
    # 1
    # 1
    # do something else
    # 2
    # 3
    # 5

    这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:

      注意,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

      在上面generator(max)的例子中,我们在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。

    同样的,把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代:

    for n in generator(6):
        print(n)
    
    #输出
    1
    1
    2
    3
    5
    8

    但是用for循环调用generator时,发现拿不到generator的return语句的返回值“done”。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIterationvalue中。(关于如何捕获错误,后面的错误处理还会详细讲解)

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

    #吃包子
    #通过生成器实现协程并行运算
    import time
    def consumer(name):
        print("%s 准备吃包子啦!" % name)
        while True:       #死循环
           baozi = yield  #接收到producer send 过来的值
    
           print("包子[%s]来了,被[%s]吃了!" % (baozi, name))
    
    def producer():
        c = consumer('A')
        c2 = consumer('B')
        c.__next__()
        c2.__next__()
        print("开始准备做包子啦!")
        for i in range(10):
            time.sleep(1)
            print("做了2个包子!")
            c.send(i)   #调用next 并传了一个值给yield
            c2.send(i)
    
    producer()

    10、迭代器

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

      一类是集合数据类型,如listtupledictsetstr等;

      一类是generator,包括生成器yield的generator function

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

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

    >>> from collections import Iterable
    >>> isinstance([], Iterable)
    True
    >>> isinstance({}, Iterable)
    True
    >>> isinstance('abc', Iterable)
    True
    >>> isinstance((x for x in range(10)), Iterable)
    True
    >>> isinstance(100, Iterable)
    False

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

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

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

    >>> from collections import Iterator
    >>> isinstance((x for x in range(10)), Iterator)
    True
    >>> isinstance([], Iterator)
    False
    >>> isinstance({}, Iterator)
    False
    >>> isinstance('abc', Iterator)
    False

    生成器都是Iterator迭代器对象,但listdictstr虽然是Iterable可迭代对象,却不是Iterator迭代器

    listdictstrIterable变成Iterator可以使用iter()函数:

    >>> isinstance(iter([]), Iterator)
    True
    >>> isinstance(iter('abc'), Iterator)
    True

    那么,为什么listdictstr等数据类型不是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
  • 相关阅读:
    学习之路总结
    一个怀旧的人
    struts2+ibatis+spring框架整合(一)
    大雪来的不知所措
    struts2+ibatis+spring框架整合(二)
    20110610上午java考试复数题
    直到永远……
    2012年10月份考试后感
    Use sp_MSForEachDB instead of your own loop
    Execute TSQL using OpenRowSet
  • 原文地址:https://www.cnblogs.com/yl-code/p/8215080.html
Copyright © 2011-2022 走看看