登录
转载

Python进阶笔记

发布于 2021-05-08 阅读 471
  • 后端
  • Python
转载

文章目录

    • 简介
    • 面向对象
    • 异常处理
    • 自定义包/模块
    • 封装
      • 多态
    • 线程
    • 进程
    • 多任务
      • 迭代器
      • 生成器
      • 协程
      • 并发下载器
    • 总结

简介

  • 基础笔记中介绍了数据类型和文件操作等基本问题,如果想了解的更深入一些可以看看面试篇
  • 进阶部分主要包括面向对象思想、异常处理、迭代器、生成器和协程

面向对象

  • Python从设计之初就已经是一门面向对象的语言
  • 类(class): 用来描述具有相同的属性和方法的对象的集合,主要有以下概念:
    • 方法:类中定义的函数
    • 类变量:类变量在整个实例化的对象中是公用的,可理解成static类型
    • 局部变量:定义在方法中的变量
    • 实例变量:也叫类属性,即用 self 修饰的变量
    • 继承:即一个派生类(derived class)继承基类(base class)的属性和方法
    • 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,也叫方法的覆盖(override)
    • 对象:类的实例
    • 构造方法:__init__,在类实例化时会自动调用的方法
    ```python class MyClass: def __init__(self): self.i = 12345 def f(self): # 注意成员函数要加self return self.i

    实例化类

    x = MyClass()

    访问类的属性和方法

    print(“MyClass 类的属性 i 为:”, x.i)
    print(“MyClass 类的方法 f 输出为:”, x.f())

     </li><li>还是得用起来,可以看一下那个基础小项目,深入理解这种思想的优势</li></ul> 
    <h2>异常处理</h2> 
    <ul><li>在Python无法正常处理程序时就会发生一个异常,我们需要捕获处理它,否则程序会终止执行</li><li>常见的异常处理方法: 
      <ul><li>使用<code>try/except</code>:检测try语句块中的错误,从而让except语句捕获异常信息并处理</li><li>还可以使用<code>raise</code>语句自己触发异常</li><li>可以是python标准异常,也可以继承并自定义</li></ul> </li><li>参看教程,过一遍即可</li><li>在我的Python面试篇(一)中也提到了异常的继承关系
    ```python
    # 一般这么写
    try:
        fh = open("testfile", "w")
        fh.write("这是一个测试文件,用于测试异常!!")
    except IOError:		# 这是一个标准异常
        print("Error: 没有找到文件或读取文件失败")
    else:
        print("内容写入文件成功")
        fh.close()
    # python3中没有了message属性,可直接str()或者借用sys的exc_info
    try:
        a = 1/0
    except Exception as e:  # 这个e就是异常提示信息
        exc_type, exc_value, exc_traceback = sys.exc_info()
        print(str(e))       # division by zero
        print(exc_value)    # division by zero
    finally:	# 无论是否发生异常都将执行最后的代码
        print('over!')
    
    # 触发异常
    def func(level):
        if level < 1:
            raise Exception("Invalid level!", level)	# 都可以输出,你写就行
            # 触发异常后,后面的代码就不会再执行
    func()	# Exception: ('Invalid level!', 0)
    
    # 自定义异常
    class Networkerror(RuntimeError):	# 继承RuntimeError
        def __init__(self, arg):
            self.args = arg
            # self.message = arg
            
    try:
        raise Networkerror("Bad hostname")
    except Networkerror as e:	# 这个e就是异常提示信息
        print(e.args)	# 也可以输出 format(e)  str(e)
    
    
  • Python3会默认使用Traceback跟踪异常信息,从下往上找!

