python全栈-函数式编程

python全栈-函数式编程

函数式编程

主要包括:

  • 高阶函数
  • 匿名函数
  • 闭包
  • 偏函数
  • map
  • reduce
  • sorted
  • filter

高阶函数

一个函数的参数,可以是另一个函数,这个函数就是高阶函数

上面这四个函数都是内置的高阶函数

def fun():
    print('fun')

def fun2(func):
    print('fun2')
    func()

fun2(fun)

这是一个无参的高阶函数fun2

def fun(a,b):
    print('fun')
    print(f'a+b={a+b}')

def fun2(func,*args,**kwargs):
    print('fun2')
    func(*args,**kwargs)

fun2(fun,5,6)

这是可以传参的高阶函数,只需要在原来的基础上,加上*args,**kwargs这个参数。我们就可以把参数从fun2传到fun里面去。

偏函数Partial function

使用functools模块

就是把函数的一些参数固定,设置默认值,然后返回新函数。再去使用新函数会更简单

  • 进制转换
def int2(x,base=2):
    return int(x,base)

print(int2('1'))
print(int2('100010'))
print(int2('100'))

int方法自带的有进制转换属性base,比如print(int(‘10000’,base=2))

可以把二进制的10000转换成十进制16.如果我们要大量去使用这种方法,就比较麻烦,所以自定义一个新函数int2去修改原来int的参数base,让他一直保存base=2.这个int2就是偏函数

上面就是实现了偏函数

然后python自带的库functools简化了自定义函数的步骤。用起来更方便了

import functools

int2 = functools.partial(int,base=2)  简化的偏函数,返回的是新函数,可以直接调用

print(int2('1'))
print(int2('100010'))
print(int2('100',base=10)) 随时可以再次修改属性

闭包closure

就是一个函数,函数体内是自由变量和内置函数

def 闭包函数:

​ 自由变量

​ def 内置函数

  • 自由变量:在闭包函数内部被定义,但是在内置函数外。就是之前学的对象的“内部函数的外部变量”
    • 自由变量会一直存在,和全局变量一样的生命周期
  • 同样的用法,在内部函数去修改自由变量需要nonlocal声明。仅访问自由变量就不需要声明
def outer(): 闭包
    print("outer")
    a = 10 自由变量,对于内部函数而言,自由变量属于父级函数
    def inner():  内部函数
        print("inner")
        print(a)  内部函数访问自由变量,使用了a,a才是自由变量
    return inner  因为闭包外面无法调用内部函数,需要返回出去

inn = outer()
inn()
  • 闭包的原理
    • 在调用内部函数的时候需要先执行一次闭包函数这个外壳
    • 此时,内部函数和自由变量放在一个内存空间。
    • 当我们使用内部函数的时候,内部函数用到了自由变量
    • 所以内部函数和自由变量会一直保存,直到这个闭包不在使用,也就是程序结束
    • 在程序结束之前,这个自由变量会和内部函数待在一起。可以被修改
  • 闭包的作用
    • 隐藏变量,避免全局污染
    • 全局变量能干的,自由变量也能干。
  • 闭包的用处
    • 解决全局变量滥用问题
    • 解决了函数内局部变量无法自增的问题,因为自由变量的值,不会被销毁
    • 当自由变量和全局变量相同的时候,不会互相干扰

自由变量不会被垃圾回收机制及时回收,可能会造成内存占用

  • 定义闭包在原函数不变的情况下增加内容
def add(fun1):
    def sum1():
        print('新增功能')  新增功能
        fun1()  调用原来的函数,实现原来的功能
    return sum1

def sum():
    print('sum')

sum = add(sum)  原函数传给闭包,把闭包里面的内部函数再赋值给变量
sum()  和原函数一模一样的调用方式,但是有了新功能

这里的原函数是sum

我们要新增一个print输出,类比项目成品里面新增功能

我们可以在原来的函数里面直接增加,那样就需要结合原函数,重新修改,比较麻烦

