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
  • 相关阅读:
    ASP.NET Core 中文文档 第四章 MVC(3.2)Razor 语法参考
    ASP.NET Core 中文文档 第四章 MVC(3.1)视图概述
    ASP.NET Core 中文文档 第四章 MVC(2.3)格式化响应数据
    ASP.NET Core 中文文档 第四章 MVC(2.2)模型验证
    ASP.NET Core 中文文档 第四章 MVC(2.1)模型绑定
    ASP.NET Core 中文文档 第四章 MVC(01)ASP.NET Core MVC 概览
    mysql 解除正在死锁的状态
    基于原生JS的jsonp方法的实现
    HTML 如何显示英文单、双引号
    win2008 r2 服务器php+mysql+sqlserver2008运行环境配置(从安装、优化、安全等)
  • 原文地址:https://www.cnblogs.com/xiaoxiaobai/p/7691061.html
Copyright © 2011-2022 走看看