dev_2

188 阅读8分钟

python高阶编程(二)

面向对象进阶

魔术方法(魔法方法、特殊方法)

双下划线开头、双下划线结尾的方法,叫魔术方法,是python定义的,不是自己定义的。

python魔术方法大全: www.cnblogs.com/nmb-musen/p…

  1. __new__方法

    创建对象时,先调用__new__方法,根据传入的类对象,new一个对象出来,再调用__init__方法对类进行初始化设置。__new__方法在Object类里面。

    一般不建议重写new方法,如果重写,需要return一个类对象,可以使用父类的new方法。

  2. 单例模式

    重写__new__方法的一个应用场景是单例模式。

    单例模式就是确保一个类只有一个实例.当你希望整个系统中,某个类只有一个实例时,单例模式就派上了用场。

    比如,某个服务器的配置信息存在在一个文件中,客户端通过AppConfig类来读取配置文件的信息.如果程序的运行的过程中,很多地方都会用到配置文件信息,则就需要创建很多的AppConfig实例,这样就导致内存中有很多AppConfig对象的实例,造成资源的浪费.其实这个时候AppConfig我们希望它只有一份,就可以使用单例模式。

    最简单的单例模式代码实现:

    class Singleton:
    
        # 定义一个属性,记录这个类是否创建过对象
        instance = None
    
        # 重写new方法
        def __new__(cls, *args, **kwargs):
    
            # 如果instance属性为None,表示没被创建过对象
            if not cls.instance:
                # 调用父类的方法,创建对象
                cls.instance = object.__new__(cls)
                return cls.instance
            # 如果instance属性不为None,表示已经被创建过对象
            else:
                # 返回已经创建了的对象
                return cls.instance
                
    t1 = Singleton()
    # 赋值给t1的name属性
    t1.name = 'susan'
    
    t2 = Singleton()
    # t2的name属性实际也是t1的name属性,这就是单例模式
    print(t2.name) # 结果为susan
    
    # 打印两个实例的id
    print(id(t1),id(t2)) # 结果是相同的地址4519242704,4519242704
    

    作业:

  3. __str__方法和__repr__方法

    __str__方法返回的是给用户看的,__repr__方法返回的是给程序开发人员看的。

  4. __call__方法

    底层实现了__call__方法就可调用,没有实现__call__方法就不可调用。

    作业:用类实现装饰器,可以装饰类,也可以装饰方法

    # 用类实现装饰器
    class Decorator:
    
        def __init__(self, func):
            self.func = func
    
        def __call__(self, *args, **kwargs):
            print("装饰器中的功能")
            self.func()
    
    @Decorator
    def test_01():
        print("--原来的功能----")
    
    test_01()
    
  5. 上下文管理器

    # 实现上下文管理器
    class MyOpen:
        def __init__(self, file_path, open_mothod, encoding):
            self.file_path = file_path
            self.open_mothod = open_mothod
            self.encoding = encoding
    
        def __enter__(self):
            print("调用 __enter__方法")
            self.f = open(self.file_path, self.open_mothod, encoding=self.encoding)
            return self.f
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            print("调用 __exit__方法")
            print("异常类型{}".format(exc_type))
            print("异常值{}".format(exc_val))
            print("异常追踪{}".format(exc_tb))
            self.f.close()
        
      with MyOpen("test.txt", "w+", 'utf8') as f:
          f.write("好好学习天天向上")
    

    作业:实现数据库操作的上下文管理器

  6. 算术运算的实现

多态

  1. 面向对象三大特征

  2. 多态

    python中的多态是伪多态。因为函数的参数是没有类型限制的,只有传入有run方法的类的对象,都能运行run方法。

  3. 鸭子类型