自定义包/模块

  • 大型的程序需要自定义很多的包(模块),既增强代码的可读性,也便于维护

  • 使用import 导入内置或者自定义模块,相当于include

    ```python import my # 导入my.py 方式一 from my import test # 导入里面的一个函数test() 方式二
     </li><li> <p>导入时,系统会从设定的路径搜索,查看系统包含的路径: <img src="https://img-blog.csdnimg.cn/20210417122643675.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveV9BbGxlbg==,size_16,color_FFFFFF,t_70#pic_center" alt="sp1" /></p> 
      <ul><li><code>''</code> 表示当前路径</li></ul> </li><li> <p>当前程序对导入的模块会防止重复导入,即导入后修改了模块,无法直接重新导入</p> 
    ```python
    # 需要使用reload模块重导
    from importlib import reload	# imp已弃用
    reload(module_name)
    
    # 查看帮助
    help(reload)	# The module must have been successfully imported before.
    
    
  • 多模块开发注意点

    • 导入方式的不同,决定了变量是全局还是局部
    • 如图所示,指向变了:
    • 如果导入的是列表,使用append()方法追加,不会重新定义变量
    • 如果直接让HADNLE_FLAG = xxx,相当于新定义变量,并未改变common中的list值 sp3
    • 因此,只能按第一种方式导入使用: sp2
  • 需要注意:

    • __name__属性
    ```python # 如果我们想在模块被引入时,模块中的某一程序块不执行,我们可以用__name__属性 # 使该程序块仅在该模块自身运行时执行

    Filename: using_name.py

    if name == ‘main’:
    print(‘程序自身在运行%s’%name)
    else:
    print(‘我来自另一模块%s’%name)

     
    

    $ python using_name.py
    程序自身在运行__main__ # 显示主模块

    $ python

    import using_name
    我来自另一模块using_name # 显示文件名

     </li></ul> 
    <h2>封装</h2> 
    <ul><li>封装、继承、多态是面向对象(类)的三大特性</li><li>如图,<code>__class__</code>属性等价于子类可以调用父类的函数(类比C++) <img src="https://img-blog.csdnimg.cn/20210417133732474.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveV9BbGxlbg==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述" /></li></ul> 
    <h3>多态</h3> 
    <ul><li> <p>类比C++</p> 
      <ul><li>虚函数重写:<code>virtual void func(int a){}</code>,特点是先不编译,不确定是哪个类调用的</li><li>在<strong>全局函数</strong>定义中传入父类指针,利用父类指针可以指向子类对象的特性,当传入子类对象时(已经确定是子类对象),通过子类VPTR调用子类虚函数表(动态联编)执行子类的重写方法</li><li>详见我的C++笔记</li></ul> </li><li> <p>在Python中类似,看个例子</p> 
    ```python
    # python中继承就写在括号里
    class MiniOS(object):	# 所有类的基类
        """MiniOS 操作系统类 """
        def __init__(self, name):	# 构造函数
            self.name = name
            self.apps = []  # 安装的应用程序名称列表	list()
    
        def __str__(self):	# __xxx__(self) 叫魔法方法
            """返回一个对象的描述信息,print对象时使用"""
            return "%s 安装的软件列表为 %s" % (self.name, str(self.apps))
    
        def install_app(self, app):	# 传入父类指针
            # 判断是否已经安装了软件
            if app.name in self.apps:
                print("已经安装了 %s,无需再次安装" % app.name)
            else:
                app.install()
                self.apps.append(app.name)
    
    
    class App(object):
        def __init__(self, name, version, desc):
            self.name = name
            self.version = version
            self.desc = desc
    
        def __str__(self):
            return "%s 的当前版本是 %s - %s" % (self.name, self.version, self.desc)
    
        def install(self):	# 相当于虚函数
            print("将 %s [%s] 的执行程序复制到程序目录..." % (self.name, self.version))
    
    
    class PyCharm(App):	# 子类继承App
        pass
    
    # 同一作用域叫重载
    class Chrome(App):
        def install(self):	# 相当于虚函数重写;	类中的普通函数叫重定义
            print("正在解压缩安装程序...")
            super().install()	# 要通过super调用,而不是直接用
    
    
    linux = MiniOS("Linux")
    print(linux)
    
    pycharm = PyCharm("PyCharm", "1.0", "python 开发的 IDE 环境")
    chrome = Chrome("Chrome", "2.0", "谷歌浏览器")	# 传入子类对象
    
    linux.install_app(pycharm)	# 相当于全局函数,传入哪个子类执行哪个子类的虚方法
    linux.install_app(chrome)
    linux.install_app(chrome)
    
    print(linux)	# Linux 安装的软件列表为 ['PyCharm', 'Chrome']
    
    
  • 仔细体会!

