python变量作用域、闭包、装饰器解析

变量作用域、闭包、装饰器

一、变量作用域

变量作用域:

在Python程序中创建、改变、查找变量名时,都是在一个保存变量名的空间中进行,我们称之为命名空间,也被称之为作用域。
python的作用域是静态的,在源代码中变量名被赋值的位置决定了该变量能被访问的范围。即Python变量的作用域由变量所在源代码中的位置决定。

作用域的类型:

在Python中,使用一个变量时并不严格要求需要预先声明它,但是在真正使用它之前,它必须被绑定到某个内存对象(被定义、赋值);这种变量名的绑定将在当前作用域中引入新的变量,同时屏蔽外层作用域中的同名变量。

  • L(local)局部作用域
    局部变量:包含在def关键字定义的语句块中,即在函数中定义的变量。每当函数被调用时都会创建一个新的局部作用域。Python中也有递归,即自己调用自己,每次调用都会创建一个新的局部命名空间。在函数内部的变量声明,除非特别的声明为全局变量,否则均默认为局部变量。有些情况需要在函数内部定义全局变量,这时可以使用global关键字来声明变量的作用域为全局。局部变量域就像一个 栈,仅仅是暂时的存在,依赖创建该局部作用域的函数是否处于活动的状态。所以,一般建议尽量少定义全局变量,因为全局变量在模块文件运行的过程中会一直存在,占用内存空间。
    注意:如果需要在函数内部对全局变量赋值,需要在函数内部通过global语句声明该变量为全局变量。
  • E(enclosing)嵌套作用域
    E也包含在def关键字中,E和L是相对的,E相对于更上层的函数而言也是L。与L的区别在于,对一个函数而言,L是定义在此函数内部的局部作用域,而E是定义在此函数的上一层父级函数的局部作用域。主要是为了实现Python的闭包,而增加的实现。
  • G(global)全局作用域
    即在模块层次中定义的变量,每一个模块都是一个全局作用域。也就是说,在模块文件顶层声明的变量具有全局作用域,从外部开来,模块的全局变量就是一个模块对象的属性。
    注意:全局作用域的作用范围仅限于单个模块文件内
  • B(built-in)内置作用域
    系统内固定模块里定义的变量,如预定义在builtin 模块内的变量。

变量名解析LEGB法则

搜索变量名的优先级:局部作用域 > 嵌套作用域 > 全局作用域 > 内置作用域

python变量作用域的几个例子

  • 示例1.在函数内不对全局变量修改,可以直接访问
#示例1.在函数内不对全局变量修改,可以直接访问
b = 5
def test(a):
	print(a)
	print(b)  #因为没有在函数内找到b,没有赋值,所以在全局作用于找b,打印外部全局变量b的数值()
test(4)	
# 运行后输出如下:
# 4
# 5
  • 示例2. 不可以在函数内对全局变量修改
# 示例2. 不可以在函数内对全局变量修改
b = 5
def test(a):
	print(a)
	b += 2  # 不可以在函数内对全局变量修改
	print(b)
test(4)    
print(b)
4
# Traceback (most recent call last):
#   File "C:\Users\ct\Desktop\test.py", line 6, in <module>
#     test(4)    
#   File "C:\Users\ct\Desktop\test.py", line 4, in test
#     b += 2
# UnboundLocalError: local variable 'b' referenced before assignment
# [Finished in 0.1s with exit code 1]
  • 示例3. 先用全局变量b,在赋值,会认为b是局部变量而报错
# 示例3. 先用全局变量b,在赋值,会认为b是局部变量而报错
b = 5
def test(a):
    print(a)
    print(b) # 因为有对b的赋值,所以认为b是局部变量,但是b的赋值在print(b) 之后,所以报错
    b = 2
test(4)  
# 运行结果如下:
# 4
# Traceback (most recent call last):
#   File "C:\Users\ct\Desktop\test.py", line 6, in <module>
#     test(4)    
#   File "C:\Users\ct\Desktop\test.py", line 4, in test
#     print(b)
# UnboundLocalError: local variable 'b' referenced before assignment
# [Finished in 0.1s]
  • 示例4. 函数内直接先对全局变量同名变量赋值, 会创建一个新的局部变量
# 示例4. 函数内直接先对全局变量同名变量赋值, 会创建一个新的局部变量
b = 5 # 全局变量b
def test(a):
	print(a)
	b = 2 # 在函数内重新为全局变量赋值,会创建局部变量覆盖全局  
	print(b) # 打印局部变量b
