python3基础知识复习 -- 函数式编程

函数式编程

  • 函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。
    对应到编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如C语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如Lisp语言。
    函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
高阶函数
  • 一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
  • 函数可以指向其他变量, f = abs --> f(-10) == 10
  • 函数名其实就是指向函数的变量!对于abs()这个函数,完全可以把函数名abs看成变量,它指向一个可以计算绝对值的函数!
    如果把abs指向其他对象,会有什么情况发生?
>>> abs = 10
  >>> abs(-10)
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
  TypeError: 'int' object is not callable

把abs指向10后,就无法通过abs(-10)调用该函数了!因为abs这个变量已经不指向求绝对值函数而是指向一个整数10!
当然实际代码绝对不能这么写,这里是为了说明函数名也是变量。要恢复abs函数,请重启Python交互环境。

注:由于abs函数实际上是定义在import builtins模块中的,所以要让修改abs变量的指向在其它模块也生效,要用import builtins; builtins.abs = 10。

常见高阶函数
  • Note: 高阶函数经常和lambda一起配合使用哦。
  • filter() — 过滤器,如果前面用函数,则返回function(item)中为true的结果,如果前面为None, 返回迭代项中为true的部分。filter()这个高阶函数,关键在于正确实现一个“筛选”函数。注意到filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list
    • 格式:filter(function or None, iterable) --> filter object --> 返回一个列表object–> list 查看
#筛选奇数,偶数的x % 2结果为0 就不会返回
 list(filter(lambda x: x % 2, range(10)))
[1, 3, 5, 7, 9]

#求3的倍数, x%3 为0是if判断直接去else了,如果不为0就去False那边(这样filter就把它直接过滤了)
list(filter(lambda : False if x % 3 else x, range(100)))

# 去除序列中的空字符串, and 的用法,s为真时返回s.strip(), s为假时返回s本身然后被filter掉:) 
>>> list(filter(lambda s: s and s.strip(), ['A', '', 'B', None, 'C', '  ']))
['A', 'B', 'C']


# 计算素数的一个方法是埃氏筛法,它的算法理解起来非常简单:
# 首先,列出从2开始的所有自然数,构造一个序列:
# 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...
# 取序列的第一个数2,它一定是素数,然后用2把序列的2的倍数筛掉:
# 3,  5,  7,  9,  11,  13,  15,  17, 19, ...
# 取新序列的第一个数3,它一定是素数,然后用3把序列的3的倍数筛掉:
# 5, 7, 8, 10, 11, 13, 14, 16, 17, 19, ...
# ...
# 不断筛下去,就可以得到所有的素数。
# 定义一个奇数[3,5,7,9...]的迭代器
def num_list():
    n = 1
    while True:
        n += 2
        yield n

# 定义素数的iterator
def primes():
    yield 2  # 第一次读取返回2
    g = num_list()  # 初始化奇数列表的迭代器
    while True:
        num = next(g)  # 每次取出该iterator的一个元素
        yield num
        g = filter(lambda x: x % num > 0, g)  # lambda表达式每次将num以及其倍数通过filter去掉,再产生新的素数迭代器


p = primes()
# 给定范围来返回,因为素数iterator是全体素数,无限~
for each in p:
    if each < 100:
        print(each)
    else:
        break
  • map() – 高阶函数之一,映射, 将每一个元素作为函数的参数进行运算加工,直到可迭代的每个元素都加工完毕,返回所有加工后的元素构成的新序列
    • map(function or None, iterable)
    • map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。由于返回的迭代器是惰性的,需要用list把整个序列都计算出来。
eg.
list(map(lambda x: x*2, range(10)))

list(map(lambda x: x % 2, range(10)))
[0, 1, 0, 1, 0, 1, 0, 1, 0, 1]
  • reduce() – 高阶函数,把一个函数作用在一个序列[x1, x2, x3, …]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

但是如果要把序列[1, 3, 5, 7, 9]变换成整数13579,reduce就可以派上用场:

 from functools import reduce
    
    DIGITS = {
        '1': 1,
        '2': 2,
        '3': 3,
        '4': 4,
        '5': 5,
        '6': 6,
        '7': 7,
        '8': 8,
        '9': 9,
        '0': 0
    }
    
    # 方法一
    '''
    def str2int(s):
    
        def fun(x, y):
            return x 10 + y
    
        def char2int(s):
            return DIGITS[s]
    
        return reduce(fun, map(char2int, s))
    '''
    
    # 方法二
    def char2int(s):
        return DIGITS[s]
    
    def str2int(s):
        return reduce(lambda x, y: x 10 + y, map(char2int, s))
    
    print(str2int('13423'))
    
    # 也就是说,假设Python没有提供int()函数,你完全可以自己写一个把字符串转化为整数的函数,而且只需要几行代码!

把“123.456” 字符串变为float 123.456

    from functools import reduce
    
    def char2float(s):
        if not isinstance(s, str):
            return
        x = s.split('.')
        return reduce(lambda a, b: a + b pow(10, -len(x[1])), map(int, x))
    
    print(char2float('123.345'))
  • sorted()(BIF)-- 返回一个全新的列表,不同于(列表的s.sort() 表示源列表被改), 可接受任何可迭代对象作为参数

对于不变对象来说,调用对象自身的任意方法,也不会改变该对象自身的内容。相反,这些方法会创建新的对象并返回,这样,就保证了不可变对象本身永远是不可变的。

sorted(s, reverse=True) -- 倒序

