Python基础(六)—函数式编程(内部函数、闭包、lambda、filter/map/reduce/sorce、偏函数)

内部函数

Python中函数的作用域由def关键字界定,函数内的代码访问变量的方式是从其所在层级由内向外,若往外直至全局作用域都查找不到的话代码会抛异常。

主要看以下代码的差别~~

  """
  def f1():
      x = 5
      def f2():
          x *= x
          return x
      return f2
  """

  def f1():
      x = [5]
      def f2():
          x[0] *= x[0]
          return x[0]
      return f2()

  print(f1())

  def f1():
      x = [5]
      def f2():
          x[0] *= x[0]
          return x[0]
      return f2

  print(f1()())

  def f1():
      x = 5
      def f2():
          nonlocal x
          x *= x
          return x
      return f2
  print(f1()())

闭包 closure

  • 闭包的一些定义

    • 闭包的定义:闭包是由函数及其相关的引用环境组合而成的实体(即:闭包=函数+引用环境)。

    • python中闭包的定义:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)

    • 闭包函数的必要条件:

      • 闭包函数必须返回一个函数对象
      • 闭包函数返回的那个函数必须引用外部变量(一般不能是全局变量),而返回的那个函数内部不一定要return
      • 闭包函数引用的外部变量不一定就是其父函数的参数,也可以是父函数作用域内的任意变量

      下面三个例子为常见的闭包

        # NO.1
        def line_conf(a, b):
            def line(x):
                return a * x + b
    
            return line
    
    
        # NO.2
        def line_conf():
            a = 1
            b = 2
    
            def line(x):
                print(a * x + b)
    
            return line
    
    
        # NO.3
        def _line_(a, b):
            def line_c(c):
                def line(x):
                    return a * (x ** 2) + b * x + c
    
                return line
    
            return line_c
    
    
  • 为何称作闭包
    按照命令式语言的规则,ExFunc函数只是返回了内嵌函数InsFunc的地址,在执行InsFunc函数时将会由于在其作用域内找不到sum变量而出 错。而在函数式语言中,当内嵌函数体内引用到体外的变量时,将会把定义时涉及到的引用环境和函数体打包成一个整体(闭包)返回。
    以下列子:

      def line_conf(a):
        b=1
        def line(x):
            return a * x + b
        return line
    
      line_A = line_conf(2)
      b=20
      print(line_A(1))  # 3
    
    • line_A对象作为line_conf返回的闭包对象,它引用了line_conf下的变量b=1,在print时,全局作用域下定义了新的b变量指向20,最终结果仍然引用的line_conf内的b。这是因为,闭包作为对象被返回时,它的引用变量就已经确定(已经保存在它的__closure__属性中),不会再被修改。
    • 即闭包函数被当作变量返回时,它的所有变量就已经固定,形成了一个封闭的对象,这个对象包含了其引用的所有外部、内部变量和表达式。当然,闭包的参数例外。
  • 显式查看闭包
    __closure__属性返回的是一个元组对象,包含了闭包引用的外部变量。
    根据NO.1,可以使用一些代码查看闭包信息:

      L = line_conf()
      print(line_conf().__closure__) #(<cell at 0x05BE3530: int object at 0x1DA2D1D0>,
      for i in line_conf().__closure__: 
          print(i.cell_contents)  # 打印引用的外部变量值,为1, 2
    
  • 闭包的作用
    闭包最主要的作用是提高代码的可复用性,根据NO.1,输出两条直线:

      #定义两条直线
      line_A = line_conf(2, 1) #y=2x+b
      line_B = line_conf(3, 2) #y=3x+2
    
      #打印x对应y的值
      print(line_A(1)) #3
      print(line_B(1)) #5
    
      # 不使用闭包的代码
      def line_A(x):
          return 2*x+1
      def line_B(x):
          return 3*x+2
    
      print(line_A(1)) #3
      print(line_B(1)) #5
    

    两条直线看不出区别,当需要定义几十几百条,闭包优势就出来了。

  • 闭包的实际运用

    • 例子1:

        def who(name):
            def do(what):
                print(name, 'say:', what)
      
            return do
      
        lucy = who('lucy')
        john = who('john')
      
        lucy('i want drink!')
        lucy('i want eat !')
        lucy('i want play !')
        
        john('i want play basketball')
        john('i want to sleep with U,do U?')
      
        lucy("you just like a fool, but i got you!")
      

匿名函数 lambda

  • 特性
    使用lambda可以精简代码、不需要考虑重名函数、可读性,lambda argument_list: expression,特性如下:
    • lambda函数是匿名的:所谓匿名函数,通俗地说就是没有名字的函数。lambda函数没有名字;
    • lambda函数有输入和输出:输入是传入到参数列表argument_list的值,输出是根据表达式expression计算得到的值
    • lambda函数一般功能简单:单行expression决定了lambda函数不可能完成复杂的逻辑,只能完成非常简单的功能。(不支持if else等逻辑判断)
  • 示例:
    • lambda x, y: xy;函数输入是x和y,输出是它们的积xy
    • lambda:None;函数没有输入参数,输出是None
    • lambda *args: sum(args); 输入是任意个数的参数,输出是它们的和(隐性要求是输入参数必须能够进行加法运算)
    • lambda **kwargs: 1;输入是任意键值对参数,输出是1