test(4)    
print(b) # test中赋值的是局部变量,所以打印的全局变量b不变
# 4 
# 2
# 5
# [Finished in 0.1s]
  • 变量作用域说明:
    函数体中,对变量有赋值操作,则证明这个变量是一个局部变量,只会从局部变量中去读取数据;
    如果不修改变量,可以直接使用全局变量;
    这样的设计可以避免我们在不知道的情况下,获取到全局变量的值,从而导致一些错误数据的出现。

global关键字

如何不在函数内部重新为全局变量赋值,而改变全局变量的值

  • 示例
b = 5
def test(a):
	print(a)
	global b # global关键字修饰b, 声明b是全局变量
	b += 2 
	print(b)
test(4)    
print(b)  # 因为test函数对全局变量b修改了, 所以b变成了7
# 4
# 7
# 7
# [Finished in 0.1s]
  • 函数局部作用域
    函数运行结束,函数的内部所有东西都会释放掉,还给内存,局部作用域里的局部变量都会消失,包括函数的参数。
# 示例: 
def fun(b):
    a = 1
    a += b
    print(a)
for i in range(3):
    fun(1)  # 代码运行3次,输出都是2, 即局部变量a每次运行完fun都会销毁 
# 2
# 2
# 2
# [Finished in 0.1s]
  • 如何保存函数的状态信息,使函数的局部变量信息在函数运行完保存下来?
    可以使用闭包,如果一个函数结束,函数的内部所有东西都会释放掉,还给内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。

二、闭包

什么是闭包?

1)函数嵌套
2)内部函数使用外部函数的变量
3)外部函数的返回值为内部函数

闭包意义?

返回的函数对象,不仅是一个函数对象还在外层包裹了一层作用域,这使得函数无论在哪里被调用,都优先使用自己外层包裹的作用域;
闭包使用了外部变量,同一个的函数使用了不同的外部变量,就实现了不同的功能;
如果它不使用外部变量,它和普通的函数就没有任何区别。

闭包形成的原因?

外层函数调用后,外层函数的函数作用域(AO)对象无法释放,被内层函数引用者。

闭包特性

根据外部函数的局部变量来得到不同的结果;
参数和变量不会被垃圾回收机制回收。

闭包的优点、缺点

优点: 保存函数的状态信息,使函数的局部变量信息依然可以保存下来、封装、代码复用
缺点: 比普通函数占用更多的内存
解决:及时释放不使用的闭包,将引用内层函数对象的变量赋值为null即可

闭包例子

# outer是外部函数 a和b都是外函数的临时变量
def outer(a):
    b = 10
    # inner是内函数
    def inner():
        #在内函数中 用到了外函数的临时变量
        print(a+b)
    # 外函数的返回值是内函数的引用
    return inner
test = outer(5)
test() # 输出15
#对于inner函数来说,对在outer函数的整个作用域(inner函数的非全局作用域的外部作用域)的变量a和b进行引用,inner函数就是闭包
def lazy_sum(*args):
	def sum():            # sum()是内部函数,可以利用外部函数的参数
		ax = 0
		for n in args:    # sum()中使用外部函数的局部变量
			ax = ax + n 
		return ax
	return sum            # 形成闭包,此时,*args保存在返回的函数中
f = lazy_sum(1, 3, 5, 7, 9)
print(f)  # 返回的f是lazy_sum函数
print(f())  # 返回的f是lazy_sum结果

def sum(*args):
	ax = 0
	for n in args:    # sum()中使用外部函数的局部变量
		ax = ax + n 
	return ax           # 形成闭包,此时,*args保存在返回的函数中
s = sum(1, 3, 5, 7, 9)
print(s) # 返回sum结果
# 输出如下:
# <function lazy_sum.<locals>.sum at 0x0000023CE6F58BF8>
# 25
# 1
# 25
# [Finished in 0.1s]

什么时候使用闭包?

只要既重用一个变量,又保护变量不像全局变量一样容易被污染时;
当对象中只有一个方法时,这时使用闭包是更好的选择;

闭包可以避免使用全局变量,此外,闭包允许将函数与其所操作的某些数据(环境)关连起来。这一点与面向对象编程是非常类似的,在面对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。

