测开-python进阶

244 阅读22分钟

推导式

"""推导式格式:
[处理迭代中的每一个元素 for语句 条件判断]
"""

# 返回0-100中所有偶数
# 方法一:for循环
result = []
for i in range(101):
    if i % 2 == 0:
        result.append(i)
print(result)

# 方法二:推导式:
list1=[i for i in range(101) if i%2==0] # 列表推导式、列表解析
print(list1)

推导式的使用场景:从一个列表中解析出新的列表,不适用于复杂场景。

print(["第"+str(i)+"题" for i in range(10)]) 
#['第0题', '第1题', '第2题', '第3题', '第4题', '第5题', '第6题', '第7题', '第8题', '第9题']

不仅适用于列表,可以用于字典:

# 字典推导式格式:
{键:值 for语句}
cookie="""d_c0="ANDvPjmklQ-PTl5-fxHxRD0UPail2PvjnVs=|1560520308"; 
__utmv=51854390.000--|2=registration_date=20170904=1^3=entry_date=20190614=1; 
__utma=51854390.2114427631.1561019220.1576818568.1577007244.35; 
_ga=GA1.2.2114427631.1561019220; __snaker__id=9F9gZhbTDko4wd8b; 
_9755xjdesxxd_=32; YD00517437729195%3AWM_TID=d9aHjjNd4X9FUFFAREN6wbHvnHwBuBMM; 
_xsrf=GBLbDuEbEbvjhmECrjSuabPBwMtZcK0w; _zap=322b88bb-236e-4584-9533-5e4524c00986; 
q_c1=bc085e50cc2640e8a921af8fa339d00f|1629216508000|1560520313000; 
captcha_session_v2="2|1:0|10:1635299810|18:captcha_session_v2|88:Ky9hOExON0RxVTNpYWNHZlJvaXBmQjdobWdQa2xSQjZ5Q2xwSzRxblUxdmNHWHkveXVxNDY4dU5EVXYwRVV1WQ==|02a8c49cfb3dcbd1b8dbee42adc4fd7202925dc11fcf33b7c4ec03bb75e499fb"; gdxidpyhxdE=Qqh1mxvI%2FPgED4VWe3BNPbRmEyNWdKdtR%2FMouv3HtqYOAZGp2n45OlKssQp67%5CdL%2Bwj5m9WuzCbTKR%2FHmN2OZ%5CGBD5bmcNbMkzEy87dCg90gZoKCIi080rnAqxW4WS9QuAtpjxZS4UKYzYjePaSXeHL8dWafx7zcP51NvU9K0u5sSe0Z%3A1635300849312; YD00517437729195%3AWM_NI=U0Tfb7YtQS3x6szdJmiit6XLrzqGFI6RZ261gXfjvToWCqk%2F1hetgim%2BKSXQz6xpw5KXEjeW9qJXk7VNudjZFYHFdq%2B6EuUazosTeLU7yj0i24cgiPKDhTSKmInCJDmBN0Y%3D"""

#将cookie以键值对的形式放入字典中

#方法一:
result={}
for i in cookie.split(";"):
    s = i.split("=",1)
    result[s[0]]=s[1]
print(result)

方法二:推导式:

print({i.split("=",1)[0]:i.split("=")[1] for i in cookie.split(";") })

自省、私有变量、可迭代对象

1.私有变量

在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问

class A(object):
    def __init__(self):
        self.__private = 3 # 私有变量

    def print_private(self):
        print(self.__private) # 可以内部访问

a = A()
a.print_private()
print(a.__private) # 报错:AttributeError: 'A' object has no attribute '__private'

2.自省

自省是指这种能力:检查某些事物以确定它是什么、它知道什么以及它能做什么。自省向程序员提供了极大的灵活性和控制力。

python自省:python运行的时候知道对象自身有哪些东西。

class Person:
    """
    测试帮助信息
    """
    name = "小王八"

print(dir(Person))  # dir是自省的一种,作用:查看自己有哪些属性
print(hasattr(Person,"name")) # hasattr也是自省的一种,作用:查看对象有没有某个属性
print(type(Person)) # 判断对象类型
print(getattr(Person,"name")) # 获取对象属性的值
setattr(Person,"name","文明人") # 设置对象属性的值
print(getattr(Person,"name"))
print(isinstance(Person,type)) # 判断对象是否属于已知类型
print(id(Person))  # 获取对象的唯一序号
print(help(Person)) # 查看Python自带的帮助文档信息
print(callable(Person)) # 判断对象是否可以被调用

