10、python函数(下)——旺仔

在这里插入图片描述

函数(下)

12、高阶函数

  • 定义:变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
# 高阶函数的特点(必须至少满足一个):
# 1. 接受一个或多个函数作为参数
# 2. 将函数作为返回值返回
def fun():
    def fun1():
        pass

    return fun1
  • 我们先来看一个普通的函数

这个函数是对列表list1进行筛选,找到是偶数的元素

list1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]


def fun(list1):
    list2 = []
    for i in list1:
        if i % 2 == 0:
            list2.append(i)
    return list2


print(fun(list1))
运行结果 》》》[2, 4, 6, 8, 10]
  • 接下来对普通的函数进行更改
list1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]


def fun(list1):

    def fun1(i):
        if i % 2 == 0:
            return True

    list2 = []
    for i in list1:
        if fun1(i):
            list2.append(i)
    return list2


print(fun(list1))
运行结果 》》》[2, 4, 6, 8, 10]

  我们这里将判断条件拿出来在做一个函数fun1,然后再fun函数中调用fun1函数,虽然他看似已经是高阶函数,但他还是不满足高阶函数的特点,因为他并没有将函数作为返回值返回,也没有接受一个或多个函数作为参数,所以我们还要对其再一步更改。

  • 更改为高阶函数
list1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

def fun1(i):
    if i % 2 == 0:
        return True

def fun(fun, list1):
    list2 = []
    for i in list1:
        if fun(i):
            list2.append(i)
    return list2


print(fun(fun1, list1))
运行结果 》》》[2, 4, 6, 8, 10]

这样更改,就满足了接受一个或多个函数作为参数这个特点,这样我们就完成了我们的高阶函数。

13、匿名函数

  匿名函数,就跟他的字面意思一样,我们不对函数进行命名,因为我们在开发的时候是协同开发,如果每个人写一个函数,都对其进行一个命名的话,会非常占用命名空间,对同事也不是特别友好,因为不能取相同的函数名,不然函数将会被覆盖,所以必须将一些非常简单效果的函数写成匿名函数

  • 匿名函数 lambda函数 就是专门用来创建一些简单的函数

filter类

  • filter() 需要传递两个参数 按照你设定的规则,过滤出你想要的数据
    1、 传递一个函数
    2 、传递一个需要过滤的序列(可迭代的)

  • filter类的使用:依旧是对数据筛选的更改

# 这还是高阶函数喔
list1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]


def fun1(i):
    if i % 2 == 0:
        return True
print(list(filter(fun1, list1)))
运行结果 》》》[2, 4, 6, 8, 10]

lambda函数

  • 下面是一些lambda函数示例:
函数示例说明
lambda x, y: x*y函数输入是x和y,输出是它们的积x*y
lambda:None函数没有输入参数,输出是None
lambda *args: sum(args)输入是任意个数的参数,输出是它们的和(隐性要求是输入参数必须能够进行加法运算)
lambda **kwargs: 1输入是任意键值对参数,输出是1
  • 实际操作(对任意2个数进行添加并输出的操作):
# 可以直接这样写
print((lambda a, b: a + b)(10, 20))
运行结果 》》》30
# 另一种写法
r = lambda a, b: a + b
print(r(10, 20))
运行结果 》》》30

filter类 与 lambda函数结合使用写高阶函数

  还是前面数据筛选的需求,这一次我们将fun1()用lambda函数代替,再加上filter类的使用,既简化了我们的代码,效果也非常好,还没有函数重名的现象。

list1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result = lambda i: i % 2 == 0
print(list(filter(result, list1)))
运行结果 》》》[2, 4, 6, 8, 10]

  我这里对lambda函数写的比较浅,为大家找到了一遍比较好的lambda函数的博客,感兴趣的小伙伴可以看一下喔!!博客链接

14、闭包

  闭包代码原理是c语言编写,咱们不用必须弄懂他,当然,我还是找了一篇博客,感兴趣的朋友可以看看,博客链接

  • 闭包是将函数作为返回值也是高阶函数我们也称为闭包,也就是说闭包必须有函数嵌套
  • 闭包的好处
    通过闭包可以创建一些只有当前函数能访问的变量
    可以将一些私有数据藏到闭包中
  • 行成闭包的条件
    函数嵌套
    将内部函数作为返回值返回
    内部函数必须要使用到外部函数的变量

  在讲闭包前必须要了解:函数在运行结束后变量会被内存清楚掉,也就是说变量会被销毁掉,不是很明白的话可以看一下——python的垃圾回收机制,正因如此,我们想要将数据保存起来,不被销毁,我们的闭包的第一个特性就能派上用场

  • 闭包的第一个特性:变量不被销毁