所以,我们把原来的函数传给闭包,让闭包里面的内部函数去调用原来的函数

同时在内部函数里面还能新增功能。

map(func,[,])依次运算

接收两种参数,一个是函数,一个是序列,就是我们给的数据

map函数将序列的值依次传递给函数,并把结果放在新的序列里面返回出来

功能也很简单,比如这个对序列所有数平方的操作

def add(x):
    return x*x

ls = [1,2,3,4,5]
ls1 = []
for x in ls:
    ls1.append(add(x))

print(ls1)

不难发现,我们需要准备两个列表,还要一个函数,以及一个循环,才能实现

def add(x):
    return x*x

ls1 = map(add,[1,2,3,4,5])  返回的数据对象是一个map对象,需要使用list序列化,就跟迭代器一样
print(list(ls1))

还可以使用匿名函数,简化函数创建的过程

ls1 = map(lambda x:x*x,[1,2,3,4,5])
print(list(ls1))

也支持多个序列作为参数

ls1 = map(lambda x,y:x*y,[1,2,3,4,5],[5,3,1])
print(list(ls1))

按照最短的那个序列为准。就像两个序列合并为字典一样,按照最短的那个序列为准

reduce(func,[,])累加

需要使用functools模块

和map的调用方式一样,一个函数参数,一个序列参数

reduce是从序列先取出两个参数,传给函数去运算,然后把运算的结果,和第三个参数传给函数运算,再拿结果跟后面的参数去运算

from functools import reduce

def add(x, y):  必须是两个参数才可以
    return x + y  这是一个求和

numbers = [1, 2, 3, 4, 5]
result = reduce(add, numbers)  
print(result)  # 输出 15,1 + 2 + 3 + 4 + 5 = 15

本质是 f(f(f(1,2)3)4) == reduce(f,[1,2,3,4])

filter过滤

过滤函数:根据函数的判断结果,对序列内部的元素进行筛选

是内置函数,不需要导入

ls = [1,2,3,4,5,6]

def jishu(x):  判断奇数
    return x%2==1

ls1 = filter(jishu,ls)  
print(list(ls1))  可以打印出被筛选过的序列

sorted([],key=func,reverse=Ture)排序

就是一个排序函数,正常来说,就是对序列排序,然后返还给你

也可以指定排序的依据,默认是从小到大,从左到右排

key可以指定比较的方式

reverse为真的时候,就是逆序

比较字符串的时候,是按首字母的ascll值排序

  • 自定义比较方式
from functools import cmp_to_key   只有自定义的时候才需要导入这个库

ls = [1,2,3,4,5,6]

def daxiao(x,y):
    if x > y:
        return -1
    if x < y:
        return 1
    return 0

ls1 = sorted(ls,key=cmp_to_key(daxiao))
print(ls1)

装饰器decorator

本质就是一个闭包函数,简化了一些操作

def outer(func):
    def inner():
        func()
        print('新增功能')
    return inner

def fun():
    print('fun')

fun = outer(fun)
fun()

这是使用闭包对原有函数,新增功能

def outer(func):
    def inner():
        func()
        print('新增功能')
    return inner

@outer  # fun = outer(fun)
def fun():
    print('fun')

fun()

这是使用了装饰器,简化了操作

多个装饰器的执行顺序

def outer(func):
    print('outer start')
    def inner():
        print('inner start')
        func()
        print('inner end')
    print('outer end')
    return inner

def outer1(func):
    print('outer1 start')
    def inner1():
        print('inner1 start')
        func()
        print('inner1 end')
    print('outer1 end')
    return inner1

@outer  # fun = outer(fun)
@outer1  # fun = outer1(outer(fun))
def fun():
    print('fun')

fun()

执行结果是

outer1 start
outer1 end
outer start
outer end
inner start
inner1 start
fun
inner1 end
inner end

