在Python程序中创建、改变、查找变量名时,都是在一个保存变量名的空间中进行,我们称之为命名空间,也被称之为作用域。
python的作用域是静态的,在源代码中变量名被赋值的位置决定了该变量能被访问的范围。即Python变量的作用域由变量所在源代码中的位置决定。
在Python中,使用一个变量时并不严格要求需要预先声明它,但是在真正使用它之前,它必须被绑定到某个内存对象(被定义、赋值);这种变量名的绑定将在当前作用域中引入新的变量,同时屏蔽外层作用域中的同名变量。
搜索变量名的优先级:局部作用域 > 嵌套作用域 > 全局作用域 > 内置作用域
#示例1.在函数内不对全局变量修改,可以直接访问
b = 5
def test(a):
print(a)
print(b) #因为没有在函数内找到b,没有赋值,所以在全局作用于找b,打印外部全局变量b的数值()
test(4)
# 运行后输出如下:
# 4
# 5
# 示例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
# 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是局部变量而报错
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
# 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. 函数内直接先对全局变量同名变量赋值, 会创建一个新的局部变量
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]
如何不在函数内部重新为全局变量赋值,而改变全局变量的值
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结果
# 输出如下:
# .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()) # Hello Toby!
print(hello('world')) # Hello world!
# 输出如下:
# Hello Toby!
# Hello world!
# [Finished in 0.1s]
Python装饰器在实现的时候,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变),为了不影响,Python的functools包中提供了一个叫wraps的decorator来消除这样的副作用。
写一个decorator的时候,最好在实现之前加上functools的wrap,它能保留原有函数的名称和docstring。
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]
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