数据和自省

  1. 私有属性

    伪私有,外部可以使用,但修改不会提醒你。

    单例模式中原本使用instance =None来判断,在外部可能被修改,为了防止修改,应该改为__instance =None,声明是私有属性,别人就不会去更改。

  2. __dict__

  3. 内置属性__slots__

    作用: 限制类对象所能绑定的属性、节约内存

    除了__slots__约定的属性,不能自定定义其他属性:

    有了__slots__将不会生成__dict__属性:

  4. 自定义属性访问

    官方文档:

  5. 描述器

    官方文档:

    描述器:

  6. ORM模型介绍

    示例:

    class CharFiled:
        pass
    
    
    class UserModel:
        name = CharFiled(max_length=20)
    	pwd = CharFiled(max_length=10)
    
    
    m = UserModel()
    m.name = 999
    m.pwd = dsefe12fkwejfieklrojger
    
    print(m.name) # 输出为999
    print(m.pwd) # 输出为dsefe12fkwejfieklrojger
    

    name、pwd无论赋值是什么,都会成功赋值,实际开发中,我们通常需要对他进行限定,比如,只能是字符串,可以通过描述器来实现。

    class CharFiled:
    
        def __init__(self,max_length=20):
            self.max_length = max_length
    
        def __get__(self, instance, owner):
            return self.value
    
        def __set__(self, instance, value):
            # 限定是字符串类型
            if isinstance(value,str):
                if len(value) <= 20:
                    self.value = value
                else:
                    raise ValueError("字符串长度不能大于{}".format(self.max_length))
            else:
                raise TypeError("need a string")
    
        def __delete__(self, instance):
            self.value = None
    
    
    class UserModel:
        name = CharFiled(max_length=20)
        pwd = CharFiled(max_length=10)
    
    
    m = UserModel()
    m.name = "999"
    m.pwd = "kjdsfh"
    
    print(m.name)
    print(m.pwd)
    

    如果不满足类型或长度,抛出异常:

    如果是满足类型或长度,正常赋值:

元类

  1. 旧式类VS新式类

    # 经典类、旧式类 ,继承于instance
    class Myclass:
        pass
    
    # 新式类
    class Test(object):
        pass
    
    # python2 ,区分经典类和新式类,经典类不是继承object,而是继承于instance,新式类才继承object
    # python3,不区分经典类和新式类,两种写法都默认继承object
    
  2. 类型和类

    class Test(object):
        pass
    
    t = Test()
    print(type(t))
    print(type(Test))
    print(type(type))
    

    实例是通过Test类创建出来的,Test类是通过type类创建出来的,type是通过type类创建出来的。

    type:python3中所有类都是通过type来创建的,type就是元类

    object:python3中所有类的顶级父类都是object

  3. 使用type动态定义类

    使用type(name,bases,dict) ,传入name(新类名),bases(父类),dict(属性和方法)可以创建一个类。

    # 使用tpye创建一个类、属性、方法
    def func(self):
        print("调用func方法")
    Test = type('Test', (object,), {"attr": 100, "__attr2": 200, "func": func})
    
    print(Test)
    t =Test()
    t.func()
    
    # 使用class创建一个类、属性、方法
    class Test1(object):
        attr = 100
        __attr2 = 200
        def func(self):
            print("调用Test1中的func方法")
    
    print(Test1)
    t1 =Test1()
    t1.func()
    

    当我们使用class创建类时,实际底层在使用type在创建类。

  4. 自定义元类

    
    # 自定义元类必须继承于type
    # type创建类需要三个参数:name、bases、attr_dict
    
    class MyMetaClass(type):
        """
        自定义元类
        """
    
        # 重写new方法创建和返回一个对象
        def __new__(cls, name, bases, attr_dict, *args, **kwargs):
            print("最基础的自定义元类")
            return super().__new__(cls, name, bases, attr_dict)
    
    class Test(object):
        pass
    
    print(type(Test)) # <class 'type'>
    
    
    # 通过自定义元类来进行创建
    class Test1(metaclass=MyMetaClass): # metaclass-元类参数,默认是type
        pass
    
    print(type(Test1)) # <class '__main__.MyMetaClass'>
    
    # 父类指定元类,子类可以继承父类所指定的元类
    class Test2(Test1):
        pass
    
    print(type(Test2)) # # <class '__main__.MyMetaClass'>
    

    自定义元类的应用

    # 将类的所有属性名变成大写
    
    class MyMetaClass(type):
        def __new__(cls, name, bases, attr_dict, *args, **kwargs):
            print("最基础的自定义元类")
            for k,v in list(attr_dict.items()): # 不加list,对字典的动态引用,如果字典中数据变化,它也会变化,所以要转换为list
                # 删掉旧key
                attr_dict.pop(k)
                attr_dict[k.upper()] = v
            return super().__new__(cls, name, bases, attr_dict)
    
    class Test(metaclass=MyMetaClass):
        name = 'Susan'
        age =20
        gender = '男'
    
    print(Test.__dict__)
    

  5. ORM模型的实现思路

    • 定义字段型类

      field.py

      # 字段的父类
      class BaseField:
          pass
      
      class IntFiled(BaseField):
      """整数类型"""
      
          def __get__(self, instance, owner):
              return self.value
      
          def __set__(self, instance, value):
              if isinstance(value,int):
                  self.value = value
              else:
                  raise TypeError("need a int")
      
          def __delete__(self, instance):
      
              self.value = None
      
      
      class CharFiled(BaseField):
      """字符类型"""
      
          def __init__(self,max_length=20):
              self.max_length = max_length
      
          def __get__(self, instance, owner):
              return self.value
      
          def __set__(self, instance, value):
              if isinstance(value,str):
                  if len(value) <= 20:
                      self.value = value
                  else:
                      raise ValueError("字符串长度不能大于{}".format(self.max_length))
              else:
                  raise TypeError("need a string")
      
          def __delete__(self, instance):
              self.value = None
      
      class BoolFiled(BaseField):
      """布尔类型"""
      
          def __get__(self, instance, owner):
              return self.value
      
          def __set__(self, instance, value):
              if isinstance(value,bool):
                  self.value = value
              else:
                  raise TypeError("need a bool")
      
          def __delete__(self, instance):
      
              self.value = None
      
    • 定义模型类

      from py_demo.field import BaseField, CharFiled, BoolFiled, IntFiled
      
      
      # 利用元类实现模型类
      # 定义元类
      
      class FiledMetaClass(type):
          """
          创建模型类的元类
          """
      
          def __new__(cls, name, bases, dic, *args, **kwargs):
              # 建立映射关系
              table_name = name.lower()  # 讲类名转化为小写,对应数据表的表名
              fields = {}  # 定义一个字典,用来存放模型类字段和数据表中字段对应的关系
              for k, v in dic.items():  # dic中有一些无关属性,遍历所有的属性,判断是否为BaseField类型
                  # 属于BaseField类型表示继承于BaseField类
                  if isinstance(v, BaseField):
                      fields[k] = v
              # 讲表名和映射关系存在dic中
              dic['t_name'] = table_name
              dic['fields'] = fields
              return super().__new__(cls, name, bases, dic)
      
      
      class User(metaclass=FiledMetaClass):
          # 用户模型类
          username = CharFiled()
          pwd = CharFiled()
          age = IntFiled()
          live = BoolFiled()
      
      
      # 通过User.fields,User.t_name访问模型类的字段、表名
      print(User.fields)
      print(User.t_name)
      
      xiaoming = User()
      xiaoming.name = "小明"
      
      xiaohua = User()
      xiaohua.name = "小花"
      
      print(xiaoming.name,xiaohua.name)
      

      这样赋值每个属性时,会比较麻烦,期望的时,实例化时,直接对各个属性赋值。需要加__init__方法,但是一般模型类都没有__init__方法,所以给模型类,一个父类,在父类中,写__init__方法。

      做如下修改:

      修改后源码:

      from py_demo.field import BaseField, CharFiled, BoolFiled, IntFiled
      
      # 利用元类实现模型类
      # 定义元类
      
      class FiledMetaClass(type):
          """
          创建模型类的元类
          """
      
          def __new__(cls, name, bases, dic, *args, **kwargs):
              # BaseModel不需要进行元类的一些操作
              if name == 'BaseModel':
                  return super().__new__(cls, name, bases, dic)
              else:
                  # 建立映射关系
                  table_name = name.lower()  # 讲类名转化为小写,对应数据表的表名
                  fields = {}  # 定义一个字典,用来存放模型类字段和数据表中字段对应的关系
                  for k, v in dic.items():  # dic中有一些无关属性,遍历所有的属性,判断是否为BaseField类型
                      # 属于BaseField类型表示继承于BaseField类
                      if isinstance(v, BaseField):
                          fields[k] = v
                  # 讲表名和映射关系存在dic中
                  dic['t_name'] = table_name
                  dic['fields'] = fields
                  return super().__new__(cls, name, bases, dic)
      
      
      # 模型类的父类
      class BaseModel(metaclass=FiledMetaClass):
          def __init__(self, **kwargs):
              for k, v in kwargs.items():
                  # 设置key,value
                  setattr(self, k, v)
      
      
      class User(BaseModel):
          # 用户模型类
          username = CharFiled()
          pwd = CharFiled()
          age = IntFiled()
          live = BoolFiled()
      
      
      class Order(BaseModel):
          # 订单模型类
          id = IntFiled()
          money = IntFiled()
      
      
      xm = User(username='小明', pwd='aaasddff', age=22, live=True)
      print(xm.username, xm.pwd, xm.age, xm.live)
      order1 = Order(id=202001, money=100)
      print(order1.id, order1.money)
      

      在如Django的框架中,实例化一个模型类的对象就是一条数据,需要将数据保存到数据库时,使用save()方法。

      xm = User(username='小明', pwd='aaasddff', age=22, live=True)
      xm.save()
      

      在BaseModel中添加save方法:

      def save(self):
       # 保存一条数据,生成一条对应的sql语句
       # 获取表名
       t_name = self.t_name
       # 获取字段
       fields =self.fields
       field_dict = {} # 用来存储键值对
       # 获取对应字段的值
       for field in fields.keys():
           field_dict[field] = getattr(self,field)
       # 生成对应的sql语句
       sql = 'INSERT INTO {} VALUE{};'.format(t_name,tuple(field_dict.values()))
       print(sql)
      

      效果如下:

内存管理

Python 对小整数的定义是[-5, 256]这些整数对象是提前建立好的,不会被垃圾回收。

在[-5, 256]中,都是指向小整数池中的地址:

不属于[-5, 256]中,会开辟新的地址:

  1. 对象引用

    • 引用计数

      python的垃圾回收采用的是引用计数机制为主和分代回收机制为辅的结合机制,当对象的引用计数变为0时,对象将被销毁,除了解释器默认创建的对象外。(默认对象的引用计数永远不会变成0)

      import sys
      a = 11
      print(sys.getrefcount(a))
      

      打印结果是15,表示对11这个整数对象的引用次数是15次。每一次赋值都会增加数据操作的引用次数。

    • 内置函数is和id

    • 数据的可变性

      可变类型,指向的对象可变,如list;不可变类型,指向的对象不可变,如下图示:

  2. 小整数池和intern机制

    • 小整数池

    • intern机制

      字符串驻留池:

  3. 深浅拷贝

    浅拷贝示意:

    当list1中添加元素时,list2都会随之修改,但是list3不会。

    当li中添加元素时,list1,list2,list3都会随之修改。

    因为在数据存储中,list1,list2,list3中对li的存储不是存储值,而是存储了引用关系。

    深拷贝示意:

    list4,会把li的值直接存在另一个空间,之后li、list1再修改,list4不会修改。

    代码实现:

    import copy
    
    li = [1,2,3]
    list1 = [11,22,li]
    list2 = list1
    list3 = list1.copy() # 浅拷贝
    list4 = copy.deepcopy(list1) # 深拷贝
    
    print(li)
    print(list1)
    print(list2)
    print(list3)
    print(list4)
    print("-----")
    
    list1.append(33)
    
    print(li)
    print(list1)
    print(list2)
    print(list3)
    print(list4)
    print("-----")
    
    li.append(4)
    print(li)
    print(list1)
    print(list2)
    print(list3)
    print(list4)
    
    

    深浅拷贝,一般只在列表嵌套列表时讨论。

  4. 垃圾回收和GC模式

    • 垃圾回收机制

      • 计数机制

        计数为0时被销毁。

      • 标记清除

        循环引用:

        li1 = [11,22]
        li2 = [33,44]
        li1.append(li2[0])
        li2.append(li1[0])
        

        如果没有被全局变量引用的话,就会被删掉。

      • 分代回收

        python里一共有三代,每个代的threshold值表示该代最多容纳对象的个数。默认情况下,当0代超过700,或1,2代超过10,垃圾回收机制将触发。

        当对象个数达到700个时,把他们作为第0代,检测对象的计数,为0的全部清除,清除了10次之后,剩余的成为第1代,同理,第一代清除了10次之后,成为第二代。

      • 三种情况触发垃圾回收

        • 调用gc.collect()

        • GC达到阀值时

        • 程序退出时

  • GC模式