# 打印结果:
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name']
True
<class 'type'>
小王八
文明人
True
140668992408704
Help on class Person in module __main__:

class Person(builtins.object)
 |  测试使用
 |  
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)
 |  
 |  ----------------------------------------------------------------------
 |  Data and other attributes defined here:
 |  
 |  name = '文明人'

None
True

3.可迭代对象

# 用for...in...的循环语法从其中依次拿到数据进行使用,我们把这样的过程称为遍历,也叫迭代。
# 可以用for操作的,都是可迭代对象:list、tuple、str、set、dict
# 可迭代对象都有__iter__属性的

print(hasattr(list,"__iter__")) # True
print(hasattr(tuple,"__iter__")) # True
print(hasattr(str,"__iter__")) # True
print(hasattr(set,"__iter__")) # True
print(hasattr(dict,"__iter__")) # True
print(hasattr(int,"__iter__")) # False

迭代器

# 如何判断某个对象是不是迭代器
# 方法一:使用isinstance(obj,Iterator),使用typing库
from typing import Iterator
obj = iter(range(10))
print(isinstance(obj,Iterator))
print("----分割线---")

# 方法二:看对象中是否有__iter__属性和__next__属性
obj = iter(range(10))
for _ in dir(obj):
    print(_)

迭代器协议:

  • 迭代器中必须实现__iter__和__next__(python2中是next)
  • __iter__必须返回self
  • __next__必须返回下一个值,如果没有下一个值返回StopIteration异常
  • 对迭代器进行for操作时,每次都会自动执行next
  • 只能迭代一遍
  • for语句会忽略StopIteration异常
# 根据迭代器协议,自己写一个迭代器:
class Next:
    def __init__(self, stop, start=0):
        self.stop = stop
        self.start = start

    def __next__(self):
        if self.start >= self.stop - 1:
            raise StopIteration
        self.start += 1
        return self.start

    def __iter__(self):
        return self

obj = Next(5)
print(obj.__next__())
print(obj.__next__())
print(obj.__next__())
print(obj.__next__())
print(obj.__next__())

迭代器就是能被next()调用得到下一次迭代值的对象,迭代器不直接保存迭代的序列值,而保存得到下一次迭代值的算法。

可迭代对象就是能被iter()方法调用得到迭代器的对象,能进行for循环的必须是可迭代对象。

for循环的底层实现原理:实质是调用内建方法iter()得到迭代器对象,然后通过每次调用next(迭代器)得到i的迭代值.

for i in 可迭代对象:
    循环体

迭代器的应用:

如果每次返回的数据不是在现有的数据集中读取的,而是通过程序按照一定的规则计算生成的,那么就意味着不需要依赖现有的数据集,也就是说不需要一次缓存所有要迭代的数据供后续依次读取,可以节省大量的存储(内存)空间。

如:list存1-10000的数据,占10000个整数内存,而迭代器,只占用几个内存。

# 斐波那契数列如果存储需要占用很多内存,但是存储计算斐波那契数列的取值方法内存相对小。
class Fibonacci(object):
    def __init__(self, all_num):
        self.all_num = all_num
        self.current_num = 0
        self.a = 0
        self.b = 1
    
    def __iter__(self):
        return self
 
    def __next__(self):
        if self.current_num < self.all_num:
            ret = self.a
            self.a, self.b = self.b, self.a + self.b
            self.current_num += 1
 
            return ret
        else:
            raise StopIteration
 
 
fibo = Fibonacci(10)
 
for num in fibo:
    print(num)

生成器

生成器的意义:为了快速方便的创建一个迭代器。生成器一定是迭代器。

yield关键字:来实现快速创建。

在 Python 中,使用了 yield 的函数被称为生成器(generator)。跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。

# 生成器 数的平方
def square(start,stop):
    for _ in range(start,stop+1):
        yield _*_

for i, vaule in enumerate(square(1,5)):
    print(i,vaule)

有了生成器,谁还用迭代器呢。哈哈

# 使用圆括号简写,简化生成器,自带yield
square2=(i*i for i in range(1,6))
for i, vaule in enumerate(square2):
    print(i,vaule)

生成器是如何执行:

在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。

yield和return的区别: 函数中有yield 时,不会执行函数的内容,会返回一个生成器对象。不是结束,是暂时离开函数。

return是结束函数并返回值。

递归

程序调用自身的编程技巧称为递归。递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。

递归的特点:

1.必须有一个明确的结束条件

2.每次进入更深一层递归时,问题规模(计算量)相比上次递归都应有所减少

