python函数进阶

闭包函数

闭包函数=函数对象+函数嵌套+名称空间作用域

闭:指的是该函数是定义在函数内的函数

包:指的是该函数引用了一个外层函数作用域的名字

因而无论在何处调用闭包函数,使用的仍然是包裹在其外层的变量。

为函数体代码传参的方案

# 方案一:直接用参数传值
def wrappera(x):
    print(x)
wrappera(111)
# # 方案二:闭包函数
def outer(x):
    # x = 111
    def wrapper():
        print(x)
    return wrapper
wrapper = outer(111)
wrapper()  # 111

两种为函数体传值的方式

import requests

#方式一:直接将值以参数的形式传入
def get(url):
    return requests.get(url).text

#方式二:将值包给函数
def page(url):
    def get():
        return requests.get(url).text
    return get

装饰器

详情参见:https://zhuanlan.zhihu.com/p/109078881

1、什么是装饰器

’装饰’代指为被装饰对象添加新的功能,’器’代指器具/工具,装饰器与被装饰的对象均可以是任意可调用对象。

装饰器就是一个用来为被装饰对象添加新功能的工具 

2、为何要用装饰器

开放封闭原则:一旦软件上线运行之后,应该对修改源代码封闭,对扩展功能开放
  原则:1、不修改函数内的源代码
        2、不修改函数的调用方式
  装饰器就是在遵循原则1和原则2的前提下,为被装饰对象添加上新功能

3、装饰器的实现

函数装饰器分为:无参装饰器和有参装饰两种,二者的实现原理一样,都是’函数嵌套+闭包+函数对象’的组合使用的产物。

4、无参装饰器

无参装饰器模板

from functools import wraps
def outer(func):
    @wraps(func)
    def wrapper(*args,**kwargs):
        res = func(*args,**kwargs)
        return res
    return wrapper

@outer
def index():
    print('index')
    
index()

登录认证功能

from functools import wraps

def login(func):
    @wraps(func)
    def wrapper(*args,**kwargs):
        inp_name = input('Username >>>: ').strip()
        inp_pwd = input('Password >>>:').strip()
        if inp_name == 'egon' and inp_pwd == '123':
            res = func(*args,**kwargs)
            return res
        else:
            print('Username or Password Error')
    return wrapper

@login
def index():
    print('index')
index()

5、有参装饰器

from functools import wraps
def auth(driver = 'file'):
    def auth2(func):
        @wraps(func)  # 使装饰器更完美
        def wrapper(*args,**kwargs):
            name = input('Username >>>: ').strip()
            pwd = input('Password >>>: ').strip()

            if driver == 'file':
                if name == 'egon' and pwd == '123':
                    print('login successful')
                    res = func(*args,**kwargs)
                    return res
                else:
                    print('Username or Password Error')
            elif driver == 'mysql':
                print('mysql')
        return wrapper
    return auth2

@auth(driver='file')
def foo(name):
    print('welcome to %s' %name)

foo('zhzhhang')

6、叠加多个装饰器

1、加载顺序(outter函数的调用顺序):自下而上
2、执行顺序(wrapper函数的执行顺序):自上而下
def outter1(func1): #func1=wrapper2的内存地址
    print('加载了outter1')
    def wrapper1(*args,**kwargs):
        print('执行了wrapper1')
        res1=func1(*args,**kwargs)
        return res1
    return wrapper1

def outter2(func2): #func2=wrapper3的内存地址
    print('加载了outter2')
    def wrapper2(*args,**kwargs):
        print('执行了wrapper2')
        res2=func2(*args,**kwargs)
        return res2
    return wrapper2

def outter3(func3): # func3=最原始的那个index的内存地址
    print('加载了outter3')
    def wrapper3(*args,**kwargs):
        print('执行了wrapper3')
        res3=func3(*args,**kwargs)
        return res3
    return wrapper3