并发和性能

并发和并行

  1. 多任务

  2. 并发和并行

  3. 同步和异步

线程

  1. threading模块介绍

    import time
    
    def fun1():
        for i in range(5):
            time.sleep(1)
            print("----事情1-----")
    
    
    def fun2():
        for i in range(6):
            time.sleep(1)
            print("----事情2-----")
            
    # 单线程工作,一个任务做完才到另一个任务
    start_time =time.time()
    fun1()
    fun2()
    end_time = time.time()
    print("耗时:{}".format(end_time-start_time))
    

    # 多任务执行
    # 创建一个线程去执行事情1,返回的是一个线程对象
    t1 = threading.Thread(target=fun1,name='th_1')
    # 创建一个线程去执行事情2,返回的是一个线程对象
    t2 = threading.Thread(target=fun2,name='th_2')
    # 开始时间
    start_time =time.time()
    # 开始执行线程1
    t1.start()
    # 开始执行线程2
    t2.start()
    # 让主线程等待子线程结束后,再继续往下执行,才能计算出完成时间
    t1.join()  # 等待t1完成
    t2.join()  # 等待t2完成
    # 结束时间
    end_time = time.time()
    print("耗时:{}".format(end_time-start_time))
    
    # 设置获取线程名-示例
    print(t1.getName())
    t1.setName("线程-1")
    print(t1.getName())
    

  2. 多线程实现多任务

  • 通过继承类Thread类来创建线程

    import requests
    
    class RequestThread(threading.Thread):
    
        # 如果要带参数,重写__init__方法
        def __init__(self,url):
            self.url = url
            # 重写init方法,最后要调用父类init方法,因为父类init方法中还有很多东西要初始化
            super().__init__()
              
        def run(self):
            res = requests.get("http://www.baidu.com")
            print("发送请求时的时间:{}".format(time.time()))
            time.sleep(3)
            print("==线程:{}---返回状态码是:{}--".format(threading.current_thread(),res.status_code))
    
    # 创建5个线程,并发起请求
    start_time = time.time()
    for i in range(5):
        t = RequestThread()
        t.start()
    t.join()
    end_time = time.time()
    print("耗时:{}".format(end_time-start_time))
    

  1. 多线程共享全局变量

    • 多线程之间修改全局变量

    • 多线程共享全局变量的问题

      # 多线程共享全局变量
      a = 100
      
      def fun1():
          global a
          for i in range(1000000):
              a = a + 1
          print("线程1修改完a:",a)
      
      def fun2():
          global a
          for i in range(1000000):
              a = a + 1
          print("线程2修改完a:",a)
      
      t1 = threading.Thread(target=fun1)
      t2 = threading.Thread(target=fun2)
      
      t1.start()
      t2.start()
      

      一个cpu的资源在短时间内多个线程之间来回切换,当数据量较大时,计算较慢时,会出现数据结果不正确。如fun1中获取到a为100,fun2中此时也获取到a为100,进行+1操作后,fun1中的a,和fun2中的a都是101,而不是应该的结果102。

      如何解决:通过同步、互斥锁、队列等可解决。

      作业:创建10个线程,执行100次请求

  2. 同步&互斥锁

    http://httpbin.org

    这个网站能测试 HTTP 请求和响应的各种信息,比如 cookie、ip、headers 和登录验证等,且支持 GET、POST 等多种方法,对 web 开发和测试很有帮助。

    代码示例:

    import threading
    import time
    
    a = 100
    
    
    def fun1():
        global a
        for i in range(1000000):
            meta.acquire() # 上锁
            a = a + 1
            meta.release() # 释放锁
        print("线程1修改完a:", a)
    
    
    def fun2():
        global a
        for i in range(1000000):
            meta.acquire()  # 上锁
            a = a + 1
            meta.release()  # 释放锁
        print("线程2修改完a:", a)
    
    # 创建锁
    meta = threading.Lock()
    s_time = time.time()
    t1 = threading.Thread(target=fun1)
    t2 = threading.Thread(target=fun2)
    
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(a)
    e_time = time.time()
    print("耗时:{}".format(e_time - s_time))
    

  3. 死锁

    两把锁时,fun1在等b锁释放,fun2在等a锁释放,于是两个方法都不能运行,死锁。

  4. GIL全局解释器锁(拓展)

    当我们的python代码有一个线程开始访问解释器的时候,GIL会把这个大锁给锁上,确保同一时间只有一个线程运行,此时此刻其他的线程只能干等着,无法对解释器的资源进行访问,这一点就跟我们的互斥锁相似。而只是这个过程发生在我们的Cpython中,同时也需要等这个线程分配的时间到了,这个线程把gil释放掉,类似我们互斥锁的lock.release()一样,另外的线程才开始跑起来,说白了,这无疑也是一个单线程。

    GIL全局解释器锁,控制线程运行的,哪个线程抢到这把锁,就是哪个线程开始运行。

    cpu密集型单线程快,io密集型是多线程快。