3.递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)

# 斐波那切数列
# 1,1,2,3,5,8,13,21,34,55...除前两项外,其余项等于前两项之和

def fabonacci(n):
    if n<=2: # 前两项为固定值
        f=1
    else: # 其余项等于前两项之和
        f = fabonacci(n-1)+fabonacci(n-2)
    return f

print(fabonacci(6))

回调函数

函数:用一块代码实现某个特点的功能

回调函数是一种设计模式。如何一个函数的参数是一个函数类型,那么我把这个参数叫做回调函数。

函数中调用函数:

  1. 调用函数本身,叫做递归
  2. 调用其他函数,且当作参数调用,叫做回调函数。
  3. 调用其他函数,叫做普通调用。

闭包

闭包,是指能读取其他函数内部变量的函数。

一般,函数结束后,函数里面的所有的局部变量都会被销毁,函数调用结束后,如何获得局部变量?

答:使用闭包,在函数里再定义一个函数,把局部变量暴露给外部。

www.jianshu.com/p/ecf9eaf54…

内置函数

map 映射:

map()会根据提供的函数对指定序列做映射。

返回值:Python 2.x 返回列表。Python 3.x 返回迭代器。

def sqa(x):
    return x*2 # 返回数字的两倍

map(sqa,[3,4,7,8]) # 计算列表各个元素乘以2,返回迭代器

print(list(map(sqa,[3,4,7,8]))) # 使用list()转换为列表

print(list(map(lambda x:x*2,[3,4,7,8]))) # 匿名函数简化代码
# [6, 8, 14, 16]

zip 打包:

函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。

zip 方法在 Python 2 和 Python 3 中的不同:在 Python 3.x 中为了减少内存,zip() 返回的是一个对象。如需展示列表,需手动 list() 转换。

a = [1, 2, 3]
b = [3, 4, 5]
zipped = zip(a, b)
print(list(zipped))  # [(1, 3), (2, 4), (3, 5)]
# 与 zip 相反,*zipped 可理解为解压,返回二维矩阵式
c = (zip(*zipped)) # [1, 2, 3] [3, 4, 5]
print(a, b)

reduce 累积:

函数会对参数序列中元素进行累积。函数将一个数据集合(链表,元组等)中的所有数据进行下列操作:用传给 reduce 中的函数 function(有两个参数)先对集合中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 function 函数运算,最后得到一个结果。

Python3.x reduce() 已经被移到 functools 模块里,如果我们要使用,需要引入 functools 模块来调用 reduce() 函数

from functools import reduce

def add(x, y):
    return x + y

print(reduce(add, [1, 2, 99, 98])) # 第一个数+第二个数得到的结果,与第三数相加,以此类推

print(reduce(lambda x,y:x+y,[1, 2, 99, 98])) # 使用匿名函数简化代码

filter 过滤:

函数用于过滤序列,过滤掉不符合条件的元素,返回一个迭代器对象,如果要转换为列表,可以使用 list() 来转换。

该接收两个参数,第一个为函数,第二个为序列,序列的每个元素作为参数传递给函数进行判断,然后返回 True 或 False,最后将返回 True 的元素放到新列表中。

# 大于5的数到一个新的列表
def fil(li):
    return [x for x in li if x>5]

print(fil([1, 2,4,7,10, 99, 98]))

使用filter函数

print(list(filter(lambda x:x>5,[1, 2,4,7,10, 99, 98])))

装饰器

装饰器是闭包的一个应用。装饰器(decorator)在 python 中用来扩展原函数的功能,目的是在不改变原来函数代码的情况下,给函数增加新的功能。

作用:1. 为代码增加新功能 2.让使用者更方便

info = "一堆资料"

def first(fun):
    def second(arg):
        print("新增代码1")
        fun(arg)
        print("新增代码2")

    return second

@first
def ori(info):
    print(info)
    print(info * 2)
    print("很多代码")

ori(info) # 在ori上加上装饰器@first,等同于first(ori(info))

# 打印结果:
"""
新增代码1
一堆资料
一堆资料一堆资料
很多代码
新增代码2
"""

给函数添加时间:

def time_record(fun):
    def new_fun():
        print("开始时间")
        fun()
        print("结束时间")
    return new_fun

@time_record
def first_fun():
    print("一些代码")


first_fun()

用类装饰某个函数:

单例模式

单例模式:只有一个实例

class Person:
    def __init__(self,name):
        self.name =name
       
    # 非单例模式时,每次都会创建对象
    def __new__(cls, *args, **kwargs):
        self = super.__new__(cls) # 分配内存
        return self

    def print_name(self):
        print(self.name)
       

