第二阶段复习

目录

面向对象

对象

对象是具备属性和方法的结合体

类是具有相同属性和方法的对象的集合体

面向对象编程

  • 优点:扩展性强
  • 缺点:编程复杂都高,无法准确预知执行结果

对扩展性要求较高时使用

类中的方法

  1. 对象绑定方法: 默认的,对象调用时会自动传入对象本身
  2. 类绑定方法: 类和对象调用都会自动传入类本身 标识符: @classmethod
  3. 非绑定方法: 就是一个普通函数,不会自动传参 标识符: @staticmethod

名称空间与查找顺序

  • 类有自己的名称空间
  • 对象也有自己独立的名称空间,每个对象之间是独立
  • 用对象可以访问到类中的内容
  • 每个对象都不同的就放到对象自己的名称空间,一样的就放到类中
  • 查找顺序: 对象-->类-->父类-->object-->元类

初始化方法 (__init__)

  • 用于给对象的属性附上初始值,或做一些其他的初始化操作
  • 会在调用类的时候自动自动执行,并传入对象本身
  • 必须保证返回值None

封装

什么是封装

封装就是对外隐藏内部实现细节,提供调用的接口

封装的作用

  • 保证数据安全
  • 隔离复杂度

封装中的三个装饰器

  • getter

    @property == getter : 将方法隐藏成属性,调用时不用加括号

  • setter

    @属性名称.setter

  • deleter

    @属性名称.deleter

封装的原理:在读取类中的代码时,会将带有双下划线的名字换成_类名__属性这样的格式

继承

什么是继承

继承是一种关系,是子类获取父类所有的属性和方法

继承的作用

  • 提高代码复用性,
  • 精简代码

派生

子类在继承父类的继承上,增加自己独有的属性或者方法

覆盖(overwrite)

子类中出现与父类中一模一样的名字

抽象

抽取多个子类之间的共同部分,形成一个新的公共父类

菱形继承问题

python中支持多继承,增加了灵活性,同时带来了不确定性,当多个父类中出现了完全一致的名字,调用顺序就不好确定

要先确定是否是菱形继承

  • 新式类: 继承了object类,python3中全是新式类

    查找顺序: 广度优先

  • 经典类: 没有继承object类,python2中才有经典类

    查找顺序: 深度优先

抽象类

  1. 抽象方法: 如果一个方法,只有声明,没有实现,那就是抽象方法

    当一个类中存在抽象方法时,类时抽象类

  2. 作用: 用于强制要求子类必须实现里面某些方法

  3. 抽象类是为了更好的编写出具备多态性的代码,当然如果我们知道这个类该怎么设计,完全可以使用鸭子类型

  4. 鸭子类型:大家默认都实现相同的方法,不需要从语法上去限制

接口

与抽象的不同之处仅在于,接口中所有方法都应该是只有声明而没有实现体的

抽象类中可以有普通方法也有抽象方法

子类中访问父类的内容的两种方式

  • super().父类的名称
  • super(子类名称,self).名称

组合

也是一种关系,类对象可以引用/当做参数/当做返回值/当做容器元素,类似于函数对象

组合与继承都是为了提高代码的复用性

多态

一种事物具备多种形态

多个不同类型的对象,可以响应同一个方法调用 , 这就被称之为多态性

其实是面向对象对于扩展性的提现

反射

什么是反射

通过字符串来操作对象的属性和方法

反射,指对象可以在创建后动态的修改自身的属性和方法

  1. hasattr: 通过字符串获取类属性
  2. getattr: 通过字符串获取类属性
  3. setattr: 通过字符串修改类属性
  4. delattr: 通过字符串删除类属性

元类

元类也是类,用来创建类的类

默认所有的元类都是type, 我们可以继承type 实现自己的元类, 目的: 为了控制类的创建过程

# 元类模板
class Mymeta(type):
    def __init__(self,class_name,class_base,class_dic):
        # 控制类的逻辑代码
        super().__init__(class_name,class_base,class_dic)
        
	def __call__(self,*args,**kwargs):
        # 控制类实例化(对象)的参数
        obj = self.__new__(self) # obj就是实例化的对象
        self.__init__(obj,*args,**kwargs)
        
        # 控制类实例化的逻辑
        
        return obj
    
# __call__   调用类实例化对象时,自动执行
# __init__   创建类对象的时候执行
# __new__    创建对象时,会调用__new__来获得一个空对象,然后调用类中的__init__方法进行初始化

单例模式

一个类只能产生一个对象

python的模块天然就是单例模式,因此我们只需要把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了

实现方法:

  • 利用模块
  • 使用装饰器
  • 使用元类

网络编程

网络架构的几种方式

CS架构

客户端直接与服务端交互

BS架构

客户端嫁接在浏览器上,浏览器和服务端交互

BS也属于CS架构

客户端: 用于浏览数据,与用户交互的应用程序

服务端: 存储以及处理数据的应用程序

OSI七层协议

