抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

[toc]

python高阶函数和装饰器一

  • First Class Object;

1.函数在python中是一等公民;
2.函数也是对象,可调用的对象;
3.函数可以作为普通变量、参数、返回值等;

  • 高阶函数

1.数据概念y=g(f(x));
2.在数学和计算机科学中,高阶函数应当是至少满足下面一个条件的函数;

接受一个多个函数作为参数;
输出一个函数;

def counter(base):
    def inc(step=1):
        nonlocal base
        base = base + step
        return base
    return inc
foo = counter(10)
foo1 = counter(10)
print(foo())
print(foo1())

自定义sort 函数

  • 排序问题

1.仿照内建函数sorted, 请自行实现一个sort函数(不使用内建函数),能够为列表元素排序;

  • 思路

1.内建函数sorted函数是一个返回一个新的列表, 可以设置升序或降序,也可以设置一个 排序的函数;自定义的sort函数也要实现这个功能;
2.新建一个列表, 遍历原列表,和新列表的值依次比较决定如何插入新列表中;

  • 思考
    sorted 函数的实现原理,扩展到map、 filter函数的实现原理;
def sort(iterable,key=lambda a,b:a<b, reverse=False):
    ret = []
    for x in iterable:
        for i,y in enumerate(ret):
            flag = key(x, y) if reverse else key(y,x)
            if flag:
                ret.insert(i,x)
                break
        else:
            ret.append(x)
    return ret
lst = [1,2,3,11,5,6,4,8,9]
print(sort(lst,reverse=True))
# 输出
[1, 2, 3, 4, 5, 6, 8, 9, 11]

内建函数-高阶函数

  • sorted(iterable[,key][,reverse]) 排序
  • filter(function, iterable) --> filter objcet 过滤数据
  • map(func, *iterables) --> map object 映射

sorted(iterable[, key][, reverse]) 排序
返回一个新的列表,对一个可迭代对象的所有元素排序,排序规则为key定义的函数,reverse表是否排序翻转
sorted(lst,key=lambda x:6-x) # 返回新列表
lst.sort(key=lambda x:6-x) # 就地修改
filter(function, iterable)

  • 过滤可迭代对象的元素,返回一个迭代器;
  • function一个具有一个参数的函数,返回bool;
  • 例如,过滤出数列中能被3整除的数字;

list(filter(lambda x: x%3 == 0,[1,9,55,150,-3,78,28,123]))
map(function, *iterables) --> map object

  • 对多个可迭代对象的元素按照指定的函数进行映射,返回一个迭代器;
  • list(map(lambda x:2*x+1,range(5)))
  • dict(map(lambda x:(x%5,x), range(500)))

柯里化(Currying)

  • 柯里化
    指的是将原来接受两个参数的函数变成新的一接受一个参数的过程。新的函数返回一个以原有第二个参数的函数;
    z = f(x,y) 转换成z= f(x)(y)的形式

  • 举例
    将加法函数柯里化

def add(x,y):
    return x +y
In [1]: def add(x):
   ...:     def _add(y):
   ...:         return x + y
   ...:     return _add
In [2]: foo = add(4)
In [3]: print(foo(5))
9
In [4]: print(add(4)(5))
9

通过嵌套函数就可以把函数转换成柯里化函数

Python 装饰器

  • 需求

一个加法函数,想增强它的功能,能够输出被调用过以及调用的参数信息;

In [5]: def add(x,y):
   ...:     return x + y

增加信息输出功能

In [6]: def add(x,y):
   ...:     print("calladd, x+y") # 日志输出到控制台
   ...:     return x+y

上面的加法函数是完成了需求,但是有以下的缺点

  • 打印语句的耦合太高;
  • 加法函数属于业务功能,而输出信息的功能,属于非业务功能代码,不该放在业务函数加法中;
In [10]: def add(x,y):
    ...:         print("call {},{}+{}".format(add.__name__,x,y))
    ...:         return x + y
    ...: add(4,5)
    ...:
call add,4+5
Out[10]: 9

做到了业务功能分离,但是fn函数调用传参是个问题

def add(x,y):
    return x + y