if __name__ == '__main__':
    xiaoming = Person("xiaoming")
    xiaoyang = Person("xiaoyang")
    print(xiaoming is xiaoyang) # False 
    print(id(xiaoming),id(xiaoyang)) #4431248400 4431248592,id不一样,不是同一个对象

如何成为单例模式:创建对象时判断是否已创建过对象,已创建过,不再创建,未创建过,再进行创建。

class Person:
    obj = None # 标识,用于后面判断是否创建过对象
    
    def __init__(self,name):
        self.name = name

    def __new__(cls, *args, **kwargs):
        if cls.obj is None: # 如果没有创建
            cls.obj = super().__new__(cls) # 分配内存
        return cls.obj

    def print_name(self):
        print(self.name)

if __name__ == '__main__':
    xiaoming = Person("xiaoming")
    xiaoyang = Person("xiaoyang")
    print(xiaoming is xiaoyang) # True
    print(id(xiaoming),id(xiaoyang)) #4392752272 4392752272

单例模式的实现方法:

  1. import导入:import时会生成一个.pyc文件,这个文件只会生成一次,所以只导入一次,如果把某个对象放在模块中进行import,那么这个对象就是单例模式

  2. 单例模式:

  3. 装饰器:

魔术方法

在Python中,所有以__双下划线包起来的方法,都统称为“Magic Method”,中文称魔术方法,例如类的初始化方法 __init__

前面讲过__new__是用来创建类并返回这个类的实例, 而__init__只是将传入的参数来初始化该实例。

__str__: 触发时机:使用print(对象)或者str(对象)的时候触发

class MyList(list):

    def __str__(self):
        return "print时调用"


nums = MyList([1, 2, 3])
print(nums) # print时调用

当使用print输出对象的时候,只要自己定义了__str__(self)方法,那么就会打印从在这个方法中return的数据

上下文管理器

with是一种上下文管理协议,目的在于从流程图中把 try,except 和finally 关键字和资源分配释放相关代码统统去掉,简化try….except….finlally的处理流程。with通过enter方法初始化,然后在exit中做善后以及处理异常。所以使用with处理的对象必须有__enter__()和__exit__()这两个方法。

class DebugA:
    def __enter__(self):
        print("enter方法")

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("exit方法")


obj = DebugA()
with obj as o:
    print("----")

打印结果:

enter方法
----
exit方法

执行顺序: enter方法--with包裹的代码块--exit方法 通常:exit方法中写退出操作,enter方法中写打开操作、初次进入的操作。

with的使用场景:open

# open方法返回一个对象,文件对象。文件对象支持上下文管理协议。文件对象有__enter__、__exit__方法
f = open("temp.txt", "w")
print(hasattr(f, "__enter__"))  # True
print(hasattr(f, "__exit__"))  # True
f.close()  # 关闭文件

# 等价于:
with open("temp.txt", "w") as f:  # open方法返回的文件对象赋值给f
    pass

使用with更优雅,更简洁,更安全。

设计模式:鸭子模型

在程序设计中,鸭子类型(duck typing)是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由"当前方法和属性的集合"决定。在鸭子类型中,关注点在于对象的行为,能作什么;而不是关注对象所属的类型。例如,在不使用鸭子类型的语言中,我们可以编写一个函数,它接受一个类型为"鸭子"的对象,并调用它的"走"和"叫"方法。

class Dog:
    def say(self):
        print("wang")


class Cat:
    def say(self):
        print("miao")


def f(obj):
    # obj 只要是有say方法的对象都行
    obj.say()

dog = Dog()
cat = Cat()
f(cat)  # miao
f(dog)  # wang

应用场景:发送消息,无论发送什么类型的消息,作为使用者,我只用调用send_msg()。

class Text:
    def send(self):
        print("发送文本消息")

class Image:
    def send(self):
        print("发送图片消息")

def send_msg(obj):
    obj.send()


send_msg(Text())  # 发送文本消息
send_msg(Image())  # 发送图片消息

进程基础

单任务:单任务的应用程序:CMD ,没办法同时执行多条命令。一次只能执行一条。

多任务:多任务的应用:操作系统、pycharm(同时打开多个项目、后台下载库)、迅雷(同时下载多个文件、边下边播)

多任务的实现方式:多线程、多进程、协程

串行运行:在linux中串行执行多条命令 &

并行运行:在linux中并行执行多条命令 &&