def func_out(num1):
    def func_inner(num2):     # 函数嵌套
        result = num1 + num2  # 外层参数调用
        print(result)

    return func_inner   # 将内部函数作为返回值返回


f = func_out(1)  # 给num1传一个1
f(2)             # 函数调用,给num2传一个2
运行结果 》》》3
f(3)
运行结果 》》》4

  这里我们就可以看到闭包的这一特性,在f(2)运行后,并不会将num1的值销毁,所以在f(3)执行的时候,依旧可以调用num1的值

  • 闭包的第二个特性:变量不可被更改
def func_out(num1):
    def func_inner(num2):     # 函数嵌套
        num1 = 10
        result = num1 + num2  # 外层参数调用
        print(result)

    print(num1)         #这里就可以很清晰的看到num1的变化
    func_inner(2)
    print(num1)

    return func_inner   # 将内部函数作为返回值返回


func_out(1)   # 函数对象是func_out   函数调用是func_out()

运行结果 》》》
1
12
1

  首先我们这样更改,在内层函数中加上了num1=10,我们等于没有调用外层函数的参数num1,这个外层函数的num1行参内层函数的变量num1压根就不是一个东西,所以这已经不满足闭包的条件:内部函数必须要使用到外部函数的变量,所以这个函数连闭包都不是。

  • 闭包中让外部变量可以修改

  我们先要了解一个相对的概念,如上述的num1,在整个py文件中,他是局部变量,但相对于内部函数,他就是全局变量,这里我们说的让外部变量修改就是对num1进行更改。
这里我们用到了nonlocal,也就是非本地的意思,将num1变量改成外部的参数,这样我们又形成了一个闭包,也让外部变量num1变得可有修改。

def func_out(num1):

    def func_inner(num2):     # 函数嵌套
        # 告诉解释器,此处使用的是外部变量num1
        nonlocal num1
        # 这里的本意是要修改外部变量num1的值,实际上是重新进行了赋值
        num1 = 10
        result = num1 + num2  # 外层参数调用
        print(result)

    print(num1)
    func_inner(2)
    print(num1)

    return func_inner   # 将内部函数作为返回值返回


func_out(1)   # 函数对象是func_out   函数调用是func_out()
运行结果 》》》
1
12
10

15、装饰器的引入

  • 为什么要引入装饰器?

我们可以直接通过修改函数中的代码来完成需求,但是会产生以下一些问题

  • 如果修改的函数多,修改起来会比较麻烦
  • 不方便后期的维护
  • 这样做会违反开闭原则(ocp)
    程序的设计,要求开发对程序的扩展,要关闭对程序的修改
  • 我们这里有一个简单的函数
def add(a, b):
    return a + b
  • 如果我们直接,修改,如下,就会违反开闭原则
# 不要这样使用
def fun1():
    print('函数开始执行')
    print('我是fun1函数')
    print('函数执行完毕')
  • 正确使用装饰

装饰fun1()函数

def fun1():
    print('我是fun1函数')


def fun():
    print('函数开始执行')
    fun1()
    print('函数执行完毕')


fun()
运行结果 》》》
函数开始执行
我是fun1函数
函数执行完毕

装饰add()函数

def add(a, b):
    return a + b


def fun(a, b):
    print('函数开始执行')
    result = add(a, b)
    print(result)
    print('函数执行完毕')


fun(1, 2)
运行结果 》》》
函数开始执行
3
函数执行完毕
  • 这样使用装饰器虽然也可以,但是我们发现每次装饰都要改很多东西,所以我们讲一下通用装饰器
def fun1():
    print('我是fun1函数')


def add(a, b):
    return a + b


def fun(fn, *args, **kwargs):
    print('函数开始执行')
    r = fn(*args, **kwargs)
    print(r)
    print('函数执行完毕')


fun(add, 1, 2)
运行结果 》》》
函数开始执行
3
函数执行完毕

fun(fun1)
运行结果 》》》
函数开始执行
我是fun1函数
None
函数执行完毕

  我们将fun函数定义几个行参,第一次是fn,可以传不同的函数,后面就是不定长参数。之后在调用的时候,实参写上我们想要传递的参数就可以辽!!!

16、装饰器的使用

  • 前面的装饰运用只是简单介绍一下概念使用,但是他还不是正式的装饰器,因为正式的装饰器是一个闭包
  • 通过装饰器,可以在不修改原来函数的情况下来对函数进行扩展
  • 在开发中,我们都是通过装饰器来扩展函数的功能的

闭包条件:
函数嵌套
将内部函数作为返回值返回
内部函数必须要使用到外部函数的变量

  • 拿上述对add()函数装饰进行修改
