python入门:函数进阶(名称空间,闭包,装饰器)

时间:2022-07-05 22:48:14

一、名称空间

    又名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('快乐大本营')

  四、带参数装饰器

新的需求:要允许用户选择用qq\weibo\weixin认证

 

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('快乐大本营')