@outter1 # outter1(wrapper2的内存地址)======>index=wrapper1的内存地址
@outter2 # outter2(wrapper3的内存地址)======>wrapper2的内存地址
@outter3 # outter3(最原始的那个index的内存地址)===>wrapper3的内存地址
def index():
    print('from index')

print('======================================================')
index()


>>>:
加载了outter3
加载了outter2
加载了outter1
======================================================
执行了wrapper1
执行了wrapper2
执行了wrapper3
from index

7、@wraps

@wraps可以保证装饰器修饰的函数的name的值保持不变
使装饰器更加的完美
from functools import wraps
# 对函数的装饰器, 对类func最好为cls
def decorate(func):
    @wraps(func)
    # 增添或修改功能的函数
    def wrapper(*args,**kwargs):
                # 执行被装饰的函数
        result = func(*args,**kwargs) 
        # 返回结果
        return result
    # 返回内层函数
    return wrapper

8、装饰器执行步骤解析

import time                                 #1.加载模块
def timmer(*args,**kwargs):                     #2.加载变量  5.接收参数True,2,3
    def wrapper(f):                             #6.加载变量  8.f = func1
        print(args, kwargs)                     #9.接收timmer函数的值True,2,3
        def inner(*args,**kwargs):                 #10.加载变量. 13.执行函数inner
            if flag:                         #14 flag = True
                start_time = time.time()             #15 获取当前时间
                ret = f(*args,**kwargs)             #16 执行func1
                time.sleep(0.3)                 #19 等待0.3秒
                end_time = time.time()             #20 获取当前时间
                print('此函数的执行效率%f' % (end_time-start_time)) #21 打印差值
            else:
                ret = f(*args, **kwargs)
            return ret                         #22 返回给函数调用者func1()
        return inner                         #11 返回给函数调用者wrapper
    return wrapper                         #7.返回给函数调用timmer(flag,2,3)
flag = True                                 #3 加载变量
@timmer(flag,2,3)      # 4.执行函数timmer(flag,2,3) 17.执行函数func1 两步:1.timmer(flag,2,3) 相当于执行wrapper  2.@wrapper 装饰器 func1 = wrapper(func1)
def func1(*args,**kwargs):
    return 666                             #18 返回给函数调用者f(*args,**kwargs)
print(func1())                             #12 执行函数 

9、面试题

"""
1.现有三个普通函数a,b,c都需要用户登陆之后才能访问。现需要你写一个装饰器校验用户是否登陆,并且用户只要登陆一次其他函数在调用时也无需再校验
"""
# 事先建立一个字典,如果用户已登录,记录用户登陆信息,如果没有登陆,则为空
user_dic={}

def auth(func):
    def inner(*args, **kwargs):
        if user_dic:
            res=func(*args,**kwargs)
            return res
        else:
            print("该用户未登录")
            login()   # 调用登录功能
    return inner
    
 @auth
 a()

迭代器

1、什么是迭代器

迭代器指的是迭代取值的工具

什么是迭代

迭代就是一个重复的过程,但是每一次重复都是在上一次的基础之上进行的

2、为何要用迭代器

1、迭代器提供了一种不依赖于索引的、通用的迭代取值方案
2、节省内存

3、如何使用迭代器

可迭代对象

1、内置有__iter__方法的对象都叫可迭代的对象

调用可迭代对象的__iter__方法,返回的是它的迭代器

迭代器对象

1、内置有__next__方法
2、内置有__iter__方法

注意

迭代器对象一定是可迭代对象,而可迭代对象不一定是迭代器对象

调用迭代器对象的iter方法得到的它自己,就跟没调用一样
调用迭代器对象的next方法返回下一个值,不依赖索引
可以一直调用next直到取干净,则抛出异常Stoplteration
# 内置的类型都是可迭代的对象
'abc'.__iter__()
[1,2,3].__iter__()
(1,2,3).__iter__()
{'k1':111}.__iter__()
{1,2,3}.__iter__()

f = open('db.txt','w') # 文件对象本身就是迭代器对象
f.__iter__()
f.__next__()

4、for循环原理