def add(a, b):
    return a + b


def fun_out(fn, *args, **kwargs):

    def fun_inner():
        print('函数开始执行')
        r = fn(*args, **kwargs)
        print(r)
        print('函数执行完毕')

    return fun_inner


f = fun_out(add, 1, 2)  
f()
运行结果 》》》
函数开始执行
3
函数执行完毕
  • 写到这里肯定会产生很多问号,为啥子要闭包,还嵌套一个东西那么麻烦,结果效果还一样,就像我刚开始听完这个,其实上面那样用还没有满足我们的需求,因为我们每次需要装饰函数的时候,都得重新修改,很是麻烦,所以我们还要讲一下@的用法。
# 通用装饰器
def fun_out(fn, *args, **kwargs):

    def fun_inner(*args, **kwargs):
        print('函数开始执行')
        r = fn(*args, **kwargs)
        print(r)
        print('函数执行完毕')

    return fun_inner


# 需要被装饰的函数
@fun_out            # 等价于 f=fun_out(fun)
def add(a, b):
    return a + b

add(1, 2)
运行结果 》》》
函数开始执行
3
函数执行完毕

  我们举个例子介绍一下为什么要装饰,当一个功能是公用的时候,我们将其写成装饰,就会很方便,比如我们在逛淘宝的时候,不管我们干什么,都需要进行登录,我们就可以把登录的程序写成装饰,然后我们在写其他功能的时候就可以直接@他,直接修改我们需要装饰的函数即可。就不用每写一次都修改各种参数。

PEP8代码编写规范

PEP8: Python代码风格指南——此处引用Cheney老师文章
PEP8 提供了 Python 代码的编写约定. 本节知识点旨在提高代码的可读性, 并使其在各种 Python 代码中编写风格保持一致.
虽然不遵守 PEP8 规范不会报错,但是会出现波浪线,而且遵守了规范看起来也很舒服!!

  1. 缩进使用4个空格, 空格是首选的缩进方式. Python3 不允许混合使用制表符和空格来缩进.
  2. 每一行最大长度限制在79个字符以内.
  3. 顶层函数、类的定义, 前后使用两个空行隔开.
  4. import 导入
    导入建议在不同的行, 例如:
import os
import sys

不建议如下导包

import os, sys

但是可以如下:

from subprocess import Popen, PIPE
  1. 导包位于文件顶部, 在模块注释、文档字符串之后, 全局变量、常量之前. 导入按照以下顺序分组:
    标准库导入
    相关第三方导入
    本地应用/库导入
    在每一组导入之间加入空行
  2. Python 中定义字符串使用双引号、单引号是相同的, 尽量保持使用同一方式定义字符串. 当一个字符串包含单引号或者双引号时, 在最外层使用不同的符号来避免使用反斜杠转义, 从而提高可读性.
  3. 表达式和语句中的空格:

避免在小括号、方括号、花括号后跟空格.
避免在逗号、分好、冒号之前添加空格.
冒号在切片中就像二元运算符, 两边要有相同数量的空格. 如果某个切片参数省略, 空格也省略.
避免为了和另外一个赋值语句对齐, 在赋值运算符附加多个空格.
避免在表达式尾部添加空格, 因为尾部空格通常看不见, 会产生混乱.
总是在二元运算符两边加一个空格, 赋值(=),增量赋值(+=,-=),比较(==,<,>,!=,<>,<=,>=,in,not,in,is,is not),布尔(and, or, not
避免将小的代码块和 if/for/while 放在同一行, 要避免代码行太长.

if foo == 'blah': do_blah_thing()
for x in lst: total += x
while t < 10: t = delay()
  1. 永远不要使用字母 ‘l’(小写的L), ‘O’(大写的O), 或者 ‘I’(大写的I) 作为单字符变量名. 在有些字体里, 这些字符无法和数字0和1区分, 如果想用 ‘l’, 用 ‘L’ 代替.
  2. 类名一般使用首字母大写的约定.
  3. 函数名应该小写, 如果想提高可读性可以用下划线分隔.
  4. 如果函数的参数名和已有的关键词冲突, 在最后加单一下划线比缩写或随意拼写更好. 因此 class_ 比 clss 更好.(也许最好用同义词来避免这种冲突).
  5. 方法名和实例变量使用下划线分割的小写单词, 以提高可读性.

课后作业

1. 请使用装饰器实现已存在的函数的执行所花费的时间。

  • time模块

简单介绍一下time模块
1、延迟功能

import time    #一定要导入time模块,下面其他的就不导入了
# 延迟功能
# 想让程序停顿几秒钟

# time.sleep(秒数)

# 例如

print(1)
time.sleep(2)
print(2)

# 结果,在打印了1后会停2秒,然后再打印2

2、获取时间戳

  • 时间戳是指格林威治时间自1970年1月1日(00:00:00 GMT)至当前时间的总秒数。
print(time.time())
运行结果 》》》
1612519773.541305

3、时间戳转化为元组

now_time = time.localtime(time.time())
print(now_time)

运行结果 》》》
time.struct_time(tm_year=2021, tm_mon=2, tm_mday=5, tm_hour=18, tm_min=9, tm_sec=33, tm_wday=4, tm_yday=36, tm_isdst=0)
序号属性
0tm_ year2021(当年的年份)
1tm_ mon1到12 (当前的月份)
2tm_ _mday1到31 (当前的天)
3tm_ hour0到23 (时)
4tm_ _min0到59 (分)
5tm_ sec0到61 (60或61是闰秒) (秒)
6tm_ wday0到6 (0是周一) (周)
7tm_ yday1到366(儒略历) (一年的第多少天)
8tm_ isdst-1, 0, 1, -1是决定是否为夏令时的旗帜

4、格式化时间

res = time.strftime("%Y-%m-%d-%H-%M-%S")
print(res)
运行结果 》》》
2021-02-05-18-47-35
符号含义
%a本地(locale)简化星期名称
%A本地完整星期名称
%b本地简化月份名称
%B本地完整月份名称
%c本地相应的日期和时间表示
%d一个月中的第几天(01 - 31)
%H一天中的第几个小时(24 小时制,00 - 23)
%l一天中的第几个小时(12 小时制,01 - 12)
%j一年中的第几天(001 - 366)
%m月份(01 - 12)
%M分钟数(00 - 59)
%p本地 am 或者 pm 的相应符
%S秒(01 - 61)
%U一年中的星期数(00 - 53 星期天是一个星期的开始)第一个星期天之前的所有天数都放在第 0 周
%w一个星期中的第几天(0 - 6,0 是星期天)
%W和 %U 基本相同,不同的是 %W 以星期一为一个星期的开始
%x本地相应日期
%X本地相应时间
%y去掉世纪的年份(00 - 99)
%Y完整的年份
%z用 +HHMM 或 -HHMM 表示距离格林威治的时区偏移(H 代表十进制的小时数,M 代表十进制的分钟数)
%Z时区的名字(如果不存在为空字符)
%%%号本身
  • 接下来我们进入实例(时间戳的使用):
# 求前20个斐波那契数列所需时间
import time
a = time.time()

# 使用函数求前20个斐波那契数列 fun(n)=fun(n-1)+fun(n-2)
def fun(n):
    # 基线条件
    if n <= 1:
        return 1
    # 递归条件
    return fun(n-1)+fun(n-2)
for i in range(20):
    print(fun(i), end=' ')

print()     # 换行
b = time.time()
c = round((b-a),3) # 取小数点后3位并四舍五入
print(f'斐波那契数列函数运行一共花了{c}秒')
运行结果 》》》
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 
斐波那契数列函数运行一共花了0.005秒
  • 知识点运用及编写思路:

  函数用的是上期博客中的求前20个斐波那契数列 。用time模块中时间戳,在函数运行前获取一次时间戳,函数结束后再获取一次时间戳,然后相减,但是他是小数点后6位的一个数字,我们这里用来round函数对其进行小数点后3位的四舍五入,最后得到只用0.005秒就可以运行完此函数。

  • 时间戳装饰
# 求前20个斐波那契数列所需时间
import time

def get_time(fun1):
    def inner():
        now = time.time()
        fun1()
        end = time.time()
        c = round((end - now), 3)
        print(f'斐波那契数列函数运行一共花了{c}秒')
    return inner


# 使用函数求前20个斐波那契数列 fun(n)=fun(n-1)+fun(n-2)
def fun(n):
    # 基线条件
    if n <= 1:
        return 1
    # 递归条件
    return fun(n-1)+fun(n-2)

@get_time
def fun1():
    list1 = []
    for i in range(20):
        list1.append(fun(i))
    print(list1)


fun1()
运行结果 》》》
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]
斐波那契数列函数运行一共花了0.005秒
  • 知识点运用及编写思路:

  这里我们将遍历也改成一个函数fun1(),并将每次调用fun()函数得到的返回值添加到列表list1[],然后对fun1()进行装饰,如上代码,把获取时间的时间戳写成装饰器,并调用fun1()函数,fun1()函数在运行中调用fun()函数,最后得到效果。

在这里插入图片描述

本期没有旺仔注:觉得我写的还行就点个赞吧,您的支持是给我最好的动力!!!ヾ(≧ ▽ ≦)ゝ

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值