队列

队列中自带锁,使用队列不需要再加锁。

  1. FIFO(先进先出)队列Queue

    import queue
    # 队列
    
    # 1. 先入先出
    # 创建一个队列,设置最大消息数量
    q = queue.Queue(3)
    # 往队列中添加数据
    q.put(1)
    q.put(11)
    q.put(11)
    # 此时队列满了
    print("----1------")
    # 新消息就会一直要等待
    q.put(11)
    print("----2------")
    # 两种方式:往队列添加数据,不等待,如果队列是满的,会直接报错
    q.put(11,block=True)
    q.put_nowait(11)
    

    同理,获取队列中的消息时:

    # 获取数据,先进先出
    print(q.get())
    print(q.get())
    print(q.get())
    # 此时队列中没有消息了,也会一直等待获取新消息
    print(q.get())
    # 两种方式:从队列中获取数据,不等待,如果队列没有数据,会直接报错报错
    print(q.get(block=False))
    print(q.get_nowait())
    
    # 获取队列中的任务数
    print(q.qsize())
    
    # 判断队列是否已满
    print(q.full())
    
    print(q.get())
    print(q.get())
    print(q.get())
    # 判断队列是否已空
    print(q.empty())
    
    # 执行完任务后要向队列发送一个信号,表示任务执行完毕
    q.task_done()
    q.task_done()
    q.task_done()
    # 判断队列中的任务是否执行完毕
    q.join() # 如果执行完就结束,没有执行完就会等在这行代码等待任务执行完毕
    
  2. LIFO(后入先出)队列LifoQueue

    可以调用的方法同先进先出可调用的方法:

    import queue
    
    # 创建一个队列
    lq = queue.LifoQueue()
    lq.put(1)
    lq.put(11)
    lq.put(111)
    print(lq.get_nowait())
    print(lq.get_nowait())
    print(lq.get_nowait())
    

  3. 优先级队列PriorityQueue

    import queue
    pq = queue.PriorityQueue()
    # 队列中的元素为元组类型:(优先级,数据)
    pq.put((1,"first"))
    pq.put((2,"second"))
    pq.put((15,"llll"))
    pq.put((100,17))
    print(pq.get_nowait())
    print(pq.get_nowait())
    print(pq.get_nowait())
    print(pq.get_nowait())
    

    数字越小的,先出来。可以调用的方法同先进先出可调用的方法。

  4. 生产者消费者模式实现

    import queue
    import time
    from threading import Thread
    
    q = queue.Queue()
    
    
    # 生产者
    class Producer(Thread):
    
        def run(self):
            count = 0
            while True:
                if q.qsize() < 50:
                    for i in range(200):
                        count += 1
                        goods = '第{}个商品'.format(count)
                        q.put(goods)
                        print("生产:{}".format(goods))
                    time.sleep(3)
    
    
    class Customer(Thread):
        def run(self):
            while True:
                if q.qsize() >= 10:
                    for i in range(3):
                        print("消费:{}".format(q.get()))
                elif q.qsize() < 10:
                    time.sleep(2)
    
    
    p = Producer()
    p.start()
    
    for i in range(5):
        c = Customer()
        c.start()
    

