2.1python高级编程1-函数式编程和装饰器

Posted 逍遥游的编程技术总结

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2.1python高级编程1-函数式编程和装饰器相关的知识,希望对你有一定的参考价值。




2.1、python高级编程1-函数式编程和装饰器

一、函数与函数式编程
1、一等函数(把函数视作对象)2、高阶函数3、匿名函数4、可调用对象和可调用类型(1)、判断可调用对象(2)、自定义可调用类型(3)、函数内省5、获取函数参数信息(1)、方法1(2)、方法26、函数注解7、支持函数式编程的包二、装饰器(注册与回调)1、闭包函数2、python装饰器3、函数装饰器(1)、函数的函数装饰器(2)、类方法的函数装饰器4、类装饰器5、装饰器链6、python装饰器库-functools


一、函数与函数式编程

1、一等函数(把函数视作对象)

在 Python 中,函数是一等对象。编程语言理论家把“一等对象”定义为满足下述条件的程序实体:

  • 在运行时创建

  • 能赋值给变量或数据结构中的元素

  • 能作为参数传给函数

  • 能作为函数的返回结果

说明python 函数是对象:这里创建了一个函数,然后调用它,读取它的 doc 属性,并且确定函数对象本身是 function 类的实例。

 def factorial(n):
   """
  return n!
  """
   return 1 if n < 2 else n * factorial(n-1)

作为函数的返回结果:

 def make_average():
   nums = []
   def average(new_value):
     nums.append(new_value)
     total = sum(nums)
     return total / len(nums)
   return average

2.1、python高级编程1-函数式编程和装饰器

2、高阶函数

接受函数为参数,或者把函数作为结果返回的函数是高阶函数。

如map,filter,reduce,sorted。

map(function, iterable, ...**)** 会根据提供的函数对指定序列做映射。第一个参数 function 以参数序列中的每一个元素调用 function 函数,返回包含每次 function 函数返回值的新列表。

fi**lter(function, iterable)** 接收两个参数,第一个为函数,第二个为序列,序列的每个元素作为参数传递给函数进行判断,然后返回 True 或 False,最后将返回 True 的元素放到新列表中。

reduce(function, iterable[, initializer])函数将一个数据集合(链表,元组等)中的所有数据进行下列操作:用传给 reduce 中的函数 function(有两个参数)先对集合中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 function 函数运算,最后得到一个结果。

sorted(iterable[, cmp[, key[, reverse]]])  排序函数

sorted示例,reverse函数作为参数,逆置的单词排序。

 # map接受factorial函数对num_list进行运算,计算出各个值的阶乘后再使用list组成新的列表
 num_list = [1, 2, 4, 5, 7]
 list(map(factorial, num_list))
 
 result = [factorial(num) for num in num_list]
 result

2.1、python高级编程1-函数式编程和装饰器

sum和reduce思想是把某个操作连续应用到序列的元素上,累计之前的结果,把一系列值规约成一个值。类似的函数还有any和all.

all(iterable)  //如果iterable的每个元素都是真值,返回True,否则返回False

any(iterable)  //只要有iterable的某个元素是真值就返回True。

3、匿名函数

lambda函数定义体中不能赋值,也不能使用while和try等python语句。适合用在参数列表中。(lambda表达式会创建函数对象)

 fruits = ['banana', 'raspberry', 'cherry', 'apple', 'fig', 'strawberry']
 sorted(fruits, key=lambda word: word[::-1]) #逆置的单词排序,代替了sorted示例中的reverse函数,反向看每个单词的字母的大小进行排序
 

2.1、python高级编程1-函数式编程和装饰器

4、可调用对象和可调用类型

(1)、判断可调用对象

可以使用()符号调用的对象即为可调用对象

如下:

用户定义的函数:def语句或lambda函数创建

内置函数:C语言实现的函数,如len或time.strftime

内置方法:C语言实现的方法,如dict.get

方法:类中定义的函数

类:调用类时会调用new方法创建一个实例,然后运行init方法初始化实例返回调用方。

类实例:如果类定义了__call方法,那么它的实例可以作为函数调用。

生成器函数:使用yield关键字的函数或方法。

可以使用callable()函数判断对象是否可以调用。

 >>>[callable(obj) for obj in (abs, str, 13)]
 [True, True, False]
(2)、自定义可调用类型