线程

  • 使用threading创建子线程 ```python import threading import time

    def func1(num1):
    for i in range(18):
    print(num1)
    time.sleep(0.1)

    def func3(str):
    for i in range(18):
    print(str)
    time.sleep(0.1)

    def func2():
    for i in range(20):
    print(‘主线程’,i)
    time.sleep(0.1)

    if name == ‘main’:
    thread = threading.Thread(target=func1, args=(555,)) # 列表参数
    thread2 = threading.Thread(target=func3, kwargs={‘str’:‘roy’}) # 关键字参数
    thread.start()
    thread2.start()
    func2() # 主线程一般放在后面,不然会先执行完主进程

     </li><li>主线程一般会等待子线程结束再退出</li><li>可以通过设置守护线程,主线程结束即全部退出
    ```python
    if __name__ == '__main__':
        thread = threading.Thread(target=func1, args=(555,))    # 元祖形式传参
        thread2 = threading.Thread(target=func3, kwargs={'str':'roy'})# 字典形式
        # 守护进程
        thread.setDaemon(True)  
        thread.start()
        # 必须都设置守护线程才会在主线程结束时退出
        thread2.setDaemon(True)
        thread2.start()
        func2()
    
    
  • 互斥锁——线程同步 ```python # 多个线程之间同时操作全局变量就会出问题,需要上锁 lock = threading.Lock() # 互斥锁 arr = 0

    def lockfunc1():
    lock.acquire() # 锁住
    global arr # 需要拿到全局变量arr
    for i in range(500):
    arr += 1
    print(‘进程1:’,arr)
    lock.release() # 释放锁

    def lockfunc2():
    lock.acquire()
    global arr
    for i in range(400):
    arr += 1
    print(‘进程2:’,arr)
    lock.release()

     </li></ul> 
    <h2>进程</h2> 
    <ul><li>每创建一个进程操作系统都会分配运行资源,真正干活的是线程,每个进程会默认创建一个线程</li><li>多进程可以是多个CPU核,但一般指的是单核CPU并发,而多线程是在一个核里进行资源调度,可以结合并行并发的概念理解
    ```python
    import multiprocessing
    
    def func1(num1):
        for i in range(18):
            print(num1)
            time.sleep(0.1)
    
    def func2(str):
        for i in range(18):
            print(str)
            time.sleep(0.1)
            
    if __name__ == '__main__':
    	multi1 = multiprocessing.Process(target=func1)
        multi2 = multiprocessing.Process(target=func2)# 每个进程自带一个线程
        multi1.start()
        multi2.start()
    
    
  • 类似的可以使用守护进程退出子进程
    • 守护进程是一种特殊的后台进程,子进程以守护进程启动,那么就会看主进程眼色行事,懂了没有!
    • 一般守护进程会在系统开机时创建,关机时才会退出,默默监视…
  • 也可以使用Precess.terminate()终止
  • 进程之间是独立的(资源分配的基本单位),不共享全局变量
  • 那么进程之间如何通信呢?消息队列 ```python queue = multiprocessing.Queue(3) # 默认可以存任意多数据
     
      <ul><li>当然,还有共享内存、管道等,可以看我的操作系统笔记</li><li>个人感觉这个小哥总结的很好,推荐!</li></ul> </li></ul> 
    <h2>多任务</h2> 
    <ul><li>了解了进程和线程,但在python中还有个特色:协程</li><li>面试可能会问到三个问题:迭代器是什么?生成器是什么?协程是什么?</li></ul> 
    <h3>迭代器</h3> 
    <ul><li> <p>Python三君子:迭代器、生成器、装饰器</p> </li><li> <p>迭代器一般用在可迭代对象,包括:列表、字典、元祖、集合,一般用在for循环中</p> 
    ```python
    # 判断是否可迭代
    from collections import Iterable
    # 使用函数:只要可以迭代,就一定能溯源到Iterable
    isinstance([], Iterable)	# isinstance:是不是一个示例,即前面的属不属于后面的
    isinstance(a,A)		# 对象a是不是类A的实例
    
    
    • 首先判断对象是否可迭代:看有无__iter__方法
    • 然后调用iter()函数:自动调用上述魔法方法,返回迭代器(对象)
    • 通过调用next()函数,不断调用可迭代对象的__next__方法,获取对象的下一个迭代值
  • 迭代器还应用在类中,怎么让类成为可迭代对象呢? sp6

    • 直接在类中实现__iter____next__方法
    • 通过for循环调用next方法
  • 也可以将类本身作为迭代器返回 sp4

    • 上面自定义个类Classmate,先实现__iter__方法,让它是一个可迭代对象,同时返回ClassIterator迭代器
    • 迭代器必须也是可迭代对象,所以也得实现__iter__方法
    • 同理,当实例化类后,调用next方法即可获得ret值
    • 也说明:迭代器一定可迭代,可迭代的不一定是迭代器(得看有没有__next__方法)
  • 下面这个例子也说明了迭代器原理:

    ```python class MyIterator: def __iter__(self): # 返回迭代器对象(初始化) self.a = 1 # 标记迭代位置 return self
    def __next__(self): # 返回下一个对象
        x = self.a
        self.a += 1	# 可以发现,这里是得到下一个值的方法
        return x
    

    myclass = MyIterator()
    myiter = iter(myclass) # 得到迭代器

    print(next(myiter)) # 1
    print(next(myiter)) # 2
    print(next(myiter)) # 3
    print(next(myiter)) # 4
    print(next(myiter)) # 5

     
      <ul><li>这里还调用了<code>iter()</code>方法,这和定义相关,因为要初始化self.a,所以这不是必须的</li><li>重点是:迭代器返回的是得到数据的方式,可以节省内存</li></ul> </li><li> <p>例如:range()和==xrange()==的区别</p> 
    ```python
    # 在python2中
    range(100) # 返回0到99的列表,占用较多内存
    xrange(100)	# 返回生成数据的方式
    
    # 在py3中range()相当于xrange()
    range()
    
    
  • 实际应用一下,彻底接受迭代器: sp7

    • 使用迭代器的形式得到斐波那契数列
      • 你不会不知道吧?0、1、1、2、3、5、8、13、21、34…叫Fibonacci sequence
    • 重点是:使用for循环的形式默认调用next方法;这也是前面为什么说应用在for循环中
    • 还有Python中这个swap的写法,很常见,记一下!
  • 当然,不止for循环可以接收迭代对象,类型转换本质也是迭代器

    ```python li = list(FibIterator(15)) print(li) tp = tuple(FibIterator(6)) print(tp)
     </li><li> <p>迭代器是什么?</p> 
      <ul><li>迭代器支持next()方法获取可迭代对象下一个值</li><li>一般应用在for循环和类中,本质是实现了iter和next魔法方法</li><li>迭代器通过给出数据生成的方式,节省程序运行时数据对内存空间的占用</li></ul> </li></ul> 
    <h3>生成器</h3> 
    <ul><li> <p>在实现一个迭代器时,需要我们手动返回,并实现next方法,进而生成下一个数据</p> </li><li> <p>可以采用更简便的生成器语法,即生成器(generator)是一类特殊的迭代器</p> </li><li> <p>方式一:</p> 
    ```python
    L = [ x*2 for x in range(5)]	# [0, 2, 4, 6, 8]
    G = ( x*2 for x in range(5))	# <generator object <genexpr> at ...
    # 区别仅在于外层的(),可以按照迭代器的使用方法来使用
    next(G)	# 0	G此时就是一个迭代器
    next(G)	# 1
    next(G)	# 2
    
    
    • 只要是迭代器,就可以用next()来发动
  • 方式二:

    • 使用yield关键字创建生成器,特点是执行到yield即返回后面的值
    • 下次迭代可以接着执行,即特殊的流程控制
    • 看个例子:还是得到斐波那契数列
    ```python def create_num(all_num): print('------1------') a, b = 0, 1 cur = 0 while(cur<all_num): print('------2------') yield a # 返回a print('返回接着执行') a, b = b, a+b cur += 1

    if name == ‘main’:
    obj = create_num(5)
    for num in obj:
    print(num)

     
      <ul><li>可以发现,相比直接用迭代器实现,这里省去了<code>return</code>和<code>__next__</code>方法</li><li>yield直接返回后面的数据,并且能在此次循环后回来,接着向下执行</li></ul> </li><li> <p>常用在爬虫中数据处理的流程控制: <img src="https://img-blog.csdnimg.cn/20210417170722765.png" alt="sp9" /></p> 
      <ul><li>如图,使用css选择器获取所有网页链接后(urls),需要对每个链接发起请求爬取源码</li><li>使用yield,到此处时把Request的执行返回给调用parse函数的对象,此对象循环(next),yield回来继续循环</li><li>当然,爬虫框架可以将此行为做成异步执行,无需阻塞等待(或者说返回的是个函数,你那边处理)</li><li>在深度学习训练中,我们需要分批次喂入数据,就可以使用yield;每次获取一批数据,返回给模型,模型调用next()方法 / 循环获取下一批数据</li></ul> </li><li> <p>使用<code>send()</code>代替next()唤醒,区别是可以传参 <img src="https://img-blog.csdnimg.cn/2021041717122361.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveV9BbGxlbg==,size_16,color_FFFFFF,t_70#pic_center" alt="sp10" /></p> </li><li> <p>现在,终于可以回到这个大标题:<strong>多任务</strong> <img src="https://img-blog.csdnimg.cn/20210417171331970.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveV9BbGxlbg==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述" /></p> 
      <ul><li>生成器实现交替任务,即简单协程</li></ul> </li><li> <p>什么是生成器?</p> 
      <ul><li>生成器是一类特殊的迭代器</li><li>一般使用yield创建生成器,包含yield关键字的函数叫做生成器函数</li><li>特点是执行到yield即返回后面的值,可以跟函数</li><li>因为返回后可以接着执行,所以是一种特殊的流程控制,一般应用在爬虫、深度学习训练中</li><li>多个函数间使用yield相当于<strong>函数间切换</strong>,这也是协程的基本原理</li></ul> </li><li> <p>注:迭代器和生成器的迭代<strong>只能往后不能往前</strong></p> </li></ul> 
    <h3>协程</h3> 
    <ul><li> <p>实现简单协程代码</p> 
    ```python
    import time
    
    def work1():
        while True:
            print("----work1---")
            yield
            time.sleep(0.5)
    
    def work2():
        while True:
            print("----work2---")
            yield
            time.sleep(0.5)
    
    def main():
        w1 = work1()
        w2 = work2()
        while True:
            next(w1)
            next(w2)
    
    if __name__ == "__main__":
        main()
    
    
  • 什么是协程:协程是python个中另外一种实现多任务的方式,只不过比线程更小占用更小执行单元

  • 它自带CPU上下文,这样只要在合适的时机, 我们可以把一个协程切换到另一个协程;

  • 与线程的区别:

    • 在实现多任务时,操作系统为了程序运行的高效性每个线程都有自己缓存Cache等等数据
    • 操作系统还会帮你做这些数据的恢复操作,所以线程的切换比较耗性能
    • 但是协程的切换只是单纯的操作CPU的上下文,所以可以一秒钟切换个上百万次系统
      • CPU上下文是CPU寄存器和程序计数器PC,是在运行任何任务前,必须的依赖环境
      • 即:协程轻装上阵,扔掉多余的状态,相当于只进行程序中函数间的切换,自带逻辑,数据从别处拿,执行完返回结果,结束!
  • 为了更好使用协程来完成多任务,python中的greenlet模块对其封装

    ```python # sudo pip3 install greenlet # pip 就安装到Python2上去了

    from greenlet import greenlet
    import time

    def test1():
    while True:
    print “—A–”
    gr2.switch()
    time.sleep(0.5)

    def test2():
    while True:
    print “—B–”
    gr1.switch()
    time.sleep(0.5)

    gr1 = greenlet(test1)
    gr2 = greenlet(test2)

    #切换到gr1中运行
    gr1.switch() # 这个函数对yield封装

    实际上这是假的多任务,完全交替执行

     </li><li> <p>更常用的是<code>gevent</code></p> 
      <ul><li>安装<code>pip3 --default-timeout=100 install gevent http://pypi.douban.com/simple/ --trusted-host pypi.douban.com</code></li><li>或者使用<code>http://mirrors.aliyun.com/pypi/simple/</code></li><li>但是报错,还是使用<code>sudo pip3 install gevent</code>,慢一点</li><li>可以用<code>pip3 list</code> 查看已安装库</li></ul> 
    ```python
    # 拿着greenlet进一步封装
    import gevent
    
    def f(n):
        for i in range(n):
            print(gevent.getcurrent(), i)
    
    g1 = gevent.spawn(f, 5)
    g2 = gevent.spawn(f, 5)
    g3 = gevent.spawn(f, 5)
    g1.join()
    g2.join()
    g3.join()
    # 运行发现是依次运行
    
    
  • 多任务:在单核中各任务并发交替执行

    ```python import gevent

    def f1(n):
    for i in range(n):
    print(gevent.getcurrent(), i)
    #用来模拟一个耗时操作,注意不是time模块中的sleep
    gevent.sleep(1) # 都要使用gevent里面的模块

    def f2(n):
    for i in range(n):
    print(gevent.getcurrent(), i)
    gevent.sleep(1) # 碰到耗时操作就切换

    def f3(n):
    for i in range(n):
    print(gevent.getcurrent(), i)
    gevent.sleep(1)

    g1 = gevent.spawn(f1, 5) # 创建一个协程
    g2 = gevent.spawn(f2, 5) # 目标函数,参数
    g3 = gevent.spawn(f3, 5)
    g1.join() # join会阻塞耗时
    g2.join() # 加入并执行
    g3.join() # 会等待所有函数执行完毕

    相当于在函数之间切换,即所谓的自带CPU上下文,节省资源

     </li><li> <p>线程依赖于进程,协程依赖于线程;协程最小</p> 
    ```python
    from gevent import monkey	# 补丁,自动转换time等为gevent
    import gevent
    import random
    import time
    
    def coroutine_work(coroutine_name):
        for i in range(10):
            print(coroutine_name, i)
            time.sleep(random.random())
    
    gevent.joinall([
            gevent.spawn(coroutine_work, "work1"),
            gevent.spawn(coroutine_work, "work2")
    ])
    
    
  • 协程是什么?

    • 协程相当于微线程
    • GIL锁和线程间切换耗费资源较多
    • 而协程自带CPU上下文,可以依赖于一个线程,实现协程间的切换,速度更快、代价更小
    • 相当于程序函数间的切换

