Python高阶函数和装饰器

高阶函数

  • First Class Object
    • 函数在Python中是一等公民
    • 函数也是对象,可调用(callable)的对象
    • 函数可以作为普通变量、参数、返回值等等
  • 高阶函数
    • 数学概念y=g(f(x))
    • 在Python中,高阶函数应该满足下列至少一个条件
      1. 接受一个或者多个函数作为参数
      2. 输出一个函数
  • 计数器
    def counter(base):
        def inc(step=1):
            base += step
            return base
        return inc
    
    分析:
        - 函数counter是一个高阶函数,因为它返回了一个函数对象
        - 上述函数的问题在于内层函数使用外层函数的本地变量,但同时有修改它,所以调用是会报错UnboundLocalError,想要更改只需要使用关键字nonlocal在要改变前声明它是外层函数的变量
        - 调用应该使用counter(base)(step)
        - f1 =counter(5)和f1 = counter(5)是不相等的,因为上述调用是两次调用,这样他们的ID就是不一样的,所以不相等

自定义sort函数

  • 排序问题:仿照内建函数sorted,自行实现一个sort函数(不使用内建函数),能够为列表元素排序
  • 思路
    • 内建函数sorted是返回一个新的列表,可以设置升序或降序,可以设置一个排序函数。自定义的函数也应该有类似功能
    • 新建一个列表,遍历原列表,和新列表的值依次比较决定如何插入到新列表中
  • 函数实现1。判断以下代码是如何排序,还能优化么?
    def sort(iterable):
        ret = []
        for x in iterable:
            for i,y in enumerate(ret):
                if x > y:       #找到大的就插入。如果换成x<y,函数就是另一种排序
                    ret.insert(i,x)     #降序
                    break
        else:       #不大于说明是最小的,尾部追加
            ret.append(x)
        return ret 
    
    print(sort([1,7,2,9,3,6,8,4,5]))
  • sort函数实现2。用一个参数控制顺序
    def sort(iterable,reverse=False):
        ret = []
        for x in iterable:
            for i,y in enumerate(ret);
                flag = x > y if reverse else x < y
                if flag:
                    ret.insert(i,x)
                    break
            else:
                ret.append(x)
        return ret
        
    print(sort([1,7,2,9,3,6,8,4,5]))
  • sort函数实现3。
    def sort iterable,fn=lambda a,b:a>b):
        ret = []
        for x in iterable:
            for i,y in enumerate(ret);
                if fn(i,x):     #函数返回值是bool型
                    ret.insert(i,x)
                    break
            else:
                ret.append(x)
        return ret
    
    print(sort([1,7,2,9,3,6,8,4,5]))

内建函数-高阶函数

  • sorted(iterable[,key][,reverse])排序
    1. 返回一个新列表1,对一个可迭代对象的所有元素排序,排序规则为可以定义的函数,reverse表示是否排序翻转
    2. sorted(lst,key=lambda x:6-x) #返回新列表
    3. list.sort(key=lambda x:6-x) #就地修改
  • filter(function,iterable)
    1. 过滤可迭代对象的元素,返回一个迭代器
    2. function是一个具有一个参数的函数,返回bool
    3. 例如,过滤除被3整除的数字:list(filter(lambda x:x%3 == 0,[1,9,55,150,-3,78,28,123]))
  • map(function,*iterables) –> map object
    1. 对多个可迭代对象的元素按照指定的函数进行映射,返回一个迭代器
    2. 例子:
      • list(map(lambda x:2*x+1,range(5)))
      • dict(map(lambda x:(X%5,x),range(500)))

柯里化

  • 指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数为参数的函数
  • z = f(x,y)转换成z = f(x)(y)的形式
  • 举例
    def add(x,y):
        return x+y
    
    转换如下:
    def add(x):
        def _add(y):
            return x+y
        return _add
        
    add(5)(6)
    
    通过嵌套函数就可以吧函数转换称柯里化函数