从结果上看,是outer1(outer(inner(inner1(fun))))
  • fun = outer1(outer(fun))

    • 从这个关系来讲,outer1的参数是outer(fun),outer的参数是原fun
    • 我们再次使用fun的时候,实际上是(outer1(outer(fun)))() 括号里面的是一个整体
    • 我们想要执行函数,但是括号里面的整体还不是可执行函数。
    • 然后我们来到了outer1这个函数,先是执行了outer1,拿到了返回值inner1,inner1=outer(fun)
    • 此时了outer(fun)还不是可执行函数,需要先执行一次outer,拿到返回值才能执行
    • 于是执行了outer,然后拿到了返回值inner,inner指向了fun
  • 这个推导比较麻烦,直接记结论

  • 先执行靠近原函数的装饰器里面的自由变量语句,再依次执行远离原函数的装饰器语句(不包括内部函数)

  • 然后所有的装饰器都执行完,再从最远的内部函数开始执行,依次执行到最近的内部函数,然后执行原函数

  • 最后内部函数由近到远,收尾

含参装饰器

def outer(br):  专门处理装饰器的参数
    def decorater(func):
        def inner(*args, **kwargs):
            if br=='是':
                print('Y')
            else:
                print('N')
            return func(*args, **kwargs)
        return inner
    return decorater

@outer('是')
def fun():
    print('fun')

fun()

需要在外部再包一层函数。最外层函数的参数,是@后面的参数。里面就是之前定义的装饰器了

类装饰器

和函数式的装饰器效果一样,功能一样。

class dec():
    def __init__(self,func):
        self.func = func

    def __call__(self,*args,**kwargs):
        print('新增功能')
        return self.func(*args,**kwargs)

@dec
def add():
    print("add")

add()

基本上就是这个模板了

缓存装饰器和计时装饰器

  • 缓存装饰器
import time

class dec():
    __dkt = {}  # 定义一个类属性字典,键是函数的名称,值是函数的返回值
    def __init__(self,func):
        self.func = func

    def __call__(self,*args,**kwargs):
        print('新增功能')
        if self.func.__name__ not in dec.__dkt:
            dec.__dkt[self.func.__name__] = self.func(*args,**kwargs)  # 把函数的返回值储存在字典里面
        return dec.__dkt[self.func.__name__]

@dec
def add():
    print("add")
    time.sleep(1)
    return 100
t1 = time.time()
print(add())
t2 = time.time()
print(add())
t3 = time.time()
print(add())
t4 = time.time()
print(f't2-t1={t2-t1}\nt3-t2={t3-t2}\nt4-t3={t4-t3}')
  • 计时装饰器
import time

class dec():
    __dkt = {}  # 定义一个类属性字典,键是函数的名称,值是函数的返回值
    def __init__(self,func):
        self.func = func

    def __call__(self,*args,**kwargs):
        print('新增功能')
        if self.func.__name__ not in dec.__dkt:
            dec.__dkt[self.func.__name__] = self.func(*args,**kwargs)
        return dec.__dkt[self.func.__name__]

def outer(func):
    def inner(*args,**kwargs):
        start = time.time()
        func(*args,**kwargs)
        end = time.time()
        print(f'本次耗时{end-start}')
        return func(*args,**kwargs) 需要返回原函数
    return inner

@outer
@dec
def add():
    print("add")
    time.sleep(1)
    return 100

t1 = time.time()
print(add())
t2 = time.time()
print(add())
t3 = time.time()
print(add())
t4 = time.time()
print(f't2-t1={t2-t1}\nt3-t2={t3-t2}\nt4-t3={t4-t3}')

这是缓存装饰器+计时装饰器。以后遇到这个需求,套模板即可。

生成器generator

延时计算,边计算边给数据

ls = [x for x in range(1,10)]
print(ls)
ls1 = (x for x in range(1,10))
for x in range(3):
    print(ls1.__next__())

列表推导式,是把列表一次性处理完,再返回出来。无论这个列表用几个数据,都是先算完。