任何python对象都可以表现得像函数,只要实现call方法。

 import random
 
 class BingoCage:
     def __init__(self, items):
         self._items = list(items)
         random.shuffle(self._items)    #打乱顺序
 
     def pick(self):
         try:
             return self._items.pop()
         except IndexError:
             raise LookupError('pick from empty BingoCage')
 
     def __call__(self, *args, **kwargs):   #实现__call__方法
         return self.pick()
 
 bingo = BingoCage(range(3))
 print(bingo.pick())
 print(bingo())     #调用bingo
 print(callable(bingo))    #判断是否可调用
 
 
 #结果:
 0
 2
 True
(3)、函数内省

函数对象不止有doc属性,还有很多其他属性,可用dir()函数查看

5、获取函数参数信息

(1)、方法1

函数的default属性保存着定位参数和关键字参数的默认值

函数的code包含许多参数信息,如:

code.co_argcount包含参数个数

code.co_varnames包含参数名称  (值得注意的是,这里包含函数体内定义的局部变量,真正的参数名称是前N个字符串,N的个数由co__argcount确定;且其中不包含前缀为*和**的变长参数)

 def clip(text, max_len=20):
     end = None
     if len(text) > max_len:
         space_before = text.rfind(' ', 0, max_len)
         if space_before >= 0:
             end = space_before
         else:
             space_after = text.rfind(' ', max_len)
             if space_after >=0:
                 end = space_after
     if end is None:
         end = len(text)
     return text[:end].rstrip()
 
 print(clip.__defaults__)          #从后往前扫描,故max_len的默认值为20
 print(clip.__code__)
 print(clip.__code__.co_argcount)    #包含局部变量
 print(clip.__code__.co_varnames)      #如结果,实际参数为2

2.1、python高级编程1-函数式编程和装饰器

(2)、方法2

使用inspect模块下的signature()函数,signature()函数返回一个signature对象,它有一个parameters属性,这是一个有序映射,把参数名和inspecr.Parameter对象对应起来。各个Parameter有各自属性:name(名称),default(默认值),kind(类型)  //特殊的inspect._empty代表没有默认值

 from inspect import signature
 
 sig = signature(clip)             #clip为上个示例的函数,参数元组
 print(sig)
 print(str(sig))
 for name, param in sig.parameters.items():
     print(param.kind, ':', name, '=', param.default)
     #打印   参数类型:参数名称:参数默认值

6、函数注解

除了name,kind,default,inspect.Parameter对象还有一个annotation(注解)属性,它的值通常为inspect._empty,但可能包含注解句法提供的函数签名元素据。

函数声明中的各个参数可以在:之后增加注解表达式。如果参数有默认值,注解放在参数名和 = 号之间。如果想注解返回值,在 )和函数声明末尾的:之间添加->和一个表达式,那个表达式可以是任意类型。

为clip函数添加注解:

 def clip(text, max_len:'int > 0'=20) -> str:            #max_len参数注解和返回值注解
     """在max_len前面或后面的第一个空格处截断文本
    """
     end = None
     if len(text) > max_len:
         space_before = text.rfind(' ', 0, max_len)
         if space_before >= 0:
             end = space_before
         else:
             space_after = text.rfind(' ', max_len)
             if space_after >=0:
                 end = space_after
     if end is None:
         end = len(text)
     return text[:end].rstrip()
 
 
 print(clip.__annotations__)
 
 #结果
 {'max_len': 'int > 0', 'return': <class 'str'>}

Python 对注解所做的唯一的事情是,把它们存储在函数的annotations 属性里。仅此而已,Python 不做检查、不做强制、不做验证,什么操作都不做。换句话说,注解对 Python 解释器没有任何意义。注解只是元数据,可以供 IDE、框架和装饰器等工具使用。标准库中还没有什么会用到这些元数据,唯有inspect.signature() 函数知道怎么提取注解,如示例所示:

sig = signature(clip)
print(sig.return_annotation)
for param in sig.parameters.values():
note = repr(param.annotation).ljust(13) #param.annotation获取注解,rerp返回注解的string形式
print(note, ':', param.name, '', param.default)


<class 'str'>
<class 'inspect._empty'> : text <class 'inspect._empty'>
'int > 0' : max_len 20

7、支持函数式编程的包

operator模块为多个算术运算符提供了对应的函数,例如可以使用mul来代替使用匿名函数lambda a,b:a * b