并发:迅雷下载,单核CPU同时下载5个电影时,5个电影下载在不停的切换,一次只能下一个电影,但是切换的速度足够快,让人觉得是在同时下载5个电影。

并行:在三个电脑上下载mysql,构成mysql集群,3个mysql服务器就是并行。是真正的同时运行。

# 多进程实现并发下载图片
from multiprocessing import Process, Queue
import requests


# 传入一个队列
def download(q):
    while True:
        # get方法:从队列中取出一个对象
        url = q.get()
        # 给服务器发送请求
        r = requests.get(url)
        image_name = url.split("/")[-1]
        # 将服务器返回的信息保存到文件中(返回的图片流)
        with open(image_name, "wb") as f:
            f.write(r.content)
        if q.empty():
            break


if __name__ == '__main__':
    tasks = Queue()
    # put方法:把对象加到队列中
    tasks.put("https://static.iyb.tm/web/pc/baoyun/components/header/logo_color.png")
    tasks.put(" https://cdn2.jianshu.io/assets/default_avatar/1-04bbeead395d74921af6a4e8214b4f61.jpg")
    # 多进程
    p1 = Process(target=download, args=(tasks,))  # 创建一个执行download方法的进程p1
    p2 = Process(target=download, args=(tasks,))  # 创建一个执行download方法的进程p2
    p1.start()  # 启动进程p1
    p2.start()  # 启动进程p2
    # 进程启动后是由操作系统控制执行的,是并发还是并行执行,假设是单核CPU,肯定是并发执行,假设是多核CPU,可以并行执行。

进程Process:进程是动态的,程序是如何运行?操作系统创建一个进程(加载代码到代码区、为变量分配内存,创建一个进程id,process_id,简称pid)

import os
print(os.getpid()) # 79625
input("任意键终止进程") # 用来阻断进程完成,方便观察资源管理器

图片.png

多进程的缺点:多进程会独享资源,多个进程不会共享变量、内存,一个进程独享一个资源,另个进程独享一个资源。(比如,一个list,多个进程操作这个list,每个进程都需要开辟内存去存储这个list)如何实现共享资源:队列、内存数据库redis等。

对于下载功能,通常是用多线程的实现。多线程占用资源少,共享变量,迅雷实际使用的是多线程技术。

线程与进程的区别

进程中为什么要用队列

from multiprocessing import Process, Queue

多进程的包里有进程类Process、队列类Queue,队列类是多进程包里特有的,要用队列不能用其他包里的队列,只能用进程包里的队列。

multiprocessing.Queue :使用了管道+序列化 技术,是进程安全的,不会出现多个进程拿到同一个数据,不需要加锁。

每开启一个进程,会将代码的变量、内存进行隔离,各自只能访问各自的。这样做的有点是安全,缺点是缺乏通信,对于值的修改不知道,没发共享同一个数据。

def work01(q):
    """

    :param q: 要处理的所有任务
    :return:
    """
    print(id(q))
    while q:
        print("work01,q中获得了{}".format(q.pop()))


def work02(q):
    """

    :param q:要处理的所有任务
    :return:
    """
    print(id(q))
    while q:
        print("work02,q中获得了{}".format(q.pop()))


if __name__ == '__main__':
    q = ["a", "b", "c"]
    p1 = Process(target=work01, args=(q,))  # 子进程1
    p2 = Process(target=work02, args=(q,))  # 子进程2
    p1.start()
    p2.start()
    print("主进程结束,代码从上到下运行就是主进程")

输出

主进程结束,代码从上到下运行就是主进程
4351581472
work01,q中获得了c
work01,q中获得了b
work01,q中获得了a
4351581472
work02,q中获得了c
work02,q中获得了b
work02,q中获得了a

因为多进程不共享变量,list不共享变量,所以每个进程都拿到了a,b,c 主进程结束,子进程也不一定结束

使用队列进行改造:

def work01(q:Queue):
    """

    :param q: 要处理的所有任务
    :return:
    """
    print(id(q))
    while not q.empty():
        print("work01,q中获得了{}".format(q.get()))


def work02(q:Queue):
    """

    :param q:要处理的所有任务
    :return:
    """
    print(id(q))
    while not q.empty():
        print("work02,q中获得了{}".format(q.get()))


if __name__ == '__main__':
    q = Queue() # 队列
    q.put("a")
    q.put("b")
    q.put("c")
    p1 = Process(target=work01, args=(q,))  # 子进程1
    p2 = Process(target=work02, args=(q,))  # 子进程2
    p1.start()
    p2.start()
    print("主进程结束,代码从上到下运行就是主进程")