生成器,是用几个数据,就给几个数据,不用的不算。

这里我使用了for循环,次数是3.

后台的生成器,也只生成了3个数据给我。不浪费内存空间,不占用过多内存。

正是这个原因,每次循环都要重新调用生成器。会造成时间的浪费。所谓时间换空间。

使用next(生成器),和生成器加下划线name是一个原理

生成器函数

一个函数包含yield关键字,这个函数就是生成器对象了。

本质是利用yield关键字,一次只返回一个结果。

程序执行到yield就阻塞。当外部调用next方法时,才进行下一次运算

生成器函数遇到return语句就会终止,并抛出异常,异常的提示信息就是return语句后面的东西

def test():
    i = 6
    print('start')
    while i<11:
        yield i   第一次运行到这里结束,把i传出去
        i+=1
    print('end')
    return '用完了'
a = test()
print(a)  可以查看生成器的类型
print(a.__next__())  调用生成器一次,到yield语句,并把yield变量的值传递出去,输出6
print(a.__next__())yield语句下面开始,再到yield停止 输出7
print(a.__next__())  输出8

当生成器里面的循环结束的时候,如果还在调用生成器,就会报错,把return后面的语句打出去

  • 生成器的短处
def test():
    i = 6
    print('start')
    while i<11:
        t = yield i   程序是在yield i这个语句停止的,严格来说是return (yield i)
        i+=1
        print(t)
    print('end')
    return '用完了'
a = test()
print(a)
print(a.__next__())  唤醒生成器
print(a.__next__())
print(a.__next__())

因为生成器遇到yield就停止。然后i的值也传了出来。我们下一次执行生成器的时候,是从temp=开始的,后面的值是上一次生成器的内容。他们没办法直接赋值。所以temp就没有值了。

只是temp接收不到i的值,i自己的值还是有的。

生成器send方法

唤醒生成器,并发送信息到生成器内部。

def test():
    i = 6
    print('start')
    while i<11:
        t = yield i
        i+=1
        print(t)
    print('end')
    return '用完了'
a = test()
print(a)
print(next(a))
print(a.send('传给生成器'))
print(next(a))
print(next(a))

send本身也是一个唤醒器,就是可以传值进去。

使用next的时候,在生成器内部的t 没有自己的值,因为值被输出了。现在send又把一个变量传进去了,位置是之前yield所在的位置。所以t 可以获取到值了。

也只是这一次有了自己的值。下一次还会因为yield变量被传出来,而变成None。

生成器可以使用for遍历,遍历到生成器无法再次使用next

迭代器lterator

  • 特点
    • 只能往前,不能后退
    • 有两个基本方法:next,iter
    • 可以记住遍历位置的对象
    • 从第一个开始访问,直到最后不能访问

可迭代对象与迭代器

实现了iter方法的对象是可迭代对象leratable

同时实现iter和next方法的对象是迭代器lterator

  • 列表,字典,字符串是可迭代对象,不是迭代器

下面是判断,一个对象是不是可迭代对象,和判断一个对象是不是迭代器。返回值是布尔值

from collections.abc import Iterable, Iterator

a = isinstance([],Iterable)判断参数一是不是可迭代对象
b = isinstance([],Iterator)判断参数一是不是迭代器
print(a)
print(b)

使用iter方法,可以把可迭代对象转化成迭代器

from collections.abc import Iterable, Iterator

a = isinstance([],Iterable)
b = isinstance(iter([]),Iterator) 把可迭代对象转化成迭代器
print(a)
print(b)
  • 迭代器也是惰性对象,用一个数据出一个数据。
  • 生成器一定是迭代器。
  • 序列一般是可迭代对象
  • 可迭代对象可以变成迭代器
  • 迭代器可以当可迭代对象去使用
  • 迭代器可以使用next方法去获取值。

for循环的本质

对python3而言,for循环的本质也是一个迭代器,每次循环就是调用一次next方法

