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

相关推荐

  • 文本处理三剑客之grep及正则表达式

    文本处理三剑客之grep及正则表达式   文本处理工具:   Linux上处理文本三剑客:     Grep:文本过滤器工具(模式:pattern)工具;     Sed: stream editor,流编辑器;文本编辑工具;   &nbs…

    Linux干货 2016-08-12
  • 基于lvs调度的web应用——Discuz程序

    实验环境: 前端主机:10.1.43.101 后端主机1:172.16.0.9   作为lvs-dr的调度器,并且提供mysql和nfs文件共享 后端主机2:172.16.0.2   作为ap服务器之一 后端主机3:172.16.0.3   作为ap服务器之一 实验拓扑: 后端主机1: [root@node3…

    Linux干货 2016-10-26
  • 磁盘管理、文件系统、挂载

    磁盘管理 一磁盘分区 磁盘分区有两种方式:MBR和GPT MBR:1)按照柱面进行分区;分区不超过2T 2)关于0磁道0扇区的前512bytes;其中的446bytes为boot loader;中间的64bytes装有分区表;2bytes为55AA 3)可分4个分区;3个主分区+1扩展(N个逻辑分区)扩展分区最多一个。 GPT分区:1)GUID patiti…

    Linux干货 2016-08-29
  • 推荐-LAMP的编译安装

    首先解释一下LAMP,L:Linux;A:apache;M:MariaDB;P:php。Linux+Apache+Mysql/MariaDB+Perl/PHP/Python一组常用来搭建动态网站或者服务器的开源软件。 本文就是介绍编译安装apache2.4,MariaDB5.5,以及php(基于模块化和fpm的这两种方式来配合php提供服务)。 因为php是…

    Linux干货 2016-04-11
  • 第六周

    1、复制/etc/rc.d/rc.sysinit文件至/tmp目录,将/tmp/rc.sysinit文件中的以至少一个空白字符开头的行的行首加#; :%s/^[[:space:]]\+/#&/ 2、复制/boot/grub/grub.conf至/tmp目录中,删除/tmp/grub.conf文件中的行首的空白字符; :%s/^[[:space:]]\…

    Linux干货 2016-09-19
  • 磁盘管理

    1、拿到一块硬盘,通常来讲,第一步是分区,然后是文件系统的创建,管理文件系统,第三步是挂载设备。 2、linux(准确的说是UNIX)哲学,whindows一切皆窗口,一切皆图形。 3、磁盘是一个硬件设备,存放在/dev/目录下,会有相应的文件来对应的表示这些设备文件,在这个目录下存放的全是设备。 4、在/dev目录下和设备相关的有两种,一种是c开头为字符,…

    Linux干货 2017-04-22