def logger(fn):
    print('begin')
    x =fn(4,5)
    print('end')
    return x
print(logger(add))
begin
end
9
def add2(x,y,z):
    return x+y+z

def logger(fn,*args,**kwargs):
    print('before')
    ret = fn(*args,**kwargs)
    print('after')
    return ret
print(logger(add2,4,z=5,y=6))
def add1(x,y):
    return x + y

def add2(x,y,z):
    return x+y+z

def logger(fn,*args,**kwargs):
    def __logger(*args,**kwargs):
        print('before')
        ret = fn(*args,**kwargs)
        print('after')
        return ret
    return __logger


foo = logger(add1)
print(foo(40,10))
print(logger(add1)(100,500))
# 输出
before
after
50
before
after
600

改进: 引入装饰器

  • 装饰器语法糖
def logger(fn):
    def __logger(*args,**kwargs):
        print('before')
        ret = fn(*args,**kwargs)
        print('after')
        return ret
    return __logger
@logger  # 相当于 add1 = logger(add1)
def add1(x,y):
    return x + y
print(add1(100,1000))
# 输出
before
after
1100

写个装饰器,它就会把下面的的函数名提取出来,传给这个名称;

  • 装饰器(无参)

它是一个函数
函数作为它的形参
返回值也是一个函数@functionname方式,简化调用

  • 装饰器和高阶函数

装饰器是高阶函数,但装饰器是对传函数的功能的装饰(功能增强)

例子:

import datetime
import time

def logger(fn):
    def wrap(*args,**kwargs):
        print("*args={},kwargs={}".format(args,kwargs))
        start = datetime.datetime.now()
        ret = fn(*args,**kwargs)
        # after 功能增强
        duration = datetime.datetime.now() - start
        print("function {} took {}s.".format(fn.__name__,duration.total_seconds()))
        return ret
    return wrap

@logger # 相当于 add = logger(add)
def add(x,y):
    print("====call add ========")
    time.sleep(2)
    return x + y

print(add(4,y=7))
# 输出结果
====call add ========
function add took 2.00511s.
11
  • 装饰器函数
  • 前置功能增强
  • 被增强函数
  • 后置功能增强

文档字符串

  • Python 的文档
  1. Python 是文档字符串的Documentation Strings;
  2. 在函数语句块的第一行,且习惯是多行的文本,所以多使用三引导;
  3. 惯例是首字母大写,第一行写概述,空一行,第三行写详细描述;
  4. 可以使用特殊属性doc访问这个文档;
def add1(x,y):
    '''This is a function

    return int
    x int
    y int
    '''
    ret = x + y
    return ret

add1(4,1000)
print(add1.__name__,add1.__doc__,sep='\n')
# 输出 
add1
This is a function

    return int
    x int
    y int

注: print(help(add1)) 函数本身就是调的__doc__

Help on function add1 in module __main__:
add1(x, y)
    This is a function
    return int
    x int
    y int
None
#!/usr/bin/env python

def copy_properties(src,dst):
    dst.__name__ = src.__name__
    dst.__doc__ = src.__doc__
    dst.__qualname__ = src.__qualname__

def logger(fn):

    def wrapper(*args,**kwargs):
        '''This is a wrapper'''
        print('before')
        ret = fn(*args,**kwargs)
        print('after')
        return ret

    copy_properties(fn,wrapper)
    return wrapper

@logger
def add(x,y):
    '''
    This is a function

    return int
    x int
    y int
    '''
    ret = x + y
    return ret

print(add.__name__,add.__doc__,add.__qualname__,sep='\n')
# 输出
add

    This is a function

    return int
    x int
    y int

add
  • 装饰器副作用

原函数对象的属性都被替换了,而使用装饰器,我们的需求是查看被封装函数的属性;

def copy_properties(src):
    def _copy(dst):
        dst.__name__ = src.__name__
        dst.__doc__ = src.__name__
        dst.__qualname__ = src.__qualname__
        return dst
    return _copy

def logger(fn):
    @copy_properties(fn) # @ copy => wrapper = _copy(wrapper)
    def wrapper(*args,**kwargs):
        '''This is a wrapper'''
        print('before')
        ret = fn(*args,**kwargs)
        print('after')
        return ret
    return wrapper