ls = iter([4,6,8,9,3])

while True:
    try:
        x = next(ls)  获取迭代器的值,一次取一个
        
        print(x)这是我们拿到单个值进行的运算
        
    except StopIteration:   用完数据就退出
        break

创建迭代器

使用类来创建迭代器

需要重写iter方法和next方法

iter需要返回一个迭代器对象

next方法就是返回下一个值

StopIteration迭代器专属异常报错

class myiterator():
    def __iter__(self):
        self.number = 11
        return self
    def __next__(self):
        if self.number < 100:
            self.number += 11
            return self.number
        else:
            raise StopIteration手动抛异常

ls = iter(myiterator())
# ls = myiterator().__iter__() 类似初始化,把可迭代对象变成迭代器
print(next(ls))
print(next(ls))
print(next(ls))
print(next(ls))

给类对象添加属性和方法

class person():
    def __init__(self, name, age):
        self.name = name
        self.age = age


p1 = person("John", 20)
p2 = person("Jimi", 25)

def run(self):
    print(f'{self.name} is {self.age} years old')

# 添加对象属性
p1.number = 10
print(p1.number) 成功
p1.run = run()  失败
p1.run() 失败

如果我们直接使用赋值的方式,只能给对象添加属性。不能添加方法

import types
class person():
    def __init__(self, name, age):
        self.name = name
        self.age = age


p1 = person("John", 20)
p2 = person("Jimi", 25)

def run(self):
    print(f'{self.name} is {self.age} years old')

# 添加对象属性
p1.number = 10
print(p1.number)

p1.run = types.MethodType(run, p1) 只能使用这个方法,动态添加方法到对象里面,参数一是方法,参数二是类对象
p1.run()

给类动态增加类方法

直接使用静态装饰器staticmethod和类装饰器classmethod去定义方法,直接就能添加到已有的类里面

class person():
    def __init__(self, name, age):
        self.name = name
        self.age = age

@staticmethod
def add():
    print('add')
person.add = add

@classmethod
def run(cls):
    print(111)
person.run = run

person.add()
person.run()

slots方法

只对实例对象的动态添加属性和方法有限制。

对类对象,没有限制。

只对slots所在的类有限制,对子类没有限制

class person():
    __slots__ = {'name','age'} # 类的实例化对象只能在本类中增加这两个属性
    def __init__(self, name, age):
        self.name = name
        self.age = age


p1 = person("John", 20)
p1.num = 10  报错了,不让添加

就是只能添加slots里面的属性。不能添加别的。

python小技巧

  • 格式化字符串f ‘{}’’
a = 10
print(f'。。。{a}。。。')  结果是10  只有变量的值被打印出来了
print(f'。。。{a=}。。。')  结果是a=10  是 变量=值 这种格式打印出来
print(f'{你好:-^20}')  打印结果就是----------你好----------,居中打印变量,空位用^前面的符号填满
print(f'{你好:->20}')  内容靠右打印 
print(f'{你好:-<20}')   内容靠左打印

b = 12.345
print(f'{b:.2f}') 就可以直接对b进行保留小数点后两位的操作了,更方便了
  • 删除前缀removeprefix/删除后缀removesuffix
aa = 'helloworld'
print(aa.removeprefix('hello'))  输出world
print(aa.removesuffix('ld'))  输出hellowor

如果参数里面的字符不是aa的前后缀,那么aa将正常原始内容输出,不会缺失

  • mapping,我们在使用字典的时候,可能会单独使用keys或者values。使用keys.mapping,可以得到原字典的内容,可以被输出。
  • 字典合并:两个字典使用或| ,就可以得到一个合并的字典了。字典1= 字典1|字典2.还可以简写成: 字典1|=字典2
  • match和case。我们在使用switch…case语句的时候,在case语句里面需要使用break分隔每个case的代码。现在只需要把switch变成match。case里面就不需要break隔断了。其余保持不变
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

兆。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值