函数的作用域
作用域:指的是标识符作用域,即变量的可见范围
def fn():
x = 10
print(x)
fn()
print(x)
- 上面的print(x)就会报NameError: name 'x' is not defined的错误,因为x的作用域仅在fn函数内,可以理解为在fn函数中开辟一个作用域,仅对作用域可见
作用域的分类
-
全局作用域:整个程序中可见,称为全部变量
-
局部作用域:仅在函数中可见,称为局部变量
# 局部变量
def fn1():
x = 1 # 局部作用域,x为局部变量,使用范围在fn1内
def fn2():
print(x)
print(x)
x = 5 # 全局变量,也在函数外定义
def foo():
print(x)
foo()
- 运行上面代码可知:局部变量无法在函数外访问,全局变量一般可以在函数内部访问
函数嵌套
def out():
def innner():
print(111)
return 111
return innner
out()
inner()
- 运行后会报NameError: name 'inner' is not defined的错误,原因在于:函数内部的函数名也是一个标识符,在函数外部不能访问
嵌套函数的作用域
def outer():
o = 100
def inner():
print(o)
inner()
print(o)
def outer2():
o = 100
def inner():
o = 10
print(o)
inner()
print(o)
- 从结果上来看内部函数可以引用外部定义的变量,如果内部函数定义了一个和外部函数变量名相同的变量,会优先使用函数内部的变量,不会覆盖外部原有的变量
易错点
def fn1():
x = 10
def inner():
print(x)
return inner
a= fn1()
a()
- 正确
def fn1():
x = 10
def inner():
x += 1
print(x)
return inner
a= fn1()
a()
- 错误
def fn1():
x = 10
def inner():
y =x + 1
print(y)
return inner
a= fn1()
a()
- 正确
x=10
def fn():
x += 1
print(x)
fn()
- 错误
第二个和第四个报错的原因是相同的:UnboundLocalError: local variable 'x' referenced before assignment
-
x+=1使用的是局部变量中的x,而内部x并没有定义
-
第二个情况下,在inner()中加上global x 或nonlocal x都可以,global x使用的是全局变量,nonlocal只有函数嵌套的情况下使用,x使用的是外部函数定义的变量x
# x = 100
def fn1():
x = 10
def inner():
# global x 在定义全局变量x的情况下可以使用
nonlocal x # 使用fn1函数下定义的x
x += 1
print(x)
return inner
a= fn1()
a()
- 第四个情况只能使用global x,因为没有函数嵌套的情况
x=10
def fn():
global x
x += 1
print(x)
fn()
闭包
在函数嵌套的情况下,内部函数引用外部函数定义的变量(也称为自由变量)
def outer():
c = [0]
def inner():
c[0] += 1
return c[0]
return inner
- 上面不会报错,个人理解:c[0] += 1,c[0]无法作为标识符,所以不会引起UnboundLocalError的错误,所以先找到标识符c,局部变量没有,会找到外部函数的变量c,修改c[0]的值
def outer():
c = 0
def inner():
c += 1
return c
return inner
- 这个就会引起UnboundLocalError错误,需要加上nonlocal c
a = 5
def fn():
nonlocal a
a += 1
print(a)
fn()
- 上面写法是错误的,因为nonlocal只在函数嵌套中引用外部变量使用
函数的销毁
- 定义一个函数就是生成一个函数对象,函数名指向的就是函数对象。
- 可以使用del语句删除函数,使其引用计数减1。
- 可以使用同名标识符覆盖原有定义,本质上也是使其引用计数减1。
- Python程序结束时,所有对象销毁。
- 函数也是对象,也不例外,是否销毁,还是看引用计数是否减为0。
变量名LEGB
变量查找顺序
-
Local,本地作用域、局部作用域的local命名空间。函数调用时创建,调用结束消亡
-
Enclosing,Python2.2时引入了嵌套函数,实现了闭包,这个就是嵌套函数的外部函数的命名空间
-
Global,全局作用域,即一个模块的命名空间。模块被import时创建,解释器退出时消亡
-
Build-in,内置模块的命名空间,生命周期从python解释器启动时创建到解释器退出时消亡。例如print(open),print和open都是内置的变量
高阶函数
数学概念 y = f(g(x)),在python中:1.至少一个函数作为参数 2.返回一个函数
def counter():
a = 0
def inner():
nonlocal a
a += 1
return a
return inner
f = counter()
print(f())
print(f())
print(f())
- f1=counter()和f2=counter()并不相等,但是f1()==f2(),因为是根据return的值比较的
柯里化
原来接受2个参数的函数变成接受一个参数并返回另一个函数,返回的函数接受第二个参数,比如fn(x,y)转换成fn1(x)(y)
def fn(x,y):
return x+y
# 转换
def f1(x):
def fn2(y):
return x+y
return fn2
装饰器
参考:www.liaoxuefeng.com/wiki/101695… 个人理解是对方法的动态增强,并没有侵入业务代码
无参装饰器
import datetime
def logger(fn):
def wrapper(*args, **kwargs):
print(f"{datetime.datetime.now()} 执行了{fn.__name__}方法")
return fn(*args, **kwargs)
return wrapper
@logger
def add(x, y):
return x + y
print(add(1, 5))
- @logger的意思是add=logger(add) (柯里化),对原来的函数名add重新赋值了,现在add=wrapper函数了,wrapper函数返回原来add(闭包得到)的执行结果,不过执行前print了一条语句增强函数。
有参构造器
import datetime
def logger(do):
def inner(fn):
def wrapper(*args, **kwargs):
print(f"在{datetime.datetime.now()} {do}方法{fn.__name__}")
return fn(*args, **kwargs)
return wrapper
return inner
@logger("execute")
def add(x, y):
return x + y
print(add(1, 9))
- 此时@logger的意思是add=logger("execute")(add)
@wraps装饰器
当函数被装饰器修饰后,一些函数属性发生了变化,比如函数名、函数文档等。这时候可以借助functools中wraps装饰器
import datetime
from functools import wraps
def executeTime(fn):
print("executeTime")
@wraps(fn)
def wrapper(*args, **kwargs):
"""execute wrapper"""
print(f"---{datetime.datetime.now()}")
return fn(*args, **kwargs)
return wrapper
@executeTime
def add(x, y):
"""add doc"""
return x + y
print(add(1, 9), add.__name__, add.__doc__)
- @wraps的实质是返回一个偏函数,调用的是update_wrapper方法,在update_wrapper方法中完成原来函数属性复制
多装饰器修饰一个函数
import datetime
from functools import wraps
def executeTime(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
"""execute wrapper"""
print(f"---{datetime.datetime.now()}")
return fn(*args, **kwargs)
return wrapper
def logger(do):
def inner(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
"""inner wrapper """
print(f"在{datetime.datetime.now()} {do}方法{fn.__name__}")
return fn(*args, **kwargs)
return wrapper
return inner
@logger("execute")
@executeTime
def add(x, y):
"""add doc"""
return x + y
print(add(1, 9), add.__name__, add.__doc__)
-
从上图得到多个装饰器装饰一个函数,从上向下执行
-
无论@logger、@executeTime 这2个装饰器先后顺序如何,最后add函数的属性都是没有被@wraps(fn)装饰的函数属性。原因:coolshell.cn/articles/11… 总结来说:多个装饰器修饰一个函数,只要有一个函数没有被@wraps装饰,最终的结果就是原函数的属性,多个装饰器没有@wraps,就要看先后顺序了
执行流程
from functools import wraps
def logger(fn):
print("enter logger")
@wraps(fn)
def wrapper(*args, **kwargs):
"""wrapper's doc"""
print("enter wrapper ")
return fn(*args, **kwargs)
print(wrapper)
return wrapper
@logger
def add(x, y):
"""add"""
@logger
def sub(x, y):
"""sub"""
-
当函数被@logger装饰,就相当于执行add=logger(add),已经执行了logger函数,在logger函数中遇到@wraps(fn),相当于执行wrapper=wraps(fn)(wrapper),源码wraps(fn)返回一个偏函数。
-
函数调用是单独执行,也就是说add的wrapper函数和sub的wrapper函数并不是同一个
-
如果把@wraps(fn)删掉,add和sub函数的函数属性都会和wrapper函数一样
匿名函数
-
lambda 参数列表: 返回值
-
参数列表可以有多个,但是返回值只能一个,如果需要返回多个值,用容器包装起来
print((lambda x: 0)(1))
# 加法匿名函数,带缺省值
print((lambda x, y=3: x + y)(5))
print((lambda x, y=3: x + y)(5, 6))
# keyword-only参数
print((lambda x, *, y=30: x + y)(5))
print((lambda x, *, y=30: x + y)(5, y=10))
# 可变参数
print((lambda *args: (x for x in args))(*range(5))) # 生成器
print((lambda *args: [x + 1 for x in args])(*range(5))) # 列表
print((lambda *args: {x % 2 for x in args})(*range(5))) # 集合
print((lambda *args: {str(x): x for x in args})(*range(5))) # 字典
print(dict(map(lambda x: (chr(65 + x), 10 - x), range(5)))) # 高阶函数,构建字典
d = dict(map(lambda x: (chr(65 + x), 10 - x), range(5))) # 高阶函数
sorted(d.items(), key=lambda x: x[1])
内部高阶函数
sorted函数
- sorted(iterable, *, key=None, reverse=False) iterable是可迭代对象,key必须函数或callable,reverse是定义正序还是倒叙排序,默认正序排序
l = [5, 4, 3, 2, 1]
print(sorted(l)) # sorted会产生新的列表
print(l) # 还是原来的列表
l.sort()
print(l.sort()) # 列表方法sort()不会返回新列表,所以输出None
print(l) # 因为排序过,l已经是排序后的列表了
-
列表方法sort和sorted用法相同,不过sorted会返回新的列表,sort是修改原来的列表
-
sorted函数不仅可以对列表排序,只要是可迭代对象都可以排序
l1 = (5, 4, 3, 2, 1)
l2 = {5, 4, 3, 2, 1}
print(sorted(l1))
print(sorted(l2))
a = {'c': 1, 'b': 3, 'a': 2}
print(sorted(a))
print(sorted(a.keys()))
print(sorted(a.values()))
print(sorted(a.items()))
print(dict(sorted(a.items(), key=lambda x: x[1]))) # 对value进行排序,返回key value元祖
- 对字典的排序默认是对字典所有的key排序,字典的items排序也是对key排序,返回一个列表,列表元素是key和value的元祖
filter函数
-
filter(function, iterable) function必须返回True或False,如果function定义为None,那么iterable中的元素为False将会过滤
-
filter返回一个迭代器
a = filter(lambda x: x % 3 == 0, range(5))
print(a)
print(next(a))
print(next(a))
print(next(a)) # 将会报错
map函数
-
map(function, iterable, ...) 第一个参数是函数对象,函数对象的参数和后面传入可迭代对象的个数对应,处理是并行处理,当最短的那个可迭代对象用完,终止
-
返回可迭代对象
print(list(map(str, range(5))))
print(dict(map(lambda x, y: (y, x), range(5), ['a', 'b', 'c', 'd'])))
zip函数
-
zip(*iterables) 将多个可迭代对象中的元素一一组成元祖
-
返回可迭代对象
print(list(zip([1, 2, 3], ['a', 'b', 'c'], 'xyz')))
print(dict(zip([1, 2, 3], ['a', 'b', 'c'])))