物理层

规定连接介质相关的协议, 传输电信号 ,,例如RJ45

数据链路层

对电信号进行分组,规定了一组电信号的长度,称之为一个数据帧

数据帧组成:

以太网头(head)

​ 发送地址(mac地址)

​ 接收地址(mac地址)

​ 数据类型

data

使用广播方式来进行通讯

网络层

给每一条机器分配一个逻辑地址,即ip地址

目前使用得是ipv4 : 0.0.0.0 - 255.255.255.255

子网掩码: 用来区分不同的网段

ARP协议: 将IP地址转换成mac地址

传输层

每一个应用程序必须绑定一个端口号,用于表示唯一的进程

是一个数字: 0-65535 0-1024 为系统保留

传输协议: tcp协议

(会话层/表示层)应用层

规定双方数据的传输格式,例如: json / HTML

用于数据交换

应用程序可以自己定义自己的协议

socket抽象成

介于应用程序和传输层之间,是一个应用程序

TCP协议的三次握手和四次挥手(重点)

三次握手建立连接

  1. 客户端向服务端发出一个带有SYN的连接请求,
  2. 服务端接收到请求后,返回一个带上SYN和ACK的响应数据给客户端
  3. 客户端接收确认信息,发送一个带上ACK的请求给服务端,服务端和客户端进入连接状态

四次挥手断开连接

  1. 客户端发出带有FIN的请求给服务端
  2. 服务端返回一个带有ACK的请求给客户端,但是不会现在就关闭,服务端可能还有遗留数据没有发完,
  3. 服务端数据发完后,才会发送一个带有FIN和ACK的请求给客户端
  4. 客户端返回一个带有ACK请求给服务端,连接正常关闭.

如果客户端没有接收到这条请求,就没有第四条请求给服务端,服务端会隔一段时间再发一次带有FIN和ACK的请求给客户端..如果在2msl时间内,客户端一直没有响应,则强行关闭

TCP和UDP协议的优缺点

  1. TCP:
    • 优点:可以保证数据的完整性
    • 缺点:效率低,需要发送确认信息,会有粘包问题
  2. UDP:是基于数据报的传输协议,每一个数据报中必须包含接收方的信息,数据报是一个独立的数据单位
    • 优点:效率高,无粘包问题
    • 缺点:无法保证数据完整性

粘包问题(重点)

如何会出现粘包问题

  • 两个数据非常小,且间隔时间又短
  • 数据太大,一次取不完,下一次还会取这个数据

解决方法:

先发送数据的长度信息,再发送真实数据,必须要保证长度信息所占的字节是固定的.

socket的使用

socket本质上就是封装了传输层相关协议

并发编程

操作系统的主要功能

  1. 隐藏了硬件系统复杂的操作,提供了简单直观的API接口
  2. 将硬件的竞争变得有序和可控
  3. GUI 图形化用户界面

多道技术 (重点)

空间复用

将内存分成多个区域,同一时间内,将多个任务分别加载到不同区域的内存中,多个进程之间内存区域相互隔离

时间复用

操作系统会在多个进程之间来回切换执行,简单总结,就是切换+保存

切换任务的两种情况:

  1. 当一个进程遇到了IO操作时,会自动切换
  2. 当一个任务执行时间超过阈值时,会强制切换

频繁的切换其实也是需要消耗资源

为什么要并发

目前程序存在的问题:默认的串行,执行效率低

并发可以同时执行多个任务,提高了程序的效率

并发编程的重要概念 (重点)

什么是串行

串行就是程序自上而下,一行一行按顺序执行,必须把当前任务执行完毕才能执行下一个任务

什么是并发

多个任务同时执行,本质是在不同的进程之间来回切换执行

什么是并行

是真正的同时运行,必须具备多核cup,有几个cup就能同时执行几个任务

以上三个概念都是用于描述处理任务的方式

阻塞

指的是程序遇到了IO操作,无法继续执行代码时的一种状态

非阻塞

指的是程序没有遇到IO操作的一种状态,它又分成了运行和就绪两种

进程的三种状态

  1. 阻塞
  2. 运行
  3. 就绪

进程的层次机构(了解)

在Linux中,进程具备父子关系,是一个树状结构,可以互相查找到对方

在Windows中没有层级管理,父进程可以将子进程的句柄转让

PID 和PPID

PID 查看当前进程的编号

PPID 查看父进程的编号

注意: 当我们运行py文件时,其实运行的是python解释器

import os
os.getpid()  # 获取当前进程编号
os.getppid() # 获取父进程编号

多进程

并发实现的三种方式

  1. 多进程 :核心原理就是多道技术
  2. 多线程
  3. 协程

进程是什么

进程指的是正在运行的程序,是操作系统调度以及进行济源分配的基本单位,是一个资源单位

线程是什么

线程是操作系统可以运算调度的最小单位,是真正的执行单位,其包含在进程中,一个线程就是一条固定的控制流程