输出

主进程结束,代码从上到下运行就是主进程
4427283344
work01,q中获得了a
4427283344
work01,q中获得了b
work01,q中获得了c

使用队列,队列是被被子进程p1\p2共享的。

多进程类的写法

from multiprocessing import Process

# 继承Process类
class MyProcess(Process):

    def __init__(self):
        # 执行下父类的__init__()
        super().__init__()

    # p1.start()会调用run方法
    def run(self):
        print("运行MyProcess类中的run方法")


if __name__ == '__main__':
    p1 = MyProcess()
    p2 = MyProcess()
    p1.start()  # 调用MyProcess类中run方法
    p2.start()  # 调用MyProcess类中run方法

进程细节

解答问题:

  1. p1,p2是怎么顺序进行运行的

  2. 怎么让主进程等待子进程结束后再结束:使用join,join要放在所有进程启动之后

import time
from multiprocessing import Process,Queue

def play_music():
    for i in range(5):
        print("play...music..")
        time.sleep(1)

def play_game():
    for i in range(5):
        print("play...game.")
        time.sleep(1)

if __name__ == '__main__':
    # 串行实现,一个接一个的
    # start_time = time.time()
    # play_music()
    # play_game()
    # end_time = time.time()
    # print("差值:{} - {} = {}".format(end_time,start_time,end_time-start_time)) # 10.03

    # 使用多进程
    start_time = time.time()
    p1 = Process(target=play_music)
    p2 = Process(target=play_game)
    p1.start()

    p2.start()
    p1.join() # 上面的代码没有执行完,不会执行后面的代码
    p2.join()
    print("主进程结束")
    end_time = time.time()
    print("差值:{} - {} = {}".format(end_time, start_time, end_time - start_time)) # 5.02

os相关:

进程号:os.getpid()

import time
from multiprocessing import Process, Queue
import os


def play_music():
    print("子进程:{}".format(os.getpid()))
    for i in range(5):
        print("play...music..")
        time.sleep(1)


def play_game():
    print("子进程:{}".format(os.getpid()))
    for i in range(5):
        print("play...game.")
        time.sleep(1)


if __name__ == '__main__':
    print("主进程:{}".format(os.getpid()))
    start_time = time.time()
    p1 = Process(target=play_music)
    p2 = Process(target=play_game)
    p1.start()

    p2.start()
    p1.join()
    p2.join()
    print("主进程结束")
    end_time = time.time()
    print("差值:{} - {} = {}".format(end_time, start_time, end_time - start_time))

守护进程: p1.daemon ,默认参数就是False。当值为True时,主进程结束会强制结束子进程。应用场景:不允许主进程结束子进程还存在的场景时。

看一个进程是否活着:p1.is_alive()

线程

操作系统:创建 并 管理进程 (用代码杀死进程,其实也是调用操作系统的方法)

管理主要包括:创建(分配资源、内存)、启动、挂起(例如等待主进程,sleep是占用cpu的,挂起是不占用cpu的)、销毁

线程:不是操作系统,线程在进程中创建,一个进程中起码有一个线程。线程是不拥有资源的。它的资源是进程拥有的(如线程1,线程2,线程3共享进程1的资源)

download.py 用来下载资源的

python 运行download.py代码 启动一个download的进程,在该进程中开了t1,t2,t3,t4,t5共享download进程中的所有资源。

线程的调度是归操作系统调度,所以多线程的调度——谁先运行谁后运行是不一定的。

cpu调度和分配的最小单位是线程。进程是系统资源分配的基本单位。

进程与线程的区别,面试题总结:

  • 资源(主要指内存资源)方面:进程有,线程用的是进程的
  • 切换:多进程的切换,进程挂起时,cpu就会把资源分给未挂起的进程,就发生了切换。多个线程挂起时也会进行切换。线程切换比进程快
  • 一个程序的运行至少有一个进程,一个进程中至少有一个线程。

什么情况下适用于多线程:

  • CPU密集型的不适合多线程:一直占用CPU的场景不适合使用多线程,工厂里的某机器不停的需要工人操作,不适合一个人操作2台机器。

  • 间歇性占有CPU的场景适合多线程(IO密集型):IO操作是不占用CPU的,网络IO(大家网卡了3秒,这3秒称为阻塞),工厂里的某机器,操作5分钟工作半小时,适合一个人操作多台机器。

练习:多线程下载

