Python高级特性
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]
下面我们用一个简单例子来看一下列表生成式的高效与简洁,现需要输出一个列表,里面是10个100以内的随机数:
#1). 老办法
# 定义一个空列表,用来存储生成的数据;
import random
nums = []
# 生成100个, 循环100次
for i in range(10):
num = random.randint(1, 100)
nums.append(num)
# 2). 列表生成式快速生成的办法
nums_quick = [random.randint(1, 100) for i in range(10)]
print(nums)
print(nums_quick)
输出结果为:
可见利用列表生成式,只需要一行核心代码就可以达到需求。
接下来再看几个通过列表生成式实现的简单例子:
# 1). 求1-50所有数的平方
square = [(i + 1) ** 2 for i in range(50)]
print(square)
# 2). 生成一个2n+1的数字列表,n为从3到11的数字。
nums_list = [2 * n + 1 for n in range(3, 12)]
print(nums_list)
# 3). 求以r为半径的圆的面积和周长(r的范围从1到10)。
import math
circle = [(math.pi * (r ** 2), 2 * math.pi * r) for r in range(1, 11)]
print(circle)
# 4). 将100以内的所有偶数拿出来;
def is_odd(num):
return num % 2 == 0
odd_nums_1 = [i for i in range(0, 101) if is_odd(i)]
print(odd_nums_1)
输出结果为:
还有一个例子,通过列表生成式把1~100以内所有的质数都找出来:
def isPrime(num):
"""
判断num是否为质数?
质数: 只能被1和它本身整除的数就是质数. (负数不是质数, 1不是质数)
:param num:
:return:Bool(True, False)
"""
if num <= 1:
return False
else:
for i in range(2, num):
if num % i == 0:
return False
else:
return True
# 找出1~100之间所有的质数。
primeNums = [num for num in range(1, 101) if isPrime(num)]
print(primeNums)
输出结果为:
在列表生成式中有一点需要注意:如果列表生成式和三元运算符结合在一起, if语句放前面。
举例如下:
"""
找出1~100之间所有奇数, 并且返回一个列表。如果能被3整除, 返回返回该数的平方,
否则返回该数的三次方 。
"""
# 如果列表生成式和三元运算符结合在一起, if语句放前面;
nums = [num ** 2 if num % 3 == 0 else num ** 3 for num in range(1, 101, 2)]
print(nums)
输出结果为:
2)集合生成式与字典生成式
字典生成式:就是用来快速生成字典
集合生成式:就是用来快速生成集合。
形式如下:
下面再看一个字典生成式的实际案例:
"""
大小写计数合并 : 已知字典{'A':10, 'b':5, 'a':2}, 合并后为{'a':12, 'b':5}
key值最终全部为小写。
"""
d= {
'A':10,
'b':5,
'a':2
}
# 合并字典; d.get(key.lower(), 0), 如果key'值不存在,默认返回0;
dict_result = {key.lower():d.get(key.lower(), 0) + d.get(key.upper(), 0)for key, value in d.items()}
print(dict_result)
结果为:
3)生成器
在Python中,一边循环一边计算的机制,称为生成器:Generator。
那在什么情况下需要使用生成器呢?
一般情况下我们不需要使用生成器,只有当我们因为性能限制才需要用到,比如我们使用python读取一 个10g的文件,如果一次性将10g的文件加载到内存处理的话(read方法),内存肯定会溢出;这里如果可以 使用生成器把读写交叉处理进行,比如使用(readline和readlines)就可以再循环读取的同时不断处理,这样就可以节省大量的内存空间。
创建生成器的两种方法:
第一种方法: 列表生成式的改写。 []改成()
第二种方法: yield关键字。
打印生成器中的元素也有两种方法:
第一种:通过for循环, 依次计算并生成每一个元素。
第二种:如果要一个一个打印出来,可以通过next()函数获得生成器的下一个返回值。
下面通过实例看一下创建生成器的第一种方法:
# 第一种方法: 列表生成式的改写。 []改成()
# nums = [i**2 for i in range(100)]
# print(nums)
nums = (i**2 for i in range(5))
# print(nums) # 输出为<generator object <genexpr> at 0x7f40e1b642a0>
# print(type(nums)) # 输出为<class 'generator'>
# 第一种打印生成器元素的方法
# for num in nums:
# print(num)
# 第二种打印生成器元素的方法
# __next__和next()效果相同(面向对象-魔术方法)
# nums.__next__()
print(next(nums))
print(next(nums))
print(next(nums))
print(next(nums))
输出结果为:
下面再通过斐波那契数列的非递归方式看一下第二种创建生成器的方法:
"""
f(1)=1, f(2)=1, f(3)=3.........f(n)=f(n-1)+f(n-2)
"""
# yield关键字
def fib(n):
"""
显示多少个fib数列
"""
# 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(5)
# for i in f:
# print(i)
while True:
try:
print(next(f))
except:
break
输出结果为:
yield 工作机制:
如果函数里面有yield关键字, 这个函数的返回值是生成器。
如果遇到yield, 函数停止执行, 当再次调用next方法时, 从停止的地方继续执行。
而如果函数里面遇到return, 函数就执行结束。
默认next方法会把yield后面的值返回回来。
有一个给生成器传递数据的方法:send方法
假如f是生成器,那么通过f.send("要传入的数据"),就可以给生成器传入数据,传入的数据的在yield位置,并且yield也有和next方法一样的工作机制。
总结一下生成器的特点:
- 节约内存
- 迭代到下一次的调用时,所使用的参数都是第一次所保留下的,也就是说在整个所有函数调用的参数都是第一次所调用时保留的,而不是新创建的。
2.迭代器
迭代是访问容器元素的一种方式。迭代器是一个可以记住遍历的位置的对象。
迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。
可迭代对象:可以直接作用于for循环的对象
- 一类是集合数据类型,如list, tuple,dict, set,str等;
- 一类是generator,包括生成器和带yield的generator function。
那么如何判断一个对象是不是可迭代对象呢?
方法如下:
# python2: from collections import Iterable
# python3: 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))
结果:
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。
注意:生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。
把list、dict、str等Iterable变成Iterator可以使用iter()函数
生成器、可迭代对象、迭代器它们之间的关系如下:
1)迭代器是一个更抽象的概念,任何对象,如果它的类有next方法和iter方法返回自己本身。对于string、list、dict、tuple等这类容器对象,使用for循环遍历是很方便的。 在后台for语句对容器对象调用iter()函数,iter()是python的内置函数。iter()会返回一个定义了next()方法的迭代器对象,它在容器中逐个访问容器内元素,next()也是python的 内置函数。在没有后续元素时,next()会抛出一个StopIteration异常。
2)生成器(Generator)是创建迭代器的简单而强大的工具。它们写起来就像是正规的函数 ,只是在需要返回数据的时候使用yield语句。每次next()被调用时,生成器会返回它脱离的位置(它记忆语句最后一次执行的位置和所有的数据值)。
它们两个的区别:
生成器能做到迭代器能做的所有事,而且因为自动创建了iter()和next()方法,生成器显得特别简洁,而且生成器也是高效的,使用生成器表达式取代列表解析可以同时节省内存。除了创建和保存程序状态的自动方法,当发生器终结时,还会自动抛StopIteration异常 。
3.闭包
什么是闭包?
闭包的概念就是当我们在函数内定义一个函数时,这个内部函数使用了外部函数的临时变量,且外部函数的返回值是内部函数的引用时,我们称之为闭包。
内部函数对外部函数作用域里变量的引用(非全局变量),则称内部函数为闭包。闭包的一个常用场景就是装饰器,后面会详细介绍装饰器的。
闭包需要满足的三个条件:
- 函数内定义一个函数
- 内部函数使用了外部函数的临时变量
- 外部函数的返回值是内部函数的引用(指的就是内部的函数名)
下面看一个闭包的简单例子:
函数line与变量a,b构成闭包。在创建闭包的时候,我们通过line_conf的参数a,b说明了这两个变量的取值,这样,我们就确定了函数的最终形式(y=x+1和y=4x+5)。
闭包的优点: 闭包也具有提高代码可复用性的作用。
下面我们通过闭包,去绘制不同一次线性函数的折线图,代码如下:
def line_conf(a, b):
def line(x):
return a * x + b
return line
# 一元线性方程x+1
line1 = line_conf(1, 1)
# 一元线性方程3x+2
line2 = line_conf(3, 2)
# 一元线性方程4x+3
line3 = line_conf(4, 3)
loopCount = 100
y1 = [line1(item) for item in range(loopCount)]
y2 = [line2(item) for item in range(loopCount)]
y3 = [line3(item) for item in range(loopCount)]
# 图形绘制(pyecharts==0.5.11)===导入绘制折线图的类
from pyecharts import Line
# 创建绘制折线图的对象lineObj
x = list(range(loopCount)) # x轴坐标必须是一个列表;
lineObj = Line(title="一元线性方程图形展示")
lineObj.add(name='y=x+1', x_axis=x, y_axis=y1)
lineObj.add(name='y=3x+2', x_axis=x, y_axis=y2)
lineObj.add(name='y=4x+3', x_axis=x, y_axis=y3)
# 将绘制的图形显示为html文件
lineObj.render('./doc/line.html')
结果显示如下:
4.装饰器Decorator
前面提到过,装饰器实际上就是闭包的应用场景。我在另一篇文章里详细介绍了装饰器,以及多个装饰器案例。
可直接点击 https://blog.csdn.net/Mr_fengzi/article/details/90758544 阅读。
5.内置高阶函数
函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返 回一个函数Python对函数式编程提供部分支持。
把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。
下面我们就看几个python中的内置高阶函数。
1)map()
map() 会根据提供的函数对指定序列做映射。
它的第一个参数接受一个函数名,后面的参数接受一个或多个可迭代的序列,返回的是一个集合,其工作机制如下:
当seq多于一个时,map可以并行(注意是并行)地对每个seq执行如下图所示的过程:
注意: map无法处理seq长度不一致、对应位置操作数类型不一致的情况,这两种情况都会报类型错误。
def line(num1, num2):
return num1 *10 + num2
# map含有多个序列
nums1 = range(2, 6)
nums2 = range(1, 5)
# nums1 = 2 3 4 5
# nums2 = 1 2 3 4
# line=====> num1 * 10 + num2
# result = 21 32 43 54
result = list(map(line, nums1, nums2))
print(result)
输出为:
2)reduce()
reduce() 函数会对参数序列中元素进行累积。
第一个参数接受一个函数名,后面的参数接受一个或多个可迭代的序列,返回的是一个结果,工作机制如下:
注意:python2直接可以使用reduce, 而python3需要导入模块。
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)
# 求10的阶乘
result = reduce(lambda x, y:x*y, range(1, 10))
print(result)
结果为:
3)filter()
filter() 函数用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表。
第一个参数接受一个函数名,后面的参数接受一个或多个可迭代的序列,返回的是一个序列,工作原理如下:
几个案例如下:
# 1.获取100以内能被3或者5整除的所有数
result1 = filter(lambda num: num % 3 == 0 or num % 5 == 0, range(100))
print(list(result1))
# 2.获取2000-2999年,所有的闰年
is_leap = lambda year: (year % 4 == 0 and year % 100 !=0) or year % 400 == 0
result2 = filter(is_leap, range(2000, 3000))
print(list(result2))
# 3.获取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 升序(默认)。
案例如下:
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])
# 将所有的偶数移动到前面, 将所有的奇数移动到最后
li =list(range(10))
sorted_li = sorted(li, key=lambda num: 0 if num%2==0 else 1)
print(sorted_li)
结果如下:
前面还提到过sort()排序方法,那么排序sort()和sorted()的区别是什么呢?
- 排序对象不同:sort 是应用在 list 上的方法,sorted 可以对所有可迭代的对象进行排序操作。
- 返回值不同:list 的 sort 方法返回的是对已经存在的列表进行操作,无返回值; 内建函数 sorted 方法返回的是一个新的 list,而不是在原来的基础上进行的操作。