进程对比线程 (重点)

  1. 概念:

    进程是一个资源单位,线程是一个执行单位

  2. 对内存的开销大小:

    创建和销毁进程的开销,远大于线程

  3. 共享数据

    进程之间相互隔离(物理层上就隔离的), 线程是共享进程内的所有资源

  4. 对资源的竞争关系

    进程之间对于硬件资源是相互竞争的, 而线程是协作关系

  5. 层级关系

    进程之间有层级关系,而线程之间没有,是平等的

  6. 是否能并行

    进程可以并行和并发,而线程只能并发不能并行,因为线程中有GIL锁

比喻:

​ 计算机是工厂, 进程是车间, 线程是流水线

python如何使用多进程

  1. 导入multiprocess 中的Process类,实例化这个类,指定要执行的任务target

    from multiprocessing import Process
    
    def task():
        print("this is sub process")
        
    if __name__ == '__main__':
        # 注意 开启进程的代码必须放在 ————main————判断下面  
        # windows  会导入父进程的代码 从头执行一遍  来获取需要处理的任务    
    	#所以在编写代码时如果是windows一定要将开启进程的代码放main判断中  
    	#linux 可以不放 
        #  实例化一个进程对象 并制定他要做的事情  用函数来指定
        p = Process(target=task)
        p.start() # 给操作系统发送消息 让它开启进程
        print("this is parent process")
        print("over")
    
  2. 导入multiprocess 中的Process类,继承这个类,覆盖run方法,将要执行的任务放入到run中,开启进程会自动执行函数

    from multiprocessing import Process
    
    class Downloader(Process):
        
        def run(self):
        	print("this is sub process")
            
    if __name__ == '__main__':
        m = Downloader()
        m.start()
        print("this is parent process")
        
    

join 函数

作用: 让主进程 等待子进程执行完毕在继续执行,开了几个start,一般就需要几个join

from multiprocessing import Process
import time
def task1(name):
    for i in range(10000):
        print("%s run" % name)

def task2(name):
    for i in range(100):
        print("%s run" % name)

if __name__ == '__main__': # args 是给子进程传递的参数 必须是元组
    p1 = Process(target=task1,args=("p1",))
    p1.start()  # 向操作系统发送指令
    # p1.join()   # 让主进程 等待子进程执行完毕在继续执行

    p2 = Process(target=task2,args=("p2",))
    p2.start()  # 向操作系统发送指令

    p2.join()  # 让主进程 等待子进程执行完毕在继续执行
    p1.join()


    #需要达到的效果是 必须保证两个子进程是并发执行的 并且 over一定是在所有任务执行完毕后执行
    print("over")

进程对象的常用属性

p.json()
p.daemon() # 守护进程
p.exitcode # 获取进程的退出码
p.is_alive() # 查看进程是否存活
p.terminate() # 终止进程

僵尸进程与孤儿进程

  1. 孤儿进程 当父进程已经结束 而子进程还在运行 子进程就称为孤儿进程 尤其存在的必要性,没有不良影响

  2. 僵尸进程 当一个进程已经结束了但是,它仍然还有一些数据存在 此时称之为僵尸进程

在linux中,有这么一个机制,父进程无论什么时候都可以获取到子进程的的 一些数据

子进程 任务执行完毕后,确实结束了但是仍然保留一些数据 目的是为了让父进程能够获取这些信息

linux中 可以调用waitpid来是彻底清除子进程的残留信息

python中 已经封装了处理僵尸进程的操作 ,无需关心

守护进程 (了解)

在python中守护进程也是一个进程

默认情况下,主进程即使代码执行完毕了,也会等待子进程结束才会结束自己

当一个进程B设置为另一进程A的守护进程时, A是被守护,B是守护进程

特点:当被守护进程A结束时,即使B的任务没有完成也会随之结束

进程安全问题 (重点)

当并发的多个任务,要同时操作同一个资源,就会造成数据错乱的问题

解决方法:

​ 将并发操作公共资源的代码,由并发变为串行,解决安全问题,但是牺牲了效率

串行方式

直接使用join函数

缺点: 将任务中所有代码全部变成串行,效率很低

互斥锁 (重点)

原理: 将要操作公共资源的代码锁起来,其他代码还是可以并发执行

加锁: 解决了安全问题,同时也带来了效率降低的问题

锁的粒度越大,锁住的代码越多,效率越低,

IPC

IPC指的是进程间通讯

实现进程间通讯的几种方式:

  1. 创建一个共享文件

    • 缺点: 效率较低
    • 优点: 理论上交换的数据量可以非常大
  2. 共享内存 (主要方式)

    • 缺点: 数据量不能太大
    • 优点: 效率高
  3. 管道

    管道也是基于文件的,它是单向的,编程比较复杂

  4. socket

    编程复杂,更适用于基于网络来交换数据

共享内存的方式:

  1. manager
  2. queue

生产者/消费者模型

要解决什么问题