operator还有itemgetter和attrgetter函数。

itemgetter常见用途:根据元组的某个字段给元组列表排序。(itemgetter[i]类似lambda fields:fields[1],创建一个接受集合的函数,返回索引位1上的元素)

from operator import itemgetter

metro_data = [
('tokyo', 'JP', 36),
('mexico', 'IN', 21),
('new york', 'US', 20),
('sao paulo', 'BR', 19),
]

for city in sorted(metro_data, key=itemgetter(1)): #按照元组第二项排序
print(city)
#结果
('sao paulo', 'BR', 19)
('mexico', 'IN', 21)
('tokyo', 'JP', 36)
('new york', 'US', 20)


for city in sorted(metro_data, key=itemgetter(2)): #按照元组第三项排序
print(city)
#结果
('sao paulo', 'BR', 19)
('new york', 'US', 20)
('mexico', 'IN', 21)
('tokyo', 'JP', 36)

多个参数传递给itemgetter,它构建的函数会返回提取的值构成的元组:

cc_name = itemgetter(1, 0)
for city in metro_data:
print(cc_name(city))

#结果
('JP', 'tokyo')
('IN', 'mexico')
('US', 'new york')
('BR', 'sao paulo')

functools.partial冻结参数

functols.partial用于部分应用于一个函数。即:基于一个函数创建一个新的可调用对象,把原函数的某些参数固定。使用这个函数可以把接受一个或多个参数的函数改编成需要回调的API,这样需要的参数更少。

from operator import mul      #乘法
from functools import partial

triple = partial(mul, 3) #把第一个参数固定为3
print(triple(7))
print(list(map(triple, range(1, 10))))

21
[3, 6, 9, 12, 15, 18, 21, 24, 27]

functools.partialmethod函数作业于partial一样,不过是用于处理方法。

以上来自《流畅的python》第五章

二、装饰器(注册与回调)

1、闭包函数

  在看装饰器之前,我们先来搞清楚什么是闭包函数。python是一种面向对象的编程语言,在python中一切皆对象,这样就使得变量所拥有的属性,函数也同样拥有。这样我们就可以理解在函数内创建一个函数的行为是完全合法的。这种函数被叫做内嵌函数,这种函数只可以在外部函数的作用域内被正常调用,在外部函数的作用域之外调用会报错

 def outfunc():
   print("1 outside func")
   def infunc():
     print("2 inside func")
   infunc()
   
 outfunc()
 infunc()

而如果内部函数里引用了外部函数里定义的对象(甚至是外层之外,但不是全局变量),那么此时内部函数就被称为闭包函数(知道Lua的应该记得Lua也有闭包函数,以此可以推断是不是很多脚本语言都有闭包)。闭包函数所引用的外部定义的变量被叫做自由变量。闭包从语法上看非常简单,但是却有强大的作用。闭包可以将其自己的代码和作用域以及外部函数的作用结合在一起。下面给出一个简单的闭包的例子:

 def count():
   a = 1
   b = 1
   def sum():
     c = 1
     return a + c  #a就是自由变量
   return sum

总结:什么函数可以被称为闭包函数呢?主要是满足两点:函数内部定义的函数;引用了外部变量但非全局变量。

2、python装饰器