"https://so.gushiwen.cn/mingjus/default.aspx?page=1&tstr=&astr=&cstr=&xstr="
"https://so.gushiwen.cn/mingjus/default.aspx?page=2&tstr=&astr=&cstr=&xstr="
"https://so.gushiwen.cn/mingjus/default.aspx?page=3&tstr=&astr=&cstr=&xstr="
"https://so.gushiwen.cn/mingjus/default.aspx?page=4&tstr=&astr=&cstr=&xstr="
import queue
import threading
import requests
import re


def download(q: queue.Queue):
    while not q.empty():
        url =q.get()
        res = requests.get(url)
        page_num = re.findall(r'page=(\d)&tstr=',url)[0]
        file_name = "诗歌"+page_num+".html"
        with open(file_name,"wb") as f:
            f.write(res.content)


# 多线程使用队列,用内置的queue库
if __name__ == '__main__':
    
    q = queue.Queue()  # Queue类
    for i in range(1, 5):
        q.put("https://so.gushiwen.cn/mingjus/default.aspx?page={}&tstr=&astr=&cstr=&xstr=".format(i)
              )
    t1 = threading.Thread(target=download,args=(q,))
    t2 = threading.Thread(target=download,args=(q,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print("finish")

多线程使用队列,用内置的queue库。 q = queue.Queue()

线程使用threading库。

多线程/多进程步骤:

  1. 任务方法:需要做的工作,如下载,入参是队列形式传入url,q.get()接收队列
def download(q: queue.Queue):
    while not q.empty():
        url =q.get()
        res = requests.get(url)
        page_num = re.findall(r'page=(\d)&tstr=',url)[0]
        file_name = "诗歌"+page_num+".html"
        with open(file_name,"wb") as f:
            f.write(res.content)

  1. 队列q.put()传入任务参数给
q = queue.Queue()  # Queue类
for i in range(1, 5):
    q.put("https://so.gushiwen.cn/mingjus/default.aspx?page={}&tstr=&astr=&cstr=&xstr=".format(i)
              )
  1. 使用多线程/多进程
# 多线程
t1 = threading.Thread(target=download,args=(q,))
t2 = threading.Thread(target=download,args=(q,))
t1.start()
t2.start()
t1.join()
t2.join()

# 多进程
p1 = Process(target=download,args=(q,))
p2 = Process(target=download,args=(q,))
p1.start()
p2.start()
p1.join()
p2.join()

线程池、进程池

python3.2之后,是新版的线程池、进程池

# 线程池
import queue
from multiprocessing.pool import ThreadPool


def download(q: queue.Queue):
    while not q.empty():
        print(q.get())
        q.task_done()  # 通知这个任务做完啦,每完成一个任务,把未完成的任务数-1,当未完成数为0时,join()结束


if __name__ == '__main__':
    q = queue.Queue()
    for i in range(1, 5):
        q.put("https://so.gushiwen.cn/mingjus/default.aspx?page={}&tstr=&astr=&cstr=&xstr=".format(i)
              )
    pool = ThreadPool(2)  # 线程池中开启多少个线程
    pool.apply_async(download, args=(q,))  # 异步调用任务函数
    q.join()  # 等待所有的任务做完

map函数:把一个可迭代对象 映射到函数

apply函数:调用函数,传递任意参数

进程池:

# 进程池
from multiprocessing import Pool
import time


def square(x):
    time.sleep(2)
    print("结果为:{}".format(x * x))
    return x * x


if __name__ == '__main__':
    start_time = time.time()
    pool = Pool(2)
    pool.map(square, [2, 4, 3, 6, 7, 1]) # map会阻塞主程序,不需要用join(),map以多进程的方式收集迭代每个元素
    pool.close()
    end_time = time.time()
    tt = end_time - start_time
    print(tt) # 6.055865049362183

使用异步map:

result = pool.map_async(square, [2, 4, 3, 6, 7, 1]) # 返回是一个map对象
res = result.get() # 使用get获取结果
# 不需要结果时,可以使用异步

apply:

def square(x):
    time.sleep(2)
    return x * x


if __name__ == '__main__':
    pool = Pool(2)
    res=pool.apply(square,(2,)) # apply,阻塞主程序,不需要用join()
    print(res)
    pool.close()

异步apply:

res=pool.apply_async(square,(2,))

异步不阻塞主程序(主进程),非异步的会阻塞主进程

并发跑测试用例,多请求接口的,用多线程。

多进程:多任务

多线程:模拟1000个用户

协程

协程

并发:JMeter并发100个请求

并行:两个进程分别在两个CPU上并行运行

同步:IO操作(输入输出,是个耗时的操作),同步需要等待操作完毕

异步:不等待操作完毕

并发是实现异步

实现异步(并发)的方法:多线程、协程

多线程:CPU调度多个线程 (内核)

协程:开发人员调度多个任务 (用户)

1)挂起当前状态(暂停)

2)激活挂起状态(恢复)