并发下载器

  • 使用协程实现一个图片下载器 ```python from gevent import monkey # 即运行时替换,python动态性的体现! import gevent import urllib.request import random

    有耗时操作时需要

    monkey.patch_all()

    def my_downLoad(url):
    print(‘GET: %s’ % url)
    resp = urllib.request.urlopen(url)
    # file_name = random.randint(0,100)
    data = resp.read()
    with open(file_name, “wb”) as f:
    f.write(data)

    def main():
    gevent.joinall([
    gevent.spawn(my_downLoad, “1.jpg”, ‘https://rpic.douyucdn.cn/live-cover/appCovers/2021/01/10/9315811_20210110043221_small.jpg’),
    gevent.spawn(my_downLoad, “2.jpg”, ‘https://rpic.douyucdn.cn/live-cover/appCovers/2021/01/04/9361042_20210104170409_small.jpg’),
    gevent.spawn(my_downLoad, “3.jpg”, ‘https://rpic.douyucdn.cn/live-cover/roomCover/2020/12/01/5437366001ecb82edfe1e098d28ebc36_big.png’),
    ])

    if name == “main”:
    main()

     </li></ul> 
    <h2>总结</h2> 
    <ul><li>进程是资源分配的单位,进程之间独立所以稳定,但切换需要的资源很最大,效率很低</li><li>线程是CPU调度的基本单位,线程切换需要的资源量一般,效率一般(不考虑GIL的情况下)</li><li>协程切换消耗资源很小,效率高,有较多网络请求(较多阻塞)时使用,可以理解为是线程的增强</li><li>多进程、多线程根据CPU核数不一样可能是并行的,即一个进程的各线程可以利用多核并行;但是协程是在一个线程中,所以是并发</li><li>下一篇介绍python高级操作 <img src="https://img-blog.csdnimg.cn/img_convert/c58117e8afb4080eaf0cdc4d7c5209a5.png" alt="" /></li></ul>

评论区

admin
14粉丝

打江山易,守江山难,负重前行,持续创新。

0

0

0

举报