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()方法。

img

可迭代对象

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)

img

可以发现,当需要读取的内容读取完之后不会停止,而是无限循环的继续返回空值。因此我们可以为其添加抛出异常(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

在这里插入图片描述

在这里插入图片描述

image-20230322220842318

image-20230322220859302

装饰器

装饰器是给现有的模块增添新的小功能,可以对原函数进行功能扩展,而且还不需要修改原函数的内容,也不需要修改原函数的调用。(有点像AOC,避免重复造轮子)

装饰器的使用符合了面向对象编程的开放封闭原则。

开放封闭原则主要体现在两个方面:

  1. 对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。

  2. 对修改封闭,意味着类一旦设计完成,就可以独立其工作,而不要对类尽任何修改。

    简单的装饰器代码

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)

img

当装饰器有参数的时候,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('测试代码执行完毕')

image-20230322222317045

九、装饰器的顺序

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()

image-20230322222927609

posted @ 2023-03-23 17:35  ZZX11  阅读(39)  评论(0编辑  收藏  举报