返回函数
不仅可以返回多个 值 ,还可以返回 函数
例:
def lazy_sum(*args) #参数是未知个数的
def sum() #在函数里面定义求和函数
ax = 0 #初值 为0
for n in args: #把agss的数遍历一遍
ax = ax + n #所有 数 相加
return ax #返回求和的值
return sum #返回求和函数
调用:
f = lazy_sum(1, 3, 5, 7, 9)
h = f()
print(h)
调用时返回的不是结果 而是一个函数,还得再调用一次才能 产生结果。
在lazy_sum中 又定义sum,内部函数sum就可以引用 外部函数lazy _sum的参数和局部变量。当lazy_sum返回函数sum 时,相关参数和变量都保存在返回的函数中,这种称为“闭包”的程序结构拥有极大的威力。
当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:
>>> f1 = lazy_sum(1, 3, 5, 7, 9)
>>> f2 = lazy_sum(1, 3, 5, 7, 9)
>>> f1==f2
False
并且f1()和f2()的调用结果互不影响。
闭包
lazy_sum返回的函数在其定义内部引用了局部变量 args,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来不容易。
另:返回的函数不会立即执行,直到调用了f()才执行
例:
def count(): #函数名
fs = [] #list
for i in range(1, 4): #循环3次
def f(): #再定义一个函数
return i*i #返回的 是i的乘积,注意这里 内部函数可以调用外部函数的变量
fs.append(f) #把f函数放到fs这个list中去
return fs #返回结果集
#调用
f1, f2, f3 = count()
>>> f1()
9
>>> f2()
9
>>> f3()
9
惊了,这三个的结果都是9而不是1,4,9,居然不是结果集。
原因是返回的函数引用了变量i,但它并非立即执行。等到3个函数都 返回时,它们所引用的变量i已经变成了 3,因此最终结果是 9。
- 返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
如果一定要引用循环变量,就再创建一个函数,用该函数的参数绑定循环变量当前的值,这样无论循环变量如何更改 ,已绑定到函数参数 的 值 不变。
def count():
def f(j): #用以绑定循环变量
def g():
return j*j
return g
fs = [] #结果集 list
for i in range(1, 4):
fs.append(f(i)) #绑定着循环变量添加
return fs
#调用
f1, f2, f3 = count()
h = f1()
y = f2()
z = f3()
print(h)
print(y, z)
结果
利用闭包返回一个计数器函数,每次调用它返回递增整数:
#对
def createCounter():
n = 0
def counter():
nonlocal n
n += 1
return n
return counter
#不对
n = 0
def createCounter():
def counter():
global n
n += 1
return n
return counter
#不对
def createCounter():
n = 0
def counter(n):
def g():
return n
return g
fs = []
fs.append(counter(n))
n += 1
return fs
#第一点 global不是这么用的 global是说 上面已经有个n了 想强调一下这两个是同一个n
#第二 这个计数器并不是返回多个函数 所以说 不是模仿刚才的那个f1f2f3
counterA = createCounter()
print(counterA(), counterA(), counterA(), counterA(), counterA()) # 1 2 3 4 5
counterB = createCounter()
if [counterB(), counterB(), counterB(), counterB()] == [1, 2, 3, 4]:
print('测试通过!')
else:
print('测试失败!')
烦人,写的不对
匿名函数
在传入函数时,不显式 的定义函数,而是 直接传入匿名函数。
比如 关键字 lambda 冒号前是参数,冒号后是内容
例: list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
匿名函数不用写return,也没有名字,不用担心函数名冲突,也可以把匿名 函数赋值给 一个变量,再利用 变量来调用函数。
例:
>>> f = lambda x: x * x
>>> f
<function <lambda> at 0x101c6ef28>
>>> f(5)
25
也可以把匿名函数作为返回值返回,比如:
def build(x, y):
return lambda: x * x + y * y
装饰器
**在代码运行期间动态增加功能的方式称之为decorator装饰器。**接受函数作为参数,并返回一个函数
由于函数也是一个对象可以赋值给变量。
>>> def now():
... print('2015-3-25')
...
>>> f = now
>>> f()
2015-3-25
这就起了别名,函数 对象有__name__
属性,通过f.__name__
可以得到函数本名。
比如要增强now()的功能,在函数调用前后自动打印日志,但又不希望修改 now()函数的定义。
def log(func) #参数是一个函数
def wrpapper(*args, **kw): #参数是未知个变量的list和kv对
print('call %s():' % func.__name__) #输出func的函数名
return func(*args, **kw) #返回func的参数
return wrapper #返回内置函数
因为它是一个decorator,所以接受一个函数 作为参数,并 返回一个函数。我们要借助python的@语法,把decorator置于函数的定义处:
@log
def now():
print('2015-3-25')
调用now()函数,不仅会运行now ()函数本身,还会在 运行now()函数前打印一行日志:
#调用
now()
#控制台
call now():
2015-3-25
把@log放到 now()函数的定义处就相当于在now执行前执行了 一句now = log(now)
。
由于log()是一个decorator,返回一个函数,所以原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,也就是log返回的那个函数wrapper() ,于是调用now将执行新函数wrapper()。
wrapper()函数的 参数定义是(*args,**kw),可以接受任意参数的调用。在wrapper()内 ,首先打印日志,在紧接着调用原始函数func(),注意这里 的 func是刚刚的参数变量,也就是now,等于又调用了 now。
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数。
例:自定义log 的文本
def log(text): #第一层,参数是文本
def decorator(func): #第二层,参数是
def wrapper(*args, **kw):
print('%s : %s()' % (text, func.__name__)) #输出自定义文本和函数的本名
return func(*args, **kw) #调用这个函数
return wrapper
return decorator
@log('execute') #相当于now = log('execute')(now)
def now():
print('2015-3-25')
now()
print(now.__name__)
结果:
分析:首先执行log(‘execute’),返回的是decorator函数,再调用decorator ,参数func是now,返回wrapper函数,调用 wrapper函数执行输出语句,输出execute和now的
__name__
,在经过 decorator装饰之后的函数,它们的__name__
已经 从原来 的‘now‘变成了‘wrapper’。
函数wrapper()的名字是‘wrapper’,函数now()的名字也是‘wrapper’了,这样有些依赖函数签名的代码就会出错,需要把原始函数的__name__
等属性复制到wrapper()函数中。
不用写wrapper.__name__=func.__name__
,用python的内置函数functools.wraps即可
故,完整的decorator装饰器如下
import functools
def log(func):
@functools.wraps(func) #相当于 wrapper = functools.wraps(wrapper) 把now的名字保留下来
def wrapper(*args,**kw):
print('call: %s' % func.__name__)
return func(*args, **kw)
return wrapper
@log
def now():
print('2020.6.28')
now()
print(now.__name__)
结果:
只用了一句@functools.wraps(func)
就让now的名字复制到函数内了
或者是带参的装饰器也是一样的
import functools
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print(' %s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
@log('execute')
def now():
print('2020.6.28')
now()
print(now.__name__)
结果:
请设计一个decorator,它可作用于任何函数上,并打印该函数的执行时间:
#乱找的 测试没有通过 但是也能实现功能
def metric(fn):
def decorator(*args, **kw):
t1 = time.time()
a = fn(*args, **kw)
t2 = time.time()
print('%s executed in %s ms' % (fn.__name__, t2 - t1))
return a
return decorator
偏函数
python的functools模块提供了 很多有用的功能 ,其中一个就是偏函数partial function。
函数可以通过设定参数的默认值,可以降低函数调用的难度,偏函数也同样可以。
例:int()把字符串转换成十进制,但还提供额外的base参数,默认值为10。要是设定为其他值,就做那个值进制的转换。
>>> int('12345', base=8)
5349
>>> int('12345', 16)
74565
而在大量转换特定进制时很麻烦,可以设置一个函数来简化。
例:
def int2(x, base=2):
return int(x, base)
functools.partial的作用就是 把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。
例:int2 = functools.partial(int, base=2)
相当于def int2(x, base=2): return int(x, base)
,也相当于上面那个函数。
创建偏函数时可以接收函数对象、*args
和 **kw
这三个参数。
当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。