zoukankan      html  css  js  c++  java
  • python入门:函数进阶(名称空间,闭包,装饰器)

    一、名称空间

        又名name space,比如变量x=1,那么名称空间正是存放名字x与1绑定关系的地方

    名称空间共3种,分别如下:

    • locals: 是函数内的名称空间,包括局部变量和形参
    • globals: 全局变量,函数定义所在模块的名字空间
    • builtins: 内置模块的名字空间

    不同变量的作用域不同是由这个变量所在的命名空间决定的。

    作用域即范围

    • 全局范围:  全局存活,全局有效
    • 局部范围:  临时存活,局部有效

    查看作用域方法globals(),locals()

    作用域查找顺序:

    level = 'L0'
    n = 22
    
    def func():
        level = 'L1'
        n = 33
        print(locals())
    
        def outer():
            n = 44
            level = 'L2'
            print(locals(), n)
    
            def inner():
                level = 'L3'
                print(locals(), n)  # 此处打印的n是多少?
            inner()
        outer()
    
    func()
    

    问题:在inner()里的打印的n的值是多少?  

    LEGB 代表名字查找顺序:locals —> enclosing  function —> globals —> __builtins__

    • locals 是函数内的名字空间,包括局部变量和形参
    • enclosing 外部嵌套函数的名字空间
    • globals 全局变量,函数定义所在模块的名字空间
    • building 内置模块的名字空间

    二、闭包

    定义: 即函数定义和函数表达式位于另一个函数的函数体内(嵌套函数)。

    而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就形成闭包。也就是说,内部函数会在外部函数返回后被执行。而当这个内部函数执行时,它仍然必需访问其它外部函数的局部变量、参数以及其它内部函数。这些局部变量,参数和函数声明(最初时)的值是外部函数时的值,但也会收到内部函数的影响。

    def outer():
        name = 'mike'
    
        def inner():
            print('在inner里打印外层函数的变量', name)
            
        return inner
    
    f = outer()
    
    f()
    

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

    三、装饰器

    比如一家视频网站,有如下板块

    def home():
        print("---首页----")
    
    def america():
        print("----欧美专区----")
    
    def japan():
        print("----日韩专区----")
    
    def henan():
        print("----河南专区----")
    

      现在需求是要对“欧美"和”河南“专区进行收费,也就是登录之后才能看到,刚开始实现如下,每个板块里调用就可以了

    user_status = False    # 用户登录了就把这个改成True
    
    
    def login():
        _username = 'mike'
        _password = '123'
        global user_staus
        if user_status == False:
            username = input('请输入用户名:').strip()
            password = input('请输入密码:').strip()
            if username == _username and password == _password:
                print('欢迎光临,%s' % username)
                user_staus = True
            else:
                print('用户名或密码错误,请重新输入!')
        else:
            print('用户已登录,验证通过!')
    
    
    def home():
        print('首页'.center(20, '-'))
    
    
    def america():
        login()   # 执行前加上验证
        print('欧美专区'.center(20, '-'))
    
    
    def japan():
        print('日韩专区'.center(20, '-'))
        
        
    def henan():
        login()   # 执行前加上验证
        print('河南专区'.center(20, '-'))
    
    
    home()
    america()
    henan()
    

      你很自信的提交到代码给team leader审核,没有几分钟之后,代码被打回了,原因是:如果有很多需要加认证模块,你的代码虽然实现了功能,但是需要更改需要加认证的各个模块的代码,这直接违反了软件开发中的一个”开放-封闭“原则,简单来说,它规定已经实现的功能代码不允许被修改,但可以扩展,即:

    • 封闭:  已实现的功能代码块不应该被修改
    • 开放:  对现有功能的扩展开放

    经过思考之后,想到了解决方案,不改源代码可以呀,可以用高阶函数:就是把一个函数当做一个参数传给另外一个函数,只需要写一个认证方法,每次调用需要验证的功能时,直接把这个功能的函数名当做一个参数传给我的验证模块就可以了,代码如下:

    user_status = False    # 用户登录了就把这个改成True
    
    
    def login(func):
        _username = 'mike'
        _password = '123'
        global user_status
        if user_status == False:
            username = input('请输入用户名:').strip()
            password = input('请输入密码:').strip()
            if username == _username and password == _password:
                print('欢迎光临,%s' % username)
                user_status = True
            else:
                print('用户名或密码错误,请重新输入!')
    
        if user_status == True:
            func()   # 看这里看这里,只要验证通过了,就调用相应功能
        # else:
        #     print('用户已登录,验证通过!')
    
    
    def home():
        print('首页'.center(20, '-'))
    
    
    def america():
        # login()   # 执行前加上验证
        print('欧美专区'.center(20, '-'))
    
    
    def japan():
        print('日韩专区'.center(20, '-'))
    
    
    def henan():
        # login()   # 执行前加上验证
        print('河南专区'.center(20, '-'))
    
    
    home()
    login(america)    # 需要验证就调用login,把需要验证的功能,当做一个参数传给login
    # america()
    # henan()
    login(henan)
    

      终于实现了老板的要求,不改变原功能代码的前提下,给功能加上了验证,此时隔壁老王来了,你跟他分享了你写的代码,老王看后,笑笑说,你这个代码还是改改吧,要不然会被开除,WHAT?会开除,明明实现了功能呀,老王讲,没错,你功能是实现了,但是你又犯了一个大忌,什么大忌?你改变了调用方式呀,想一想,现在每个需要认证的模块,都必须调用你的login()方法,并把自己的函数名传给你,之前可不是这么调用的,试想,如果有100个模块需要认证,那这100个模块都得改变调用方式,这么多模块肯定不止是一个人写的,让每个人再去修改调用方式才能加上认证,你会被骂死的。。。。你觉得老王说的对,但问题是,如何即不改变原功能代码,又不改变原有调用方式,还能加上认证呢?你酷似不得其解,

    老王说:学过匿名函数没有?

    你:学过,就是lambda,

    老王:那lambda与正常函数的区别是什么?

    你:最直接的区别是,正常函数定义时需要写名字,但lambda不需要

    老王:没错,那lambda定好后,为了多次调用,可否也给它命个名?

    你:可以呀,可以写成plus= lambda x:x+1 类似这样,以后再调用plus就可以了,但这样不就失去了lambda的意义了,明明人家叫匿名函数呀,你起来名字有什么用呢?

    老王:我不是要给你讨论它的意义,我想通过这个让你明白一个事实,说这老王在画板上面谢了以下代码:

    def plus(n):
        return n+1
    
    
    plus2 = lambda x:x+1  

    老王:上面这种写法是不是代表同样的意思?

    你:是的

    老王:我给lambda X:X+1 起了个名字叫plus2,是不是相当于def plus2(x)?

    你:你还别说,还真是,但老王呀,你想说明什么?

    老王:没啥,只想告诉你,给函数赋值变量名就像def func_name 是一样的效果,如下面的plus(n)函数,你调用时可以用plus名,还可以再起个其它名字,如:

    calc = plus
    
    calc(n)
    

      你明白想传达什么意思了么?

    你:不太明白

    老王:你之前写的下面这段调用认证的代码

    home()
    login(america)    # 需要验证就调用login,把需要验证的功能,当做一个参数传给login
    # america()
    # henan()
    login(henan)
    

      老王:你之所改变了调用方式,是因为用户每次调用时需要执行login(henan),类似的。其实稍一改就可以了呀

    home()
    america=login(america)    # 需要验证就调用login,把需要验证的功能,当做一个参数传给login
    henan=login(henan)
    

      这样,其他人调用henan时,其实相当于调用了login(henan),通过login里的验证后,就会自动调用henan功能

    你:我靠,还真是,老王,你牛B,不过,等等,我这样写好之后,那用户调用时,应该是下面这个样子

    home()
    america=login(america)    # 需要验证就调用login,把需要验证的功能,当做一个参数传给login
    henan=login(henan)
    # 那用户调用时依然写
    america()
    

      但问题在于,还不等用户调用,你的america=login(america)就会先自己把america执行了呀。。。,你应该等用户调用的时候,再执行才对呀,不信是给你看。。。

    老王:你说的没错,这样搞会出现这个问题?但你想想有没有解决办法呢?

    你:你指点思路呀

    老王:算了,估计你也想不出来。。。学过嵌套函数没有?

    你:yes,然后呢?

    老王:想实现一开始你写的america=login(america)不触发你函数的执行,只需要在这个login里面再定义一层函数,第一次调用america=login(america)只调用到外层login,这个login虽然会执行,但不会触发认证了,因为认证的所有代码被封装在login里层的新定义的函数里了,login值返回里层函数的函数名,这样下次再执行america()时,就会调用里层函数啦

    你:什么?什么意思?懵逼了

    老王:还是给你看代码吧

    def login(func):  # 把要执行的模块从这里传进来
        
        def inner():  # 再定义一层函数
            _username = 'mike'  
            _password = '123'
            global user_status
            
            if user_status == False
                username = input('请输入用户名:').strip()
                password = input('请输入密码:').strip()
                
                if username == _username and password == _password:
                    print('欢迎登录成功, %s' % username)
                    user_status = True
                    
                else:
                    print('输入的用户名或密码错误!')
                    
            if user_status == True:
                func()  # 看这里看这里,只要验证通过了,就调用相应功能
                
        return inner # 用户调用login时,只会返回inner的内存地址,下次再调用时加上()才会执行inner函数
    

      老王:这是开发中一个常用的玩法,叫语法糖,官方名称:装饰器,其实上面的写法,还可以更简单,可以把下面代码去掉

    america = login(america)   #你在这里相当于把america这个函数替换了
      

    只在你要装饰的函数上面加上下面代码:

    @login
    def america():
        # login()   # 执行前加上验证
        print('欧美专区'.center(20, '-'))
    
    
    def japan():
        print('日韩专区'.center(20, '-'))
    
    @login
    def henan():
        # login()   # 执行前加上验证
        print('河南专区'.center(20, '-'))
    

      效果是一样的,然后你给‘河南专区”板块加上了一个参数,然后出错了

    你:老王,这么传个参数就不行了呢?

    老王:那必然呀,你调用henan时,其实是相当于调用的login,你的henan第一次调用时nenan=login(henan),login就返回了inner的内存地址,第2次用户自己调用henan(“电影"),实际上相当于调用的时inner,但你的inner定义时病没有设置参数,但你给它传了参数,所以自然报错了呀:

    你:但是版块需要传参数呀,不传不行呀。。

    老王:稍作改动便可,如果有多个参数,可以用非固定参数:*args,**kwargs...

    代码如下:

    user_status = False
    def login(func):  # 把要执行的模块从这里传进来
    
        def inner(*args, **kwargs):  # 再定义一层函数
            _username = 'mike'
            _password = '123'
            global user_status
    
            if user_status == False:
                username = input('请输入用户名:').strip()
                password = input('请输入密码:').strip()
    
                if username == _username and password == _password:
                    print('欢迎登录成功, %s' % username)
                    user_status = True
    
                else:
                    print('输入的用户名或密码错误!')
    
            if user_status == True:
                func(*args, **kwargs)  # 看这里看这里,只要验证通过了,就调用相应功能
    
        return inner # 用户调用login时,只会返回inner的内存地址,下次再调用时加上()才会执行inner函数
    
    
    def home():
        print('首页'.center(20, '-'))
    
    @login
    def america():
        # login()   # 执行前加上验证
        print('欧美专区'.center(20, '-'))
    
    
    def japan():
        print('日韩专区'.center(20, '-'))
    
    @login
    def henan(style):
        '''
        :param style: 喜欢看什么节目,就传进来
        :return:
        '''
        # login()   # 执行前加上验证
        print('湖南专区'.center(20, '-'))
    
    home()
    henan = login(henan)
    
    america()
    
    henan('快乐大本营')
    

      四、带参数装饰器

    新的需求:要允许用户选择用qqweiboweixin认证

    user_status = False
    def login(auth_type):  # 把要执行的模块从这里传进来
        def auth(func):
    
    
            def inner(*args, **kwargs):  # 再定义一层函数
                if auth_type == 'qq':
                    _username = 'mike'
                    _password = '123'
                    global user_status
    
                    if user_status == False:
                        username = input('请输入用户名:').strip()
                        password = input('请输入密码:').strip()
    
                        if username == _username and password == _password:
                            print('欢迎登录成功, %s' % username)
                            user_status = True
    
                        else:
                            print('输入的用户名或密码错误!')
    
                    if user_status == True:
                        func(*args, **kwargs)  # 看这里看这里,只要验证通过了,就调用相应功能
                else:
                    print('只能qq登录')
    
            return inner # 用户调用login时,只会返回inner的内存地址,下次再调用时加上()才会执行inner函数
        return auth
    
    
    def home():
        print('首页'.center(20, '-'))
    
    @login('qq')
    def america():
        # login()   # 执行前加上验证
        print('欧美专区'.center(20, '-'))
    
    
    def japan():
        print('日韩专区'.center(20, '-'))
    
    @login('weibo')
    def henan(style):
        '''
        :param style: 喜欢看什么节目,就传进来
        :return:
        '''
        # login()   # 执行前加上验证
        print('湖南专区'.center(20, '-'))
    
    home()
    henan = login(henan)
    
    america()
    
    henan('快乐大本营')
    

      

  • 相关阅读:
    C#多线程操作界面控件的解决方案
    InvokeHelper,让跨线程访问/修改主界面控件不再麻烦
    .netCF中后台多线程与UI界面交互的冻结问题
    c#设计模式第一天
    C#代理
    界面
    第一章面向对象涉及原则
    C# 为webBrowser设置代理
    设计模式等
    下载: Intel® 64 and IA32 Architectures Software Developer Manuals
  • 原文地址:https://www.cnblogs.com/mike-liu/p/9039560.html
Copyright © 2011-2022 走看看