# while循环
nums  = [1,2,3]
num_iter = iter(nums)
while True:
    try:
        res = next(num_iter)
        print(res)
    except StopIteration:
        break

# for循环
# 1、先调用in后面那个对象的__iter__方法,拿到迭代器对象
# 2、res = next(迭代器对象),执行一次循环体代码
# 3、循环往复步骤2,直到值取干净抛出异常StopIteration,for循环会捕捉异常结束循环
for res in nums:
    print(res)

5、迭代器的优缺点

优点

1、为序列和非序列类型提供了一种统一的迭代取值方式。

2、惰性计算:迭代器对象表示的是一个数据流,可以只在需要时才去调用next来计算出一个值,就迭代器本身来说,
同一时刻在内存中只有一个值,因而可以存放无限大的数据流,而对于其他容器类型,如列表,需要把所有的元素都存放于内存中,受内存大小的限制,可以存放的值的个数是有限的。

缺点

1、除非取尽,否则无法获取迭代器的长度

2、只能取下一个值,不能回到开始,更像是‘一次性的’,迭代器产生后的唯一目标就是重复执行next方法直到值取尽,
否则就会停留在某个位置,等待下一次调用next;若是要再次迭代同个对象,你只能重新调用iter方法去创建一个新的迭代器对象,如果有两个或者多个循环使用同一个迭代器,必然只会有一个循环能取到值。

6、自定义迭代器的方案

(1)列表推导式

列表生成式语法是固定的,[]里面for 前面是对列表里面数据的运算操作,后面跟平常for循序一样遍历去读取,运行后会自动生成新的列表。

l = []
for i in range(10):
    l.append(i)
print(l)  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

res = [x for x in range(10)]
print(res)  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

(2)用yield生成器

创建一个生成器对象有两种方式,一种是调用带yield关键字的函数
def func():
    print('xxx')
    yield 111
    print('yyy')
    yield 222
    print('zzz')
    yield 333
# 当函数内出现yield关键字,再调用函数并不会触发函数体代码,会返回一个生成器
g = func()
# print(g)  # 生成器就是一个自定义的迭代器
res = next(g)  # xxx
# print(res)   # 111



另一种就是生成器表达式,与列表生成式的语法格式相同,只需要将[]换成(),即:
>>> [x*x for x in range(3)]
[0, 1, 4]
>>> g=(x*x for x in range(3))
>>> g
<generator object <genexpr> at 0x101be0ba0>

生成器

详情参见:https://zhuanlan.zhihu.com/p/109084444

1、什么是生成器

若函数体包含yield关键字,再调用函数,并不会执行函数体代码,得到的返回值即生成器对象,并且不会执行函数内部代码。

生成器内置有__iter__和__next__方法,所以生成器本身就是一个迭代器。

2、yield表达式应用

def func():
    print('xxx')
    yield 111
    print('yyy')
    yield 222
    print('zzz')
    yield 333
# 当函数内出现yield关键字,再调用函数并不会触发函数体代码,会返回一个生成器
g = func()
# print(g)  # 生成器就是一个自定义的迭代器
res = next(g)  # xxx
# print(res)   # 111

3、yield与return的异同

相同点:返回值层面用法一样

不同点:return只能返回值一次,而yield可以返回多次

4、小案例

def func():
    res = 1
    while True:
        yield res
        res+=1
        print(res)
g = func()
next(g)
next(g)
next(g)

三元表达式

三元表达式是python为我们提供的一种简化代码的解决方案,语法如下

res = 条件成立时返回的值 if 条件 else 条件不成立时返回的值

针对下述场景
def max2(x,y): if x > y: return x else: return y res = max2(1,2)
用三元表达式可以一行解决 x
=1 y=2 res = x if x > y else y # 三元表达式

列表生成式

1、列表生成式

列表生成式语法是固定的,[]里面for 前面是对列表里面数据的运算操作,后面跟平常for循序一样遍历去读取,运行后会自动生成新的列表。

l = []
for i in range(10):
    l.append(i)
print(l)  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