有了闭包函数的概念,我们再去理解装饰器会相对容易一些。python装饰器本质上就是一个函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外的功能,装饰器的返回值也是一个函数对象(函数的指针)。装饰器函数的外部函数传入我要装饰的函数名字,返回经过修饰后函数的名字;内层函数(闭包)负责修饰被修饰函数。从上面这段描述中我们需要记住装饰器的几点属性,以便后面能更好的理解:

    实质:是一个函数

    参数:是你要装饰的函数名(并非函数调用

    返回:是装饰完的函数名(也非函数调用

    作用:为已经存在的对象添加额外的功能

    特点:不需要对对象做任何的代码上的变动

python装饰器有很多经典的应用场景,比如:插入日志、性能测试、事务处理、权限校验等。装饰器是解决这类问题的绝佳设计。并且从引入中的列子中我们也可以归纳出:装饰器最大的作用就是对于我们已经写好的程序,我们可以抽离出一些雷同的代码组建多个特定功能的装饰器,这样我们就可以针对不同的需求去使用特定的装饰器,这时因为源码去除了大量泛化的内容而使得源码具有更加清晰的逻辑

3、函数装饰器

(1)、函数的函数装饰器
 import time
 
 def decorator(func):
   def wrapper(*args, **kwargs):
     start_time = time.time()
     func()
     end_time = time.time()
     print(end_time - start_time)
     
   return wrapper
 
 @decorator
 def func():
   time.sleep(0.8)
   
 func()

在上面代码中 func是我要装饰器的函数,我想用装饰器显示func函数运行的时间。@decorator这个语法相当于 执行 func = decorator(func),为func函数装饰并返回。在来看一下我们的装饰器函数 - decorator,该函数的传入参数是func (被装饰函数),返回参数是内层函数。这里的内层函数-wrapper,其实就相当于闭包函数,它起到装饰给定函数的作用,wrapper参数为args, **kwargs。args表示的参数以列表的形式传入;**kwargs表示的参数以字典的形式传入(这个在我们上节已经说过了,这里就不多说了)。其实就是c/c++中的回调函数和实例化函数指针以及调用回调函数的一个过程。

(2)、类方法的函数装饰器

类方法的函数装饰器和函数的函数装饰器类似,只是将实例化函数指针和写回调函数的过程封装到一个类中而已。

 import time
 
 def decorator(func):
     def wrapper(me_instance):
         start_time = time.time()
         func(me_instance)
         end_time = time.time()
         print(end_time - start_time)
     return wrapper
 
 class Method(object):
     @decorator
     def func(self):
         time.sleep(0.8)
 
 p1 = Method()
 p1.func() # 函数调用

对于类方法来说,都会有一个默认的参数self,它实际表示的是类的一个实例,所以在装饰器的内部函数wrapper也要传入一个参数 - me_instance就表示将类的实例p1传给wrapper,其他的用法都和函数装饰器相同。

4、类装饰器

前面我们提到的都是让 函数作为装饰器去装饰其他的函数或者方法,那么可不可以让 一个类发挥装饰器的作用呢?答案肯定是可以的,一切皆对象嚒,函数和类本质没有什么不一样。类的装饰器是什么样子的呢?

 class Decorator(object):
     def __init__(self, f):
         self.f = f
     def __call__(self):
         print("decorator start")
         self.f()
         print("decorator end")
 
 @Decorator
 def func():
     print("func")
 
 func()

这里有注意的是:__call__()是一个特殊方法,它可将一个类实例变成一个可调用对象:

 p = Decorator(func) # p是类Decorator的一个实例
 p() # 实现了__call__()方法后,p可以被调用

要使用类装饰器必须实现类中的__call__()方法,就相当于将实例变成了一个方法,即利用类在创建对象的时候调用__init__时传入func函数,然后利用调用类对象的时候会默认调用__call__方法的特性事先在其中调用回调函数即可。

5、装饰器链

一个python函数也可以被多个装饰器修饰,要是有多个装饰器时,这些装饰器的执行顺序是怎么样的呢?

 def makebold(f):
   return lambda: "<b>" + f() + "</b>"
 
 def makeitalic(f):
   return lambda: "<i>" + f() + "</i>"
 
 @makebold
 @makeitalic
 def say():
   return "Hello"
 
 print(say())

可见,多个装饰器的执行顺序:是从近到远依次执行。

6、python装饰器库-functools

 def decorator(func):
     def inner_function():
         pass
     return inner_function
 
 @decorator
 def func():
     pass
 
 print(func.__name__)
 
 # 输出:inner_function

上述代码最后执行的结果不是 func,而是 inner_function!这表示被装饰函数自身的信息丢失了!怎么才能避免这种问题的发生呢?

可以借助functools.wraps()函数:

 from functools import wraps
 def decorator(func):
     @wraps(func)
     def inner_function():
         pass
     return inner_function
 
 @decorator
 def func():
     pass
 
 print(func.__name__)
 
 #输出:func


部分内容参考自:

https://www.cnblogs.com/lht-record/p/10241966.html

https://www.cnblogs.com/lianyingteng/p/7743876.html


以上是关于2.1python高级编程1-函数式编程和装饰器的主要内容,如果未能解决你的问题,请参考以下文章

12 - 函数式编程

python函数式编程之装饰器

Python 函数式编程装饰器以及一些相关概念简介

python基本知识:函数式编程,装饰器

Python 函数式编程: 匿名函数高阶函数装饰器

Python实用笔记 (15)函数式编程——装饰器