函数作用域,闭包
高阶函数
函数柯里化
装饰器
变量的作用域
全局作用域
- 全局变量
局部作用域
- 局部变量
x = 5
def foo():
x += 1
print(x)
foo()
------------------------------------------------------------------------------
UnboundLocalError Traceback (most recent call last)
<ipython-input-81-715f310d8cc6> in <module>()
3 x += 1
4 print(x)
----> 5 foo()
<ipython-input-81-715f310d8cc6> in foo()
1 x = 5
2 def foo():
----> 3 x += 1
4 print(x)
5 foo()
UnboundLocalError: local variable 'x' referenced before assignment
foo 函数中 x = x + 1,= 右边,重新定义局部变量 x,x 还没赋值就拿来做 + 1 运算了
global 语句
x = 5
def foo():
global x
x += 1
print(x)
foo()
------------
6
使用 global 关键字的变量,将 foo 内的 x 声明为使用外部的全局作用域中定义的 x
def foo():
global x
x = 10
x += 1
print(x)
foo()
print(x)
----------------
11
11
使用了 global,foo 中的 x 不再是局部变量了,它是全局变量
- 总结:global 使用原则
- 外部作用域变量会在内部作用域可见,但也不要在这个内部的局部作用域中直接使用,因为函数的目的就是为了封装,尽量与外界隔离
- 如果函数需要使用外部全局变量,请尽量使用函数的形参定义,并在调用传实参解决
- 避免使用 global
默认值作用域
- 函数对象把函数的默认值放在函数对象的
__defaults__
属性中,他是元组
# 对比如下两个例子
# 例1
def foo(a=1,b=1):
a += 1
b += 1
print(a)
print(b)
print(id(foo),foo.__defaults__)
foo()
print(id(foo),foo.__defaults__)
foo()
print(id(foo),foo.__defaults__)
--------------
1497519961768 (1, 1)
2
2
1497519961768 (1, 1)
2
2
1497519961768 (1, 1)
# 例2
def foo(a=[], b=1):
a.append(1)
b += 1
print(a)
print(b)
print(id(foo),foo.__defaults__)
foo()
print(id(foo),foo.__defaults__)
foo()
print(id(foo),foo.__defaults__)
----------------
1497519961632 ([], 1)
[1]
2
1497519961632 ([1], 1)
[1, 1] # 注:在函数对象__defaults__属性中保存是a对象的引用
2
1497519961632 ([1, 1], 1)
+
和+=
# 对比如下两个例子
# 例1
def x(a=[]):
a += [5] # 修改原列表,在其后追加一个列表,类似extend方法
print(x.__defaults__)
x()
x()
print(x.__defaults__)
-------------------------
([],)
([5, 5],)
# 例2
def x(a=[]):
a = a + [5] # 合并两个列表并返回一个全新的列表
print(x.__defaults__)
x()
x()
print(x.__defaults__)
--------------------------
([],)
([],)
闭包closure
- 自由变量:未在本地作用域中定义的变量,如定义在内层函数外的外层函数的作用域中的变量
- 闭包:在嵌套函数中,内层函数引用了外层函数的自由变量(内部函数访问外部函数的局部变量)
- 只有涉及嵌套函数时才有自由变量和闭包的问题
# 对比如下两个例子
# 例1
def counter():
c = [0]
def inc():
c[0] += 1 # 不报错,只是修改c的元素,并不是重新定义局部变量c
return c[0]
return inc
foo= counter()
print(foo())
# 例2
def counter():
c = 0
def inc():
c += 1 # 报错,重新定义局部变量做+1运算前未赋值,可用global解决或者nonlocal解决
return c
return inc
foo = counter()
print(foo())
- nonlocal
- 将变量标记为不在本地作用域定义,而是在上级的某一级局部作用域中且非全局作用域
def counter():
c = 0
def inc():
nonlocal c # 声明变量c不是在本地作用域中
c += 1 # 不报错,这样形成了内层函数引用了外部作用域的自由变量,即闭包
return c
return inc
foo = counter()
print(foo())
- 综上:
- 闭包是指函数对象可以引用在函数外部定义的变量,并且在函数内部可以使用这些外部变量
- 在 Python 中,当函数定义在另一个函数内部时,内部函数可以引用外部函数的变量,即使外部函数已经执行完毕,内部函数仍然可以访问这些变量。这种函数和其所引用的外部变量的组合被称为闭包
- 闭包通常用于函数式编程和回调函数中。在函数式编程中,闭包可以用来创建类似于**柯里化(currying)和偏函数(partial function)**的效果。在回调函数中,闭包可以保存一些上下文信息,使得回调函数可以访问到一些在注册回调时不可知的数据
- 闭包可以用于封装数据和行为,保护数据不被外部直接访问和修改。通常情况下,我们可以将闭包看作是一个函数和它所引用的外部变量组成的包裹,可以将它作为一个整体进行操作和传递
# 补充示例
def outer_function(x):
def inner_function(y):
return x + y
return inner_function
closure = outer_function(10)
print(closure(5))
高阶函数
接收函数为参数,或者把函数作为结果返回的函数为高阶函数
柯里化curring
- 将原来接受两个参数的函数变成新的接受一个参数的函数的过程,如 add(4,5) -> add(4)(5)
- 新的函数返回一个以原有第二个参数为参数的函数,类似 t = add(4) 是个函数, t(5) 完成调用
- 用嵌套函数实现
# 将如下函数柯里化如add(4,5)
def add(x,y):
return x + y
# 柯里化效果add(4)(5)
def add(x):
def _add(y):
return x + y
return _add
装饰器decorator
- 装饰器是一种函数或类,它可以接受另一个函数或类作为输入,并返回一个新的函数或类,用于修改或扩展输入函数或类的行为。可以将装饰器看作是一种“包装器”,用于在不改变原始函数或类代码的情况下,为其添加新的功能或行为
- 装饰器是Python语言的一种语法糖,它允许在代码运行期间动态地修改函数或类的行为,可以使用
@functionname
语法糖方式调用 - 无参装饰器和有参装饰器
- 典型例子
# 例1
# 即对logger(fn,*args,*kwargs)柯里化写成嵌套结构
def logger(fn):
def wrapper(*args,**kwargs):
print('begin') #增强函数功能,在函数调用前做些事情
x = fn(*args,**kwargs)
print('end') # 在函数调用后做些事情
return x
return wrapper
# 无参装饰器实际上是一个单形参的函数
@logger #等价于add = logger(add)
def add(x,y):
return x + y
print(add(45,40))
print(add.__name__)
@logger
相当于add = logger(add)
且更加简洁
add.__name__
获取函数元信息(不再是以前的那个add()函数,而是被 wrapper() 函数取代了)。为了解决这个问题,通常使用内置的装饰器@functools.wrap
,它会帮助保留原函数的元信息(也就是将原函数的元信息,拷贝到对应的装饰器函数里)
例子2: 获取函数执行时间
import time
import functools
def timer(func):
print("my decorator start....")
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
ret = func(*args, **kwargs)
end = time.perf_counter()
cost_time = end - start
print("wrapper....")
return ret, cost_time
print("my decorator end.....")
return wrapper
@timer
def add(*args):
time.sleep(1)
result = 0
for i in args:
result += i
return result
@timer
def sub(x, y):
time.sleep(1)
return x - y
print(add(1, 2, 3, 4))
print(sub(10, 11))
print(add.__name__, sub.__name__)
文档字符串
- 在函数语句块的第一行,可使用
__doc__
访问到
def add(x,y):
"""This is a function of addition"""
return x + y
print("name={}\ndoc={}".format(add.__name__, add.__doc__))
print(help(add))
--------------------------------
name=add
doc=This is a function of addition
Help on function add in module __main__:
add(x, y)
This is a function of addition
None
装饰器属性替换问题
- 一个计算函数执行时间的装饰器
import datetime
import time
def logger(fn):
def wrapper(*args, **kwargs):
'''I am wrapper'''
# before 功能增强
print("args={}, kwargs={}".format(args,kwargs))
start = datetime.datetime.now()
# 计算时长
ret = fn(*args, **kwargs)
# after 功能增强
duration = datetime.datetime.now() - start
print("function {} took {}s.".format(fn.__name__, duration.total_seconds()))
return ret
return wrapper
@logger # 相当于add = logger(add)
def add(x, y):
'''This is a function for add'''
print("===call add===========")
time.sleep(1)
return x + y
print(add(4, y=7))
print("name={}, doc={}".format(add.__name__, add.__doc__))
--------------------------------------------------------------
args=(4,), kwargs={'y': 7}
===call add===========
function add took 1.000302s.
11
name=wrapper, doc=I am wrappe #add函数的__name__和__doc__属性被替换,怎么解决?
带参装饰器
- 使用 @functionname(参数列表) 方式调用
# 例1: 自定义一个参数装饰器,控制函数执行次数
import functools
def repeat(num):
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for i in range(num):
func(*args, **kwargs)
return wrapper
return my_decorator
@repeat(3)
def func():
print("hello world")
func()
print(func.__name__)
- 提供一个函数解决函数属性被替换的问题
例2: 不使用functools解决函数属性被替换的问题(利用带参装饰器)
def copy_properties(src):
def _copy(dst):
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
return dst
return _copy
def logger(fn):
@copy_properties(fn) # 带参装饰器wrapper = copy_properties(fn)(wrapper)
def wrapper(*args, **kwargs):
'''I am wrapper'''
start = datetime.datetime.now()
ret = fn(*args, **kwargs)
duration = datetime.datetime.now() - start
print("function {} took {}s.".format(fn.__name__, duration.total_seconds()))
return ret
return wrapper
@logger # 相当于add = logger(add)
def add(x, y):
'''This is a function for add'''
time.sleep(1)
return x + y
print(add(4, y=7))
print("name={}, doc={}".format(add.__name__, add.__doc__))
-----------------------------------------------------------------
function add took 1.000287s.
11
name=add, doc=This is a function for add # 解决了函数属性被替换的问题
类装饰器
class Count:
def __init__(self, func):
self.func = func
self.num_calls = 0
def __call__(self, *args, **kwargs):
self.num_calls += 1
print('num of calls is: {}'.format(self.num_calls))
return self.func(*args, **kwargs)
@Count
def example():
print("hello world")
example()
example()
--------------
num of calls is: 1
hello world
num of calls is: 2
hello world
类也可以作为装饰器。类装饰器主要依赖于函数
__call__()
,每当你调用一个类的示例时,函数__call__()
就会被执行一次
关于__call__
可进一步参考魔法方法
装饰器嵌套
import functools
def my_decorator1(func):
print('1111111111')
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('execute decorator1')
func(*args, **kwargs)
return wrapper
def my_decorator2(func):
print('2222222222')
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('execute decorator2')
func(*args, **kwargs)
return wrapper
# 等价于 greet = my_decorator1(my_decorator2(greet))
@my_decorator1
@my_decorator2
def greet(message):
print(message)
greet('hello world')
-----------------------
2222222222
1111111111
execute decorator1
execute decorator2
hello world
@decorator1 @decorator2 @decorator3 def func(): ...
它的执行顺序从里到外
上面的语句等效于:decorator1(decorator2(decorator3(func)))
- 补充示例
def decorator_outer(func):
print(3, "我是外层装饰器") # 3
def wrapper():
print(5, '外层装饰器,函数运行之前')
func() # decorator_inner 的 wrapper
print(9, '外层装饰器,函数运行之后')
print(4, '外层装饰器闭包初始化完毕') # 4
return wrapper
def decorator_inner(func):
print(1, "我是内层装饰器") # 1
def wrapper():
print(6, '内层装饰器,函数运行之前')
func() # func
print(8, '内层装饰器,函数运行之后')
print(2, '内层装饰器闭包初始化完毕') # 2
return wrapper
@decorator_outer
@decorator_inner # func = decorator_outer(decorator_inner(func))()
def func():
print(7, "我是函数本身")
func()
###################输出结果###################
1 我是内层装饰器
2 内层装饰器闭包初始化完毕
3 我是外层装饰器
4 外层装饰器闭包初始化完毕
5 外层装饰器,函数运行之前
6 内层装饰器,函数运行之前
7 我是函数本身
8 内层装饰器,函数运行之后
9 外层装饰器,函数运行之后
wrapper外面的代码,内层装饰器先运行,外层装饰器后运行。但是wrapper里面的代码,是外层装饰器先开始运行,后运行完毕,内层装饰器后开始运行,先运行完毕
装饰器的作用
装饰器的主要作用是实现代码的复用和简化,它可以让代码更加易读、易维护、易扩展。常见的装饰器有函数装饰器和类装饰器,它们可以用来实现日志记录、性能测试、缓存、认证授权等功能
参考
- magedu
- 一文读懂Python装饰器