@logger
def add(x,y):
    '''
    This is a function
    return int
    x int
    y int
    '''
    ret = x + y
    return ret
print(add.__name__,add.__doc__,add.__qualname__,sep='\n')
# 输出
add add add
  • python 提供一个函数,被封装函数属性 == copy ==> 包装函数属性;
  • 能过copy_properties函数将被包装函数的属性覆盖掉包装函数;
  • 凡是被装饰的函数都城要复制这些属性,这个函数很通用;
  • 可以将复制属性的函数构建成装饰器函数,带参装饰器;

带参装饰器

import datetime
import time

def logger(t):
    def __logger(fn):
        def wrap(*args,**kwargs):

            start = datetime.datetime.now()
            ret = fn(*args,**kwargs)
            duration = (datetime.datetime.now() - start).total_seconds()
            if duration > t:
                print("function {} took {} s.".format(fn.__name__,duration))
            return ret
        return wrap
    return __logger

@logger(3)
def add(x,y):
    print("===call add===")
    time.sleep(5)
    return x + y
print(add(4, y=7))
# 输出
===call add===
function add took 5.001952 s.
11
  • 带参装饰器
  1. 它是一个函数;
  2. 函数作为它的的形参;
  3. 返回值是一个不带参的装饰器函数;
  4. 使用@functionname(参数列表)方式调用;
  5. 可以看做在装饰器外层又加了一层函数;
  • 将记录的功能提取出来,这样就可以能过外部提供的函数来灵我没有的控制输出
  • 再次改造
import datetime
import time

def copy_properties(src):
    def _copy(dst):
        dst.__name__ = src.__name__
        dst.__doc__ = src.__name__
        dst.__qualname__ = src.__qualname__
        return dst
    return _copy

def logger(duration,func=lambda name,duration: print("{} took {}s".format(name,duration))):
    def _logger(fn):
        @copy_properties(fn)
        def wrapper(*args,**kwargs):
            start = datetime.datetime.now()
            ret = fn(*args,**kwargs)
            delta = (datetime.datetime.now() - start).total_seconds()
            if delta > duration:
                func(fn.__name__,duration)
            return ret
        return wrapper
    return _logger

@logger(3)
def add(x,y):
    print("===call add===")
    time.sleep(5)
    return x + y
print(add(4, y=7))
# 输出
===call add===
add took 3s
11

functools 模块

  • functools.update_wrapper(wrapper,wrapped, assigned=WRAPPER_ASSGNMENTS, update=WRAPPER_UPDATES)
  1. 类似copy_properties 功能;
  2. wrapper 包装函数,wrapped被包装函数;
  3. 元组WRAPPER_ASSIGNMENTS中是要被覆盖的属性;
'__module__','__name__','__qualname__','__doc__','__annotations__'
模块名、名称、限定名、文档、参数注解
  • 元组WRAPPERUDPATES中是要被更新的属性, `_dict`属性字典
  • 增加一个__wapped__属性,保留着wrapped函数;

funtools.update_wrapper 实例

def copy_properties(src):
    def _copy(dst):
        dst.__name__ = src.__name__
        dst.__doc__ = src.__name__
        dst.__qualname__ = src.__qualname__
        return dst
    return _copy

import functools
def logger(fn):
    def wrapper(*args,**kwargs):
        '''This is a wrapper'''
        print('before')
        ret = fn(*args,**kwargs)
        print('after')
        return ret
    functools.update_wrapper(wrapper,fn)
    return wrapper
@logger
def add(x,y):
    '''
    This is a function
    return int
    x int
    y int
    '''
    ret = x + y
    return ret
print(add.__name__,add.__doc__,add.__qualname__,sep='\n')
print('#'*50)
print(add.__wrapped__)
## 输出 
add

    This is a function
    return int
    x int
    y int

add
##################################################
<function add at 0x11539e488>

格言

凡人做一事,便须全副精神注在些一事,首尾不懈,不可见异思迁,做这样想那样,坐之山望 那山,人而无恒,终身一事无成

评论