Python
列表推导式
列表推导式语法与应用案例
列表推导式(又称列表解析式)提供了一种简明扼要的方法来创建列表。它的结构是在一个中括号里包含一个表达式,然后是一个for语句,然后是 0 个或多个 for 或者 if 语句。那个表达式可以是任意的,意思是你可以在列表中放入任意类型的对象。返回结果将是一个新的列表,在这个以 if 和 for 语句为上下文的表达式运行完成之后产生。
列表推导式在逻辑上等价于一个循环语句,只是形式上更加简洁。语法形式为:
[expression for expr1 in sequence1 if condition1
...
for exprN in sequenceN if conditionN]
列表推导式的执行顺序:各语句之间是嵌套关系,左边第二个语句是最外层,依次往右进一层,左边第一条语句是最后一层。
>>> aList = [x*x for x in range(10)]
#相当于
>>> aList = []
>>> for x in range(10):
aList.append(x*x)
>>> freshfruit = [' banana', ' loganberry ', 'passion fruit ']
>>> aList = [w.strip() for w in freshfruit]
#等价于下面的代码
>>> aList = []
>>> for item in freshfruit:
aList.append(item.strip())
使用列表推导式实现嵌套列表的平铺。
vec = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> [num for elem in vec for num in elem]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> vec = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> result = []
>>> for elem in vec:
for num in elem:
result.append(num)
>>> result
[1, 2, 3, 4, 5, 6, 7, 8, 9]
//正好可以竖着看,对于最上面式子是从左往右看
在列表推导式中可以使用if子句对列表中的元素进行筛选,只在结果列表中保留符合条件的元素。下面的代码可以列出当前文件夹下所有Python源文件:
>>> import os
>>> [filename for filename in os.listdir('.') if filename.endswith(('.py', '.pyw'))]
从列表中选择符合条件的元素组成新的列表:
>>> aList = [-1, -4, 6, 7.5, -2.3, 9, -11]
>>> [i for i in aList if i>0] #所有大于0的数字
[6, 7.5, 9]
查找列表中最大元素的所有位置。
>>> from random import randint
>>> x = [randint(1, 10) for i in range(20)]
#20个介于[1, 10]的整数
>>> x
[10, 2, 3, 4, 5, 10, 10, 9, 2, 4, 10, 8, 2, 2, 9, 7, 6, 2, 5, 6]
>>> m = max(x)
>>> [index for index, value in enumerate(x) if value == m]
#最大整数的所有出现位置
[0, 5, 6, 10]
- 在列表推导式中同时遍历多个列表或可迭代对象。
>>> [(x, y) for x in [1, 2, 3] for y in [3, 1, 4] if x != y]
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
对于包含多个循环的列表推导式,一定要清楚多个循环的执行顺序或“嵌套关系”。例如,上面第一个列表推导式等价于
result = []
for x in [1, 2, 3]:
for y in [3, 1, 4]:
if x != y:
result.append((x,y))
result
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
切片
Python切片
在形式上,切片使用2个冒号分隔的3个数字来完成。
[start: end: step]
- 第一个数字start表示切片开始位置,默认为0;
第二个数字end表示切片截止(但不包含)位置(默认为列表长度);
第三个数字step表示切片的步长(默认为1)。 - 当start为0时可以省略,当end为列表长度时可以省略,当step为1时可以省略,省略步长时还可以同时省略最后一个冒号。
- 当step为负整数时,表示反向切片,这时start应该在end的右侧才行。
- :n代表n之前的;
::n代表间隔为n;
n:代表n之后的。
一、使用切片获取列表部分元素
- 在列表推导式中同时遍历多个列表或可迭代对象。
>>> >>> aList = [3, 4, 5, 6, 7, 9, 11, 13, 15, 17]
>>> aList[::] #返回包含原列表中所有元素的新列表
[3, 4, 5, 6, 7, 9, 11, 13, 15, 17]
>>> aList[::-1] #返回包含原列表中所有元素的逆序列表
[17, 15, 13, 11, 9, 7, 6, 5, 4, 3]
>>> aList[::2] #隔一个取一个,获取偶数位置的元素
[3, 5, 7, 11, 15]
>>> aList[1::2] #隔一个取一个,获取奇数位置的元素
[4, 6, 9, 13, 17]
>>> aList[3:6] #指定切片的开始和结束位置
[6, 7, 9]
>>> aList[0:100] #切片结束位置大于列表长度时,从列表尾部截断
[3, 4, 5, 6, 7, 9, 11, 13, 15, 17]
>>> aList[100] #抛出异常,不允许越界访问
IndexError: list index out of range
>>> aList[100:] #切片开始位置大于列表长度时,返回空列表
[]
>>> aList[-15:3] #进行必要的截断处理
[3, 4, 5]
>>> len(aList)
10
>>> aList[3:-10:-1] #位置3在位置-10的右侧,-1表示反向切片
[6, 5, 4]
>>> aList[3:-5] #位置3在位置-5的左侧,正向切片
[6, 7]
二、使用切片为列表增加元素
可以使用切片操作在列表任意位置插入新元素,不影响列表对象的内存地址,属于原地操作。
>>> aList = [3, 5, 7]
>>> aList[len(aList):] = [9] #在列表尾部增加元素
>>> aList[:0] = [1, 2] #在列表头部插入多个元素
>>> aList[3:3] = [4] #在列表中间位置插入元素
>>> aList
#插入后的列表:
[1, 2, 3, 4, 5, 7, 9]
三、使用切片替换和修改列表中的元素
>>> [(x, y) for x in [1, 2, 3] for y in [3, 1, 4] if x != y]
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
- 在列表推导式中同时遍历多个列表或可迭代对象。
>>> aList = [3, 5, 7, 9]
>>> aList[:3] = [1, 2, 3] #替换列表元素,等号两边的列表长度相等
>>> aList
[1, 2, 3, 9]
>>> aList[3:] = [4, 5, 6] #切片连续,等号两边的列表长度可以不相等
>>> aList
[1, 2, 3, 4, 5, 6]
>>> aList[::2] = [0]*3 #隔一个修改一个(用三个0替换)
>>> aList
[0, 2, 0, 4, 0, 6]
>>> aList[::2] = ['a', 'b', 'c'] #隔一个修改一个
>>> aList
['a', 2, 'b', 4, 'c', 6]
>>> aList[1::2] = range(3) #序列解包的用法
>>> aList
['a', 0, 'b', 1, 'c', 2]
>>> aList[1::2] = map(lambda x: x!=5, range(3))
>>> aList
['a', True, 'b', True, 'c', True]
>>> aList[1::2] = zip('abc', range(3)) #map、filter、zip对象都支持这样的用法
>>> aList
['a', ('a', 0), 'b', ('b', 1), 'c', ('c', 2)]
>>> aList[::2] = [1] #切片不连续时等号两边列表长度必须相等
ValueError: attempt to assign sequence of size 1 to extended slice of size 3
四、使用切片删除列表中的元素
>>> aList = [3, 5, 7, 9]
>>> aList[:3] = [] #删除列表中前3个元素
>>> aList
[9]
- 在列表推导式中同时遍历多个列表或可迭代对象。
>>> [(x, y) for x in [1, 2, 3] for y in [3, 1, 4] if x != y]
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
也可以结合使用del命令与切片结合来删除列表中的部分元素,并且切片元素可以不连续。
>>> aList = [3, 5, 7, 9, 11]
>>> del aList[:3] #切片元素连续
>>> aList
[9, 11]
>>> aList = [3, 5, 7, 9, 11]
>>> del aList[::2] #切片元素不连续,隔一个删一个
>>> aList
[5, 9]
迭代器&生成器
一、什么是迭代器
迭代是python中访问集合元素的一种非常强大的一种方式。迭代器是一个可以记住遍历位置的对象,因此不会像列表那样一次性全部生成,而是可以等到用的时候才生成,因此节省了大量的内存资源。迭代器对象从集合中的第一个元素开始访问,直到所有的元素被访问完。迭代器有两个方法:iter()
和next()
方法。
可迭代对象
python中可迭代的对象有list(列表)、tuple(元组)、dirt(字典)、str(字符串)set等。
也可以创建一个可迭代的对象:只要此对象含有__iter__方法,那么它就是一个可迭代的对象,如下面的例子:其中定义了一个__iter__方法,我们通过isinstance()
函数以及Iterable
来判断由Classmate
类创建的class1
对象是否是可迭代的对象:若class1
是一个Iterable
(可迭代对象)则结果返回为True;否则,结果为False。
from collections import Iterable
class Classmate(object):
"""定义一个同学类"""
def __init__(self):
self.name = list()
self.name_num = 0
def add(self,name):
self.name.append(name)
def __iter__(self):
pass
class1 = Classmate()
class1.add("张三")
class1.add("李四")
class1.add("王五")
print("判断是否是可迭代的对象:", isinstance(class1,Iterable))
1.一个类(对象)只要含有“iter”、"next"两个方法,就将其称为迭代器。__iter__方法返回一个特殊的迭代器对象,而这个迭代器对象自动实现了_next__方法,并返回一个值,最后通过抛出异常StopIteration来结束迭代。我们来给上一个例子增加``next__
方法:
2.只是名义上的 可迭代对象/迭代器 还不够,具有相应的功能才算是完整。首先,对于__iter__方法,它需要具有一个可以返回一个迭代器对象的功能(这个对象可以是自己(前提是本身就是一个迭代器),也可以是其它迭代器);对于__next__方法,它需要标记并返回下一个迭代器对象。代码如下(为防止迭代速度过快,我们添加sleep来控制速度):
from collections import Iterable
from collections import Iterator
import time
class Classmate(object):
"""定义一个同学类"""
def __init__(self):
self.name = list()
self.name_num = 0
def add(self,name):
self.name.append(name)
def __iter__(self):
return self # 返回本身
def __next__(self):
# 记忆性返回数据
if self.name_num < len(self.name):
ret = self.name[self.name_num]
self.name_num += 1
return ret
class1 = Classmate()
class1.add("张三")
class1.add("李四")
class1.add("王五")
print("判断是否是可迭代的对象:", isinstance(class1,Iterable))
print("判断是否是迭代器:", isinstance(class1,Iterator))
for name in class1:
print(name)
time.sleep(1)
可以发现,当需要读取的内容读取完之后不会停止,而是无限循环的继续返回空值。因此我们可以为其添加抛出异常(StopIteration,python中用于标标识迭代完成,防止出现无限循环的情况)的程序,当需要的数据读取完成时就抛出异常,从而结束:
四、迭代器的应用
迭代器最核心的功能就是可以通过__next__方法的调用来返回下一个值。而这个值不是从已有的数据中读取的,而是通过程序按照一定的规则生成的。这也就意味着我们可以不再依赖一个现存的数据集合来存放数据,而是边用边生成,这样的好处就是可以节省大量的内存空间。我们通过对比以下问题的两个解决方法,来进一的理解其应用优势:
问题:试编写一个程序实现斐波那契数列(0,1,1,2,3,5,8,13,21……后一项总是等于前两项的和):
a = 0
b = 1
myFibonacci = list()
nums = int(input("请输入需要生成Fibonacci数列项的个数:"))
i = 0
while i < nums:
myFabonacci.append(a)
a,b = b,a+b
i += 1
for num in nums:
print(num)
方法二。
class Fibonacci(object):
"""斐波那契数列得迭代器"""
def __init__(self,nums):
self.nums = nums # 传入参数,生成斐波那契数列的个数
self.a = 0
self.b = 1
self.i =0 # 用于记忆生成的个数
def __iter__(self):
return self
def __next__(self):
ret = self.a # 记忆第一个数
if self.i < self.nums:
self.a, self.b = self.b,self.a +self.b
self.i += 1
return ret
else:
raise StopIteration # 停止迭代
nums = int(input("请输入需要生成Fibonacci数列项的个数:"))
fobo = Fibonacci(nums)
for num in fobo:
print(num)
虽然结果相同,但实际效果却相差巨大。
来看一下方法一,他是通过while循环立即生成一个列表用来存放数据,接着再从已有的数据中读取所需数据,而这需要占用一定的内存空间;
再来看一下方法二,它并没有用到列表,而是返回一个迭代器,在需要的时候生成相关数据。纵观以上两种方法,在生成个数较小时,两者相差不大,但当生成个数是10万,100万,1000万呢?前者需要消耗大量的内存资源,而后者仅需占用一点内存即可。这也是python2中range()函数和python3中range()函数的不同点,python3的range()函数采用了迭代器的方式,不再依赖于现有的数据集合,也就相当于python2中的xrange()函数。
补充举例:当我们到达终点并且没有更多数据要返回时,它将引发 StopIteration
异常。下面是一个例子:
# define a list
my_list = [2013, 14, 15926]
# get an iterator using iter()
my_iter = iter(my_list)
# iterate through it using next()
# Output: 2013
print(next(my_iter))
# Output: 14
print(next(my_iter))
# next(obj) is same as obj.__next__()
# Output: 15926
print(my_iter.__next__())
# This will raise error, no items left
next(my_iter)
一种更优雅的自动迭代方式是使用 for 循环。使用它,我们可以迭代任何可以返回迭代器的对象,例如列表、字符串、文件等。
>>> for element in my_list:
... print(element)
...
2013
14
15926
# create an iterator object from that iterable
iter_obj = iter(iterable)
# infinite loop
while True:
try:
# get the next item
element = next(iter_obj)
print(element)
# do something with element
except StopIteration:
# if StopIteration is raised, break from loop
break
所以在内部,for 循环通过在可迭代对象上调用 iter() 创建一个迭代器对象 iter_obj。具有讽刺意味的是,这个 for 循环实际上是一个无限的 while 循环。
在循环内部,它调用 next() 来获取下一个元素并使用该值执行 for 循环的主体。在所有项目耗尽后,StopIteration 被引发,内部捕获并结束循环。请注意,任何其他类型的异常都会通过。
Python 迭代器的好处
使用迭代器的好处是可以节省资源。
- 代码减少。
- 代码冗余得到极大解决。
- 降低代码复杂度。
- 它为编码带来了更多的稳定性。
生成器
生成器继承于迭代器,迭代器继承于可迭代对象,他是一个特殊的迭代器,拥有迭代器的所有特性
创建生成器
1、函数中使用了关键字yield,那这个函数就不再是一个普通的函数了,而是一个生成器函数
2、生成器函数调用时,不会执行函数内部的代码,会直接返回一个生成器对象
3、关键字yield的作用
是用来生成数据
yield data
第一种方法:通过类似于列表生成式来创建(将列表换为元组)
注意:yield:是没有返回值的,如果赋值的话会打印None
装饰器
装饰器是给现有的模块增添新的小功能,可以对原函数进行功能扩展,而且还不需要修改原函数的内容,也不需要修改原函数的调用。(有点像AOC,避免重复造轮子)
装饰器的使用符合了面向对象编程的开放封闭原则。
开放封闭原则主要体现在两个方面:
-
对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
-
对修改封闭,意味着类一旦设计完成,就可以独立其工作,而不要对类尽任何修改。
简单的装饰器代码
import time
def baiyu():
print("我是攻城狮白玉")
time.sleep(2)
def count_time(func):
def wrapper():
t1 = time.time()
func()
print("执行时间为:", time.time() - t1)
return wrapper
if __name__ == '__main__':
baiyu = count_time(baiyu) # 因为装饰器 count_time(baiyu) 返回的时函数对象 wrapper,这条语句相当于 baiyu = wrapper
baiyu() # 执行baiyu()就相当于执行wrapper()
这里的count_time是一个装饰器,装饰器函数里面定义一个wrapper函数,把func这个函数当作参数传入,函数实现的功能是把func包裹起来,并且返回wrapper函数。wrapper函数体就是要实现装饰器的内容。
当然,这里的wrapper函数名是可以自定义的,只要你定义的函数名,跟你return的函数名是相同的就好了
四、装饰器的语法糖@
其实使用装饰器的时候,@ 默认传入的参数就是被装饰的函数。
import time
def count_time(func):
def wrapper():
t1 = time.time()
func()
print("执行时间为:", time.time() - t1)
return wrapper
@count_time
def baiyu():
print("我是攻城狮白玉")
time.sleep(2)
if __name__ == '__main__':
# baiyu = count_time(baiyu) # 因为装饰器 count_time(baiyu) 返回的时函数对象 wrapper,这条语句相当于 baiyu = wrapper
# baiyu() # 执行baiyu()就相当于执行wrapper()
baiyu() # 用语法糖之后,就可以直接调用该函数了
五、装饰器传参
import time
def count_time(func):
def wrapper(*args, **kwargs):
t1 = time.time()
func(*args, **kwargs)
print("执行时间为:", time.time() - t1)
return wrapper
@count_time
def blog(name):
print('进入blog函数')
name()
print('我的博客是 https://blog.csdn.net/zhh763984017')
if __name__ == '__main__':
# baiyu = count_time(baiyu) # 因为装饰器 count_time(baiyu) 返回的时函数对象 wrapper,这条语句相当于 baiyu = wrapper
# baiyu() # 执行baiyu()就相当于执行wrapper()
# baiyu() # 用语法糖之后,就可以直接调用该函数了
blog(baiyu)
七、类装饰器
上面咱们一起学习了怎么写装饰器函数,在python中,其实也可以同类来实现装饰器的功能,称之为类装饰器。类装饰器的实现是调用了类里面的__call__函数。类装饰器的写法比我们装饰器函数的写法更加简单。
当我们将类作为一个装饰器,工作流程:
通过__init__()
方法初始化类
通过__call__()
方法调用真正的装饰方法
import time
class BaiyuDecorator:
def __init__(self, func):
self.func = func
print("执行类的__init__方法")
def __call__(self, *args, **kwargs):
print('进入__call__函数')
t1 = time.time()
self.func(*args, **kwargs)
print("执行时间为:", time.time() - t1)
@BaiyuDecorator
def baiyu():
print("我是攻城狮白玉")
time.sleep(2)
def python_blog_list():
time.sleep(5)
print('''【Python】爬虫实战,零基础初试爬虫下载图片(附源码和分析过程)
https://blog.csdn.net/zhh763984017/article/details/119063252 ''')
print('''【Python】除了多线程和多进程,你还要会协程
https://blog.csdn.net/zhh763984017/article/details/118958668 ''')
print('''【Python】爬虫提速小技巧,多线程与多进程(附源码示例)
https://blog.csdn.net/zhh763984017/article/details/118773313 ''')
print('''【Python】爬虫解析利器Xpath,由浅入深快速掌握(附源码例子)
https://blog.csdn.net/zhh763984017/article/details/118634945 ''')
@BaiyuDecorator
def blog(name):
print('进入blog函数')
name()
print('我的博客是 https://blog.csdn.net/zhh763984017')
if __name__ == '__main__':
baiyu()
print('--------------')
blog(python_blog_list)
当装饰器有参数的时候,init() 函数就不能传入func(func代表要装饰的函数)了,而func是在__call__函数调用的时候传入的。
class BaiyuDecorator:
def __init__(self, arg1, arg2): # init()方法里面的参数都是装饰器的参数
print('执行类Decorator的__init__()方法')
self.arg1 = arg1
self.arg2 = arg2
def __call__(self, func): # 因为装饰器带了参数,所以接收传入函数变量的位置是这里
print('执行类Decorator的__call__()方法')
def baiyu_warp(*args): # 这里装饰器的函数名字可以随便命名,只要跟return的函数名相同即可
print('执行wrap()')
print('装饰器参数:', self.arg1, self.arg2)
print('执行' + func.__name__ + '()')
func(*args)
print(func.__name__ + '()执行完毕')
return baiyu_warp
@BaiyuDecorator('Hello', 'Baiyu')
def example(a1, a2, a3):
print('传入example()的参数:', a1, a2, a3)
if __name__ == '__main__':
print('准备调用example()')
example('Baiyu', 'Happy', 'Coder')
print('测试代码执行完毕')
九、装饰器的顺序
def BaiyuDecorator_1(func):
def wrapper(*args, **kwargs):
print('zzx1')
func(*args, **kwargs)
print('我是装饰器1')
return wrapper
def BaiyuDecorator_2(func):
def wrapper(*args, **kwargs):
print('zzx2')
func(*args, **kwargs)
print('我是装饰器2')
return wrapper
def BaiyuDecorator_3(func):
def wrapper(*args, **kwargs):
print('zzx3')
func(*args, **kwargs)
print('我是装饰器3')
return wrapper
@BaiyuDecorator_1
@BaiyuDecorator_2
@BaiyuDecorator_3
def baiyu():
print("我是攻城狮白玉")
if __name__ == '__main__':
baiyu()