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

相关推荐

  • Redis学习手册(持久化)

    一、Redis提供了哪些持久化机制:     1). RDB持久化:     该机制是指在指定的时间间隔内将内存中的数据集快照写入磁盘。         2). AOF持久化:     该机制将以日志…

    Linux干货 2015-02-25
  • 磁盘运作方式及分区,挂载等操作

    磁盘运作方式及分区,挂载等操作 磁盘是我们存储数据的空间,而为了方便我们对数据的管理,我们需要对磁盘进行分区。而在我们创建过分区之后 ,我们为了能 够访问磁盘 ,需要对磁盘进行挂载 。文件系统通过为每个文件在分区上分配文件块的方式把数据存储在硬盘上。所以,就是使用文件系统在 硬盘分区上对数据块的各种信息的操作。这样就需要我们了解磁盘的运作方式,学会磁盘的分区…

    Linux干货 2016-08-29
  • Linux文件管理初步(第二周)

    第二周作业 1.Linux上的文件管理类命令都有哪些,其常用的使用方法及其相关示例演示 目录管理管理类命令 mkdir make directories mkdir [OPTION]… DIRECTORY… 注意:路径基名作为命令的作用对象时,基名之前的路径必须要存在;     -p:自动按…

    Linux干货 2016-09-05
  • grep的基本用法详解

     grep是linux上常用的一个文本处理工具之一。它有着强大的文本处理能力,学会它,可以让工作更有效率。 一、初识grep   grep: Global search Regular Experssion and Print out line   从名字上也可以直观的了解到它是基于正则表达式进行全局搜索,并把结果打印到屏幕上来…

    系统运维 2015-05-25
  • bash脚本之进阶

    bash脚本 1、终端打印 1、单双引号(echo) 双引号: ①在双引号里面打不出! ②双引号,可以在文本中使用“;”,“;”在bash中被用作命令定界符。 单引号: 变量替换在单引号中无效。 2、printf:不会自动添加换行符,要手动添加 如下: 结果: 3、补充: echo 要使用转义系列需要加e,!号除外 2、变量和环境变量 脚本语言通常…

    Linux干货 2016-11-28
  • select与case组合循环

    select与case: 格式: select variable in list do 循环体命令 done 例1:     #!/bin/bash PS3="what do you want: " select i in a b c d         #在例2中省略…

    Linux干货 2016-08-22