Python3 - 函数式编程(高阶函数、闭包、装饰器、偏函数)

函数式编程

  • 将计算视为函数而非指令

  • 纯函数式编程:不需要变量,没有副作用,测试简单

  • 支持高阶函数,代码简介

  • Python支持的函数式编程

    • 不是纯函数编程:允许有变量
    • 支持高阶函数:函数也可以作为变量传入
    • 支持闭包:有了闭包就能返回函数
    • 有限度地支持匿名函数
  • Python中的高阶函数

    • 高阶函数:能接收函数做参数的函数(如内置的map()等)
    >>> def add(x,y,f):
    ...     return f(x) + f(y)
    ...
    >>> add(1,2,abs)
    3
    
    # map(function, iterable, ...)
    # python2 返回列表,py3返回迭代器
    # 第一个参数function以参数序列中的每一个元素调用 function 函数
    def format_name(s):
    	return s.capitalize()   # 首字母大写,其他字母小写
    print(list(map(format_name, ['adam', 'LISA', 'barT'])))
    
    # reduce(function, iterable[, initializer]):
    # 先对集合中的1,2进行functino的运算,在将返回结果以此与后面元素进行计算,最后得到一个结果(第三个参数是初始值)
    # 求积
    def prod(x, y):
    	return x * y 
    
    print reduce(prod, [2, 4, 5, 7, 12])
    
    	# filter(function, iterable)
    	# 用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表。
    	# Pyhton2.7 返回列表,Python3.x 返回迭代器对象
    	# filter()过滤出1~100中平方根是整数的数
    	import math
    	def is_sqr(x):
    		a = int(math.sqrt(int(x)))
    		return a * a == int(x)
    
    	print(list(filter(is_sqr,range(1,101))))
    
    	# sorted()也是一个高阶函数,它可以接收一个比较函数来实现自定义排序
    	# 比较函数的定义是,传入两个待比较的元素 x, y,
    	# 如果 x 应该排在 y 的前面,返回 -1,
    	# 如果 x 应该排在 y 的后面,返回 1,
    	# 如果 x 和 y 相等,返回 0。
    	# 忽略大小写排序
    	def cmp_ignore_case(s1, s2):
    		s1 = s1.lower()
    		s2 = s2.lower()
    		if s1 < s2 :
    			return -1
    		if s1 > s2 :
    			return 1
    		return 0
    	print(sorted(['bob', 'about', 'Zoo', 'Credit'], cmp_ignore_case))
    
  • 内层函数引用了外层函数的变量(参数也算变量),然后返回内层函数的情况,称为闭包

  • 闭包的特点是返回的函数还引用了外层函数的局部变量,所以,要正确使用闭包,就要确保引用的局部变量在函数返回后不能变。举例如下:

    	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并没有被调用,所以,此时他们并未计算 i*i,当 f1 被调用时
    >>> f1()
    9     # 因为f1现在才计算i*i,但现在i的值已经变为3
    
    • 因此,返回函数不要引用任何循环变量,或者后续会发生变化的变量。
    	# 正确写法:输出:1 4 9 
    	def count():
    		fs = []
    		for i in range(1, 4):
    			def f(j):
    				def g():
    					return j * j
    				return g
    			r = f(i)
    			fs.append(r)
    		return fs
    
    	f1, f2, f3 = count()
    	print(f1(), f2(), f3())
    
  • Python匿名函数

    	>>> map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])
    	[1, 4, 9, 16, 25, 36, 49, 64, 81]
    
    # 利用匿名函数简化以下代码:
    
    def is_not_empty(s):
    	return s and len(s.strip()) > 0
    filter(is_not_empty, ['test', None, '', 'str', '  ', 'END'])
    # 结果
    print filter(lambda s:s and len(s.strip()) > 0 , ['test', None, '', 'str', '  ', 'END'])
    
  • Python中decorator装饰器

    • 定义了一个函数,想在运行时动态增加功能,但又不想改动函数本身
    	>>> def f1(x):
    ...     return x * 2
    ...
    >>> def new_fn(f):	# 装饰器函数
    ...     def fn(x):
    ...             print("call"+f.__name__+"()")
    ...             return f(x)
    ...     return fn
    	(1)
    	>>> g = new_fn(f1)
    	>>> g(2)
    	callf1()
    	4
    	(2)
    	>>> f1 = new_fn(f1)	# f1的原始定义被彻底隐藏了
    	>>> f1(2)
    	callf1()
    	4
    
    • Python内置的@语法就是为了简化装饰器的调用
    • 如上述可以简写为:
    >>> @new_fn
    ... def f1(x):
    ...     return x*2
    >>> f1(2)
    callf1()
    4
    
    相当于:
    def f1(x):
    	return x*2
    f1 = new_fn(f1)
    
    # 打印函数调用时间
    # 可获取任意数量参数
    import time
    
    def performance(f):
    	def fn(*args , **kw):
    		st = time.time()
    		r = f(*args , **kw)
    		ed = time.time()
    		print('call %s() in %fs' %(f.__name__,(ed-st)))
    		return r
    	return fn
    
    @performance
    def factorial(n):
    	return reduce(lambda x,y: x*y, range(1, n+1))
    
    print factorial(10)
    
    • 带参数的decorator
    >>> 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():
    ...     return x*2
    ...
    =====相当于=====
    log_decorator = log('DEBUG')
    @log_decorator
    def test(x):
    	return x*2
    ===============
    
    >>> test(2)
    [DEBUG] test()...
    4
    
    import time
    def performance(unit):
    	def fn(f):
    		def wrapper(*args,**kw):
    			st = time.time()
    			r = f(*args,**kw)
    			ed = time.time()
    			t = (ed-st)*1000 if unit == 'ms' else (ed-st)
    			print('call %s() in %f%s' %(f.__name__,t,unit))
    			return r
    		return wrapper
    	return fn
    
    @performance('ms')
    def factorial(n):
    	return reduce(lambda x,y: x*y, range(1, n+1))
    
    print factorial(10)	
    
    • 经过@decorator“改造”后的函数,和原函数相比,除了功能多一点外,还改变了函数的__doc__等其它属性,这对于那些依赖函数名的代码就会失效,如果要让调用者看不出一个函数经过了@decorator的“改造”,就需要把原函数的一些属性复制到新函数中:
      • Python内置的functools可以用来自动化完成这个“复制”的任务
      import functools
      def log(f):
      	@functools.wraps(f)
      	def wrapper(*args, **kw):
      		print 'call...'
      		return f(*args, **kw)
      	return wrapper
      
      • 注意@functools.wraps应该作用在返回的新函数上。
      # 带参数的@decorator
      import time, functools
      
      def performance(unit):
      	def pref_decorator(f):
      		@functools.wraps(f)
      		def wrapper(*args, **kw):
      			t1 = time.time()
      			r = 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 r
      		return wrapper
      	return pref_decorator
      
      @performance('ms')
      def factorial(n):
      	return reduce(lambda x,y: x*y, range(1, n+1))
      
      print factorial.__name__
      
  • Python中的偏函数

    • 当一个函数有很多参数时,调用者就需要提供多个参数。如果减少参数个数,就可以简化调用者的负担。
    >>> int('12345', base=8)
    5349
    >>> int('12345', 16)
    74565
    
    def int2(x, base=2):
    	return int(x, base)
    
    >>> int2('1000000')
    64
    
    • functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2:
    import functools
    int2 = functools.partial(int,base=2)
    >>> int2('1000000')
    64
    
    • functools.partial可以把一个参数多的函数变成一个参数少的新函数少的参数需要在创建时指定默认值,这样,新函数调用的难度就降低了。
    # sorted自定义(忽略大小写升序排序)
    # Python2 写法
    import functools
    sorted_ignore_case = functools.partial(sorted,cmp=lambda s1,s2:cmp(s1.lower(),s2.lower()))
    
    print sorted_ignore_case(['bob', 'about', 'Zoo', 'Credit'])
    # Python3 写法
    >>> def cmp(s1,s2):
    ...     s1 = s1.lower()
    ...     s2 = s2.lower()
    ...     if s1 < s2 :
    ...             return -1
    ...     elif s1 > s2 :
    ...             return 1
    ...     else:
    ...             return 0
    ...
    >>> sorted_ignore_case = functools.partial(sorted,key=(functools.cmp_to_key(cmp)))
    >>> print(sorted_ignore_case(['bob','abount','Zoo','Credit']))
    ['abount', 'bob', 'Credit', 'Zoo']
    	
    # functools.cmp_to_key()将py2的cmp()过渡到py3
    # 或者这样
    sorted_ignore_case = functools.partial(sorted, reverse=Ture, key=lambda x:x.lower()print(sorted_ignore_case(['bob', 'about', 'Zoo', 'Credit']))
    

你可能感兴趣的:(python)