生产者与消费者处理速度不同,一个快一个慢,则双方需要相互等待,效率低

  • 生产者: 泛指产生数据的一方
  • 消费者: 泛指处理数据的一方

解决方法

  1. 先将双方解开耦合,让不同的进程负责不同的任务
  2. 提供一个共享的容器,来平衡双方的能力,之所以用进程队列, 是因为队列可以在进程间共享

队列 Queue

import Queue

# 创建一个双方能共享的容器
q = Queue

# 将数据存入队列
q.put(数据)

# 将数据从队列取出
q.get()

虽然队列能解决生产者和消费者模型的问题,但是会带来一个新问题,那就是消费者无法直到生产者到底生产了多少数据,可能会造成数据没有取完就关闭程序

joinableQueue

继承自Queue 用法一致, 并且增加了join 和 taskDone ,解决了生产者与消费者信息一致性问题

join是一个阻塞函数, 会阻塞直到taskdone 的调用次数等于存入的元素个数, 可以用于表示队列任务处理完成

from multiprocessing import Process,JoinableQueue
import requests
import re,os,time,random

"""
生产者 负责生产热狗 
消费者 负责吃热狗  


"""

# 生产者任务
def product(q,name):
    for i in range(5):
        dog = "%s的热狗%s" % (name,(i + 1))
        time.sleep(random.random())
        print("生产了",dog)
        q.put(dog)

# 吃热狗
def customer(q):
    while True:
        dog = q.get()
        time.sleep(random.random())
        print("消费了%s" % dog)
        q.task_done() # 标记这个任务处理完成

        
if __name__ == '__main__':

    # 创建一个双方能共享的容器
    q = JoinableQueue()

    # 生产者进程
    p1 = Process(target=product,args=(q,"上海分店"))
    p2 = Process(target=product,args=(q,"北京分店"))

    p1.start()
    p2.start()


    # 消费者进程
    c = Process(target=customer,args=(q,))
    # c.daemon = True # 可以将消费者设置为守护进程 当主进程确认 任务全部完成时 可以随着主进程一起结束
    c.start()


    p1.join()
    p2.join()  # 代码走到这里意味着生产方完成

    q.join() # 意味着队列中的任务都处理完成了

    # 结束所有任务
    c.terminate() # 直接终止消费者进程

    # 如何判定今天的热狗真的吃完了
    # 1.确定生成者任务完成
    # 2.确定生出来的数据已经全部处理完成

多线程

是系统运算的最小单位,是真正的执行单位

为什么用线程

  1. 有多个任务要并发处理
  2. 当要并发处理的任务有很多时候,不能使用进程,进程资源开销太大,线程开销小,适用于任务数非常多的情况

使用进程的两种方法

from threading  import Thread
# #使用方法1: 直接实例化thread类
def task():
     print("子线程 run")

# 与进程不同之处1   不需要加判断 开启线程的代码放哪里都可以
t = Thread(target=task)
t.start()
print("over")


# 使用方法2  继承Thread类
class MyThread(Thread):

    def run(self):
        # 把要在子线中执行的代码放入run中
        print("子 run")

mt = MyThread()
mt.start()
print("over")

线程安全问题

只要并发访问了同一资源,一定会产生安全问题,解决方案和多进程一样,就是给操作公共资源代码加锁 (Lock)

from threading import  Thread,Lock
import time
a = 10

l = Lock()

def task():
    global a
    l.acquire()  # 加锁
    temp = a
    time.sleep(0.1)
    a = temp - 1
    l.release()  # 解锁

ts = []
for i in range(10):
    t = Thread(target=task)
    t.start()
    ts.append(t)
for t in ts:t.join()

print(a)

守护线程(了解)

一个线程a设置为b的守护线程,a会随着b的结束而结束

默认情况下 主线程即使代码执行完毕 也会等待所有非守护线程完毕后程序才能结束 因为多个线程之间是协作关系

死锁现象 (重点)

死锁指的是某一个资源被占用后,一直得不到释放,导致其他需要这个资源的线程进入阻塞状态

产生死锁的情况

  1. 对同一把互斥锁,加锁了多次,
  2. 一个共享资源要访问,必须具备多把说,但是这些锁被不同的线程或者进程持有,就会导致相互等待对方释放,从而卡死程序

解决方法:

  1. 加锁多少次,就释放多少次
  2. 按照相同顺序去抢锁 并且加上超时,如果超时,就放弃执行

递归锁(了解)

同一个线程可以对一个锁,执行多次acquire

解决方法: 必须保证加锁的次数和解锁的次数相同

信号量 (了解)

可以限制同时并发执行公共代码的线程数量

如果限制数量为1,则与普通互斥锁没有区别

注意: 信号量不是用来解决安全问题的,而是用来限制最大并发量

GIL全局锁(重点)

什么是GIL锁

在cpython中,这个全局解释器锁或者说GIL 是一个互斥锁, 是为了阻止多个本地线程在同一时间执行python字节码.