推荐一个比较好的blog: https://www.cnblogs.com/s-1314-521/p/9763376.html

三、装饰器

什么是装饰器?

装饰器本质上是一个Python函数,也可以说是闭包,它可以让其它函数在不作任何变动的情况下增加额外功能;
在不改变源代码的基础上扩展新需求,经常用于有切面需求的场景。比如:插入日志、性能测试、事务处理、缓存、权限校验等。可以抽离出大量的与函数功能无关的雷同代码进行重用;
装饰器是一种特殊的函数,接受函数/类作为输入参数,并返回一个函数/类;
函数可以作为参数传递的语言,可以使用装饰器;
把被装饰的函数内存地址当参数传入装饰器函数体,通过参数调用被装饰的函数

什么样的语言能够用装饰器?

函数可以作为参数传递的语言(可以实现闭包的语言),可以使用装饰器。

装饰器的原则

不改变源代码
不改变原函数调用顺序

什么是面向切面编程?

面向切面编程主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。比如插入日志、性能测试、事务处理、缓存、权限校验等场景。

示例

@标记是语法糖(syntactic sugar),可以让你以简单易读得方式装饰目标对象。

  • 装饰器无参数
from functools import wraps
def logged(func):
    @wraps(func)  #如果不加这句函数的名字会变成with_logging
    def with_logging(*args, **kwargs):
        print(func.__name__)      # 输出 'f'
        print(func.__doc__)       # 输出 'does some math'
        return func(*args, **kwargs)
    return with_logging
@logged
def f(x):
   """does some math"""
   return x + x * x
f(1)
# 输出如下:
# f
# does some math
# [Finished in 0.1s]
  • 装饰器有参数
    那么你就需要在原来的装饰器上再包一层,用于接收这些参数。
    这些参数传递到内层的装饰器里后,闭包就形成了。
    所以说当你的装饰器需要自定义参数时,一般都会形成闭包。(类装饰器例外)
def html_tags(tag_name):
    def wrapper_(func):
        def wrapper(*args, **kwargs):
            content = func(*args, **kwargs)
            return "<{tag}>{content}</{tag}>".format(tag=tag_name, content=content)
        return wrapper
    return wrapper_
@html_tags('b')
def hello(name='Toby'):
    return 'Hello {}!'.format(name)
# 不用@的写法如下
# hello = html_tag('b')(hello)
# html_tag('b') 是一个闭包,它接受一个函数,并返回一个函数
print(hello())  # <b>Hello Toby!</b>
print(hello('world'))  # <b>Hello world!</b>
# 输出如下:
# <b>Hello Toby!</b>
# <b>Hello world!</b>
# [Finished in 0.1s]

python装饰器的wraps作用

Python装饰器在实现的时候,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变),为了不影响,Python的functools包中提供了一个叫wraps的decorator来消除这样的副作用。
写一个decorator的时候,最好在实现之前加上functools的wrap,它能保留原有函数的名称和docstring。

  • 示例1. 不带@warps, 函数名被装饰器覆盖
from functools import wraps   
def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        '''decorator doc '''
        print('decorator function')
        return func(*args, **kwargs)
    return wrapper  
 
@my_decorator 
def test():
    '''test doc'''
    print('test function')

print('func name: ', test.__name__)
print('func  doc: ', test.__doc__)
# 输出如下:
# func name:  test
# func  doc:  test doc
# [Finished in 0.1s]
  • 示例2. 带@warps,函数名没有被装饰器覆盖
from functools import wraps   
def my_decorator(func):
    #@wraps(func)
    def wrapper(*args, **kwargs):
        '''decorator doc '''
        print('decorator function')
        return func(*args, **kwargs)
    return wrapper  
 
@my_decorator 
def test():
    '''test doc'''
    print('test function')

print('func name: ', test.__name__)
print('func  doc: ', test.__doc__)
# 输出如下:
# func name:  wrapper
# func  doc:  decorator doc 
# [Finished in 0.1s]

参考:
https://jingyan.baidu.com/article/574c52196b604c6c8d9dc133.html
https://www.cnblogs.com/s-1314-521/p/9763376.html 推荐看一下这个
https://www.cnblogs.com/whyaza/p/9505205.html
https://www.cnblogs.com/jiajialove/p/9049612.html
https://www.cnblogs.com/fireporsche/p/7813961.html
https://www.php.cn/python-tutorials-411559.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值