进程

  1. 进程介绍

  2. 进程、线程对比

  3. multiprocessing模块

    import time
    
    from multiprocessing import Process
    # 多进程
    
    # 多进程执行多任务
    
    num = 100
    
    def work1():
        for i in range(10):
            global num
            print("这个是任务1--{}--".format(num))
            num += 1
            time.sleep(0.5)
    
    def work2():
        for i in range(10):
            global num
            print("这个是任务2--{}--".format(num))
            num += 1
            time.sleep(0.5)
    if __name__ == '__main__':
    
        # 创建两个进程
        p1 =Process(target=work1)
        p2 =Process(target=work2)
    
        # 开始
        p1.start()
        p2.start()
    
    

    在windows下为什么不加__name__ == 'main'会报错?

    会陷入无限无限递归,创建了新的进程,进程之间的资源是独立的,运行时,会把所有的代码导到另一块独立空间去。

    根据此可以看出,多进程不共享全局变量。

  4. 进程间通信-Queue

  • 进程中Queue的使用

    from multiprocessing import Process,Queue
    
    # 多进程之间的通讯问题 multiprocessing模块中的Queue
    import requests
    
    count = 1
    
    def work1(q):
        while q.qsize() > 0:
            global count
            # 获取任务
            url = q.get()
            requests.get(url)
            print("work1正在执行任务---{}".format(count))
            count += 1
    
    
    def work2(q):
        while q.qsize() > 0:
            global count
            # 获取任务
            url = q.get()
            requests.get(url)
            print("work2正在执行任务---{}".format(count))
            count += 1
    
    
    if __name__ == '__main__':
        q = Queue()
        for i in range(10):
            q.put("http://httpbin.org/get")
        # 传了同一个对象q进去,所以两个进程共享一个对象
        p1 = Process(target=work1,args=(q,))
        p2 = Process(target=work2,args=(q,))
    
        # 开始
        p1.start()
        p2.start()
    
    

    这段代码在MacOS上会报错,NotImplementedError,这个是python3.8 以下是官方文档说明,此异常应属于平台问题。无法正常使用多进程中的Queue.qsize()和Queue.empty()

  1. 进程池Pool

    import os
    import time
    from multiprocessing import Pool
    
    def work():
        print("这是一个任务--{}".format(os.getpid()))
        time.sleep(1)
    
    
    # 创建进程池
    pool = Pool(3)
    for i in range(10):
        pool.apply_async(work)
    
    pool.close()
    pool.join()
    

    三个进程,达到3个进程时,等待。

  2. 进程池中的Queue

    import os
    from multiprocessing import Pool,Manager
    import requests
    
    def work(q):
        global count
        # 获取任务
        url = q.get()
        requests.get(url)
        print("work正在执行任务---{}".format(os.getpid()))
        count += 1
    
    # 进程池之间的队列
    q = Manager().Queue()
    for i in range(10):
        q.put("http://httpbin.org/get")
    
    
    # 创建进程池
    pool = Pool(5)
    for i in range(10):
        pool.apply_async(work,args=(q,))
    
    pool.close()
    pool.join()
    

    五个进程:

    作业:1000个任务,用3个进程或3个线程执行,谁快?

    进程快?

    任务数量少于cpu数量:并行

    线程快?

    全局解释器锁GIL的存在,并发(不可能同时执行3个任务)

    做多任务时,进程快,只用进程,不合理,一个进程占用的资源很大。

    通过进程和线程的搭配完成。

    3个线程:

    import time
    import requests
    import queue
    import threading
    
    # 线程的队列 (只能在一个线程中使用)
    q = queue.Queue()
    for i in range(1000):
        q.put('http://httpbin.org/get')
        
    def work():
    while q.qsize() > 0:
        url = q.get()
        requests.get(url=url)
        
    s_time = time.time()
    t1 = threading.Thread(target=work)
    t2 = threading.Thread(target=work)
    t3 = threading.Thread(target=work)
    t1.start()
    t2.start()
    t3.start()
    t1.join()
    t2.join()
    t3.join()
    e_time = time.time()
    print("时间{}".format(e_time - s_time))	 # 时间374.8668460845947
    

    3个进程:

    import time
    import requests
    from multiprocessing import Queue, Manager,Pool
    
    
    
    def work(q):
        while q.qsize() > 0:
            url = q.get()
            requests.get(url=url)
    
    
    # 进程池中的队列(给进程池中的各个进程直接使用)
    q3 = Manager().Queue()
    for i in range(1000):
        q3.put('http://httpbin.org/get')
    pool = Pool(3)
    s_time = time.time()
    for i in range(3):
        pool.apply_async(work,args=(q3,))
    # 关闭进程池
    pool.close()
    # 等待进程池中所有进程执行完毕再向下运行
    pool.join()
    e_time = time.time()
    print("时间{}".format(e_time - s_time)) # 时间159.42530608177185
    