因为cpython的内存管理是非线程安全的,这个锁是非常必要的,因为其他越来越多的特性依赖这个特性

为什么需要GIL锁

线程安全问题具体的表现

python程序本质上就是一堆字符串,所以运行一个python程序时,必须要开启一个解释器,但是一个python程序中的解释器只有一个,而所以代码都要交给它来解释执行,当有多个线程都要执行代码时,就会产生线程的安全问题.

cpython 解释器与GC的问题

python会自动帮我们处理垃圾,而清理垃圾也是由一堆代码,也需要开启一个线程来执行(GC),也就是说就算程序没有开启自己的线程,内部也有多个线程

因此,GC线程就与我们程序中的线程会产生安全问题

带来的问题

GIL锁是一把互斥锁,互斥锁将导致效率降低

在cpython中无法并行执行任务,因为解释器只有一个,只能并发执行

如何解决(重点)

没有办法解决,只能尽可能的避免GIL锁影响我们的效率

  1. 使用多进程能够实现并行,从而更好的用多核cpu

  2. 对任务进行区分

    任务分为两类

    • 计算密集型: --->使用进程

      基本没有IO操作, 大部分时间都在计算, 由于多线程不能并行, 那么就应该使用多进程,将任务分给不同的CPU执行. 例如:人脸识别,图像处理等

    • IO密集型: --->使用线程

      计算任务非常少, 大部分时间都在等待IO操作,由于网络IO速度对比CPU处理速度非常慢,多线程并不会造成太大的影响.

关于性能的讨论

​ 之所以加锁是为了解决线程安全问题

​ 由于有了锁 导致Cpython中多线程不能并行只能并发

​ 但是我们不能因此否认python

​ 1.python是一门语言 二GIL是Cpython解释器的问题 还有Jpython pypy

​ 2.如果是单核CPU GIL不会造成任何影响

​ 3. 由于目前大多数程序都是基于网络的,网络速度对比CPU是非常慢的, 导致即使多核CPU也无法提高效率

​ 4. 对于IO密集型任务 不会有太大的影响

​ 5.如果没有这把锁 我们程序猿将必须自己来解决安全问题

线程池和进程池

线程池就是装线程的容器, 进程池就是装进程的容器

为什么要装到容器中

  1. 可以避免频繁的创建和销毁(进程/线程)带来的资源开销
  2. 可以限制同时存在的线程或者进程数量,以保证服务器不会为资源不足而导致崩溃
  3. 帮我们管理了线程或者进程的生命周期
  4. 管理任务的分配
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor

# 创建线程池
pool = ThreadPoolExecutro(数量)
# 提交任务到池子中
pool.submit(任务)


# 创建进程池
pool = ProcessPoolExecutor(数量)
# 提交任务到池子中
pool.submit(任务)

同步异步(重点)

同步

指的是,提交任务后必须在原地等待,知道任务结束, 不等于阻塞

异步

指的是, 提交任务后不需要再原地等待,可以继续往下执行

异步效率高于同步,但是异步会导致一个问题:任务的发起方,不知道任务何时处理完毕

同步异步: 指的是提交任务的方式

阻塞和非阻塞: 指的是处理任务的方式

解决方法

  1. 轮询: 每隔一段时间,就询问一次

    缺点:效率低,无法及时获取结果,不推荐

  2. 异步回调: 让任务的执行方主动通知

    可以及时拿到任务的结果,推荐方式

# 异步回调
from threading import Thread
# 具体的任务
def task(callback):
    print("run")
    for i in range(100000000):
        1+1
    callback("ok")
   

#回调函数 参数为任务的结果
def finished(res):
    print("任务完成!",res)


print("start")
t = Thread(target=task,args=(finished,))
t.start()  #执行task时 没有导致主线程卡主 而是继续运行
print("over")

线程池中回调的使用

# 使用案例:
def task(num):
    time.sleep(1)
    print(num)
    return "hello python"

def callback(obj):
    print(obj.result())


pool = ThreadPoolExecutor()
res = pool.submit(task,123)
res.add_done_callback(callback)
print("over") 

Event事件

事件本质就是一个标志,科颜氏是Flase 或者True,它包含了一个wait函数,可以阻塞当前线程,直到状态从False 变成 True

Event事件就是为了解决, 线程间状态同步

现象:

把一个任务丢到了子线程中,这个任务将异步执行,如何获取这个任务的执行状态,

执行状态和执行结果并不是一个概念,

我们可以用异步回调来获取执行结果,但是无法获取执行状态

如何获取执行状态:

  1. 采用轮询的方法,每次都去读取一遍,

    缺点:

    • 浪费了CPU资源
    • 会有延迟,不能立即获取状态
  2. 使用Event事件来完成状态同步

案例:

from threading import  Thread,Event
import time

e = Event()

def start_server():
    print("starting server......")
    time.sleep(3)
    print("server started!")

    # 修改事件的值为True
    e.set()