装饰器

  • 需求
    • 一个加法函数,想要加强它的功能,能够输出被调用过以及调用的参数信息
      def add(x,y):
          return x+y
      
      增加信息输出功能
      def add(x,y):
          print('call add,{}+{}'.format(x,y))
          return x+y
    • 上面的加法函数是完成了需求,但是有以下缺点:
      1. 打印语句的耦合太高
      2. 加法函数属于业务功能,而输出信息的功能,属于非业务功能代码,不应该放在业务函数内部
  • 推演过程
    首先将业务函数和增强功能分开,但发现fn函数调用传参是个问题
    def add(x,y):
        return x + y
    
    def logger(fn):
        print('begin')
        x = fn(4,5)
        print('end')
        return x
    
    print(logger(add))
    
    解决传参的问题
    def add(x,y):
        return x + y
    
    def logger(fn,*args,**kwargs):    #这里*args和**kwargs代表可变参数
        print('begin')
        x = fn(*args,**kwargs)      #*args,**kwargs在这表示参数解构
        print('end')
        return x
    
    print(logger(add,5,y=50))
    
    柯里化
    def add(x,y):
        return x + y
    
    def logger(fn):
        def _logger(*args,**kwargs):
            print('begin')
            x = fn(*args,**kwargs)
            print('end')
            return x
        return _logger
    
    print(logger(add)(5,y=50))  
    #最后也可以这么写
    # add = logger(add)
    # print(add(x=5,y=10))
  • 装饰器语法
def logger(fn):
    def _logger(*args,**kwargs):
        print('begin')
        x = fn(*args,**kwargs)
        print('end')
        return x
    return _logger
    
@logger #等价于add= logger(add),这就是装饰器语法
def add(x,y):
    return x+y
    
print(45,50)
  • 上面的语法是无参装饰器
    1. 它是一个函数
    2. 函数作为他的形参
    3. 返回值也是一个函数
    4. 可以使用@functionname方式,简化调用
  • 装饰器是高阶函数,但装饰器对传入函数的功能的装饰(功能增强)

文档字符串

  • Python中文档字符串Documentation Strings
  • 在函数体语句块的第一行,且习惯是多行的文本,所以多使用三引号
  • 惯例是首字母大写,第遗憾写概述,空遗憾,第三行写详细描述
  • 可以是使用特殊属性__doc__访问这个文档
def add(x,y):
    '''This is a function of addition'''
    a = x+y
    return a

print('name={}\ndoc={}'.format(add.__name__,add.__doc__))
print(help(add))

使用装饰器的副作用

  • 使用装饰器后原函数对象的属性都被替换了,而使用装饰器,希望查看原函数的属性,如何解决
    1. 可以自己定义一个函数将被包装函数的属性覆盖掉包装函数
    def copy_properties(src,dst):   #自定义复制属性的函数
        dst.__name__ = src.__name__
        dst.__doc__ = src.__doc__
        
    
    def logger(fn):
        def _logger(*args,**kwargs):
            print('begin')
            x = fn(*args,**kwargs)
            print('end')
            return x
        copy_properties(fn,_logger) #函数调用,复制被包装函数属性覆盖包装函数属性
        return _logger
    
    @logger 
    def add(x,y):
        return x+y
    
    print(45,50)
    1. 凡是被装饰的函数都需要赋值这些属性,这个函数很通用
    2. 可以将被复制属性的函数构建成装饰器函数,带参装饰器
    def copy_properties(src):   #柯里化
        def _copy(dst):
            dst.__name__ = src.__name__
            dst.__doc__ = src.__doc__
            return dst
        return _copy
    
    def logger(fn):
        @copy_properties(fn)    #_logger = copy_properties(fn)(_logger)
        def _logger(*args,**kwargs):
            print('begin')
            x = fn(*args,**kwargs)
            print('end')
            return x
        
        return _logger
    
    @logger 
    def add(x,y):
        """this is """        
        return x+y
    
    print(45,50)

带参装饰器

  • 它是一个函数
  • 函数作为他的形参
  • 返回值是一个不带参的装饰器函数
  • 使用@functionname(参数列表)方式调用
  • 可以看作在装饰器外层又加了一层函数