映射map/reduce

  • map
    • 功能: 求一个序列或者多个序列进行函数映射之后的值,就该想到map这个函数,它是python自带的函数,在python3.*之后返回的是迭代器,同filter,需要进行列表转换
    • 调用: map(function,iterable1,iterable2),function中的参数值不一定是一个x,也可以是x和y,甚至多个;后面的iterable表示需要参与function运算中的参数值,有几个参数值就传入几个iterable
    • 实例:
      x = [1,2,3,4,5]
      y = [2,3,4,5,6]
      list(map(lambda x,y:(x*y)+2,x,y))
      # 输出:
      [4, 8, 14, 22, 32]
    
  • reduce
    从python 3 开始移到了 functools 模块
    • reduce()函数接收的参数和 map()类似,一个函数 f,一个list,但行为和 map()不同,reduce()传入的函数 f 必须接收两个参数,reduce()对list的每个元素反复调用函数f,并返回最终结果值。
    • reduce()还可以接收第3个可选参数,作为计算的初始值
def f(x, y):
    return x + y

reduce(f, [1, 3, 5, 7, 9])  # 25

reduce(f, [1, 3, 5, 7, 9], 100)  # 125,计算初始值和第一个元素:f(100, 1),结果为101

过滤器 filter

  • 功能: filter的功能是过滤掉序列中不符合函数条件的元素,当序列中要删减的元素可以用某些函数描述时,就应该想起filter函数。

  • 调用: filter(function,sequence),function可以是匿名函数或者自定义函数,它会对后面的sequence序列的每个元素判定是否符合函数条件,返回TRUE或者FALSE,从而只留下TRUE的元素;sequence可以是列表、元组或者字符串

  • 实例:

      x = [1,2,3,4,5]
      list(filter(lambda x:x%2==0,x)) # 找出偶数。python3.*之后filter函数返回的不再是列表而是迭代器,所以需要用list转换。
      # 输出:
      [2, 4]
    

sorted、sort

  • 基本
    前面也说有高阶函数:map/reduce、filter。sorted()也是一个高阶函数。用sorted()排序的关键在于实现一个映射函数。
  • 默认是升序排序,可用:reverse=True 实现倒叙排序。
  • 默认情况下,对字符串排序,是按照ASCII的大小比较的,由于’Z’ < ‘a’,结果,大写字母Z会排在小写字母a的前面。可用:key=str.lower 忽略首字母的大小写。
  • sorted和sort
    • sort方法是列表类型list的内置方法,使用sort方法对list排序会修改list本身,不会返回新的list,sort方法只能用于列表,不能对字典、元祖等其他可迭代对象进行排序。
      list.sort( key=None, reverse=False)
    • sorted() 函数能对所有可迭代的对象进行排序操作,sorted()函数不会改变原来的对象,而是会返回一个新的已经排序好的对象。
      sorted(iterable, key=None, reverse=False)
  • 排序与lambda
    lambda可用实现字典key或者是value的排序,又或者是JSON格式的排序。样例如代码:
list = [2, 5, -4, 11, 7]
print(list.sort())
print(list)
print(sorted(list, reverse=True))

dict = {'a':2,'E':3,'f':8,'d':4}
print(sorted(dict))
print(sorted(dict, reverse=True))
print(sorted(dict, reverse=True, key=str.lower))
print(sorted(dict.items(), key=lambda x : x[1]))

array = [{"age":20,"name":"a"},{"age":25,"name":"b"},{"age":10,"name":"c"}]
array = sorted(array,key=lambda x:x["age"], reverse=False)
print(array)

偏函数

functools模块提供有wrap与偏函数,可以设定参数的默认值,降低函数的调用难度。

例如,我们有个需求,需要将二进制转为int,那应该是:

int(‘100000101’, base=2) # 261

每次转换都这样,比较麻烦,于是写了函数int2

def int2(num, base=2):
  return int(num, base)

使用functools.partial就是帮助创建一个这样的偏函数,不用自己定义int2()

import functools

int2 = functools.partial(int, base=2)
int2('101010110')


创建偏函数时,实际上可以接受 函数对象、*args、**kw 这三个参数,以上例子实际上固定了int()函数的关键参数base,也就是:
int2(‘101010110’)
相当于:
kw = {‘base’, 2}
int(‘101010110’, **kw)
当传入:
max2 = functools.partial(max, 10)
实际上会将10作为 *args 的一部分自动加到左边,也就是:
max2(4, 9) 相当于: max2(10, 4, 9)
偏函数用于固定住原函数的部分参数,使得调用更简单。

个人博客:Loak 正 - 关注人工智能及互联网的个人博客
文章地址:Python基础(六)—函数式编程(内部函数、闭包、lambda、filter/map/reduce/sorce、偏函数)

原文地址:https://www.cnblogs.com/l0zh/p/13739757.html