1.生成式与生成器
1.列表生成式
列表生成式就是一个用来生成列表的特定语法形式的表达式。
是Python提供的一种生成列表的简洁形式, 可快速生成一个新的list。
普通的语法格式:[exp for iter_var in iterable]
带过滤功能语法格式: [exp for iter_var in iterable if_exp]
循环嵌套语法格式: [exp for iter_var_A in iterable_A for iter_var_B in iterable_B]
例:求1-50所有数的平方 square = [(i + 1) ** 2 for i in range(50)]
求以r为半径的圆的面积和周长(r的范围从1到10)
import math
circle = [(math.pi * (r ** 2), 2 * math.pi * r) for r in range(1, 11)]
集合生成式和字典生成式
字典生成式:用来快速生成字典;
集合生成式:用来快速生成集合
2.生成器Generator
什么叫生成器?
在Python中,一边循环一边计算的机制,称为生成器:Generator。
什么时候需要使用生成器?
一般情况下我们不需要使用生成器,只有当我们因为性能限制才需要用到,比如我们使用python读取一
个10g的文件,如果一次性将10g的文件加载到内存处理的话(read方法),内存肯定会溢出;这里如果可以
使用生成器把读写交叉处理进行,比如使用(readline和readlines)就可以再循环读取的同时不断处理,这样就可以节省大量的内存空间.
如何创建生成器?
第一种方法: 列表生成式的改写。 []改成()
第一种方法: yield关键字。
如何打印生成器的每一个元素呢?
通过for循环, 依次计算并生成每一个元素。
如果要一个一个打印出来,可以通过next()函数获得生成器的下一个返回值。
生成器的特点是什么?
- 节约内存
- 迭代到下一次的调用时,所使用的参数都是第一次所保留下的,即是
说,在整个所有函数调用的参数都是第一次所调用时保留的,而不是新
创建的。
生成器的应用案例: 斐波那契数列(非递归方式)
除第一个和第二个数外,任意一个数都可由前两个数相加得到.
1, 1, 2, 3, 5, 8, 13, 21, 34,
代码:
def fib(n):
"""
显示多少个fib数列
:param n:
:return:
"""
# a代表第一个数, b代表第2个数, 也就是要显示的数; count: 当前已经显示fib数列的个数;当前为0;
a, b, count = 0, 1, 0
# 0<5
while count < n:
# print(b)
yield b
# a, b = b, a+b
a, b = b, a+b
# 已经显示的次数加1
count += 1
# f是一个生成器(函数里面有yield)
f = fib(10)
# for i in f:
# print(i)
while True:
try:
print(next(f))
except:
break
代码:
"""
基于yield计算平均值
# 17行;
"""
def averager():
# 所有数的和, 默认为0;
total = 0.0
# 数值的个数;
count = 0
# 平均值结果;
average = None
# 所有数值存储的容器(List);
all_items = []
while True:
# 函数包含yield关键字
new_item = yield average, all_items
all_items.append(int(new_item))
total += new_item
count += 1
average = total / count
def main():
# AVERAGER是个生成器;
AVERAGER = averager()
# 第一次调用next方法, 遇到yield停止,
next(AVERAGER)
# 死循环, 依次求解平均值;
while True:
new_num = input("请输入求平均值的数: ")
if new_num == 'q':
print("程序执行结束.....")
break
# 1). 通过send方法将求平均值的数值传到yield所在位置,(14行);
# 2). send方法的返回值是求平均值的列表和平均值结果;
average, all_items = AVERAGER.send(int(new_num))
print(all_items, "的平均值为:", average)
main()
生产者/消费者模型
- 为什么要使用生产者消费者模型?
在并发编程中,如果生产者处理速度很快,而消费者处理速度比较慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个等待的问题,就引入了生产者与消费者模型。让它们之间可以不停的生产和消费。 - 实现生产者消费者模型三要素:
生产者,消费者,队列(或其他的容哭器,但队列不用考虑锁的问题) - 什么时候用这个模型?
程序中出现明显的两类任务,一类任务是负责生产,另外一类任务是负责处理生产的数据的(如爬虫) - 用该模型的好处?
1、实现了生产者与消费者的解耦和
2、平衡了生产力与消费力,就是生产者一直不停的生产,消费者可以不停的消费,因为二者不再是直接沟通的,而是跟队列沟通的。
传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。
现在改用协程(yield),生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高。
例代码:
import time
import random
def Consumer(name):
goods = ['电视','手机','电脑']
buy_name = random.choice(goods)
print('[%s]需要购买%s'%(name,buy_name))
while True:
game = yield buy_name
print('[%s]购买%s成功'%(name,game))
def Produder(name):
consumers = [Consumer('消费者%s'%(i+1)) for i in range(10)]
for consumer in consumers:
buy_name = next(consumer)
print('[%s]正在生产%s'%(name,buy_name))
time.sleep(1)
print('[%s]生产%s成功' % (name,buy_name))
consumer.send(buy_name)
def main():
Produder('星')
main()
运行截图:
2.迭代器
迭代是访问容器元素的一种方式。迭代器是一个可以记住遍历的位置的对象。
迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。访问完后再调用迭代器遍历发现没有内容,因为next()方法从上一次停止的地方继续执行,只能向后算(相当与指针到了最后)。
可迭代对象:可以直接作用于for循环的对象(如何判断是否可以迭代?)
一类是集合数据类型,如list, tuple,dict, set,str等;
一类是generator,包括生成器和带yield的generator function。
Python面试真题: 迭代器和生成器的区别
- 生成器 --(都是)—> 迭代器(next) ----(都是)—> <–(不一定是iter)---- 可迭代对象(for)
如何判断一个对象是不是可迭代对象?
在collections包里导入Iterable,用isinstance()方法判断类型,返回True/False
代码:
# py2: from collections import Iterable
# py3: from collections.abc import Iterable
from collections.abc import Iterable
print(type('hello') == str)
print(isinstance('hello', str))
print(isinstance(1, Iterable))
print(isinstance('hello', Iterable))
#判断文件对象是不是可迭代对象?
with open('./doc/passwd.txt', 'r') as f:
print(isinstance(f, Iterable))
3.闭包
什么是闭包?
闭包的概念就是当我们在函数内定义一个函数时,这个内部函数使用了外部函数的临时 变量,且外部函数的返回值是内部函数的引用时,我们称之为闭包。
内部函数对外部函数作用域里变量的引用(非全局变量),则称内部函数为闭包。
闭包需要满足的三个条件:
1. 函数内定义一个函数
2. 内部函数使用了外部函数的临时变量
3. 外部函数的返回值是内部函数的引用(指的就是内部的函数名)
闭包的一个常用场景就是装饰器。在装饰器里会大量用闭包。
例:
函数line与变量a,b构成闭包。在创建闭包的时候,我们通过line_conf的参数a,b说明了这两个变量的取值,
这样,我们就确定了函数的最终形式(y=x+1和y=4x+5)。
优点: 闭包也具有提高代码可复用性的作用。
4.装饰器Decorator
装饰器本质上是一个函数,该函数用来处理其他函数,它可以让其他函数在不需要修改代码的前提下增加额外的功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,
比如:插入日志、性能测试、事务处理、缓存、权限校验等应用场景。
为什么需要装饰器?
写代码要遵循 开放封闭 原则,虽然在这个原则是用的面向对象开发,但是
也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,
但可以被扩展,即:
封闭:已实现的功能代码块
开放:对扩展开发
写好一个装饰器后用’@'符调用装饰器(就是语法糖),写在被装饰函数前面。
例:
被装饰器装饰果的函数,看起来是在调用这个函数,其实这个函数指向的是装饰器所返回的那个函数decorator,实际是在调用decorator函数。
1.通用装饰器
由于不同的函数里面传的参数不同,如果装饰器里面参数用一种格式,那么它所可以装饰的函数就只能是一种类型,比如函数里传一个参数和传俩个参数是不一样的,这样极大的限制了装饰器的使用,所以我们要用一种通用的装饰器来装饰所有种类的函数。
如上图所示:只要在wrapper里写入(*args,**kwargs),不管传进来的函数有何种类型的参数,装饰器都可以使用。
装饰器:
def decorate(fun):
def wrapper(*args, **kwargs):
result = fun(*args, **kwargs)
# 返回被装饰函数的返回值;
return result
return wrapper
@decorate ===> add=decorate(add) ---> add指向wrapper函数位置
def add():
return 'ok'
add()
2.多个装饰器装饰
当有多个装饰器装饰同一个函数时,上面装饰器先执行,下面的装饰器后执行,但其真正原理是函数先被下面的装饰器先装饰,下面装饰器做返回的wrapper又被上面的装饰器装饰。
举个例子:
# 1). 判断用户是否登录?
# 2). 判断用户是否有权限?
# 系统中的用户信息;
db = {
'root': {
'name': 'root',
'passwd': 'westos',
'is_super': 0 # 0-不是 1-是
},
'admin': {
'name': 'admin',
'passwd': 'westos',
'is_super': 1 # 0-不是 1-是
}
}
# 存储当前登录用户的信息;
login_user_session = {}
def is_login(fun):
"""
判断用户是否登录, 如果没有登录,先登录
:param fun:
:return:
"""
def wrapper1(*args, **kwargs):
if login_user_session:
result = fun(*args, **kwargs)
return result
else:
print("跳转登录".center(50, '*'))
user = input("User: ")
passwd = input('Password: ')
if user in db:
if db[user]['passwd'] == passwd:
login_user_session['username'] = user
print('登录成功')
# ***** 用户登录成功, 执行删除学生的操作;
result = fun(*args, **kwargs)
return result
else:
print("密码错误")
else:
print("用户不存在")
return wrapper1
def is_permission(fun):
def wrapper2(*args, **kwargs):
print("判断是否有权限......")
current_user = login_user_session.get('username')
permissson = db[current_user]['is_super']
if permissson == 1:
result = fun(*args, **kwargs)
return result
else:
print("用户%s没有权限" % (current_user))
return wrapper2
"""
**** 被装饰的过程:
1). delete = is_permission(delete) # delete = wrapper2
2). delete = is_login(delete) # delete = is_login(wrapper2) # delete = wrapper1
"""
@is_login # delete = is_login(delete)
@is_permission
def delete():
return "正在删除学生信息"
"""
*******被调用的过程:
delete() ------> wrapper1() ---> wrapper2() ---> delete()
"""
result = delete()
print(result)
3.带参数的装饰器
因为装饰器里只能传函数,所有如果要给装饰器里传递参数,就要给这个装饰器外再加一层函数,相当于双层装饰器,先返回外层装饰器(函数),然后由返回的函数执行内层装饰器
运行代码时设置断点看就会很清晰
例:
# 如果装饰器需要传递参数, 在原有的装饰器外面嵌套一个函数即可
def auth(type):
def wrapper1(fun):
def wrapper(*args, **kwargs):
if type=='local':
user = input("User:")
passwd = input("Passwd:")
if user == 'root' and passwd == 'westos':
result = fun(*args, **kwargs)
return result
else:
print("用户名/密码错误")
else:
print("暂不支持远程用户登录")
return wrapper
return wrapper1
"""
# python中装饰器的语法糖结构: @funName ===>
# python中装饰器结构如果为@funName() ====> 默认跟的不是函数名, 先执行外函数, 获取函数返回结果 ====》 @funNameNew
@函数名 add=函数名(add)
@函数名() @新的函数名
"""
@auth(type='remote')
def home():
print("这是主页")
home()
5.内置高阶函数
函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!Python对函数式编程提供部分支持。
高阶函数英文叫Higher-order function。什么是高阶函数?
把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。
1.map()
map() 会根据提供的函数对指定序列做映射。
第一个参数接受一个函数名,后面的参数接受一个或多个可迭代的序列,返回的是一个集合。
例:
例1.使用map转换数值为整形
map_num = map(int, nums)
sorted_num = sorted(map_num)
print(sorted_num)
例2.
def line(num1, num2):
return num1 *10 + num2
# map含有多个序列
nums1 = range(2, 6)
nums2 = range(1, 6)
# nums1 = 2 3 4 5
# nums2 = 1 2 3 4 5
# line=====> num1 * 10 + num2
# result = 21 32 43 54
result = list(map(line, nums1, nums2))
print(result)
2.reduce()
reduce() 函数会对参数序列中元素进行累积。
第一个参数接受一个函数名,后面的参数接受一个或多个可迭代的序列,返回的是一个结果。
例:
from functools import reduce
# 求两个数值和的匿名函数定义;
add = lambda x, y: x + y
# reduce的工作机制: result=add(add(add(1, 2), 3), 4)
result = reduce(add, [1, 2, 3, 4])
print(result)
3.filter()
filter() 函数用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表。
第一个参数接受一个函数名,后面的参数接受一个或多个可迭代的序列,返回的是一个序列。
例:
获取1000内容所有的素数;
def is_prime(num):
"""判断是否为素数"""
if num < 2:
return False
for i in range(2, num):
if num %i == 0:
return False
else:
return True
result3 = filter(is_prime, range(1001))
print(list(result3))
4.sorted/max/min
sorted() 函数对所有可迭代的对象进行排序操作。返回重新排序的列表。
sorted(iterable, key=None, reverse=False)
key: 主要是用来进行比较的元素,只有一个参数,
reverse: 排序规则,True 降序 ,False 升序(默认)
python排序sort()和sorted()的区别是什么?
- 排序对象不同: sort 是应用在 list 上的方法,sorted 可以对所有可迭代的对象进行排序操作。
- 返回值不同:
list 的 sort 方法返回的是对已经存在的列表进行操作,无返回值,
内建函数 sorted 方法返回的是一个新的 list,而不是在原来的基础上进行的操作。
如果对商品信息排序,那么用sorted()方法可以按照价格或数量等对其进行排序,但用max或min方法可以直接返回所求的商品的全部信息(返回链表)。
例:
goods = [
["苹果", 2, 1000],
["电脑", 9999, 300],
["手机", 5999, 790]
]
top_price = max(goods, key=lambda x: x[1])
print(top_price)
print("价格最高的商品名称: ", top_price[0])
low_count = min(goods, key=lambda x: x[2])
print("库存量最少的商品名称: ", low_count[0])
high_count = max(goods, key=lambda x: x[2])
print("库存量最多的商品名称: ", high_count[0])
运行截图:
实战练习
1.记录日志装饰器练习题
好的日志对一个软件的重要性是显而易见的。如果函数的入口都要写一行代码来记录日志,这种方式实在是太低效了。 那么请你创建一个装饰器, 功能实现函数运行时自动产生日志记录。
日志格式如下:
程序运行时间 主机短名 程序名称: 函数[%s]运行结果为[%s]
产生的日志文件并不直接显示在屏幕上, 而是保存在 file.log 文件中, 便于后期软件运行结果的分析.
代码:
import os
import time
import sys
# 装饰器: 用来添加日志信息的
def add_log(fun):
"""
May 26 09:50:14 foundation0 systemd: Removed slice user-0.slice.*args
"""
def wrapper(*args, **kwargs): # args=(1, 2) =====> *args=1 2 #
# 获取被装饰的函数的返回值
result = fun(*args, **kwargs) # add(*args) ====> add(1, 2)
# 返回当前的字符串格式时间
now_time = time.ctime()
# 获取主机名
hostname = os.environ['USERNAME']
# 运行的程序
process_full_name = sys.argv[0]
process_name = os.path.split(process_full_name)[-1]
# 日志内容
# info = "正在运行程序: " + str(fun)
# 获取函数名: 函数名.__name__
info ="函数[%s]的运行结果为%s" %(fun.__name__, result)
log = " ".join([now_time, hostname, process_name, info])
with open('log', 'a+',encoding='utf-8') as f:
f.writelines(log+'\n')
return result
return wrapper
@add_log # 语法糖====music = add_log(music)
def movie():
time.sleep(1)
print("正在看电影.....")
@add_log
def add(x, y):
return x+y
@add_log
def roll(name, age, **kwargs):
print(name, age, kwargs)
movie()
add(1,2)
运行截图:
2.斐波那契数列的装饰器练习: 实现高速缓存递归
在数学上,费波那契数列是以递归的方法来定义:
用文字来说,就是费波那契数列由 0 和 1 开始,之后的费波那契系数就是由之前的两数相加而得出。首几个费波那契系数是:
0,1,1,2,3,5,8,13,21,34,55,89,144,233……(OEIS 中的数列 A000045)
装饰器 1: 添加高速缓存的装饰器 num_cache
如果第一次计算 F(5) = F(4) + F(3) = 5
第二次计算 F(6) = F(5) + F(4)
显然 F(5)已经计算过了, F(4)也已经计算了, 我们可否添加一个装饰器, 专门用来存储费波那契数列已经计算过的缓存, 后期计算时, 判断缓存中是否已经计算过了, 如果计算过,直接用缓存中的计算结果. 如果没有计算过, 则开始计算并将计算的结果保存在缓存中.
装饰器 2: 程序运行计时器的装饰器 timeit
该装饰器用来测试有无高速缓存求斐波那契数列, 它们两者运行的时间效率高低.
代码:
"""
1, 1, 2, 3, 5, 8......
F(n) = F(n-1) + F(n-2)
"""
import os
import time
def timmer(fun):
def wrapper(*args, **kwargs):
start_time = time.time()
s=fun(*args, **kwargs)
end_time = time.time()
time1 = end_time - start_time
print(time1)
print("%s run %s" % (fun.__name__, time1))
# wrapper需要返回的是被装饰函数的返回值;
return s
return wrapper
def num_cache(fun):
"""
添加高速缓存的装饰器 num_cache
如果第一次计算 F(5) = F(4) + F(3) = 5
第二次计算 F(6) = F(5) + F(4)
显然 F(5)已经计算过了, F(4)也已经计算了, 我们可否添加一个装饰器, 专门用
来存储 费波那契数列已经计算过的缓存, 后期计算时, 判断缓存中是否已经计算过了, 如
果计算过,直接用缓存中的计算结果. 如果没有计算过, 则开始计算并将计算的结果保存在缓存中
:param fun:
:return:
"""
cache={}
def wrapper(num):
print(cache)
if num in cache:
result = cache.get(num)
return result
else:
# 如果求的num不在缓存里面, 手动求Fib结果
result = fun(num)
# 将求出来的结果存储到缓存中;
cache[num] = result
return result
return wrapper
@num_cache
def fib(num):
if num <= 2:
return 1
else:
return fib(num - 1) + fib(num - 2)
@timmer
def fun1():
result = fib(40)
print('F(100)=', result)
@timmer
def fun2():
result = fib(99)
print('F(99)=', result)
@timmer
def fun3():
result = fib(102)
print('F(102)=', result)
fun1()
fun2()
fun3()