def logger(duration):
    def _logger(fn):
        def wrapper(*args,**kwargs):
            start = datetime.datetime.now()
            ret = fn(*args,**kwargs)
            delta =(datetime.datetime.now()-start).total_seconds()
            print('so slow') if delta >duration else print('so fast')
            return ret
        return wrapper
    return _logger

@logger(5)
def add(x,y):
    time.sleep(3)
    return x+y
print(add(5,6))
  • 将记录的功能提取出来,就可以通过外部提供的哈数来灵活的控制输出
def logger(duration,func=lambda name,duration:print('{}took{}s'.format(name,duration))):
    def _logger(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)
            print('so slow') if delta >duration else print('so fast')
            return ret
        return wrapper
    return _logger

functools模块

  • functools.update_wrapper(wrapper,wrapped,assigned=WRAPPER_ASSIGNMENTS,updated=WRAPPER_UPDATES)
    • 类似copy_properties功能
    • wrapper包装函数,wrapped被包装函数
    • 元组WRAPPER_ASSIGNMENTS中是要被覆盖的属性
    • 元组WRAPPER_UPDATES中是要被更新的属性,__dict__属性字典
    • 增加一个__wrapped__属性,保留着wrapped函数
  • functools.wraps(wrapped,assigned=WRAPPER_ASSIGNMENTS,updated=WRAPPER_UPDATES)和上个方法用法基本相同

本文来自投稿,不代表Linux运维部落立场,如若转载,请注明出处:http://www.178linux.com/88009

(0)
KX_ilKX_il
上一篇 2017-10-22
下一篇 2017-10-23

相关推荐

  • linux-用户和组的基本管理

    用户和组

    2018-03-20
  • 再不自动化就晚啦!优云教你4步打造基于CentOS的产品镜像

    随着Linux程序的增多,软件的安装过程中经常出现如下问题: 1、硬件配置类似或者相同时,批量安装系统和软件,希望实现自动化安装,减少安装时间和人为出错。 2、工程实施人员在不同客户现场进行系统和软件安装(硬件配置不同),由于硬件有差别,不容易实现自动化。 笔者针对以上场景,从提高生产效率,减少误操作的立场,提出如下解决方案,希望能达到抛砖引玉的目的,并能与…

    系统运维 2016-07-16
  • locate、find命令使用总结

    一、简介    在linux系统中存在"一切皆文件"的说法,这就足以说明文件的重要性,因此查找文件也是我们必须要掌握的技能。这时候熟练使用locate、find命令也就显得至关重要。尤其是find命令常用于日常工作中如安装完某个软件之后要查看这些软件的安装配置路径,或是需要按指定条件直接查找我们需要操作的文件。因此更需…

    Linux干货 2015-08-31
  • redis主从复制(2)— replication buffer与replication backlog

    1、redis主从复制过程先不解释replication buffer和replication backlog,而先看看redis主从复制的过程。 redis的主从复制分为两个阶段: 1)同步(sync rdb snapshot):slave复制master的某时间点(t)的全量数据,t为master接收到slave的sync命令后执行rdb bgsave的…

    Linux干货 2016-04-05
  • 软件包管理

    1.程序包管理器 源代码–>目标二进制格式–>组织称为一个或有限几个“包”文件;     安装、升级、卸载、查询、校验 程序包管理器: debian(Ubuntu):dpt,工具:dpkg,程序包以“.deb”结尾 redhat:redhat package manager&nbsp…

    Linux干货 2016-08-21
  • ssh+rsync批量管理,批量分发

    现在我简单架设了一个7台服务器的集群集体如下,架设集群的过程我就省略了… [nfs存储一台]192.168.42.10[负载均衡2台]192.168.42.40192.168.42.41[web服务器2台]192.168.42.30192.168.42.31[备份1台]192.168.42.20[mysql 1台]192.168.42.50 我现…

    Linux干货 2017-04-22