sorted(s, key=len) --- key是排序方法,这里引入len函数,无括号

    s = ["peach", "pitaya", "durain", "dragon", "tangerine"]
    
     sorted(s)
    ['dragon', 'durain', 'peach', 'pitaya', 'tangerine']
    
     sorted(s, reverse = True)
    ['tangerine', 'pitaya', 'peach', 'durain', 'dragon']
    
     sorted(s, key = len)
    ['peach', 'pitaya', 'durain', 'dragon', 'tangerine']
    
    # 按照字母顺序,无视大小写
    sorted(s, key = str.lower)
lambda 函数

  • 创建匿名函数,更加精简, 一句话函数

lambda <参数1,参数2…> : <return 表达式>

    eg:
     def ds(x):
    	return x + 2
    
     ds(3)
     5
    
    >>> lambda x : x + 2
    <function <lambda> at 0x02F225C8> #产生的是一个函数,需要赋值使用
    
     g = lambda x : x + 2  # 可以赋值给一个变量使用
     g(3)
     5

lambda表达式作用:

  • 用python写一些之小脚本时,可以省下定义函数的过程,eg.只需要写个简单的脚本管理服务器时间,而不需要专门定义一个函数再写调用,使代码更简洁
  • 对于一些比较抽象且整个程序执行下来只需调用一两次的函数,使用lambda不需要考虑函数命名的问题
  • 简化代码的可读性,普通函数每次要去找def 部分,lambda可以省略这些
  • 限制:就是只能有一个表达式,不用写return,返回值就是该表达式的结果。
装饰器(decorator)
  • 现在,假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
>>> def now():
...     print('2015-3-25')

本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:

def log(func):
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)  # func.__name__ 表示函数的名称
        return func(*args, **kw)
    return wrapper

观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:

@log
def now():
    print('2015-3-25')

调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志:

>>> now()
call now():
2015-3-25

把@log放到now()函数的定义处,相当于执行了语句:

now = log(now)

由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。

wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,再紧接着调用原始函数。

  • 如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:
def log(text):
    def decorator(func):
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

这个3层嵌套的decorator用法如下:

@log('execute')
def now():
    print('2015-3-25')

执行结果如下:

>>> now()
execute now():
2015-3-25

和两层嵌套的decorator相比,3层嵌套的效果是这样的:

>>> now = log('execute')(now)

我们来剖析上面的语句,首先执行log(‘execute’),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数。

以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有__name__等属性,但你去看经过decorator装饰之后的函数,它们的__name__已经从原来的’now’变成了’wrapper’:

>>> now.__name__
'wrapper'

因为返回的那个wrapper()函数名字就是’wrapper’,所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。

不需要编写wrapper.name = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下:

    import functools
    
    def log(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print('call %s():' % func.__name__)
            return func(*args, **kw)
        return wrapper

或者针对带参数的decorator:

    import functools
    
    def log(text):
        def decorator(func):
            @functools.wraps(func)
            def wrapper(*args, **kw):
                print('%s %s():' % (text, func.__name__))
                return func(*args, **kw)
            return wrapper
        return decorator

eg: 设计一个decorator打印一个函数的执行时间

    # -*- coding: UTF-8 -*-
    import time, functools
    
    # 不加参数装饰器
    def metric(fn):
    
        @functools.wraps(fn)
        def wrapper(*args, **kw):
            t1 = time.time()
            result = fn(*args, **kw)
            t2 = time.time()
            print('function %s run %f ms' % (fn.__name__, t2 - t1))
            return result
    
        return wrapper
    
    
    @metric
    def fast(x, y):
        time.sleep(0.0012)
        return x + y
    
    
    @metric
    def slow(x, y, z):
        time.sleep(0.1234)
        return x y z
    
    
    f = fast(11, 22)
    s = slow(11, 22, 33)
    print(f, s)
    
    function fast run 0.006482 ms
    function slow run 0.138556 ms
    33 7986
   # 装饰器加不加参数都可运行
   # -*- coding: UTF-8 -*-
   import time, functools
   
   def log(text):
       def decorator(fn):
           @functools.wraps(fn)
           def wrapper(*args, **kw):
               if isinstance(text, str):
                   print('%s %s' % (text, fn.__name__))
               else:
                   print('calling %s' % fn.__name__)
               return fn(*args, **kw)
   
           return wrapper
   
       if isinstance(text, str):
           return decorator
       else:
           return decorator(text)
   
   
   @log
   def fast(x, y):
       time.sleep(0.0012)
       return x + y
   
   
   @log('execute')
   def slow(x, y, z):
       time.sleep(0.1234)
       return x y z
   
   
   f = fast(11, 22)
   s = slow(11, 22, 33)
   print(f, s)
偏函数(Partial Function)
  • 偏函数(Partial function)来自functools.partial(func, *args, **kwargs)
  • functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。
  • 作用:当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单

假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去:

>>> int('101101', base=2)
45

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

这样,我们转换二进制就非常方便了:
>>> int2('1010101')
85

functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2:

>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85

注意到上面的新的int2函数,仅仅是把base参数重新设定默认值为2,但也可以在函数调用时传入其他值:

>>> int2('1000000', base=10)
1000000

最后,创建偏函数时,实际上可以接收函数对象、*args和**kw这3个参数,当传入:

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

实际上固定了int()函数的关键字参数base,也就是:

int2('10010')
==>
kw = { 'base': 2 }
int('10010', **kw)

当传入:

max2 = functools.partial(max, 10)

实际上会把10作为*args的一部分自动加到左边,也就是:

max2(5, 6, 7)

相当于:

args = (10, 5, 6, 7)
max(*args)

结果为10。


有用的三连一下哦 😃

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值