在开始本节之前,首先介绍下偏函数partial。首先借助help来看下partial的定义

python中partial python中partial可变长参数_偏函数

首先来说下第一行解释的意思:

partial 一共有三个部分:

(1)第一部分也就是第一个参数,是一个函数,这个函数可以是你定义的,也可以是Python内置函数

(2)第二部分是一个可变参数,*args,比如内置函数max的参数就是一个可变参数,max(1,2,3,4,5)=5

(3)第三部分是一个关键字参数,比如内置函数int的第二个参数就是命名关键字参数,默认base=10,表示int转换时默认是10进制的:

partial函数的作用就是:将所作用的函数作为partial()函数的第一个参数,原函数的各个参数依次作为partial()函数的后续参数,原函数有关键字参数的一定要带上关键字,没有的话,按原有参数顺序进行补充。

 

偏函数的使用

 

A、偏函数的第二个部分(可变参数),按原有函数的参数顺序进行补充,参数将作用在原函数上,最后偏函数返回一个新函数(类似于,装饰器decorator,对于函数进行二次包装,产生特殊效果;但又不同于装饰器,偏函数产生了一个新函数,而装饰器,可改变被装饰函数的函数入口地址也可以不影响原函数)

 

 

 

案例:我们定义一个sum函数,参数为*args可变,计算这些可变参数的和。

 

扩展:我们想要对sum函数求和后的结果,再加上10加上20甚至加更多,得到一个新的结果

 

实现:我们分别用decorator和partial来实现,对比一下二者的区别

(一)装饰器 decorator 实现

from functools import wraps

 

def sum_add(*args1): #我们要给我们的装饰器decorator,带上参数
    def decorator(func):
        @wraps(func) #加上这句,原函数func被decorator作用后,函数性质不变
        def my_sum(*args2): #注意,参数要和原函数保持一致,真正实行扩展功能的是外层的装饰器
            my_s = 0
            for n in args1:
                my_s = my_s +n #这个是我们新加的求和结果
            return func(*args2) + my_s #这个,我们在原求和函数的结果上再加上s,并返回这个值
        return my_sum #返回my_sum函数,该函数扩展原函数的功能
    return decorator  #返回我们的装饰器
 
@sum_add(10,20) #启用装饰器 对sum函数进行功能扩展
def sum(*args):
    s = 0
    for n in args:
        s = s+n
    return s
print(sum(1,2,3,4,5))
print(sum.__name__)

 

sum最后返回的值应该是10+20+15 = 45,这样一来,我们的decorator就实现了我们想要的扩展功能,最后,发现,原函数sum的name属性,仍然是sum,说明,这种装饰扩展功能,不影响我们的原函数:

(二)偏函数 partial function 实现

A:普通函数可变参数顺序执行

1. def sum(*args):
2.     s = 0
3.     for n in args:
4.         s = s + n
5.     return s
6. print(sum(10,20)+sum(1,2,3,4,5))

我们如果想实现+10+20的效果,必须写两遍sum,这样写,显然是最易懂的,但是,却显得很邋遢

B:普通函数可变参数加关键字参数组合

def sum(*args,**others):
    s = 0
    for n in args:
        s = s + n
    s1 = 0
    for k in others:
        s1 = s1 + others[k] #我们还要算一下,关键字参数里蕴藏的求和结果,k是dict中的关键字key
    return s+s1 #最终,我们实现扩展功能,顺序参数和关键字参数结果相加
   
D= {'value1':10,'value2':20}
print(sum(1,2,3,4,5,**D))

代码看起来,是显得专业了,但是感觉冗余,没必要

 

C:偏函数可变参数顺序填充一步到位

from  functools import partial
 
def sum(*args):
    s = 0
    for n in args:
        s = s + n
    return s
 
sum_add_10    = partial(sum,10)    #10 作用在sum第一个参数的位置
sum_add_10_20 = partial(sum,10,20)  #10 20 分别作用在sum第一个和第二个参数的位置
print('A____________我们看下原函数sum的函数地址入口:')
print(sum)
print('B______我们看下partial函数返回函数的地址入口:')
print(partial(sum,10))
print(sum_add_10(1,2,3,4,5))    # --> 10 + 1 + 2 + 3 + 4 + 5 = 25
print(sum_add_10_20(1,2,3,4,5)) # --> 10 + 20 + 1 + 2 + 3 + 4 + 5 = 45

可以看出,我们针对sum函数的求和结果,再加上10,或者加10加20,甚至加更多,都是可以通过偏函数来实现的,注意偏函数的第二部分,参数是可变的,是按顺序走的,因此,偏函数产生的新函数,sum_add_10 实际上等同于sum(10,*args):

 

 

下面来看可自定义属性的装饰器

from functools import wraps,partial
import logging
def attach_wrapper(obj,func=None):
    if func is None:
        return partial(attach_wrapper,obj)
    setattr(obj,func.__name__,func)
    return func
 
def logged(level,name=None,message=None):
    def decorate(func):
        logname=name if name else func.__module__
        log=logging.getLogger(logname)
        logmsg=message if message else func.__name__
        @wraps(func)
        def wrapper(*args,**kwargs):
            log.log(level,logmsg)
            return func(*args,**kwargs)
        @attach_wrapper(wrapper)
        def set_level(newlevel):
            nonlocal level
            level=newlevel
        @attach_wrapper(wrapper)
        def set_message(newmsg):
            nonlocal logmsg
            logmsg=newmsg
        return wrapper
    return decorate
 
@logged(logging.DEBUG)
def add(x,y):
    return x+y
 
@logged(logging.CRITICAL,'example')
def spam():
print("Spam!")

 

首先用attach_wrapper来装饰set_level以及set_message。在调用的时候首先是attach_wrapper(wrapper)(set_level)。初次调用的时候由于func为空,因此调用partial(attach_wrapper,obj),将obj也就是wrapper函数作为第一个参数,并返回一个新的attach_wrapper函数,第一个参数是wrapper函数实例,再次调用的时候就变成attach_wrapper(wrapper,set_level). 在这次调用中set_level被设置为wrapper的属性。setattr(obj,func.__name__,func)也就等于wrapper.set_level=set_level

通过这种方式将set_message和set_level设置成了wrapper的方法。

在set_message和set_level中引用了nonlocal来修改内部变量。这样就可以控制logging的等级以及输出信息。

if __name__=="__main__":
    logging.basicConfig(level=logging.DEBUG)
    add(2,3)
    add.set_message('Add called')
    add(2,3)
    add.set_level(logging.WARNING)
    add(2,3)
运行结果如下:
DEBUG:__main__:add
DEBUG:__main__:Add called
WARNING:__main__:Add called