def connect_server():
    e.wait() # 等待事件从False 变为true
    
    if e.is_set():  # 如果事件状态为True,则开始连接
        print("连接服务器成功!")

t1 = Thread(target=start_server)
t2 = Thread(target=connect_server)

t1.start()
t2.start()

协程

协程:是单线程下的并发,又称微线程,纤程;是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的

为什么出现协程

线程因为GIL全局解释器锁的原因,无法做到真正的并行,只能并发,当线程在处理高并发的情况下,尤其是对于计算密集型任务而言,线程并发处理的效率非常低,因此,有人提出了在单线程下实现并发处理.

如何实现 (重点)

实现方法:

​ 利用生成器原理(生成器会自动保存执行状态)

实现原理解析:

并发本质就是 切换任务 + 保存状态, 因此我们只要保证在两个任务之间切换执行并保存状态,那么我们就可以实现单线程并发;

python中的生成器就具备这样一个特点,每次调用next都会回到生成器函数中执行代码,这意味着任务之间可以切换执行,并且是基于上一次运行的结果.

def task1():
    while True:
        yield
        print("task1 run")

def task2():
    g = task1()
    while True:
        next(g)
        print("task2 run")
task2()

greenlet 模块(了解)

虽然我们用生成器实现了单线程并发,但是效率并不高,且代码结构复杂,因此有人专门对yield进行了封装,这就是greenlet模块

greenlet只是提供了一种比generator更加便捷的切换方式,当切到一个任务执行时如果遇到io,那就原地阻塞,仍然是没有解决遇到IO自动切换来提升效率的问题,

任务的代码通常会既有计算操作又有阻塞操作,我们完全可以在执行任务1时遇到阻塞,就利用阻塞的时间去执行任务2。。。。如此,才能提高效率,这就用到了Gevent模块。

gevent模块(重点)

Gevent 是一个第三方库, 以轻松通过gevent实现并发编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。

用法

#用法
#创建一个协程对象g1,
g1=gevent.spawn(func,1,,2,3,x=4,y=5)
#spawn括号内第一个参数是函数名,如eat,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数eat的
g2=gevent.spawn(func2)

g1.join() #等待g1结束

g2.join() #等待g2结束

#或者上述两步合作一步:gevent.joi	nall([g1,g2])

g1.value#拿到func1的返回值

monkey补丁

monkey补丁的原理是把原始的阻塞方法替换为修改后的非阻塞方法,即偷梁换柱,来实现IO自动切换

遇到IO阻塞时会自动切换任务

import gevent,sys
from gevent import monkey # 导入monkey补丁
monkey.patch_all() # 打补丁 
import time

print(sys.path)

def task1():
    print("task1 run")
    # gevent.sleep(3)
    time.sleep(3)
    print("task1 over")

def task2():
    print("task2 run")
    # gevent.sleep(1)
    time.sleep(1)
    print("task2 over")

g1 = gevent.spawn(task1)
g2 = gevent.spawn(task2)
#gevent.joinall([g1,g2])
g1.join()
g2.join()
# 执行以上代码会发现不会输出任何消息
# 这是因为协程任务都是以异步方式提交,所以主线程会继续往下执行,而一旦执行完最后一行主线程也就结束了,
# 导致了协程任务没有来的及执行,所以这时候必须join来让主线程等待协程任务执行完毕   也就是让主线程保持存活
# 后续在使用协程时也需要保证主线程一直存活,如果主线程不会结束也就意味着不需要调用join

需要注意:

1.如果主线程结束了 协程任务也会立即结束。

2.monkey补丁的原理是把原始的阻塞方法替换为修改后的非阻塞方法,即偷梁换柱,来实现IO自动切换

必须在打补丁后再使用相应的功能,避免忘记,建议写在最上方

我们可以用threading.current_thread().getName()来查看每个g1和g2,查看的结果为DummyThread-n,即假线程

monke补丁原理

数据库

​ 什么是数据库 存储数据的仓库,本质上是一套CS结构的软件程序,分为客户端和服务器, 我们通常说安装数据,装的其实是服务器

​ 客户端连接服务器后发送指令给服务器,服务器收到后解析指令,.将数据存储到文件中

​ 数据库概念 与 文件系统的对应关系

​ 某一列 一个数字或符号

​ 一条记录 一行内容

​ 一个表 一个文件

​ 一个库 对应文件夹

​ DBMS 数据库管理系统(软件)

​ 数据库服务器 一台计算机

库的操作

​ create database

​ drop database

​ alter database

​ show databases

​ show create database

表的操作

​ create table

​ drop table

​ alter table add|modify|drop |change

​ show tables

​ show create table

​ desc 表名

​ rename table a to b

记录的操作

​ insert into

​ delete from table

​ truncate table

​ update 表名 set xx = xx

​ select * from 表名

数据类型

​ int float

​ char varchar text blob enum set

​ year date time datetime timestamp

约束:

​ unsigned

​ unique

​ not null

​ null

​ default