从生成器到协程

从python2到python3,协程发生了翻天覆地的变化。

简单的生成器

python2.3中加入了新的关键字yield

在PEP255中,引入yield表达式

规定yield语句只能在函数中使用,包含yield语句的函数被称为生成器函数。

当执行到yield语句时,函数的状态会被冻结(挂起所有状态,如:局部变量、指令指针、堆栈状态等),以便下载next时恢复状态继续执行。

通过生成器实现协程

协程的底层架构是在PEP342中定义,在python3.5实现的。

实现思维:使用yield挂起生成器,使用send方法激活生成器,这样生成器就具备了实现协程的功能。

执行generator.send(None)完全等同于调用生成器的next方法,使用其他参数调用send也有同样的效果,不同的是,当前生成器表达式产生的值会不一样。

协程的演变

python3.3 增加了yield from语法,使调用嵌套生成器变得简单。

python3.5 加入关键字async和await,将生成器和协程彻底分开。

gevent

gevent 是应有非常广泛的异步网络库,底层使用的是greenlet协程。

协程的核心思想

为了实现异步IO

若干个协程任务,当某个任务遇到阻塞时,自动切换到非阻塞的任务上。

阻塞:IO阻塞,磁盘IO:写数据是需要时间的,网络IO:网络请求数据是需要时间的。

切换有两种:

用户态切换:切换不需要CPU调度。

核心态切换:线程切换,进程切换,切换需要CPU调度。

生成器实现并发:

import time

# 生成器实现并发,没有对IO阻塞进行检测


def work01():
    for i in range(5):
        print("work1:听音乐")
        time.sleep(1)
        yield


def work02():
    for i in range(5):
        print("work2:打游戏")
        time.sleep(1)
        yield


def main():
    # 并不会调用work01,work02方法
    g1 = work01()
    g2 = work02()
    # 调用next方法,才会调用work01,work02方法
    while True:
        try:
            next(g1)
            next(g2)
        except StopIteration:
            break


if __name__ == '__main__':
   
    start_time = time.time()
    main()
    print("所以任务执行完毕")
    end_time = time.time()
    print(end_time - start_time) # 10.088864088058472

gevent实现协程:

import time
import gevent


# 用gevent实现协程

def work01():
    for i in range(5):
        print("work1:听音乐")
        gevent.sleep(1)


def work02():
    for i in range(5):
        print("work2:打游戏")
        gevent.sleep(1) # gevent的阻塞,会通知自己正在阻塞,给没有在阻塞的协程使用


if __name__ == '__main__':
    start_time = time.time()
    g1 = gevent.spawn(work01)  # 创建协程1对象
    g2 = gevent.spawn(work02)  # 创建协程2对象
    g1.join()
    g2.join()
    print("所以任务执行完毕")
    end_time = time.time()
    print(end_time - start_time) # 5.133851766586304

使用time.sleep(1)时,gevent检测不到阻塞,需要打补丁。

gevent是使用greenlet协程来实现异步网络框架,它提供了一个异常方便了monkey模块,可以在不修改原来使用的python标准库函数的程序的情况下,将程序转换成可以使用gevent框架的异步程序。

from gevent import monkey

monkey.patch_all()

def work01():
    for i in range(5):
        print("work1:听音乐")
        time.sleep(1)

使用python3.5之后,也不再使用gevent,使用asycio实现协程,不需要导入第三方库。

import asyncio
import time


# 异步IO库,能实现非阻塞IO,单线程实现并发,即协程。
# async await 关键字

async def work1():  # async 声明work1是异步的
    for i in range(5):
        print("work1:听音乐")
        await asyncio.sleep(1)  # 见到await时,把当前挂起,去执行别的任务


async def work2():
    for i in range(5):
        print("work2:打游戏")
        await asyncio.sleep(1)


async def main():
    task1 = asyncio.create_task(work1())  # 任务1
    task2 = asyncio.create_task(work2())  # 任务2
    await task1
    await task2

if __name__ == '__main__':
    start_time = time.time()
    asyncio.run(main())
    print("所以任务执行完毕")
    end_time = time.time()
    print(end_time - start_time) # 5.064110517501831

image.png