zoukankan      html  css  js  c++  java
  • Python基础第七天——global与nonlocal、闭包函数、装饰器

    鸡汤:

    突破Python基础第五、六天,算是正式上路了!路漫漫其修远兮 吾将上下而求索!持之以恒才是胜利的关键,加油,奔跑吧小白!

    一、拾遗

    1、函数的返回值

    语法:

      在函数内部后面加上:“return 值”

    特点:

      (1)一次return可以返回一个或多个值,且返回值没有类型限制,返回多个值时以逗号作为分隔符隔开

      (2)返回的一个值是字符串的形式,返回多个值是以元组的形式存储

      (3)如果不写return,默认返回:None

      (4)一个函数中若有多个rentun,只能返回第一个return,函数就终止

      

    2、函数参数的使用

    函数的参数分为两大类:形参实参

    (1)形参特点和种类:

      <1>特点:

            形参在定义阶段定义,调用时需要接收实参传过来的值

      <2>种类:

          I、位置形参特点: 必须被传值,不能多传也不能少传

          II、默认参数特点: 在定义阶段已为形参赋值,定义阶段有值,调用阶段可以不传值。    例:def (x,y=1):中的"y=1"就是默认形参,通常情况下把固定不变或很少变的参数作为默认参数。

          III、*args:      特点:用来接收实参传过来的可变长的位置实参*用来接收实参传过来的可变长按位置定义的参数,然后赋值给变量名args

          IV、**kwargs特点: 用来接收实参传过来的可变长的关键字实参**

          V命名关键字参数

                 特点:*后面的参数,这些参数必须要被传值,且实参必须以关键字的形式传值

                    在 *args  后面的参数也是命名关键字参数,只是 *args 可以接收从实参传过来的任意长度的的位置实参。

      <3>形参种类在定义时的位置排序:

          (位置形参,默认参数,*args,命名关键字参数,**kwargs)

          ------------------------------------------------------------->

                  从左至右排序排序

     

    (2)实参特点和种类:

      <1>特点:

            实参在调用阶段时定义,调用时要传入的值。简单地说,实参就是给形参传值的

      <2>种类:

            I、位置实参:    特点:位置传值,必须与形参一 一对应

          II、关键字实参特点:按关键字以“key=value”的形式传值

      <3>实参种类在定义时的位置排序:

            (位置实参,关键字实参)

            ------------------------>

            从左至右排序排序

    (3)形参与实参的关系:

    形参相当于变量名,实参相当于变量的值,在python中变量的定义都没有储值的功能,所以它们两是一种绑定的关系。这种绑定关系在函数调用时生效,函数调用结束后失效

    (4)*args和**kwargs的作用:

    <1>在定义阶段:

    两者一起连用表示能接收从实参传过来的任意长度、任意形式的参数。

    *接收任意长度的参数后以元组的形式保存并赋值给变量名:args

    **接收任意形式的参数后以字典的形式保存并赋值给变量名:kwargs

    <2>在调用阶段:

    *args表示将传进去的这些参数全都拆开,拆成按位置的形式,如:(1,2,3,4,5)

    **kwargs表示将传进去的这些参数全都拆开,拆成按关键字的形式,如(a=1,b=2,c=3)

    3、函数的嵌套

    分为两种:

    (1)函数的嵌套调用

    定义:在函数内部嵌套调用其它函数

    (2)函数的嵌套定义

    定义:在函数内部使用def关键字再定义一个函数

    4、函数对象

    定义:函数可以被当作数据去处理。

    (1)可以被引用

    (2)可以当作参数传入

    (3)可以当作函数的返回值

    (4)可以当作容器类的元素

    5、名称空间与作用域(global和nonlocal)

    (1)名称空间

    定义:存放名字和值的绑定关系

    <1>种类:

    内置名称空间:python解释器一启动就会产生。

    全局名称空间:python解释器开始执行文件的时候就会产生。

    局部名称空间:执行文件的过程中可能会调用到函数,调用函数则会产生局部名称空间,它是临时产生的,调用结束则失效。

     <2>三种名称空间按产生顺序排序

    内置名称空间——>全局名称空间——>局部名称空间

     <3>三种名称空间查找名字的顺序排序

    局部名称空间——>全局名称空间——>内置名称空间

    (2)作用域

    定义:生效范围

    分类按照每个名称空间的作用范围不同分成了两个区域:

    <1>全局作用域:

      定义:定义在全局作用域名字叫全局变量。包含内置名称空间和全局名称空间

      特点:在文件的任何位置都可以被引用

      失效:文件结束完了或del直接干掉了

    <2>局部作用域:

      定义:定义在局部作用域的名字叫局部变量,局部名称空间

      特点:只能在局部使用,不能从全局作用域看局部作用域的名字,但是能在局部作用域看全局作用域的名字

    <3>全局作用域和局部作用域按查找顺序排序

      局部作用域——>全局作用域

      补充:

        即使当前位置是全局作用域,也要按照以上的顺序来,因为全局作用域的局部作用域仍然是全局作用域

       查看局部作用域的名字:locals

       查看全局作用域的名字:globals

     

     

     二、global与nonlocal的补充

    1、global

    作用:局部作用域修改名字,会在全局作用域的名字中找

    函数内加上global关键字它会一下从最里层跳到最外一层找全局的了

    例1:

    例2:

    要求:在局部作用域中修改全局作用域的名字 (不推荐

      上述编程风格在实际工作中一定要尽可能要避免,因为在局部进行更改会导致全局也跟着改变,这样很容易改乱。

      例如,抢票软件。假设一张车票,有几十万人同时在线,大家都发现了这张票,(读一个全局的变量,这几十万人都能读到),于是这几十万人便开始抢,都把这张票加到购物车后付钱,(此时相于每个人的后台都要减去这一张票,这就相当于大家都在改一个值)最快速度付完款的人则能抢到票,但是在最快抢到票的那段时间内,同一时间这几十万人当中可能同时不止一个人抢到了票,可能会多个人同时抢到这张票,这时就会出现一张票被多人抢到手的问题了。(相当于同一时间内有多人改1个值

      为了避免以上问题,解决的方法是:不要使用全局的变量,要使用局部变量

     

    2、nonlocal

    作用:局部作用域内修改名字,只在函数内部找

    函数内加上nonlocal关键字它会找当前层的上一层。

    例1:

    例2:若将以上f2函数中“x”注释掉,nonlocal则会继续去再上一层的函数中找变量"x",

    本例得到的结果仍然是"from f2 function",那是因为f1函数体中的“x”值只是定义而没有加打印,而且调用了f2函数体中的print('from f2 function',x)这段代码,所以才会得到“from f2 function,xiaobai“

     例3:若将函数中所有的变量x都注释掉,则会报错,提示变量"x"未绑定

    三、 闭包函数

    1、定义:

    函数内部定义函数,称为内部函数

    该内部函数对外部作用域,而不是对全局作用域名字的引用

    闭包函数的格式:

    def 外部函数名():

      内部函数需要的变量

      def 内部函数():

        引用外部变量

      return 内部函数

    例1:

    本例中的闭包函数就是返回的foo函数名以及附带的x=2的值,把调用func函数的结果赋值给变量名f,此时变量“f”又是一个全局的变量,也就是此时的func函数能在任意位置被调用了

     例2:

    补充:即使以下闭包函数中的foo函数不加return也算是闭包函数,只要这个函数是内部函数,且对外部函数的引用就是闭包函数。但是为了调用时不受层级限制,通常函数函数与return一起使用。

    将例1中的闭包函数进行调用

    2、闭包函数的特点

    (1)特性:

    闭包函数永远自带着自己的状态,可以被赋值给变量名,调用时不受函数定义时的层级限制

    (2)验证函数是否为装饰函数:__closure__

    验证一个函数是否为闭包函数则用“__closure__”来验证。(closure的中文意思就是闭包)

    闭包函数中不一定要有return返回值,但是为了调用时不受层级限制,通常要与return一起使用,只需要将这个函数名后加上.__closure__查看结果就可判断是否为闭包函数,如果打印的结果有值,则为闭包函数

    (3)取值:

    通过“cell_contents”得到闭包中的值。

     例1:闭包中附带1个值

    例2:闭包中附带多个值

     例3:内部函数引用的全局变量则不称为闭包函数,此时用“__closure__”验证得到“None”

    以下是非闭包函数

     (4)补充:

    补充1:凡是在函数内部看到有调用时,一定要看去找这个被调用函数定义时的位置。

    例:

    补充2:在闭包函数中加上nonlocal关键字时,这个函数仍然是闭包函数,只是这可以在闭包函数内部对外面一层的函数里的值进行修改

    例:包两层的情况

     3、多层闭包函数

    思路:想让一个函数保存一个状态,不断地包它就行了,包一层外面定义一个变量,把这个它包在一个函数内部,想要基于这个函数的基础之上再往里面包内容则再包一层,再把那个函数返回来

    例:

    4、闭包的应用场景

    闭包函数可应用于爬虫项目。

    什么是爬虫?

    答:爬虫是通过一个链接把网站的内容下载至本地,然后通过一些规则(如正则表达式)提取对自己有用的信息。

    例:写一个爬虫的雏形项目

    通过urlopen模块可以向网页发起请求将代码下载至本地

    from urllib.request import urlopen

    (1)爬虫的基本原理

    (2)把输入的链接用函数做成一个功能,通过传参的方式统一定义。

    (3)为了省去记网址的麻烦,则将程序改为闭包函数。将这种多次访问同一个链接地址称为一种状态。这种状态可以通过闭包的形式保存起来,此时调用函数时不必传参,当要用到时只需要加上括号即可。

    (4)以上步骤中的url变量里的网址是写死的,如果要分别爬多个网站,则需要修改源代码,为了避免修改源代码,将以上代码修改为:

     四、装饰器

    1、定义:

    依照目前所学的知识可以将装饰器理解为以下定义:

      装饰器本身就是函数且是可以任意调用的对象,被装饰的对象也是任意可调用对象。

      (可调用对象:加括号就能运行的函数)

    通俗地说装饰器就是闭包函数的一种应用而已。

    装饰器的目的是,给被装饰对象添加新功能,并且不让用户察觉感觉到装饰器的存在。

    2、特性:

    装饰器遵循开放封闭原则 (即不修改被装饰对象的源代码以及调用方式)为被装饰对象添加新功能

    开放封闭原则:对扩展是开放的,对修改是封闭的(所有软件开发都要遵循这个原则)

    以上这句话的详细意思是:

      例如写一个软件,不可能一次性地把功能都写全了再上线运行,一定会根据客户需要将软件进行功能扩展,这就是对扩展开放。

      对软件源代码是不能修改的,因为这些源代码在函数里时,定义阶段可能会被许多位置使用到,这就意味着一旦把某个函数中的源代码改了,影响的可能是全局,引起连锁反应。除非你能保证不会影响全局的情况下才能进行对源代码修改。

    例:给一个函数的加上统计运行时间的功能

    # 要求:给index函数加上统计index函数的运行时间
    import time     # 时间模块
    import random   # 随机模块
    
    # 装饰器
    def timmer(func):
        # func = index
        def wrapper():
            start_time = time.time()    # time.time()表示显示当前时间
            func()
            stop_time = time.time()
            print('run time is %s'%(stop_time - start_time))
        return wrapper
    
    # 被装饰对象
    def index():
        time.sleep(random.randrange(1,4))   # 表示随机取1-4中的任意一个数
        print('welcome to index page')
    
    
    
    index = timmer(index)  # timmer(index)表示把被装饰对象传给timmer装饰器
    index()
    

    图示:

    3、装饰器的流程解析:

    例:

    4、装饰器的语法:

    在被装饰对象的正上方单独写一行@装饰器名

    例:将上面的装饰器使用装饰器语法

    # 要求:给index函数加上统计index函数的运行时间
    import time
    import random
    
    # 装饰器
    def timmer(func):
        # func = index
        def wrapper():
            start_time = time.time()
            func()
            stop_time = time.time()
            print('run time is %s'%(stop_time - start_time))
        return wrapper
    
    
    
    # 被装饰对象1
    @timmer # 装饰器语法,相当于 index = timmer(index)
    def index():
        time.sleep(random.randrange(1,4))
        print('welcome to index page')
    
    # 被装饰对象2
    @timmer # 装饰器语法,相当于 home = timmer(home)
    def home():
        time.sleep(random.randrange(1,3))
        print('welcome to home page')
    
    
    index()
    home()
    

    输出结果:

    welcome to index page
    run time is 1.0005803108215332
    welcome to home page
    run time is 1.0001659393310547
    View Code

    5、我自已理解的装饰器原理。

    def  装饰器名(形参名):             # 装饰器中的形参是用来接收值的,这个传过来的值是被装饰对象的函数名

      def  内部函数名():

        装饰器功能代码....

        被装饰对象名()              # 调用被装饰对象,被装饰对象的名字与装饰器括号内的形参名字是一致的。

        装饰器功能代码....

      return  内部函数名

    @ 装饰器名          # 装饰器语法:相当于被装饰对象名 = 装饰器名(被装饰对象名)

    def 被装饰对象名():

      函数体

      ......

    被装饰对象名()            # 调用被装饰对象

    6、多个装饰器同时被使用

    执行程序的流程是从上往下依次执行的。

    而多个装饰器同时使用时,则是从下往上执行的顺序依次计算的。按排序最上面的装饰器先执行最下面的装饰器先计算。对我们有用的就是执行!

    例1:

    例2:多个装饰器的执行流程

    import time        # 1      执行顺序由数字说明
    import random      # 2
    # 装饰器
    def timmer(func):  # 3
        def wrapper(): # 7
            start_time = time.time()
            func()
            stop_time = time.time()
            print('run time is %s'%(stop_time - start_time))
        return wrapper # 8
    
    def auth(func):   # 4
        def deco():  # 9
            username = input("Please input your username:")    # 12
            password = input("Please input your password:")
            if username == 'xiaobai' and password == '123456':
                print('Login successful!')
                func()
            else:
                print('Login error!')
        return deco    # 10
    # 被装饰对象
    @auth           # 5   将加上wrapper闭包的index当作参数传入auth函数内,返回deco函数,此时得到deco的闭包,再赋值给index
    @timmer         # 6  将index当作参数传入timmer函数,返回wrapper函数,此时得到wrapper的闭包,再赋值给index
    def index():
        time.sleep(3)
        print('welcome to index page')
    index()    # 11
    

     7、有参装饰器

    (1)例:

    # 有参装饰器
    import time
    import random
    #装饰器
    def timmer(func):
        def wrapper(name):
            start_time = time.time()
            func(name)
            stop_time = time.time()
            print('run time is %s'%(stop_time - start_time))
        return wrapper
    
    # 装饰器语法
    @timmer
    # 被装饰对象
    def home(name):
        time.sleep(random.randrange(1,3))
        print('Welcome to %s home page'%name)
    
    # 调用阶段
    home('xiaobai')
    

    (2)装饰器接收任意长度的参数

    被装饰对象有多个,且有的是无参函数,有的是有参函数时:

    在装饰器的接收位置加上*args,**kwargs可接收任意长度的参数,

    在装饰器内调用被装饰对象时也要加上*args,**kwargs

     例2:

    import time
    import random
    #装饰器
    def timmer(func):
        def wrapper(*args,**kwargs):    # 不管被装饰对象是否有参数,用*args和**kwargs来接收传过来的任意长度的参数
            start_time = time.time()
            func(*args,**kwargs)        # 怎么接收的就要怎么给。
            stop_time = time.time()
            print('run time is %s'%(stop_time - start_time))
        return wrapper
    
    
    # 装饰器语法
    @timmer
    # 被装饰对象1——有参函数
    def home(name):
        time.sleep(random.randrange(1,3))
        print('Welcome to %s home page'%name)
    
    
    # 装饰器语法
    @timmer
    # 被装饰对象2——无参函数
    def index():
        time.sleep(random.randrange(1,5))
        print('welcome to index page')
    
    
    # 调用阶段
    home('xiaobai')
    index()
    

    (3)当被装饰对象有返回值时

    例:

    import time
    import random
    
    #装饰器
    def timmer(func):
        def wrapper(*args,**kwargs):
            start_time = time.time()
            res = func(*args,**kwargs)       # 调用被装饰对象并赋值给res
            stop_time = time.time()
            print('run time is %s'%(stop_time - start_time))
            return res                      # 返回res,如果被装饰对象中有返回值,则原封不动地返回,如果没有则返回None
        return wrapper
    
    # 装饰器语法
    @timmer
    # 被装饰对象2——无参函数
    def index():
        time.sleep(random.randrange(1,5))
        print('welcome to index page')
    
    
    # 装饰器语法
    @timmer
    # 被装饰对象1——有参函数
    def home(name):
        time.sleep(random.randrange(1,3))
        print('Welcome to %s home page'%name)
        return 'I am King in the world!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'
    
    
    
    # 调用阶段
    res1 = index()          # 此时的index不是原始的index,而是调的wrapper函数
    print('index return %s'%res1)
    res2 = home('xiaobai') # 此时的home不是原始的home,而是调的wrapper函数
    print('home return %s'%res2)
    

    输出结果:

    welcome to index page
    run time is 2.0005133152008057
    index return None
    Welcome to xiaobai home page
    run time is 1.0001530647277832
    home return I am King in the world!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    View Code
  • 相关阅读:
    雷林鹏分享:EJB安全
    雷林鹏分享:EJB事务管理
    雷林鹏分享:EJB Blobs/Clobs
    雷林鹏分享:EJB嵌入对象
    雷林鹏分享:EJB拦截器
    Linux 学习
    数学知识总结
    信贷风控模型开发----模型流程&好坏样本定义
    信贷风控模型开发----模型简介
    TreeMap源码剖析
  • 原文地址:https://www.cnblogs.com/xiaoxiaobai/p/7691061.html
Copyright © 2011-2022 走看看