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 23:25
下一篇 2017-10-23 09:08

相关推荐

  • 计算机与操作系统

    计算机系统=Hardware+Software Linux操作系统=GNU/kernel+程序 (user space) ————– apps   shell    (人机交互接口) lib call(半层)  (kernel space) —&#…

    Linux干货 2017-02-14
  • CentOS系统启动流程

       开机不是只要单击电源钮,而关机只要关掉电源钮就可以了吗?话是这样没错啦,但是由于 Linux 是一套多人多任务的操作系统,你难保你在关机时没有人在在线,如果你关机的时候碰巧一大群人在在线工作, 那会让当时在在线工作的人马上断线的!那不是害死人了!一些数据可是无价之宝。    另外 Linux 在执行的时候,虽然你…

    Linux干货 2016-09-19
  • shell编程if及find查找作业

    写一个脚本/root/bin/createuser.sh,实现如下功能:使用一个用户名做为参数,如果指定参数的用户存在,就显示其存在,否则添加之;显示添加的用户的id号等信息 [root@www sh.log]# cat createuser.sh  #!/bin/bash #author #使用一个用户名作为参数,如…

    Linux干货 2016-08-16
  • linux文件权限详解

    基本命令 1.cut : cat /etc/passwd | cut -d’:’ -f7| uniq -c| sort -nr 2.authconfig 修改加密方式 –passalgo=sha256 — update 3.scp 上传文件 -r dir ip:path 传目录 file ip:path传文件 …

    Linux干货 2017-04-03
  • Linux ansible 服务

                      Linux ansible 服务 Ansible:    运维工具的分类: agent:基于专用的agent程序完成管理功能,puppet, func, zabbix, … agentless:基于ss…

    系统运维 2016-11-19
  • 07程序包管理器简介

    源代码命名方式,name-version.tar.gz|bz2|xz,    –>version:major.minor.release 要将一个源代码程序安装到Linux系统上,一般有两个方法。 1、找到源代码,手动编译安装。解压,./configure  –> make –&g…

    Linux干货 2016-11-03