协程

import time

# 协程
def work1():
    for i in range(10):
        print("====work1==={}".format(i))
        time.sleep(0.1)
        yield

def work2():
    for i in range(10):
        print("====work2==={}".format(i))
        time.sleep(0.1)
        yield


# 通过生成器实现多任务
g1 = work1()
g2 = work2()

while True:
    try:
        next(g1)
        next(g2)
    except StopIteration:
        break

# 协程:又叫微线程。
# 协程的本质是单任务,利用yield关键字,在多任务之间快速切换。
# 协程依赖于线程。
# 协程相对于线程,占用的资源更小,几乎不要占用什么资源。

  1. 什么是协程

  2. 协程与线程的差异

  3. greenlet

    import time
    from greenlet import greenlet
    
    def work1():
        for i in range(10):
            print("====work1==={}".format(i))
            # 切换到g2
            g2.switch()
            time.sleep(0.1)
    
    def work2():
        for i in range(10):
            print("====work2==={}".format(i))
            # 切换到g1
            g1.switch()
            time.sleep(0.1)
    
    g1 = greenlet(work1)
    g2 = greenlet(work2)
    # 切换到g1中运行,任意启动一个即可。
    g1.switch()
    
  4. gevent

    import gevent
    from gevent import monkey
    import time
    
    # 补丁
    gevent.monkey.patch_all()
    
    def work1():
        for i in range(10):
            print("====work1==={}".format(i))
            # gevent.sleep(0.1)
            time.sleep(0.1)
    
    def work2():
        for i in range(10):
            print("====work2==={}".format(i))
            # gevent.sleep(0.1)
            time.sleep(0.1)
    
    # 创建2个协程
    g1 = gevent.spawn(work1)
    g2 = gevent.spawn(work2)
    g1.join()
    g2.join()
    
    # 协程存在于线程中,线程默认不会等待协程执行,
    # spawn():开启协程,第一个参数是协程要执行的任务
    # join():让线程等待协程执行结束
    # 协程之间切换的条件:gevent.sleep() ,协程耗时等待的情况下才会切换
    
    # gevent的补丁
    # gevent.monkey.patch_all() ,使用了补丁,无论是什么耗时操作,都会切换协程,不再需要使用gevent.sleep()
    

    3个协程

    import gevent
    import requests
    import time
    import queue
    
    q = queue.Queue()
    for i in range(100):
        q.put('http://httpbin.org/get')
    
    def work():
        while q.qsize()>0:
            url = q.get()
            requests.get(url=url)
    
    s_time = time.time()
    # 创建3个协程
    g1 = gevent.spawn(work)
    g2 = gevent.spawn(work)
    g3 = gevent.spawn(work)
    g1.join()
    g2.join()
    g3.join()
    e_time = time.time()
    print("时间{}".format(e_time - s_time)) # 600s左右
    

    3个协程,时间长很多,但是协程是可以加很多的,比如加几十个,这样就会大大降低了耗时,同时几十个协程所需的资源较少。

进程、线程、协程对比

并发时,占用资源、内存的角度,优先考虑协程 > 线程 > 进程 。