​ primary key auto_increment

​ foreign key

表的关系

​ 一对多

​ 1.分清主从表关系

​ 2.在多的一边增加外键字段 关联一的一边的主键

​ 多对多

​ 1.创建关系表

​ 2.至少两个字段分别指向主表的主键字段

​ 3.设置为联合唯一约束

​ 一多一

​ 1.分清 先后顺序

​ 2.在后有的一方添加外键字段

查询语句 *****

mysql 多表关系 查询语句 索引

添加数据补充:

将一个查询结果插入到另一张表中

create table student(name char(10),gender int);
insert into student values("jack",1);
insert into student values("rose",0);

create table student_man(name char(10),gender int);
insert into student_man select * from student where gender = 1;

所有的select 关键字

select distinct *  from table_name
	where 
	group by 
	having 
	order by
	limit a,b

必须存在的有: 
select 
	* 可以换成任意的一个或多个字段名称  
	from
	table_name
#注意: 关键字的顺序是固定的不能随意变化

where 条件

select * from  table_name
where 


where 后面可以是 

1.比较运算符 
	>  <  >=  <=  =  != 
	
2.成员运算符
	in  not in    后面是一个set

3.逻辑运算符 
	and or not 	
	not 要放在表达式的前面   and 和 or 放到两个表达式中间 
4.模糊查询 
	like 
	% 表示 任意个数的任意字符
	_ 表示一个任意字符

 #
 请查询 姓小的  数学小于 80 分  并且  英语 > 20分   的人的 数学成绩
 select math,name  from stu where math < 80 and english > 20 and name like "小%"; 

	

distinct 去除重复记录

select distinct * from stu; 
# 注意仅当查询结果中所有字段全都相同时 才算重复的记录

指定字段

1.星号表示所有字段
2.手动指定需要查询的字段
3.还可也是四则运算   
4.聚合函数 


#请查询  英语及格的人的 平均分 
select name,(math+english) / 2 平均分 from stu where english >= 60;

取别名

select name,math+english as 总分 from stu where name = "赵云";

as 可以省略 

统计函数

​ 也称之为聚合函数

​ 将一堆数据经过计算得出一个结果

求和   sum(字段名)
平均数  avg(字段名)
最大值  max(字段名)
最小值  min(字段名)
个数    count(字段名)    # 字段名称可以使用* 代替   另外如果字段为空会被忽略

可以用在  字段的位置  或是分组的后面   
例如: 查询所有人的平均工资  
select avg(salary) from emp

错误案例: 查询工资最高的人的姓名 
select name,max(salary) from emp; 
	#默认显示的第一个name  因为name有很多行  而max(salary) 只有一行    两列的行数不匹配
	# 不应该这么写 逻辑错误
select name from emp where salary = max(salary);
	# 报错  
	# 原因: 伪代码
 for line in file:
       if salary = max(salary)  # 
    #分析  where 读取满足条件的一行  ,max()先要拿到所有数据 才能求最大值,
    #这里由于读取没有完成所有无法 求出最大值
#结论  where 后面不能使用聚合函数 

group by

group 是分组的意思 即将一个整体按照某个特征或依据来分为不同的部分

为什么要分组 分组是为了统计,例如统计男性有几个 女性有几个

语法:
select xxx from table_name group by 字段名称;

需求:统计每个性别有几个人 
select sex,count(*) from emp group by sex;

需求: 查询每个性别有几个 并且显示名字
select name,sex,count(*) from emp group by sex;

# mysql 5.6下  查询的结果是name仅显示该分组下的第一个  
# 5.7以上则直接报错 ,5.6也可以手动开启这个功能  

# 我们可以用group_concat 将分组之外的字段 做一个拼接 ,但是这是没有意义
# 如果要查询某个性别下的所有信息 直接使用where 即可  

#结论: 只有出现在了group by 后面得字段才能出现在select的后面

having

​ 用于过滤,但是与where不同的是,having使用在分组之后

案例:

# 求出平均工资大于500的部门信息 
select dept,avg(salary) from emp  group by dept having avg(salary) > 5000;


#查询 部门人数少于3的 部门名称 人员名称 人员个数

select dept,group_concat(name),count(*) from emp group by dept having count(*) < 3;

order

根据某个字段排序

语法:
select * from table_name order by 字段名称;
# 默认是升序

# 改为降序 
select * from table_name order by 字段名称 desc;

# 多个字段  第一个相同在按照第二个    asc 表示升序
select * from table_name order by 字段名称1 desc,字段名称2 asc;


案例:
select * from  emp order by salary desc,id desc;

limit

用于限制要显示的记录数量

语法1:
select * from table_name limit 个数;
语法2:
select * from table_name limit 起始位置,个数;


# 查询前三条 
select * from  emp limit 3;

# 从第三条开始 查询3条   3-5
select * from  emp limit 2,3;


#  注意:起始位置 从0开始

