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

一、名称空间

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

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

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

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

作用域即范围

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

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

作用域查找顺序:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

level = 'L0'

= 22

def func():

    level = 'L1'

    = 33

    print(locals())

    def outer():

        = 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 内置模块的名字空间

二、闭包

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

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

1

2

3

4

5

6

7

8

9

10

11

def outer():

    name = 'mike'

    def inner():

        print('在inner里打印外层函数的变量', name)

         

    return inner

= outer()

f()

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

三、装饰器

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

1

2

3

4

5

6

7

8

9

10

11

def home():

    print("---首页----")

def america():

    print("----欧美专区----")

def japan():

    print("----日韩专区----")

def henan():

    print("----河南专区----")

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

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

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

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

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的意义了,明明人家叫匿名函数呀,你起来名字有什么用呢?

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

1

2

3

4

5

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名,还可以再起个其它名字,如:

1

2

3

calc = plus

calc(n)

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

你:不太明白

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

1

2

3

4

5

home()

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

# america()

# henan()

login(henan)

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

1

2

3

home()

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

henan=login(henan)

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

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

1

2

3

4

5

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()时,就会调用里层函数啦

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

老王:还是给你看代码吧

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

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函数

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

1

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

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

1

2

3

4

5

6

7

8

9

10

11

12

13

@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...

代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

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认证

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

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

  • 20
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值