res = [x for x in range(10)]
print(res)  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

小案例

# 案例一:
res = [x for x in range(10) if x > 5]
print(res)

# 案例二:
names = ['lxx','hxx','wxx','lili']
res = [ name+'sb'  for name in names]
print(res)  #   ['lxxsb', 'hxxsb', 'wxxsb', 'lilisb']

# 案例三:
names = ['egon','lxx_sb','hxx_sb','wxx_sb']
res = [name for name in names if name.endswith('sb')]
print(res)  # ['lxx_sb', 'hxx_sb', 'wxx_sb']

2、字典生成式

res = {i:i**2 for i in range(5)}
print(res)  # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

items = [('k1',111),('k2',222),('k3',333)]
print(dict(items))  # {'k1': 111, 'k2': 222, 'k3': 333}
print({k:v for k,v in items})  # print({k:v for k,v in items})  #

3、集合生成式

res = {i for i in range(5)}
print(res)

生成器表达式

创建一个生成器对象有两种方式,一种是调用带yield关键字的函数,另一种就是生成器表达式,与列表生成式的语法格式相同,只需要将[]换成(),即:

对比列表生成式返回的是一个列表,生成器表达式返回的是一个生成器对象


>>> [x*x for x in range(3)]
[0, 1, 4]
>>> g=(x*x for x in range(3))
>>> g
<generator object <genexpr> at 0x101be0ba0>

对比列表生成式,生成器表达式的优点自然是节省内存(一次只产生一个值在内存中)
如果我们要读取一个大文件的字节数,应该基于生成器表达式的方式完成

函数递归

详情参见:https://zhuanlan.zhihu.com/p/109119148

在调用一个函数的内部又调用自己,所以递归调用的本质就是一个循环的过程

大前提:递归调用一定要在某一层结束

递归的两个阶段:
    1、回溯:向下一层一层挖井
    2、递推:向上一层一层返回

items
=[[1,2],3,[4,[5,[6,7]]]] def func(items): for x in items: if type(x) is list: # 满足未遍历完items以及if判断成立的条件时,一直进行调用 func(x) else: print(x,end=' ') func(items)

匿名函数

详情参见:https://www.cnblogs.com/linhaifeng/articles/7580830.html#_label3

1、什么是匿名函数

没有名字的函数
特定:临时用一次

​ 对比使用def关键字创建的是有名字的函数,使用lambda关键字创建则是没有名字的函数,即匿名函数,语法如下

lambda 参数1,参数2,...: expression

举例

# 1、定义
lambda x,y,z:x+y+z

#等同于
def func(x,y,z):
    return x+y+z

# 2、调用
# 方式一:
res=(lambda x,y,z:x+y+z)(1,2,3)

# 方式二:
func=lambda x,y,z:x+y+z # “匿名”的本质就是要没有名字,所以此处为匿名函数指定名字是没有意义的
res=func(1,2,3)

2、有名字的函数与匿名函数的对比

有名函数:循环使用,保存了名字,通过名字就可以重复引用函数功能

匿名函数:一次性使用,随时随时定义


"""
匿名函数与有名函数有相同的作用域,但是匿名意味着引用计数为0,使用一次就释放,所以匿名函数用于临时使用一次的场景,匿名函数通常与其他函数配合使用。
"""

3、应用

# map
names = ['lxx','hxx','lili']
l = [name + 'sb' for name in names]

res = map(lambda name:name+'sb',names)
print(list(res))
# print(res.__next__())

# filter
names = ['lxx_sb','hxx','lili_sb','egon']
res = [name for name in names if name.endswith('sb')]
print(res)  # ['lxx_sb', 'lili_sb']

res = filter(lambda name:name.enfswith('sb'),names)
print(list(res))

# reduce
from functools import reduce
res = reduce(lambda x,y:x+y,[1,2,3])
print(res)  # 6

4、小案例

salariex = {
    'egon':3000,
    'tom':100000,
    'zxx':100
}