# 经典的使用场景:分页显示  
1.每一页显示的条数   a  = 3
2.明确当前页数   b = 2
3.计算起始位置   c = (b-1) * a      


select * from emp limit 0,3;
select * from emp limit 3,3;
select * from emp limit 6,3;


# django 提供了现成的分页组件  但是它是先查询所有数据 丢到列表中 再取出数据   这样如果数据量太大可能会有问题 




子查询

​ 将一个查询语句的结果作为另一个查询语句的条件或是数据来源

​ 当我们一次性查不到想要数据时就需要使用子查询

in 关键字子查询

​ 当内层查询 (括号内的) 结果会有多个结果时, 不能使用 = 必须是in ,另外子查询必须只能包含一列数据

​ 需求: 指定一个部门名称,获取改部门下的所有员工信息

1.查询出 平均年龄 大于25的部门编号

select  dept_id from emp  group by  dept_id  having avg(age) > 25;



2.再根据编号查询部门的名称  

select name from  dept where id in (select  dept_id from emp  group by  dept_id  having avg(age) > 25);

子查询的思路:
1.要分析 查到最终的数据 到底有哪些步骤 
2.根据步骤写出对应的sql语句
3.把上一个步骤的sql语句丢到下一个sql语句中作为条件 

exists 关键字子查询
当内层查询 有结果时 外层才会执行
 案例:
 select* from dept where exists (select * from dept where id = 1);
 # 由于内层查询产生了结果 所以 执行了外层查询dept的所有数据 

多表查询

笛卡尔积查询

select * from   table1,table2,......

# 笛卡尔积查询的结果会出现大量的错误数据即,数据关联关系错误!
添加过滤条件 从表外键值 等于 主表的主键值

# 并且会产生重复的字段信息  例如员工里的 部门编号  和 部门表的id字段 
在select 后指定需要查询的字段名称 

案例:
select  dept.name 部门 ,dept.id 部门编号,emp.name 姓名,emp.id 员工编号,sex from emp ,dept where dept.id = dept_id;

内连接查询:

本质上就是笛卡尔积查询

语法:
select * from  table1 inner join table2;
案例:
select * from  emp inner join dept where dept_id = dept.id;

inner可以省略
select * from  emp join dept where dept_id = dept.id;

左外连接查询

左边的表无论是否能够匹配都要完整显示

右边的仅展示匹配上的记录

需求: 要查询所有员工以及其所属的部门信息 
select * from emp left join dept on dept_id = dept.id;
注意: 在外连接查询中不能使用where 关键字 必须使用on专门来做表的对应关系   

右外连接查询

右边的表无论是否能够匹配都要完整显示

左边的仅展示匹配上的记录

需求: 要查询所有部门以及其对应的员工信息 
select * from emp right join dept on dept_id = dept.id;

全外连接查询

无论是否匹配成功 两边表的数据都要全部显示

需求:查询所有员工与所有部门的对应关系  
select * from emp full join dept on dept_id = dept.id;

注意:mysql不支持全外连接 


我们可以将 左外连接查询的结果  和 右外连接查询的结果 做一个合并  
select * from emp left join dept on dept_id = dept.id
union
select * from emp right join dept on dept_id = dept.id;

 
union的用法:
select * from emp
union 
select * from emp;

# union将自动去除重复的记录  
# union all 不去重复 


select sex,name from emp 
union 
select * from dept;
# 注意  union 必须保证两个查询结果 列数相同  一般用在多个结果结构完全一致时

总结: 外连接查询 查到的是没有对应关系的记录,但是这样的数据原本就是有问题的,所以最常用的是内连接查询

内连接表示 只显示匹配成功的记录

外连接 没有匹配成功的也要实现

多表查询案例:

create table stu(id int primary key auto_increment,name char(10));

create table tea(id int primary key auto_increment,name char(10));

create table tsr(id int primary key auto_increment,t_id int,s_id int,
foreign key(s_id) references stu(id),
foreign key(t_id) references tea(id));

insert into stu values(null,"张三"),(null,"李四");
insert into tea values(null,"egon"),(null,"wer");
insert into tsr values(null,1,1),(null,1,2),(null,2,2);


#egon老师教过哪些人? 
select tea.name,stu.name from tea join tsr join stu
on 
tea.id = t_id and stu.id = s_id
where tea.name = "egon";


# 子查询实现 
select * from stu where id in (select s_id from tsr where t_id = (select id from tea where name = "egon"));

小结:

select [distinct] *|字段名|四则运算|函数  from table_name
	where    比较运算符   逻辑运算符    成员运算符   区间  between and  模糊匹配 like   exists   																	regexp 正则匹配
	group by    
	having   通常根聚合函数  count sum max  min avg 
	order by 
	limit a,b

子查询

多表查询

笛卡尔积 内连接 外连接

内连接最常用

通常一个需求 可以用连表 也可以 用子查询

posted @ 2019-07-29 16:40  萨萌萌  阅读(135)  评论(0编辑  收藏  举报