在函数内部定义的函数和外部定义的函数是一样的,只是他们无法被外部访问:
def g():
print 'g()...'
def f():
print 'f()...'
return g
将 g 的定义移入函数 f 内部,防止其他代码调用 g:
def f():
print 'f()...'
def g():
print 'g()...'
return g
但是,并不是所有的函数内部的函数都可以移出来:
def calc_sum(lst):
def lazy_sum():
return sum(lst)
return lazy_sum
上面的例子 没法把 lazy_sum 移到 calc_sum 的外部,因为它引用了 calc_sum 的参数 lst。
像这种内层函数引用了外层函数的变量(参数也算变量),然后返回内层函数的情况,称为闭包(Closure)。
闭包的特点是返回的函数还引用了外层函数的局部变量,所以,要正确使用闭包,就要确保引用的局部变量在函数返回后不能变
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()
你可能认为调用f1(),f2()和f3()结果应该是1,4,9,但实际结果全部都是 9(请自己动手验证)。
原因就是当count()函数返回了3个函数时,这3个函数所引用的变量 i 的值已经变成了3。由于f1、f2、f3并没有被调用,所以,此时他们并未计算 ii,当 f1 被调用时:
f1()
9 # 因为f1现在才计算ii,但现在i的值已经变为3
因此,返回函数不要引用任何循环变量,或者后续会发生变化的变量。
修改:
def count():
fs = []
for i in range(1, 4):#以下是正确的闭包
def f(j):#定义函数f(j) j为形参
def g():#定义函数g
return j*j#返回值为j的平方
return g #返回值为函数g
fs.append(f(i))#i每变化一次都往list里面放入一个函数g,且里面的形参至关重要为外函数的i,故每次调用f1()不会因为i已经变为3而出错
return fs
f1, f2, f3 = count()
print f1(),f2(), f3()
或者
def count():
fs = []
for i in range(1, 4):
def f(j):
return lambda:j*j # lambda 为匿名函数,和上面的g 类似,没有参数,因为 f1 f2 f3无参数
fs.append(f(i))
return fs
f1, f2, f3 = count()
print f1(), f2(), f3()
参考: https://www.imooc.com/qadetail/307290
map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])
[1, 4, 9, 16, 25, 36, 49, 64, 81]
通过对比可以看出,匿名函数 lambda x: x * x 实际上就是:
def f(x):
return x * x
关键字lambda 表示匿名函数,冒号前面的 x 表示函数参数。
匿名函数有个限制,就是只能有一个表达式,不写return,返回值就是该表达式的结果。
例子: myabs = lambda x: -x if x < 0 else x
问题:定义了一个函数,想在函数运行时动态增加功能,又不想改动函数本身的代码:例如增加log
方法1:
给函数本身的代码增加print语句
方法2:
通过高阶函数返回新函数
def decorator(f):
def dec2(x,y):
print "i m decorator"
return f(x,y)
return dec2
def add(x,y):
return "add " + str(x) + str(y)
add=decorator(add)
print add(2,3)
def add2(x, y):
return "add2 " + str(x) + str(y)
add2=decorator(add2)
print add2(2,3)
import time
def performance(f):
def timer(*args, **kw):
t1 = time.time()
res = f(*args, **kw)
t2 = time.time()
print 'call %s() in %f' % (f.__name__, (t2 - t1))
return res
return timer
@performance
def factorial(n):
return reduce(lambda x,y: x*y, range(1, n+1))
print factorial(10)
参考: https://www.imooc.com/code/6066 不是很好理解
@log(‘DEBUG’)
def my_func():
pass
把上面的定义翻译成高阶函数的调用,就是:
my_func = log(‘DEBUG’)(my_func)
上面的语句看上去还是比较绕,再展开一下:
log_decorator = log(‘DEBUG’)
my_func = log_decorator(my_func)
上面的语句又相当于:
log_decorator = log(‘DEBUG’)
@log_decorator
def my_func():
pass
所以,带参数的log函数首先返回一个decorator函数,再让这个decorator函数接收my_func并返回新函数:
def log(prefix):
def log_decorator(f):
def wrapper(*args, **kw):
print '[%s] %s()...' % (prefix, f.__name__)
return f(*args, **kw)
return wrapper
return log_decorator
@log('DEBUG')
def test():
pass
print test()
上一节的@performance只能打印秒,请给 @performace 增加一个参数,允许传入’s’或’ms’:
import time
def performance(unit):
def perf_decorator(f):
def wrapper(*args, **kw):
t1 = time.time()
print unit
res = f(*args, **kw)
t2 = time.time()
t = (t2 - t1) * 1000 if unit=='ms' else (t2 - t1)
print 'call %s() in %f %s' % (f.__name__, t, unit)
return res
return wrapper
return perf_decorator
def factorial(n):
return reduce(lambda x,y: x*y, range(1, n+1))
f1 = performance('ms')
factorial = f1(factorial)
print factorial(10)
@performance('ms')
def factorial2(n):
return reduce(lambda x,y: x*y, range(1, n+1))
print factorial2(10)
@decorator可以动态实现函数功能的增加,但是,经过@decorator“改造”后的函数,和原函数相比,除了功能多一点外,有没有其它不同的地方?
def log(f):
def wrapper(*args, **kw):
print 'call...'
return f(*args, **kw)
return wrapper
@log
def f2(x):
pass
print f2.__name__
输出: wrapper
可见,由于decorator返回的新函数函数名已经不是’f2’,而是@log内部定义的’wrapper’。这对于那些依赖函数名的代码就会失效。decorator还改变了函数的__doc__等其它属性。如果要让调用者看不出一个函数经过了@decorator的“改造”,就需要把原函数的一些属性复制到新函数中:
def log(f):
def wrapper(*args, **kw):
print 'call...'
return f(*args, **kw)
wrapper.__name__ = f.__name__
wrapper.__doc__ = f.__doc__
return wrapper
这样写decorator很不方便,因为我们也很难把原函数的所有必要属性都一个一个复制到新函数上,所以Python内置的functools可以用来自动化完成这个“复制”的任务:
import functools
def log(f):
@functools.wraps(f)
def wrapper(*args, **kw):
print 'call...'
return f(*args, **kw)
return wrapper
最后需要指出,由于我们把原函数签名改成了(*args, **kw),因此,无法获得原函数的原始参数信息。
def int2(x, base=2):
return int(x, base)
>>> int2('1000000')
64
>>> int2('1010101')
85
functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2:
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85
所以,functools.partial可以把一个参数多的函数变成一个参数少的新函数,少的参数需要在创建时指定默认值,这样,新函数调用的难度就降低了。
例:在第7节中,我们在sorted这个高阶函数中传入自定义排序函数就可以实现忽略大小写排序。请用functools.partial把这个复杂调用变成一个简单的函数:
import functools
def mycmp(a,b):
return cmp(a.lower(), b.lower())
sorted_ignore_case = functools.partial(sorted, cmp=mycmp)
print sorted_ignore_case(['bob', 'about', 'Zoo', 'Credit'])