max、min、sorted原理解析详情参见:https://blog.csdn.net/weixin_30642561/article/details/95944976
"""
max(iterable, key, default) 求迭代器的最大值,其中iterable 为迭代器,max会for i in … 遍历一遍这个迭代器,
然后将迭代器的每一个返回值当做参数传给key=func 中的func(一般用lambda表达式定义) ,然后将func的执行结果传给key,然后以key为标准进行大小的判断。
""" print(max(salariex,key= lambda key:salariex[key])) # tom print(min(salariex,key= lambda key:salariex[key])) # zxx # 默认从小到大 print(sorted(salariex,key= lambda key:salariex[key])) # ['zxx', 'egon', 'tom'] # 从大到小 print(sorted(salariex,key= lambda key:salariex[key],reverse=True)) # ['tom', 'egon', 'zxx']

5、map、reduce、filter

详情参见:https://zhuanlan.zhihu.com/p/109125933

"""
array=[1,2,3,4,5]
"""

map

map函数可以接收两个参数,一个是函数,另外一个是可迭代对象

要求一:对array的每个元素做平方处理,可以使用map函数

>>> res=map(lambda x:x**2,array)
>>> res
<map object at 0x1033f45f8>
>>>

解析:map会依次迭代array,得到的值依次传给匿名函数(也可以是有名函数),而map函数得到的结果仍然是迭代器。

>>> list(res) #使用list可以依次迭代res,取得的值作为列表元素
[1, 4, 9, 16, 25]

reduce

reduce函数可以接收三个参数,一个是函数,第二个是可迭代对象,第三个是初始值

要求二:对array进行合并操作,比如求和运算,这就用到了reduce函数

# reduce在python2中是内置函数,在python3中则被集成到模块functools中,需要导入才能使用
>>> from functools import reduce 
>>> res=reduce(lambda x,y:x+y,array)
>>> res
15

解析:
1、没有初始值,reduce函数会先迭代一次array得到的值作为初始值,作为第一个值数传给x,然后继续迭代一次array得到的值作为第二个值传给y,运算的结果为3

2、将上一次reduce运算的结果作为第一个值传给x,然后迭代一次array得到的结果作为第二个值传给y,依次类推,知道迭代完array的所有元素,得到最终的结果15

也可以为reduce指定初始值

>>> res=reduce(lambda x,y:x+y,array,100)
>>> res
115

filter

filter函数用于过滤序列,过滤掉不符合条件的元素,返回有符合条件元素组成的新列表

要求三:对array进行过滤操作,这就用到了filter函数,比如过滤出大于3的元素

>>> res=filter(lambda x:x>3,array)

解析:filter函数会依次迭代array,得到的值依次传给匿名函数,如果匿名函数的返回值为真,则过滤出该元素,而filter函数得到的结果仍然是迭代器。

>>> list(res) 
[4, 5]

内置函数

详情参见:https://www.cnblogs.com/linhaifeng/articles/7580830.html#_label4

面向过程编程

详情参见:https://zhuanlan.zhihu.com/p/109125933

面向过程编程:
  核心是“过程”二字,过程就是解决问题的步骤,即先干啥,再干啥,后干啥
  所以基于该思想编写程序就好比设计一条一条的流水线

1、优点

将复杂的问题流程化,进而简单化

2、缺点

牵一发而动全身、扩展性差

程序的可扩展性极差,因为一套流水线或者流程就是用来解决一个问题,就好比生产汽水的流水线无法生产汽车一样,
即便是能,也得是大改,而且改一个组件,与其相关的组件可能都需要修改,
这就造成了连锁反应,而且这一问题会随着程序规模的增大而变得越发的糟糕。

3、应用场景

面向过程的程序设计一般用于那些功能一旦实现之后就很少需要改变的场景, 
如果你只是写一些简单的脚本,去做一些一次性任务,用面向过程去实现是极好的,
但如果你要处理的任务是复杂的,且需要不断迭代和维护, 那还是用面向对象最为方便。
原文地址:https://www.cnblogs.com/ZhZhang12138/p/14867882.html