python进阶

1:python字符编码

  字符:一个简单的文符号(bite不代表字符,也不代表字节),一个符号
  一个符号就是一个字符,A,@---这就是字符
  字符集:字符的集合(一套文字符号及其编码的描述)---按照某种规则进行编码

  文字符号:
    计算机存在最底层硬盘里面:01二进制,
    文件存到硬盘:按照某种规则进行编码(文字符号及其编码的描述,这就是字符集)
  字符编码一旦搞错,基本会乱码和报错

  字符集:字符的集合(一套文字符号及其编码的描述)---按照某种规则进行编码

  有ASCLL,USC,GBK,BIG5。。。。等各种不同的编码,这些不同的字符集,从收录到编码都各不相同,那么软件移植和信息交互就有问题了

  给各种字符编码:
  乱码:一个编码代表多个字符会造成编码紊乱
  字符编码----
  ascll编码

2:概念 (重点)
  位:计算机的最小的单位,二进制中的一位,用二进制0/1表示
  字节:八位组成一个字节---------10101010这就是8位1字节
  字符:肉眼可见的文字或者符号:元、a、1都是字符
  字符集:字符的集合(一套文字符号及其编码的描述)
  编码:将字符转换成计算机可识别的0/1二进制(编辑文件永久保存到硬盘里面就是编码)
  解码:将计算机表示的0/1编码转换成肉眼可见的字符(读取硬盘文件就是解码)

3:字符编码起源----8个可以开合的晶体管来表达世间万物(开关两种状态--二进制0/1)
  用八个可以开合的晶体管---字节(8位一个字节)发明时候就是这么定义的
  处理字节的机器,字节组成各种状态---机器名称(计算机)
  8个可以开合的晶体管---2的8次方---组合成256种不同的状态--美国人256状态够用
  计算机用不同的字节来存储英文,

  ascll编码保存英文---美国标准
    很多字符是ascll当初定义的时候没有的----2的8次方不够用----中国汉字(常用6千多个)


  GB2312(127之后全部取消掉,127之前的保留)

    assll--兼容ascll编码,规定:字节小于127,其含义和之前相同(如果两个大于127的字节连在一起就表示一个汉字)
    8字节8字节8字节
    大于127的两个字节表示一汉字----2个连续的大于127当成汉字(两个8字节的数前后两个字节的值都大于127那么合并起来就是一个汉字)

  GB2312把简体汉字编辑进去了,基本满足日常所需,但是中国汉字太多了,很多生僻字,对GB2312进行新的扩展

  GB2312新的扩展(+生僻字)不要求两个连续的,只有第一个字节大于127就是汉字,后面跟了第二个是多还是少都是汉字

    这就是GBK(容量更加庞大,增加了2w多新汉字)
    兼容少数名族文字----GBK再扩展---GB18030---一个汉字算两个字节,一个汉字算两个英文字符

  ascll
  GB2312:两个连续大于127的字节才当成汉字
  GBK :不要求两个连续的,只要第一个字节大于127就固定表示汉字,不管后面跟的第二个是多还是少,

    都把这两个字节当成汉字(这就是GBK,容量更加庞大,增加了2w多新汉字)
    汉字问题解决了,少数名族又有问题,少数民族文字也需要在计算机里表示,gbk再次扩充
  GB181030:GBK的扩充,少数民族的文字也加进去(中华名族文化完整的在计算机这套编码里面保存)这个阶段一个汉字算两个字节,一个汉字算两个英文字符
        中华民族文化完整了,其他国家的不行,
        想电脑显示汉字,必须装一个汉字系统,用来处理汉字的显示和输入的问题。每个地区的系统不同,

  GB2312:(127之后全部取消掉,127之前的保留)----assll--兼容ascll编码,字节小于127,其含义和之前相同(两个大于127的字符连在一起就表示一个汉字)
    8字节8字节8字节
    大于127的两个字节表示一汉字----2个连续127以上的才是汉字
    GB2312新的扩展(+生僻字)不要求两个连续的,只要第一个字节大于127就是汉字,

    后面跟了第二个是多还是少都是汉字---这就是GBK(容量更加庞大,增加了2w多新汉字)
    兼容少数名族文字----GBK再扩展---GB18030---一个汉字算两个字节,一个汉字算两个英文字符

    百家争鸣---各种编码各不支持

    汉字系统处理汉字的显示和输入的问题--汉字系统
    台湾--倚天汉字系统

    字符编码的现状:大一统:国际标准化的国际组织:去掉地区性编码,包括地球上所有文化的编码,这就是UCS编码

    unicode(16位表示所有的字符,美国人不干了,大公司)
    unicode1.0(GOOGLE,apple等美国公司建立)-----和国际标准unicode不兼容

        设计UTF编码(可变长,内部多了套算法)--英文1个字节,汉字2个字节,最多三个字节表示一个字符
    UTF8---可变长,包含各国语言------UTF-8

  UCS编码:unicode:就规定16位表示所有的字符,美国人不干了,一个英文字母8位,现在需要16位,--尤其是是大公司,

  unicode1.0:GOOGLE,apple等美国公司建立的编码规范,和国际标准unicode不兼容,

        设计UTF编码(可变长,内部多了套算法)--英文1个字节,汉字2个字节,最多三个字节表示一个字符

  UTF8:(最终产物,内部多了一套算法,导致可以可变长,)可变长,包含各国语言------UTF-8
      如果是一个英文8个字节,如果是一个汉字一般就2个字节,最多三个字节表示一个字符,

      UTF8解决了这些问题,UTF8还包含了世界各国语言,可变长没有浪费空间。容量大

4:python的字符串编码
  1:现在使用python3:python3默认字符编码unicode,默认文件编码UTF-8
  2:python2默认字符编码和文件编码都是ASCLL码(python2需要标注编码才支持中文,默认不支持)
  3:内存的编码需要看操作系统

    文件编码----文件存到硬盘需要编码,用什么类型(utf8还是gbk还是其他编码)的编码存
    文件处理---with open(文件编码)
    字符编码和文件编码可以不一致也可以一致(两个不是同一件事情)

  中文乱码问题:无论编码在内存当中是怎么显示字符的,在硬盘上都是2进制,所以编码不对问题就会出现,
  注意:存到硬盘上时是以何种编码存,再从硬盘上读取出来的时候就得以何种编码读(不然会出现乱码或者报错)
  编码的时候用的什么解码的时候就要用什么

5:怎么让python2支持中文---
  #-*- conding utf-8 -*- 作用告诉python解释器,解释此py文件用utf-8,需要用utf-8去读取 #conding =utf-8

  只有文件开头的注释才有特殊意义

6:bytes数据类型,和字典和字符串一样的,python当中的一个数据类型---bytes类型和str类型是python当中的两个数据类型
  bytes类型(python中的数据类型)
  a=b"abc" 字符串ascll前加字母b,不能是中文或者其他字符前面加b如 b"袁文韬" 是错误的

  汉字怎么变成bytes(编码-解码)
  b="袁文韬"


  a=b"abc" :bytes类型 -----<class 'bytes'>,这就是bytes类型,python当中的数据类型,

  b=b"袁文韬" --print(type(b))报错SyntaxError: bytes can only contain ASCII literal characters.(无效的语法,bytes只能包含ascll编码,只能把asccl编码以内的转成
    转成bytes类型,需要把汉字也转成bytes类型--编码,解码)

  encode() :将字符串转成bytes类型,windows电脑一般用的gbk的编码,mac和Linux用的一般是uft8.软件代码移植到其他人的就容易出问题,
      encode需要指明编码如下:

      字符串转成bytes类型:encode
        b="袁文韬"
        b=b.encode("utf8")
        print(type(b)) #<class 'bytes'>
        print(b) #b'\xe8\xa2\x81\xe6\x96\x87\xe9\x9f\xac' 这就是bytes类型,将字符串转成bytes类型


  bytes类型转成字符串:decode
    b="袁文韬"
    b=b.encode("utf8")
    print(b.decode("utf8")) #袁文韬 将bytes转位字符串,存到硬盘上何种编码存的,读就得以何种编码读,
    编码的时候用的是什么,解码的时候就得用什么,----一致

  字符编码(应用在字符得编码个解码上得)和文件编码()
  b="袁文韬"
  b=b.encode("utf8") b转成bytes类型
  b.decode("utf8") 然后bytes类型转str类型,这里使用utf8,是字符编码--

  test1.py这个文件存到磁盘当中也用得utf8,这叫文件编码---字符编码和文件编码和编码没有关系看用来干什么(utf8=utf-8)
  用来把文件存到硬盘就是文件编码,用来处理字符就是字符编码,


  python代码当中没有去识别--在encode当中参数没有识别unicode,


  x="abcde"
  x.encode("gbk") gbk对x进行编码,
  print(x)
  print(x.decode('gbk')) 解码需要使用gbk,---编码和解码保持一致

  encode 函数,编码函数 (支持得编码格式:utf8,gbk,big5,)unicode叫做utf8了,后买你不支持unicode
  encoding open函数的一个参数

7:两个函数
  ord("")         单个汉字,单个字符转成10进制表示,将单个字符转成整形表示
  print(ord("一"))     19968 ,

  位权,十进制转成二进制,

  chr()      将十进制的整形数字转成字符,将整形转为单个字符有一个问题,首先不管16个字节还是8个字节,2的16次方当中不是每个位置都被用上了,
          可能有的地方没有被使用,如右恰好找到了一个没有被使用的位置,调用chr()函数转成单个字符的时候,

          就会出现一个问题,1:找不到就报错 2:超过最大上限也是找不到
          底层都是找不到找不到就会报错
  print(ord("一"))        #19968
  print(chr(19968))        #一

  1:找不到就报错
  2:超过最大上限也是找不到
    比如说:print(chr(1924968)) #ValueError: chr() arg not in range(0x110000)--找不到就报错,不是所有的位置都被用上了

 

8:处理文件,如下

    处理文件一:
        with open("./a.txt","w",encoding="gbk") as f:                #直到编码格式写文件
            f.write("袁文韬")

        with open("./a.txt","r",encoding="gb2312") as f:            #指定编码格式读取文件
            print(f.read())   #袁文韬
            
        gbk写进入的gb2312能够读取出来。gbk是对gb2312的扩展,gbk包含了gb2312所有的内容,--上面的代码是一个巧合,正好取字符内容
            是gbk和gb2312重合都能识别的字符(中间重叠的部分),如果取了gb2312以外的内容再用gb2312去读取,显然读取失败
            如果使用ascll编码读取肯定不行----这里就会出现问题,编码和解码一定要一致,不去碰运气

    处理文件二:图片文件复制一份重新取名  (二进制的模式去读和写) 
        with open("./q11.png","rb") as f:         #需要以rb模式以二进制的方式读,不需要指定encoding编码了,这时候需要做这件事情了,这种图片读出来之后
            print(f.read())                        #这样读出来也看不懂,没什么意义
            data=f.read()                        二进制读取更快,rb模式中间少了个转化的过程,所有更快了,只读取01010101
                                                


        with open("./q22.png","wb") as f:  #二进制写进去
            f.write(data)  

        上面就是图片的复制粘贴还能重命名,rb    data=f.read()--bytes类型读取,f.write(data)二进制格式写入
        


    处理文件三:怎么知道文件是什么编码的,编码之间有重叠的,一个内容可能可以多种编码都能解码,概率的存在,
        import chardet  #

        #获取文件编码类型
        def get_encoding(file):
            #拿出文件当中一部分二进制的内容,以二进制方式读出文件的一部分内容才可以
            with open(file,"rb") as f:
                data=f.read(1024)
                return chardet.detect(data)

        encoding_data=get_encoding("./a.txt")
        print(encoding_data)      ###{'encoding': 'IBM855', 'confidence': 0.5119798157077455, 'language': 'Russian'打印一个字典
                                    encoding:
                                    confidence:像似率:百分之51
                                    language:判断是Russian
        
        这个函数判断文件的编码格式,但是不准

9:总结
  ./ 一个点代表当前路径, ../ 两个点代表上一层路径

  字符集:文字符号及其编码描述,(一个汉字:文字符号 存在计算机硬盘当中就要是二进制的形式:每个文字符号指定唯一的编码对应起来,这就是文字符号及其编码的描述)

  编码和解码所使用的字符集不一样 造成乱码(我用usc,你用gbk)

  字符串
  ord()获取字符的整数形式,单个
  chr()整数转成对应字符
  ssh服务---装乌班图(linux系统,核心工作都需要连到服务器)centos
  测试,开发---linux命令必学

10:python对外部程序的调用,

  1:处理关于文件,关于目类关于你系统操作的一些任务的时候需要调用外部程序----
  2:完整的知识体系---

  方法一:os.system函数:特征
    0:相当于打开操作系统的命令行cmd,执行命令,并将执行结果打印出来
    1:阻塞式调用,
    2:返回值为0,代表外部程序运行成功,返回值1,代表外部程序运行失败

    1:通过os模块里面的函数os.system()调用外部程序
      def system(*args, **kwargs): 不定长参数**kwargs,里面可以接受一个字符串作为参数 作用相当于在cmd黑窗口里面执行命令
      os.system("cd") #C:\Users\ywt\PycharmProjects\pythonAdvanced\day2
      这个函数接受字符串作为参数,相当于用这个字符串作为命令在cmd黑窗口里面执行,

      并且打印出结果,执行结果打印出来,不需要print打印,直接使用os.system函数就会有打印

    2:阻塞式调用
      os.system("mspaint")  打开画板(阻塞式调用,调用的外部程序退出之前,python程序会一种等在这里,直到外部程序退出才接着往下运行)
      print('after')

  3:有的程序退出后会给个状态码,成功还是失败--状态码在哪里---状态码在函数的返回值里面,如下:

import os
res=os.system("mspaint")
print(res) #打印0,打印0正常运行的,失败的话打印1
print("yuan")

模拟os调用外部程序错误的场景
import os
res = os.system("mspain")
print(res) # 打印1,现在输出报错信息乱码,需要修改pycham的global encoding和project encoding设置才能不乱码显示
print("yuan") 

  方法二:subprocess模块,python的内置模块,也提供方法在os.system基础上进行了强化

      subprocess.check_output特征:

        1:阻塞式调用

         2:功能也是执行指定的命令

         3:运行命令但是不打印(os.system运行的话直接打印命令执行结构,subprocess.check_output不会打印出来)

        4:执行指定的命令,并将结果以字节字符串的形式返回,(作为返回值)

    1:函数的基本使用
        import subprocess                                        python内置模块,在  os.system基础上进行了强化
        subprocess.check_output()                                也是使用命令参数的,输入字符串同样也可也去执行
        subprocess.check_output('mspaint')                      打开画板(也是阻塞式调用)
        subprocess.check_output('ipconfig')                      没有打印    

    2:执行指定的命令,并将结果以字节字符串的形式返回
        import subprocess
        import os
        res=subprocess.check_output("ipconfig")
        print(res.decode("gbk"))    

    os.system执行外部命令执行结果直接打印出来,res接收不到打印的参数,

    所以如果需要接收执行命令的接口来使用的话需要使用subprocess.check_output函数。返回值是字节字符串类型,

  方案三:Popen函数,subprocess模块第二部分内容,新的部分,Popen

    Popen特征:1:非阻塞式调用  2:

    1:Popen函数:执行并且打印执行结果 Popen没打印出来或者打印出来一小部分是正常的(Popen)

      (非阻塞的特性),因为还没来得及打印出来程序跑完了,
      非阻塞式调用,ipconfig的时候,Popen想要往执行结果里面打印的,但是有的时候速度比较快

      ,Popen打印的速度比较快,来得及打印,有的时候速度比较慢,python程序
      都结束了,他还没开始打印,还可能打印小部分-python程序结束了(因为Popen非阻塞式调用,

      所以python运行和Popen运行互不相干,不会等Popen打印)

        import subprocess
        subprocess.Popen("ipconfig")        

    2:非阻塞式调用

    import subprocess
    subprocess.Popen("mspaint") #执行的时候,画板还没关,执行的python程序就结束

    3:Popen在阻塞和非阻塞之间任意切换

        import subprocess
        subprocess.Popen("ipconfig")
        print("after")                #after大概率在ipconfig命令之前打印,
        

        阻塞调用:在后面调用一个wait:如下---现在式阻塞式调用
            import subprocess
            subprocess.Popen("ipconfig").wait()
            print("after")
            
            

        class Popen(object):          Popen式一个class,可以使用一个对象实例化如下:上面的代码可以如下操作
            
        import subprocess
        child=subprocess.Popen("ipconfig")             #Popen是一个类,类的实例化
        child.wait()                                #类的实例调用函数
        print('after')

    4:在直接打印结果和获取返回值之间任意切换

        os.system    结果直接打印到屏幕上和Popen默认情况一致的,但是有的时候想返回值接收执行结果Popen也能做到,如下:
        
        接收返回值的操作:指定一个参数         stdout=subprocess.PIPE
        import subprocess
        child=subprocess.Popen(
            'ipconfig',
            stdout=subprocess.PIPE                #    
        )


        stdout是一母三胞胎
            self.stdin = None        stdin标准输入文件,默认是键盘    
            self.stdout = None        stdout标准输出文件,默认是屏幕,标准输出文件,默认none,是在屏幕上输出的,
                        现在把这个标准输出文件重新导了个方向,导到了subprocess.PIPE 相当于架设了一条管道,输出到另外的地方,屏幕上不展示了,想要读出来,需要用到专门的方法接收外部程序的输出, child.communicate()#接收外部外部程序的输出 self.stderr
= None stderr标准错误文件,报错信息存在里面的,正常看不见的,除非主动去读取 示例代码: import subprocess child=subprocess.Popen( 'ipconfig', stdout=subprocess.PIPE #Popen默认是打印到屏幕上的,但是这里用stdout=subprocess.PIPE,修改打印输出的方向,导向PIPE ) child.wait() #修改成阻塞式(主动的等待被调用的程序结束,阻塞) print('after') #接收外部程序的输出,接收到的东西放在返回值里面,没有产生错误err返回值为空,产生错误err里面才会有东西 output,err=child.communicate() #communicate接受本该输出的打印到output里面,bytes类型的数据(communicate提供专门读取的方法) print(output.decode("gbk")) #默认打印了bytes类型,需要decode转码 print(err)

11:乱码:编码和解码方式不一致所导致,

  所有字符编码都兼容ascll码 windows微软中国运行的默认编码是GBK,cmd返回的报错信息可能是gbk编码的,

  但是pycham解码显示默认utf8(pycgam设置--file encoding--这块设置)
  python文件开头加了注释:# conding =utf-8,python文件被python解释器去执行的时候用的UTF-8的编码
  pycganm --file encoding设置这块设置的编码是:pycham只是用来写代码的工具,影响的是pycham字符的显示,pycham怎么解码把字符显示出来
  global encoding:本地的编码是什么
  project encoding:项目的编码是什么 项目存再硬盘里面用的什么编码
  global encoding和project encoding的设置影响的是pycham的这个程序和python解释器解释python代码没有关系
  影响的是pycham字符的显示,pycham怎么解码把字符显示出来----这两个设置成gbk再执行3里面的代码报错信息就不会乱码了

12:函数作用域:局部作用域,定义在函数里面,叫局部作用域,外边叫全局作用域,

  局部作用域:被定义在函数内部的作用域就是局部作用域
  全局作用据:定义在整个python文件当中的就叫全局作用域(对python文件当中最上层的)

    g=199    #全局作用域,定义在python文件最顶层(全局作用据,在模块层次之间定义的变量,
            #每一个模块都是一个全局作用域,我这个就是全局作用据,自己python文件任意位置都可以找到其他模块找不到的)
    def foo():
                    #python中除def/class/lambda(这几个关键字能够改变作用域,包含在def/class/lambda这些关键字定义的语句块之中在这里面定义的变量就是局部作用域)之外
                        其他如if/elif/else/try/excrpt/while/for这些并不能改变作用域,定义在他们之内的变量外部可以访问
                        with open也不改变作用域
                        
                        #with open也不会改变作用域
        x=1                #局部作用域(定义在函数里面)(和第一个print(x)在同一作用域)
        print(x)
    foo()        #可以成功使用
    print(x)    #不能成功,报错:NameError: name 'x' is not defined,--需要全局作用域(定义在整个python文件当中的,最上层的)
    for i in range(10):
        x1=99           #这也是全局作用域

13:函数作用域,全局作用域:

  在模块层次去定义的变量,每一个模块都是一个全局作用域(全局作用域可以在python文件任意位置都可以找到,但是不能跨模块寻找)

  g=199  #全局作用据 

14:嵌套作用域

def foo()
    x=1  

    def bar()  
        l='hello world'              #对于上层来说我是局部,对于下层来说我是嵌套    
                    
        print(l) 
    bar()                            #bar函数内部使用l和打印l是ok的,bar函数外部不能使用l        bar函数嵌套在foo函数里面,嵌套作用域,相对而言的,也包含在关键字作用域当中,
                                        嵌套作用域,和局部作用域是相对的,嵌套作用域对于更上层的而言也是局部,上层对他来说,是嵌套,x是嵌套,l是局部,


def foo()
    x=1  #(嵌套)局部作用据()

    def bar()                       #bar函数嵌套在for里面,嵌套作用域是相对而言的
        l='hello world'   --------    #l对于x来说是局部作用域,l对于l2来说就是嵌套作用域
                                    #重点#对于上层上层来说我是局部,对于下层来说我是嵌套    
        print(l)
        def inner():
            l2='hello python'    -----l2对于l来说是局部作用域,对于比他更低一层的就是嵌套
    bar()

嵌套和局部作用域,嵌套作用域对于更上层的而言也是局部,上层对他来说是嵌套
x和l2----x式嵌套,l2式局部
局部和嵌套式相对而言的----

15:内置作用域,系统内固定模块定义的变量

  print, def,__main__, :内置作用域:系统内固定模块定义的变量----

  内置作用据,局部作用域,嵌套作用域,全局作用域之间有什么关系
    python代码:def里面的变量调用优先寻找本层的,本层找不到找他上一层的,

    (不会找他下层的,也不会找他同级的作用域)----找不到一直找,直到找到内置作用域还找不到报错  

  变量调用的顺序:
    l--local---局部作用域
    e--enclosing---嵌套作用域
    j--global---全局作用域
    b--built-in---内置作用据
    现在自己这层找,找不到一层层向外扩展,不会向内扩展,也不会找自己平级的

16:高阶函数 :高阶函数三特点

  一:函数名可以赋值给其他变量
  二:函数名还可以作为参数传递
  三:函数名还可以作为返回值,传递函数名不能加括号

    a=[1,2,3]
    b=a
    print(b)        
    变量的定义:函数和列表,字符串,整型,列表一样都是对象,函数是最高级的对象。
        函数是最高级的对象,列表的调用直接调用就行,函数的不行,函数对象的调用必须来一对括号,括号去掉就是一个变量名(函数名字)


    一:函数名可以赋值给其他变量如下
        a=[1,2,3]            #定义一个列表变量赋给a

        def foo():            定义一个函数赋值给foo,foo就是变量名,
            print("abc")
        f=foo                #foo去掉括号就是变量名(函数赋值,函数名可以被赋值给其他变量,函数名还可以作为参数传递,对象传递给f)
        f()                   #能打印abc,


    二:函数名还可以作为参数传递
        def foo():
            x=1
            print(x)
        def bar(x):
            x()
        bar(3)          #这样执行报错    TypeError: 'int' object is not callable    int类型不能被调用,没有3()这种语法
        bar(foo)        #这样使用ok,函数名能够作为参数传递


    三:函数名还可以作为返回值,传递函数名不能加括号
        def wrapper():
            def inner():
                print("我是inner")            
            return inner()                #传递函数名不能加括号,加了括号相当于这里执行inner,执行了inner函数的结果none(因为inner函数没有返回值执行结果收None,这里return None)

        print(wrapper())


        def wrapper():
            def inner():
                print("我是inner")
            return inner

        print(wrapper())            #<function wrapper.<locals>.inner at 0x015EBA48>返回函数对象

        wrapper()()                    #相当于执行inner     相当于inner()

        f=wrapper()                    #相当于f=inner
        f()                            #这样也是相当于执行inner    相当于inner()

17:闭包:在一个内部函数里面(inner是一个内部函数),对在外部作用域的变量进行引用,

  但是要求这变量不能是全局作用域:如下的inner函数

  1:内部函数   2:引用了外部作用域变量x   3:x在外部作用域outer里面但不是全局作用域 那么这个内部函数就被认为是闭包)

    def outer()
        x = 10
        def inner():
            print(x)
        return inner                #这个inner就是个闭包

    #现在我想调用inner怎么办?
    outer()()
    f=outer()
    f()

18:装饰器

  装饰器:装饰本质上就是一个函数,该函数用来处理其他函数,让其他函数在不需要修改代码的前提下增加额外的功能
    开放封闭原则:允许你扩展已有的功能代码,但是禁止你修改以有的功能代码

    
计算测试用例执行时间:
    import time
    def foo():
        print("执行测试用例")

方法一:计算测试用例执行时间:        修改源代码的方法
    import time
    def foo():
        starttime=time.time()
        print("执行测试用例")
        time.sleep(1)
        endtime=time.time()
        print("总计执行时间",endtime-starttime)
    foo()
方法二:计算测试用例执行时间:        新定义了个show_time,                foo =show_time(foo)    foo是一个函数对象
    import time
    def foo():
        print("执行测试用例")


    def show_time(func):
        begin_time=time.time()
        func()
        end_time=time.time()
        print("总计执行时间=",end_time-begin_time)
    show_time(foo)
方法三:计算测试用例执行时间        装饰器:装饰本质上就是一个函数,该函数用来处理其他函数,让其他函数在不需要修改代码的前提下增加额外的功能
以前需要调用foo函数,但是要修改功能逻辑,所有人需要调用show_time函数,并且foo参数传递进去,如果foo=show_time(foo)(函数名可以作为返回值)

如上方法二:其他同事原本需要调用foo函数,现在需要调用show_time新函数,并且foo参数传递到show_time函数里面,
    如下能让foo =show_time(foo)这个问题就解决了,
    函数名可以作为返回值 

    import time
    def foo():
        print("执行测试用例")
            
    def show_time(func):                                #show_time就是装饰器,show_time本质就是一个函数,本质上处理其他函数,处理foo函数
        def inner():                                    #show_time函数内部定义了一个inner,inner函数有show_time函数的所有业务逻辑代码,最后将inner返回出去
            begin_time=time.time()
            func()              -                     #函数名可以当参数传递,foo相当于参数传递给func,fun()相当于foo()-----执行最开始的foo函数
            time.sleep()
            end_time=time.time()
            print("总计执行时间=",end_time-begin_time)
        return inner

    foo=show_time(foo)         #这样写了以后调用foo函数的时候,不需要做任何修改就能获取新功能
                            #foo=show_time(foo)的时候,那么foo等于show_time函数的返回值,show_time函数的返回值就是return inner,所以先当于foo =inner,
                                以后调用foo()相当于调用了inner()    这就是装饰器

    foo                        #这个foo相当于变成了inner=show_time(foo(这是最开始的foo))    #inner函数写好新功能逻辑,foo变量指向inner函数的地址单元,
                  (相当于foo=inner) foo() # #在这里相当于调用inner () inner就是新的增加的代码-------执行修改后的foo func()什么意思:函数名可以作为参数传递:foo=show_time(foo) foo相当于参数传递给了show_time函数的形参func,fun()相当于foo() 这里的foo()执行foo相当于执行inner,执行inner相当于先去begin_time,再去执行func,执行func等于执行最开始的foo函数, 所以foo=show_time(foo) 里面的foo和后面show_time(foo)里面的foo不一样,后面的foo是最开始的foo,前面的foo相当于已经变成了inner 执行了foo之后,再time.sleep()一下,再来end_time。打印总计执行时间 现在show_time就是装饰器,装饰器本质上就是函数,show_time是一个函数,该函数用来处理其他函数,show_time把foo函数处理了,能在其他不修改代码的前提下增加额外功能, foo代码没有被修改但是功能增加了, 这就是装饰器

  上面这样写装饰器没问题,但是python提供了一个简写装饰器的语法糖:如下
  语法糖:每一次装饰函数都需要手动执行foo=show_time(foo),可以省略掉foo=show_time(foo)
  在被装饰函数上去@装饰器,上下定义的关系,用语法糖的话def show_time函数要放在被装饰函数的上面,不然@找不到--代码如下:

import time
def show_time(func):
    def inner():
        begin_time=time.time()
        func()
        end_time=time.time()
        print("总计执行时间=",end_time-begin_time)
    return inner

@show_time                                            #相当于foo=show_time(foo) (#foo=show_time(foo)    #inner函数写好新功能逻辑,foo变量指向inner函数的地址单元,)
def foo():
    print("执行测试用例")
foo()

19:装饰器:带参函数的装饰

    def bar(func):                      #3:装饰器函数接收一个参数,这个参数是为了foo传给他
                                        #函数接收一个参数,函数里面定义一个内部函数,将定义的内部函数作为返回值返回,
                                        #func:接收的参数就是被装饰的函数,也就是需要新增功能的函数
                                        #return :inner 装饰完之后的函数
        def inner(name):                #1:里边定义一个内部函数,
            func(name)
            print("这是新加的功能")
        return inner                    #2:返回这个内部函数
        
    #以后再执行foo相当于执行inner了,inner里面就是主逻辑了,主逻辑包含func,func里面接收name参数,
    #这里的func相当于调用foo,这个foo的接收的参数name,name通过
    #以后下面执行foo,相当于执行inner,所以foo接收参数相当于inner接收参数name,所以下面的foo("张三")相当于inner("张三")

    @bar
    def foo(name):
        print("测试订单:",name)

    foo("张三")


固定套路:
    @bar    
    def foo(name):            
        print("测试订单:",name)

    foo("张三")

#foo被装饰函数,bar是装饰函数,装饰函数,先定义一个函数,函数接收一个参数,这个参数用来把被装饰函数的函数名传递给他,
    装饰函数内部定义一个新的内部函数,将内部函数函数名return出去,            固定套路     内部函数里面1:保留被装饰函数原有的功能,所以通过传参的形式调用被装饰函数
                                                                                                
装饰器本质上是函数,装饰器带参,

20:带参数的装饰器,装饰器带参数,

    #这个bar函数就是带参数的装饰器
    def wraper(age):
        def bar(func):
            def inner(name):
                func(name)
                print("这是新加的功能")
                print("年龄是:",age)
            return inner
        return bar

    @wraper(18)     #第一层:foo=wraper(18)=bar ,foo=bar   第二层:foo=bar函数了,再执行bar函数得到foo=inner
                    #执行foo()相当于执行inner(),
    def foo(name):
        print("测试订单:",name)
    foo("张三")

#三层,分别对应一层层自动执行,直到最后一层,:@wraper(18)  调用了wraper,18作为参数传递进去了,不会把foo作为参数传进去,
    往下走,走到wraper执行完了,wraper定义了一个bar函数,然后将bar函数return了出去,所以第一层 foo=bar ,然后它用于自动执行一遍bar函数
    
    bar函数接收一个参数func,这时候,到了第二层相当于@
    wraper(18)相当于bar,  @wraper(18)相当于@bar    @bar相当于回到了最上面    @bar相当于foo=bar(foo)等同于foo=inner 
    
    装饰器可以用到多个函数上----

  装饰器实际的应用场景:加日志
    接口请求的时候可以用装饰器捕捉接口请求失败的
    日志装饰器(如果被装饰函数在运行的过程当中报错了,那么日志装饰器会捕捉到这个错误并且把错误信息写进日志里面)
    日志装饰器看文件夹自己学习----重点 (日志装饰器公开课视频---在网盘可以看)


  java原生就是多态的
    a=3
    a="abc"这在java里面不允许的

    replace("-","","-1") ----- -1是全部替换的意思

21:多线程原理

  多线程:快速晃动一根手指,感觉有很多根,视觉暂停,视觉残留(和多线程并发类似)
  计算机工业:一台电脑一个cpu,同一时间只能做一件事情(一台主机连50个显示器50键盘同时50人使用---1个cpu快速在50人之间切换,看上去50台电脑一样)

  并发----多个任务如:task1和task2,在task1和task2之间来回切换,感觉同时执行两个任务一样(分配在每一个任务上停留的时间尽可能减少)----欺骗(这就是并发)

  小明接到一个任务,上山挖两个坑,一把锄头,左边刨一下,右边刨一下(两个坑同时变大的)微观角度来说不可能同时挖两个坑,宏观角度来说这两个坑是同时变大的
  这里的并发是逻辑上的同时执行,并不是真正意义的同时执行
  硬件发展电脑不止一个cpu了。 多个cpu可以并行

  并发:逻辑上具备同时处理多个任务的能力(并不是正真意义的一起执行)
  并行:物理上在同一时间时刻执行多个并发任务(两个cpu或者多个cpu)

22:线程和进程(一个进程里面有多个线程)

  进程:开个qq,开个迅雷一个进程
  线程:在qq这个进程里面传输文字开一个线程,传输语言开一个线程,传个文件又是一个线程:如腾讯课堂文字,是评论,声音,跟多个人聊天都是线程


  什么是线程、什么是进程
    开个QQ,开了一个进程;开了迅雷,开了一个进程
    在QQ的这个进程里,传输文字开一个线程、传输语音开了一个线程,传输文件又开了一个线程
    所以运行某个软件,相当于开了一个进程。在这个软件运行的过程中),多个工作同时运转,完成了软件的运行,那么这多个工作分别对应一个线程,
      所以一个进程管着多个线程,一个进程有且至少有一个线程

  一般来说一个软件一个进程---少数软件可能额外开一个进程(很少人这么做,资源消耗大,进程比较重,

    而线程比较轻,没特殊要求不会开进程,一般来说都是开线程并发处理任务)

  线程和进程的区别(了解)
    线程共享创建它的进程的地址空间;进程有自己的地址空间。
    线程可以直接访问其进程的数据段;进程有它们自己的父进程的数据段副本
    线程可以直接与进程中的其他线程通信;进程必须使用进程间通信来与兄弟进程通信。

23:python当中的线程和进程,python线程的创建与调用(python有个模块可以帮助我们去做这件事情 import threading)

  线程:一般使用线程单独处理一个任务,通过创建函数去给他分配任务:如下

    import threading
    import time
    def foo(something,num):
        for i in range(num):
            print("cpu正在:",something)
            time.sleep(1)


                                                                #创建线程实例,Thread这是一个class类,t1是创建线程实例(实例化给一个对象,赋值给一个变量t1)
    t1=threading.Thread(target=foo,args=["看电影",10])            #创建线程实例.实例化一个对象赋值给t1,Thread需要传两个参数,target指向任务函数foo函数,args为target指向的函数foo函数传数
                                                                    args传递多个参数需要使用列表格式


    t2=threading.Thread(target=foo,args=["听音乐",10])            #两个线程实例t1和t2,target指向任务函数,args为target指向的函数传参,args传参列表和元组都行

                                                                #启动线程,t1和t2线程交替运行(debug影响线程的执行),t1,t2两个(任务)线程太短
                                                                #t1执行一小段时间然后再执行别的任务,t1执行时间实在太短(打印三下时间短,还没来得及切换t1就结束了# )
    t1.start()                                                    #启动线程1,                                                    
    t2.start()                                                    #启动线程2,
                                                                #cpu拿到t1之后,会把他执行一小段时间然后再去执行别的任务,t1如果执行的时间太短,还没有来得及切换t1可能就结束了
                                                                    又去运行t2.t2又没来得及切换,t2又运行完了,线程时间太短-----看不出来效果----加个sleep
                                                                #两个线程执行起来没有规律,可能连续输入两个看电影,也可能连续输出两个听音乐
                                                                #因为资源竞争---线程创建之后各自为政,想要资源自己抢夺 t1抢到cpu t1执行,t2抢到t2执行


                                                                #线程的创建与调用.py  这个文件点击右键运行:有1个进程(点击运行就一个进程--开辟资源),
                                                                    进程这块开辟资源需要线程执行任务,这时候执行任务的是主线程,主线程就是py文件的代码,一行行往下运行,需要一个线程                                                                
                                                                    3个线程(t1,t2,执行任务主线程--py文件的代码一行行往下走,线程t1,t2需要主线程去创建,t1,t2线程也需要主线程去启动)
                                                                    文件点击右键运行就有个主线程,主线程运行到t1=threading.Thread(target=foo,args=["看电影",10])这行代码
                                                                    创建一个子线程t1,运行到和t2=threading.Thread(target=foo,args=["听音乐",10])两行代码,
                                                                    创建一个子线程t2,所以整个运行过程当中一共出现了三个线程,一个进程
                                                                    
    资源竞争的典形案例:可能还没打印完就被别的进程抢走了资源    

24:多线程的特点和优缺点

  多线程优点缺点,什么时候使用,不同场景下什么特点
  多线程执行任务有什么特点,

  实例:cpu执行任务,100M数据写入磁盘,cpu输出100M数据0.01s,磁盘接收100M数据需要10s

    第一种方法:cpu输出100m数据0.01s,然后等磁盘100m数据全部写进去后再接着往下运行 这叫串行(一个个来)
    第二种方法:cpu不等待,告诉磁盘慢慢写,cpu执行其他事情,磁盘写完了再通知cpu 并发

  上面就是串行和并发:模拟代码如下,这种写磁盘等待的任务叫io型任务(i=input o=output)io任务有阻塞型的,
    有等待的,有输入输出的任务,(接口请求的时候需要等一等,运行文件写入时候也需要等,这种任务叫io任务,)

    一:线程处理io型密集任务(还有计算型任务)
    io任务和计算型任务交替存在
    io任务
    1:模拟串行
        import threading
        import time
        def foo(something):       #定义任务函数不同线程打印不同
            print(something)
            time.sleep(1)
          

        模拟串行,一个个执行
        begain_time=time.time()
        foo("磁盘写入100M数据")
        foo("cpu去做其他事情")
        end_time=time.time()
        print("总计消耗时间:",end_time-begain_time)                                                   #总计消耗时间: 2.00126051902771



    2:模拟并发()
        import threading
        import time
        def foo(something):  # 任务函数
            print(something)
            time.sleep(1)

        #模拟并发
        begain_time=time.time()
        #先创建线程实例
        t1=threading.Thread(target=foo,args=["磁盘写入100M数据"])
        t2=threading.Thread(target=foo,args=["cpu去做其他事情"])

        #启动线程
        t1.start()
        t2.start()
        end_time=time.time()
        print("总计消耗时间:",end_time-begain_time)                                                #总计消耗时间: 0.0009579658508300781    
        
    程序运行后主线程往下走了,到了t1.start()的时候,创建了t1子线程并且启动,到了t2.start()的时候,创建了t2子线程并且启动,往下走 
        现在已经有三个线程,互不干扰,资源竞争,cpu执行到t1或者t2的时候,会发现sleep休眠,cpu跳转去执行其他线程,于是t1和t2都在休眠
        只有主线程没有休眠,主线程往下一直走,走到了print("总计消耗时间:",end_time-begain_time)打印运行时间,结束了,这时候t1和t2执行没有结束
        end_time-begain_time不是整个程序运行的时间,
        
        需要能够阻塞住主线程,不让他往下运行,并且恰巧在子线程结束的时候解除阻塞,这就是join方法的作用,但是他只针对单个线程声明有效
        t1.join()   #相当于在线程t1结束之前阻塞住主线程,不让其继续往下运行,t1.join只对t1生效    如下

    import threading
    import time
    def foo(something):   #任务函数
        print(something)
        time.sleep(1)
    begain_time=time.time()
    #先创建线程实例
    t1=threading.Thread(target=foo,args=["磁盘写入100M数据"])
    t2=threading.Thread(target=foo,args=["cpu去做其他事情"])
    #启动线程
    t1.start()
    t2.start()
    t1.join()                                                           #相当于在线程t1结束之前阻塞住主线程,不让其继续往下运行,t1.join只对t1线程生效
    t2.join()
    end_time=time.time()
    print("总计消耗时间:",end_time-begain_time)                           #总计消耗时间: 1.0028109550476074
                                                                        #python多线程的一个现象,主线程往下走,运行到t1.start()创建一个子线程t1,t1线程 start往下走
                                                                        # t2.start()启动t2,t2子线程往下走,-----三个线程互不干扰,
                                                                            资源竞争,cpu运行t1或者t2时候发现里面有sleep函数,cpu离开然后找到另外
                                                                        #一个,比如t2发现又sleep,又离开,调转到主线程,主线程往下一直走,
                                                                            走到结束的print("总计消耗时间:",end_time-begain_time) 代码
                                                                        #所以执行时间很短,但是这时候t1和t2子线程没有结束,所以end_time-begain_time不是整个程序运行的时间,
                                                                            运行时间答案不对
                                                                        #阻塞主线程不让主线程往下执行,子线程结束后解除阻塞----t1.join()   
                                                                            相当于在线程t1结束之前阻塞住主线程,不让其继续往下运行

    cpu的运行速率也不是恒定的,每次运行秒数都有细微差距

    io密集型任务串行时间2s+以上,并发时间1s+,并发更加节约时间
    线程处理io密集型任务
    二:线程处理计算密集型任务:io密集型任务cpu会有空闲。计算型任务cpu没有空闲
        电商平台--高并发---(多线程)

        import threading
        import time

        def foo():                                                            #这种就是计算型任务,cpu没有空闲
            num=0
            for i in range(10000000):
                num+=1
            return num

        1:串行的成绩,执行两遍foo函数
            '''
            begin_time=time.time()
            foo()
            foo()
            end_time=time.time()
            print("总计消耗时间:",end_time-begin_time)   #总计消耗时间: 1.4870405197143555
            '''


        2:并发的成绩
            begain_time=time.time()
            #先创建线程实例
            t1=threading.Thread(target=foo)
            t2=threading.Thread(target=foo)            #foo函数不传参不需要args,可以没有
            #启动线程
            t1.start()
            t2.start()
            t1.join()
            t2.join()
            end_time=time.time()
            print("总计消耗时间:",end_time-begain_time)   #总计消耗时间: 1.7612583637237549

            处理计算型任务使用并发和串行消耗的时间差不多或者更长    python2.7以上对运行做了优化(线程之间切换会消耗时间)
                io型任务省时间,计算型任务并没有节省时间(花费的时间更长),但是实际运行时间更短了

    python能够调用cpu多核来处理任务,使用进程的就行了,进程和线程的用法完全一样的

    性能测试:主要关心业务在多长时间能响应客户的业务,而不关心是否正确执行(业务多久时间能够响应)    
    功能测试:主要关系业务逻辑是否正确
    对不对交给功能测试,快不快交给性能测试

    很多公司业务不考虑性能的,自动化岗位更多
        功能自动化----检验正确与否
        性能自动化----快不快就完事

25:join的功能+守护线程    t1.setDaemon(True) 守护线程,主线程结束不等待t1子线程,直接退出整个进程执行

    二:线程和join---join作用是
        join的用法:

        import threading
        import time
        def foo():
            time.sleep(3)
            print("阳光明媚")
            
        t1=threading.Thread(target=foo)                    
        t1.start()                                                                    #启动线程
        t1.join()                                                                      #在线程t1结束运行之前阻塞住主线程。不让继续往下运行
        print("主线程最后一行代码")                              #不用join的话最后一行打印出来的时候整个python程序没有结束,因为t1进程还没运行完
                                                                    主线程走到最后一行代码,主线程已经完成任务了,
                                         在结束想要退出的时候回去检查所有的子线程,但凡有一个子线程还没结束, 主线程就会等待,等待子线程结束再退出程序 一:守护线程使用场景:不想主线程等待子线程结束后再关闭程序
---- 两个任务,一个任务生产数据,另外一个任务负责消费数据,消费数据到一定程度后就不需要你再生产数据了,所有该结束就结束 这时候主线程结束运行不去检查你子线程的运行状态 实现方法:守护线程 代码如下: import threading import time a=[] def foo(): #不停生产数据 while True: a.append("1") print("生产了一个数据") time.sleep(1) t1=threading.Thread(target=foo) #启动子线程不停的生产数据,主线程消耗数据 t1.setDaemon(True) 设置守护线程,必须在start之前设置,启动后设置没有意义,需要启动之前设置 作用就是在主线程想要退出进程的时候不需要等待子线程运行情况自己运行结束,直接退出就行了 t1设置了守护线程,主线程想要退出的时候不会等t1, 如果有t2没有设置守护线程,那么主线程结束之后会等t2结束,每个线程都需要进行设置的 守护线程是设置在子线程上面的,其作用是主线程想要退出的时候不需要等自己结束 不需要消费数据之后有可能还会被生产一下,因为我cpu有可能执行完打印这句话之后, 就离开主线程,去子线程打印了一下。t1子线程生产数据 t1.start() for i in range(10): #主线程消耗数据,消耗10个数据 if a: #列表不为空才去消耗数据 a.remove("1") print("消费了一个数据") time.sleep(1) print("不需要再消费数据了") 不需要再消费数据了之后有可能还会打印一个生产了一个数据,这一个是因为主线程退出之前打印了一个出来 刚好在结束之前打印的,cpu可能执行完不需要再消费数据了这句话之后cpu立刻主线程了, 又去子线程地方打印了一下 思考一下为什么只打印了9个主线程消耗数据,可能消费的时候列表为空没有数据消耗

26:不安全的并发

  使用场景:
    小明有一张银行 卡余额500元,这张银行卡绑定了小明妻子的微信和支付宝手中。
    小明今天发工资了,收入人命币10000元,在银行汇款的同时小明的妻子通过支付宝消费口红一支300元。
    ●两件事 都需要操作一个变量-->账户余额
    ●两件事几乎同时发生,所以同时取到余额这个变量balance ,都是500元
    ●线程A计算工资收入: 500+10000 = 10500元 balance = 10500
    ●线程B计算消费支出: 500- 300 = 200元 balance = 200
    ●显然、并发操作数据是不安全的

    银行卡往里面存钱的时候又消费(往里存钱需要操作账户余额,消费也需要操作账户余额,消费和存钱同时发生,需要同时取到余额这个变量500元)
    工资余额500,工资银行通过接口等方法取到余额500,存到自己系统变量当中,a=500 a=a+10000 a=10500,银行计算完毕结果返回出去,余额覆盖成10500

    与此同时覆盖500余额之前买口红了,两取余额的方式不是一家(淘宝+银行)(支付宝页拿到500余额值存到自己系统变量,a=500,a=a-300,a=200,银行卡余额200)
    支付宝速度慢了一点点,余额先被覆盖成10500,然后马上被覆盖成200,
    情况二:支付宝速度比较快500覆盖成200,然后再200被覆盖成10500.最后是10500余额

一:不安全的并发模拟代码:
import threading
import time

balance=500                                                   #银行卡账户余额,
def foo(num): 
     global balance                                             #声明全局变量(局部作用域修改全局变量必须去global声明一下)
                                                            #模拟接口取到数据存到自己系统中,银行卡账户余额存到自己的变量account_balance
                                                            (模拟接口取到数据之后存入自己的系统当中,拿到数据后网变量里存储)
    account_balance=balance                                    #将变量存到自己系统(balance飘红,在局部作用据引用全局变量没有问题,但是我要修改全局变量必须
                                                            #global声明一下, global balance )
                                                            
    time.sleep(1)                                              #防止代码太少,cpu执行速度太快,t1和t2变成串行
    
    #一系列操作之后,计算出结果
    account_balance=account_balance+num
    #将结果传递回去,account_balance覆盖balance
    balance=account_balance                                    #局部作用域引用全局变量没有问题,但是局部作用域修改全局变量必须去global声明一下,


t1=threading.Thread(target=foo,args=[-300])                    #创建线程,消费300元

t2=threading.Thread(target=foo,args=[10000])                #创建线程,收入10000

#启动线程
t1.start()
t2.start()

#阻塞线程,打印最终的所有线程运行结束之后余额balance为多少
t1.join()
t2.join()

print("所有线程运行结束之后,余额为:",balance)                #先启动的线程具有更多的优势:先运行的    

上面的代码就是不安全的并发,    

  解决办法:修改前先锁定(上锁),一些重要的数据操作的时候上锁,别人不能轻易使用,重要数据操作的时候上锁,声明一把锁(同步锁)r=threading.Lock() 代码如下:
  二:同步锁解决不安全的并发

  同步锁的特点:
    # 锁通常被用来实现对共享资源的[同步]访问。
    # 为每一个共享资源创建一个Lock对象,
    # 当你需要访问该资源时,调用acquire方法来获取锁对象(如果其它线程已经获得了该锁,则当前线程需等待其被释放),
    # 待资源访问完后,再调用release方法释放锁:

  这把锁是同步锁
    同步锁必须上锁和解锁成对,否则会产生bug,如果上锁之后不解锁,再次上锁代码会阻塞
    如果解锁之后,没有锁的时候,又进行解锁,代码会报错

    同步锁代码如下
    import threading
    import time

    balance=500                                                               #账户余额
    r=threading.Lock()                                                      #声明一把锁,赋值给变量r

    def foo(num):                                                              #模拟接口渠道数据存到自己系统中
        global balance                                                      #声明全局变量
        #重要数据操作之前上锁,
        r.acquire()                                                         #上锁,锁在重要数据操作之前上锁
        account_balance=balance                                                #将变量存到自己系统(balance飘红,在局部作用据引用全局变量没有问题,但是我要修改全局变量必须
                                                                            #global声明一下, global balance )
        time.sleep(1)                                                          #防止代码太少,cpu执行速度太快,t1和t2变成串行
        #一系列操作之后,计算出结果
        account_balance=account_balance+num
        #将结果传递回去,account_balance覆盖balance
        
        balance=account_balance
        #重要数据操作结束之后解锁
        r.release()
        
    #创建线程,消费300元
    t1=threading.Thread(target=foo,args=[-300])
    #创建线程,收入10000
    t2=threading.Thread(target=foo,args=[10000])

    #启动线程
    t1.start()
    t2.start()

    #阻塞线程
    t1.join()
    t2.join()

    print("所有线程运行结束之后,余额为:",balance)                            #现在输出的数据是非常正确的10200了

27:死锁   递归锁解决死锁

  死锁+解决的方法递归锁
  死锁:假设银行系统中,用户a试图转账100块给用户b,与此同时用户b试图转账200块给用户a,则可能产生死锁。2个线程互相等待对方的锁,互相占用着资源不释放。
    用户a的余额是一个数据资源,用户b的余额是一数据个资源,a账户转账给b账户这一个操作需要操作a这个数据资源,也需要操作b这个数据资源,
    a转账给b启动线程进程操作,一开始a数据资源上锁了,现在需要转账给b,需要操作b的余额,操作b余额这个资源的时候,
    发现b余额这个资源已经被上锁了,就等b余额数据解锁,但是与此同时b在给a转账,已经把b转账给a的操作把b的余额资源锁住了,现在需要
    给a账号转账,也需要操作a余额这个资源,此时发现a余额这个资源也被锁住了,此时转账的两个线程都在等对方的资源释放-----死锁

  很像:你不给我发简历我就不回答问题,你不回答问题我就不发简历----这就是死锁
    小明需求offer,被面试官占有,小明拥有问题的答案,面试官需求问题的答案,被小明占有,
    面试官有offer,小明和面试官分别占有一部分资源,然后同时又想要对方的资源,系统判断两部分资源都在使用(死锁)

    线程中,2个线程分别占有一部分资源,小明占有一部分资源,面试官占有一部分资源,

    同时等对方的资源---死锁(系统判断这部分资源正在使用),两个线程无外力作用下将一种等待下去

  死锁怎么处理---递归锁
  两个线程同一个任务函数 同步锁
  两个线程不同任务函数,占用同样的资源 递归锁(支持同一线程中多次请求同一资源)提供了递归锁也叫可重入锁

1:死锁代码和逻辑:
    import threading
    import time

    lockA=threading.Lock()   #面试官的锁
    lockB=threading.Lock()   #小明的锁

    #面试官的任务函数
    def foo1():
        lockA.acquire()                            #面试官先上一把锁,对a资源上个锁
        print("请回答问题")
        time.sleep(1)

        lockB.acquire()                            #小明也上了一把锁,对b资源上一个锁
        print("发offer")
        time.sleep(1)

        lockA.release()                            #解锁操作
        lockB.release()                            #解锁操作

    #小明的任务函数
    def foo2():
        lockB.acquire()
        print("发offer")                        #请给面试官的资源
        time.sleep(1)

        lockA.acquire()
        print("请回答问题")                        #请给小明的资源
        time.sleep(1)

        lockA.release()
        lockB.release()


    t1=threading.Thread(target=food1)
    t2=threading.Thread(target=food2)
    t1.start()
    t2.start()

  死锁理解:小明和和面试官是两个资源,线程t1运行的时候需要小明和和面试官两个资源,

    线程t2运行的时候需要小明和和面试官是两个重要资源
    t1线程运行到需要面试官重要数据的资源上锁,线程并发同时t2线程运行到小明数据资源上锁,

    线程切换到t1线程现在需要使用小明的资源了,但是t2线程把小明资源上锁了
    所以t1线程缺少资源不能运行了,跳转到t2线程,t2线程需要面试官的资源但是面试官资源被t1上锁了,

    此时来回切换,面试官和小明的资源都被上锁了都不能往下执行释放   死锁   

  t1启动,t2启动,foo1--locka上锁,print后等待1s,这时候cpu去执行t2,loob上锁,print后等待1s,又去执行food1,
  loob还没来得及解锁,lockb又要上锁(所以需要上递归锁,两个任务需要占有你需要的资源,)
  我需要你的资源,我占有你需要的资源,你占有我需要资源,互相想要对方的资源,
  数据就是一个资源,上锁后就是我占用资源,需要解锁后这个资源才能别人使用

  我的理解:t1启动,t2启动,foo1--locka上锁,print后等待1s,这时候cpu去执行t2,loob上锁,print后等待1s,又取执行food1,
    这时候执行food1需要 lockB.acquire() lockb上锁,但是b已经上锁了所以t1线程需要等待资源,去执行t2线程,lockA.acquire(),

    locka上锁,但是locka也已经上锁了,来回一直循环---死锁

  上面的代码会卡死:解决办法:递归锁

2:递归锁的代码和逻辑:
    import threading
    import time

    #lockA=threading.Lock()   #面试官的锁
    #lockB=threading.Lock()   #小明的锁
    lockR=threading.RLock()                                               #递归锁,也称之为可重入锁
    '''
    递归锁特点:递归锁内部维护一个计数器和一把锁,每次上锁计时器+1,每次解锁计数器-1
    计数器可以大于0也可以等于0,不能小于0(无锁可解除会出问题)
    '''

    #面试官的函数
    def food1():
        lockR.acquire()
        print("请回答问题")
        time.sleep(1)

        lockR.acquire()
        print("发offer")
        time.sleep(1)

        lockR.release()
        lockR.release()


    def food2():
        lockR.acquire()
        print("发offer")
        time.sleep(1)

        lockR.acquire()
        print("请回答问题")
        time.sleep(1)

        lockR.release()
        lockR.release()

    t1=threading.Thread(target=food1)
    t2=threading.Thread(target=food2)

    '''
    t1启动,t2启动,foo1--locka上锁,print后等待1s,这时候cpu去执行t2,loob上锁,print后等待1s,又取执行food1,
    loob还没来得及解锁,lockb又要上锁(所以需要上递归锁,两个任务需要占有你需要的资源,)
    我需要你的资源,我占有你需要的资源,你占有我需要资源,互相想要对方的资源,
    数据就是一个资源,上锁后就是我占用资源,需要解锁后这个资源才能别人使用
    '''

    t1.start()
    t2.start()

  递归锁(计数器)可以理解为4把锁,1,2,3,4----一个线程内所有acquire都被release,其他线程才能获取资源(串行)------

  递归锁:锁上后,线程1操作数据,线程2拿到锁,可以进来,但是不能操作线程1正在操作的数据(上锁的资源),

  悲观锁:总觉得我拿数据的时候也有别人去修改数据,只要拿数据就上锁,(数据库层面的东西)

  乐观锁:每次从数据库拿数据的时候都认为别人不会修改数据,不上锁,只在提交更新的时候判断一下有没有人更新数据,

      不觉得有人在我拿数据的时候他也去修改了(数据库层面的东西)

  死锁锁住的本质:如果上锁之后不解锁,再次上锁,代码阻塞:t1启动,t2启动,foo1--locka上锁,

       print后等待1s,这时候cpu去执行t2,loob上锁,print后等待1s,又去执行t1,

      t2把lockb上锁之后还没来得及解锁,t1又要把lockb上锁(所以这种情况需要上递归锁,两个任务我需要你的资源,我占有你需要的资源,)

      你占有我需要资源,互相想要对方的资源,这种情况下适合用递归锁,反之适合同步锁

      数据就是一个资源,上锁后就是我占用资源,需要解锁后这个资源才能别人使用

  不安全的并发问题本质---解决办法同步锁(上锁后强制变成串行运行)

    银行取到500后余额数据上锁,其他任何人想要访问看到这把锁就进不去,这时候支付宝只能外面等着,

    进不去,这时候被迫成为了串行,等银行操作数据结束,10500写进去了,这时候锁打开

  支付宝才能进去取数据10500余额,10500-300=10200不安全并发被解决了

 

  递归锁:递归锁允许多次上锁,递归锁在操作数据的时候也同样的

  线程1操作数据的时候,线程2能拿到锁,可以进来,但是如果想操作线程1正在操作的数据,是不行的,

  递归锁有几个计数器,

28:计算机网络:

  osi7层模型:(物理层,数据链路层,网络层,传输层,会话层,表示层,应用层)

29:OSI模型详解:

  QQ应用层:一些终端的应用,比如说FTP (各种文件下载),WEB (IE浏览),QQ之类QQ

  Hello pythonHello world

  表示层:主要是进行对接收的数据进行解释、加密与解密、压缩与解压缩

  会话层:通过传输层(端口号:传输端口与接收端口)建立数据传输的通路.

  传输层:定义了一些传输数据的协议和端口号(WWW端口80等) ,如: TCP

  网络层:主要将从下层接收到的数据进行IP地址(例192.168. 0. 1) 的封装与解封装

  数据链路层:主要将从物理层接收的数据进行MAC地址的封装与解封装

  物理层:主要定又物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等

 

  小明上班,老婆发消息让他下班时候晚上回来买个灯泡换个新的,qq发这样的消息,这条消息从小红手机上的qq传到小明手机上的qq

    网络传输,网络当中的主机,设备怎么查找,小红的消息怎么准确找到小明的手机

  一要素:ip地址--精准找到小明手机,

  二要素:小明手机上还有很多软件,qq,微信,:服务端口---像身份证,能保证消息准确找到你这个应用,

      qq发的找qq,标识进程的逻辑地址(不同的进程还有不同的端口标识,唯一的),

      身份证号码,仅仅在一台设备上有效,你手机上qq的端口号和我手机上qq的端口号显然没有关系)

  三要素:数据在网络当中传输,要进行转化,字符变成bytes类型,bytes再变成字符(二进制转换),

      应用不断成熟,开发模型不断成熟,交流沟通渐渐频繁,摸索出更加复杂的处理信息方式

      传输协议,tcp(网络中传的二进制),udp(网络中传的也是二进制),

 

  网络通信三要素:ip地址(用来标识网络上一台独立的主机) 

          端口号(标识进程的逻辑地址,不同进程不同的端口标识,,想把数据发到对方指定的应用程序上,标识应用程序,给这些应用

 

      程序是数字进行标识,为了方便去称呼这些数字,所以取名端口号,逻辑上的地址)   

          传输协议(通信的规则,怎么收怎么发,什么编码。。。。tcp协议,udp协议)

  网络通信三要素(掌握的知识)

  IP地址

    (1)用来标识网络上一台独立的主机

    (2)iP地址=网络地址+主机地址(网络号:用于识别主机所在的网络/网段。主机号:用于识别该网络中的主机)

    (3)特殊的IP地址: 127.0.0.1 (本地回环地址、保留地址,点分十进制)可用于简单的测试网卡是否故障。表示本机。

  端口号:

    (1)用于标识进程的逻辑地址。不同的进程都有不同的端口标识:

    (2)端口:要将数据发送到对方指定的应用程序上,为了标识这些应用程序,所以给这些网络应用程序都用数字进行标识

        为了方便称呼这些数字,则将这些数字称为端口。(此端口是一个逻辑端口)

  传输协议:通讯的规则。例如: TCP、 UDP协议(好比两个人得用同种语言进行交流)

    1:UDP: User Datagram Protocol用户数据报协议

    特点

      面向无连接:传输数据之前源端和具的端不需要建立连接。

      每个数据报的大小都限制在64K (8个字节)以内

      面向报文的不可靠协议。(即: 发送出去的数据不一定 会接收得到)

      传输速率快,效率高

      现实生活实例:邮局寄件、实时在线聊天、视频会议....等:

    2:TCP: Transmission Control Protocol 传输控制协议

    特点:

      面向连接:传输数据之前需要建立连接。

      在连接过程中进行大量数据传输

      通过“三次握手的方式完成连接,是安全可靠协议。

      传输速度慢,效率低。

30:Socket编程

  要想理解socket,就要先来理解TCP, UDP协议

  TCP/IP (Transmi ssion Control Protocol/Internet Protocol )即传输控制协议/网间协议,

    定义了主机如何连入因特网及数据如何再它们之间传输的标准,从字面意思来看TCP/IP是TCP和IP协议的合称,

    但实际上TCP/IP协议是指因特网整个TCP/IP协议族。不同于IS0模型的七个分层,TCP/IP协议参考模型把所有的TCP/IP系列协议归类到四个抽象层中

 

  应用层: TFTP, HTTP, SNMP, FTP, SMTP, DNS, Telnet 等等

  传输层: TCP, UDP

  网络层: IP, ICMP, 0SPF, EIGRP, IGMP

  数据链路层: SLIP, CSLIP, PPP, MTU

 

  每一抽象层建立在低一层提供的服务上,并且为高一层提供服务,看起来大概是这样子的利用ip地址士协议+端口号唯二标示网络中的一个进程。能够唯二标示网络中的进程后,

  它们就可以利用socket进行通信了,我们经常把socket翻译为套接字,socket是在应用层 和传输层(TCP/IP协议族通信)之间的一个抽象层,是一组接口,

  它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。

  应用程序两端通过“套接字”向网络发出请求或者应答网络请求。可以把socket理解为通信的把手(hand)

  socket起源于UNIX,在Unix一切皆文件哲学的思想下,socket是种”打开一读/写一关闭”模式的实现,

  服务器和客户端各自维护一个”文件”,在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。

  socket的英文原义是“插槽”或“插座”,就像我们家里座机一样,如果没有网线的那个插口,电话是无法通信的。

  Socket是实现TCP, UDP协议的接口,便于使用TCP, UDP

  http和https是应用层的协议

  tcp和udp是传输层的协议

31:socket编程的实现:代码层面:

  一:python怎么进行网络上的通信(服务端,客户端)

    移动手机ip地址一直变为什么每次都能找到设备(切换网络),切换ip地址都需要登录,wifi切到4g,断网到联网过程,qq需要重新登录,这时候qq和服务器有所沟通,

    这时候让连接客户端主动告诉服务器客户端的ip地址+端口号,其它人找你也是通过服务器找的(服务器知道设备ip和端口号)

    先有服务端,服务端先运行起来(服务端起来后ip地址和端口号绑定死不许修改,否则用户找不到)这栋大楼是一个socket对象

 

  二:实现流程

    创建一个Socket对象(tcp服务端大楼)

    socket绑定ip地址和端口号(bind大楼绑定ip和端口号)

    listen(监听客户端有没有请求过来,设置监听端口,等待客户端的请求)

    Accept()客户端连接过来之前,服务器无事可做,处于阻塞的状态,直到有客户端连接过来(连接一定是客户端主动连接服务端)

    Socket(客户端也是一个cocket对象))

 

    connect(客户端通过connect方法连接指定计算机,ip地址端口号,能够连接到指定计算机的指定服务)

    连接之后阻塞服务器状态解除了,工作时候客户端和服务器需要相互交流

    频繁的收发消息互相recv和send数据

    close (业务完成后客户端发送退出请求,然后客户端关闭socket,服务端也关闭这一个柜台释放)

   

    服务器第一个socket理解为银行整体,或者银行大堂经理,大的银行整体

    客户端第一个socket一个客户,

    第二个人socket对象理解为柜台,当一个客户端办完业务后,这个柜台就可以释放出来了,空闲了

 

  以上是网络编程的基本逻辑

32:代码方面socket实现简单的服务端和客户端通信:(一次聊天)

    1:服务端代码:
        import socket
        
        sk=socket.socket()                                                #使用socket需要先创建socket对象,赋值给sk,sk就是socket对象

        
        sk.bind(("127.0.0.1",13000))                                    #绑定ip地址和端口号,传参数据是元组,元组第0个元素就是本地ip地址,服务器ip地址
                                                                        #端口号自己定义,找个没有被占用的空闲端口               windows查看端口号:netstat -ano

        
        sk.listen()                                                        #监听,监听客户端的请求,
        
        print("服务器已经启动了......")                                    #到这一步服务端已经启动了
        

                                                        #服务端启动后代码就就阻塞在这里一直监听客户端,不会往下执行,除非收到客户端的数据
                                                        #等待传入连接(协议已经在socket模块定义好了,tcp协议)协议不存在高度自定义,大部分用的都是一样的,
                                                        #连接成功之前,服务器有人连接前处于阻塞状态,连接成功之后,会返回一个新的套接字和客户端的ip地址(套接字就是socket对象)
                                                        #返回新的套接字就是柜台,银行的柜台,和客户端的ip地址
        conn,addr=sk.accept()                                              #等待传入连接,accept有返回值,conn接收套接字,addr接收客户端的ip地址,
        print("客户端ip地址:",addr)

                                                        #服务端接收数据,conn是我们的银行的柜台,柜台接受数据,知道客户端什么请求,服务器运行资源有限,
                                                        #conn.recv接收数据,收到数据存到变量client_data,
                                                        #服务器不会一次性接受很大的数据,每一次接受数据指定一个大小值,超过这个大小你循环发,我循环接收,
                                                        #设置conn.recv参数1024,每次只接受1024个字节
        client_data=conn.recv(1024)                                     
        print("客户端:",client_data.decode("utf8"))    #recv接收到的数据也是bytes类型,所以需要decode解码成str类型


                                                        #接受到客户端的数据后还需要发送数据,发送一个键盘输入(本地键盘输入信息)
        conn.sendall(input("请输入>>>>").encode("utf8"))                #网络当中传递数据不能直接传递字符串,
                                                                        # 这里input输入的内容是个str类型的字符串,需要先转成bytes类型才能send

                                                        #释放资源,释放的是银行里面的一个柜台,而不是释放整个银行,conn柜台被释放,sk不会被释放,sk释放了服务端不能继续提供服务了
        conn.close()
        sk.close()                                                       #sk服务端一般不会被释放的,释放了服务端就不会再提供服务,




    2:客户端代码:
        import socket

        sk=socket.socket()                                                #创建socket对象,赋值给sk

        
        sk.connect(("127.0.0.1",13000))                                    #发起连接,这里也是元组传参,参数是连接的服务器的ip地址和端口号才可以,不能连接别的
        
        sk.sendall(input("请输入>>>").encode("utf8"))                    #发送数据(连接完成后对于客户端来说之间可以收发数据了)
        
        server_data=sk.recv(1024)
        print("服务器:",server_data.decode("utf8"))                    #接收数据

        sk.close()                                                        #释放资源

    上面代码完成,只能聊天一次:先运行服务端代码,再运行客户端代码,服务器启动后代码阻塞在conn,addr=sk.accept()这里,一直监听客户端,
        客户端运行起来,发送连接请求,连接成功后,服务端阻塞就解除了,把客户端的ip地址打印出来了,客户端的ip地址就是手机的ip地址,客户端的
        端口号一般由操作系统进行分配,然后客户端输入数据    服务端接受数据,服务端发送数据后代码结束    一次聊天代码

  socket基本处理ok

33:socket实现,不间断聊天功能和退出:

服务端:
    import socket
    #服务端

    sk=socket.socket()
    sk.bind(("127.0.0.1",13002))
    sk.listen()
    print("女神上线,并且发了一条朋友圈...")

    #和多个人循环聊天
    while True:      #现在一直循环的是单线程,所以只能一次和一个人聊天,所以需要多线程聊天,聊天写成函数,多线程调用一下
                     #还有新知识处理,并发处理
        '''
        负责接受不同人的请求聊天
        '''
        print("女神空闲了.....")  #服务端等待客户端发送连接请求,阻塞这里
        conn,addr=sk.accept()
        print("有人来找女神聊天了",addr)

        while True:  #跟人持续不间断聊天,聊天
            client_data=conn.recv(1024).decode("utf8")
            if client_data=="exit":
                break
            print(client_data)

            #回复消息
            conn.sendall(input("请输入>>>").encode("utf8"))   #我这里边键盘输入后再encode编码成utf8二进制
        conn.close()


    客户端:
    import socket
        
        sk=socket.socket()
        sk.connect(("127.0.0.1",13002))
        print("客户端启动了...")
        
        while True:
            #发送数据
            inp=input("请输入>>>")
            sk.sendall(inp.encode("utf8"))
        
            if inp =="exit":
                break
        
            ser_data=sk.recv(1024).decode("utf8")
            print(ser_data)
        
        sk.close()

客户端都是一样的,上面的代码需要服务端和一个聊完后才能和另外一个聊天,单线程,循环一直在先进来的大白里面,还没退出去,大白只要
    不退出,阿强就一直进不来。大白一退出,女神阿思就能收到阿强的消息,现在是单线程,        需要多线程聊天
    服务端写成函数,多线程调用一下(自己尝试一下)

新的知识,并发处理,

34:socket(实现上传下载文件)同一网段模拟

  文件传到服务器--上传

  文件下载到本地--下载   

  动作一样,方向相反

一:socket---上传代码:如下
思考:上传和下载这两个动作拆出来形成一个函数,服务端这里面是得到文件,定义函数get_file得到文件

    服务器端:
    
    import socket
    
    sk=socket.socket()                                                            #创建socket对象

    sk.bind(("127.0.0.1",13001))                                                #给服务端绑定ip地址和端口号
    
    sk.listen()                                                                    #监听

    #上传和下载两个动作拆分形成两个函数
    
    def get_file(sk_obj):                                                        #服务端接收数据,需要一个socket对象,参数传递(客户端需要先发服务端才能得)
        '''
        接收文件
        :param sk_ob:     socket对象
        :return:
        '''

        file_name=sk_obj.recv(1024).decode("utf8")            #接受文件名,服务器接收数据逻辑要和post_file想对应,你发我收,你收我发
        
        sk_obj.sendall("我接收完了".encode("utf8"))            #接收了文件名告诉对方一下我接收完了,

        
        file_size=int(sk_obj.recv(1024).decode("utf8"))        #接收文件大小,传来的是二进制,解码后是字符串,需要int成整形
        
        sk_obj.sendall("我接收完了".encode("utf8"))            #接收了文件大小后告诉对方一下我接收完了,


        with open("./%s" % file_name,"wb") as f:            #接收完文件大小后,接收文件正文(需要with_open,边接边存)        #wb以二进制数据写
            while file_size>0:                                #循环接收文件正文
                f.write(sk_obj.recv(1024))                  #接收的1024个字节写到文件进去
                file_size=file_size-1024                    #剩余没有接受的数据大小file_size,

    
    conn,addr=sk.accept()                                    #监听后等客户端连接,客户端连接后返回conn,addr

    get_file(conn)                                          #开始接收文件        #conn负责和客户端交流,sk是银行,conn是柜台,负责和客户端交流

    conn.close()                                            #文件接收完了之后
    sk.close()


二:客户端:发文件的函数(客户端需要先发服务端才能得)
    import os
    import socket

    sk=socket.socket()                                                    #创建socket对象

    sk.connect(("127.0.0.1",13001))                                        #连接发起

    def post_file(sk_obj,file_path):                #客户端发送文件函数,文件是通过网络传输,需要使用socket对象传参数,
                                                    #所以使用socket对象传递参数,还需要文件路径file_path,文件从哪里来
        '''
        上传文件函数
        :param sk_obj:         socket对象
        :param file_path:     文件路径
        :return:
        '''
        
                                                                        #发送文件名(发送文件首先需要发送文件名    )
        sk_obj.sendall(os.path.split(file_path)[1].encode("utf8"))      #获取文件名方法,这个函数拆分路径,分成文件夹名和文件名,放在一个列表里面,取列表第一个元素文件名称。str类型
                                                                            把文件名转成bytes类型进行传输
        
        sk_obj.recv(1024)                                                 #粘包操作,发送一次数据后阻塞住进程,需要recv得到服务端的数据后才继续运行下面的,避免发送数据挤在一起紊乱
                                                                        #发送下一条数据的前提是需要我这边数据完全发出去,被服务器接受,并且给了我返回的数据才执行下面的

                                                                        #发送文件大小,每一次只能接受1024,需要知道文件大小,循环发送几次,
        file_size=os.stat(file_path).st_size                             #获取文件大小st_size ,这里st_size是一个整形,整型不能直接转为bytes类型,
                                                                            #要先转成str类型,之后转为encode-bytes类型 
        
        sk_obj.sendall(str(os.stat(file_path).st_size).encode("utf8"))    #获取文件大小st_size ,这里st_size是一个整形,整型不能直接转为bytes类型,
                                                                                要先转成str类型,之后转为encode-bytes类型 
                                                                                转为bytes类型之后可以sk_obj.sendall发送文件大小
        
        
        sk_obj.recv(1024) #粘包操作,粘包

                                                                        #发送文件内容(肯定要先读文件,with open rb模式二进制读取)
        with open(file_path,"rb")as f:                                  #循环发送多少次,需要根据文件file_size大小来判断,每次传1024,剩余的未传部分就是file_size-1024,
                                                                            这样循环发送,每次传1024,直到最后一次两种情况:1:恰好还剩1024字节,都传过去,这时候file_size-1024=0了
                                                                            2:剩余不到1024字节,把不到1024的部分全部传出去,x-1024<0,所以循环条件 file_size >0,只要 file_size >0,
                                                                            证明还有没有传完的部分,继续传,
                                                                            
            while file_size >0:                                         #file_size大于0,就证明还有没有传完的部分
                sk_obj.sendall(f.read(1024))                              #每次读取1024个字节然后发送出去,
                file_size=file_size-1024                                #每次传完1024,剩余未传部分就是file_size-1024,

    file_path="C:\\Users\\ywt\\PycharmProjects\\untitled\\python 进阶\\day_4\\socket初识\\文件上传\\客户端\\qq.png"        #指定文件路径
    
    post_file(sk,file_path)                                                #运行传输文件代码

    上面就是发送的逻辑,发三次服务端接三次,发过去的字节流流水一样流过去,流过去文件名,再流过去文件大小,接连不断发送的,连续起来的,再往下发送文件正文内容,
        流水字节流连续不断的,服务器接收收据,假设放了三个桶,没那么智能,不知道什么时候该断开,所以数据来的时候有可能接收到不属于自己这桶的数据,
        可能出现bug,数据发混了,数据错开了,可能一个桶接收了文件大小和名字合并了,数据就乱了,    做一个再发数据这边做一个截断的操作,每次发一个数据过去
        主动截断一下,这样客户端自己区分----    

连包:三段数据,三桶水,数据在网络当中进行传输,网络当成管道,第一桶水倒进去了。第二桶水也紧接着倒进去了,第三桶水也紧接着倒进去了,
    代码执行连续不断的,三桶水是源源不断的倒进去,另外一边去接这个水,一个个桶去接这个水,这个水是连在一起流过来的,拿桶去接受它,无法保证
    在第一段数据和第二段数据中间位置截断让第二个桶继续接,---所以可能少接可能多接,需要做个避免连包的操作
    
socket短连接和长连接的区别
python文件指针 seek(断点续传逻辑使用指针)

上面是文件上传,下载方向一下

35:并发处理之:socket实现多线程和多人一起聊天,

    python socketserver  这个模块帮我们做了线程的处理,不需要自己再去做了,socketserver的ThreadingTCPServer    是一个class,继承了两个类:如下 
        class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass    ,
      继承了ThreadingMixIn(帮我们进行多线程任务操作)和TCPServer(帮我们提供tcp服务网络编程的功能) 服务端: import socketserver #这个模块做了线程的处理 #并发处理多人聊天
class MyServer(socketserver.BaseRequestHandler): #MyServer请求处理类要求必须继承socketserver.BaseRequestHandler class BaseRequestHandler: 重写 def handle(self):方法 handle方法就是业务主要逻辑,
                                            这里聊天的时候就会执行handele当中的逻辑 def handle(self): #重新写父类函数BaseRequestHandler的handle方法,handle方法就是业务主要逻辑,
                                            #这里聊天会执行handle里面的逻辑 print(
"有人来和女神搭讪了...") #连接逻辑已经做好了,不需要自己写了 while True: #接收数据 client_data=self.request.recv(1024).decode("utf8") #self.request.recv相当于conn.recv,接收数据存到变量client_data if client_data=="exit": #如果是exit就break退出,下一位 break print(client_data) #发送数据 self.request.sendall(input("请输入>>>").encode("utf8")) self.request.close() #退出循环就关闭连接 #class ThreadingTCPServer(ThreadingMixIn, TCPServer): ThreadingMixIn 提供多线程,TCPServer提供tcp服务 server=socketserver.ThreadingTCPServer(("127.0.0.1",13004),MyServer) #实例化 参数:MyServer,请求处理类,就是业务函数的主要逻辑 print("女神上线了.....") server.serve_forever() #需要先主动启动服务端,serve_forever 客户端: import socket sk=socket.socket() sk.connect(("127.0.0.1",13004)) print("客户端启动了...") while True: #发送数据 inp=input("请输入>>>") sk.sendall(inp.encode("utf8")) if inp =="exit": break ser_data=sk.recv(1024).decode("utf8") print(ser_data) sk.close() 上面的代码能够同时收到多个客户端的消息了额,但是服务器回复会优先回复先发过来的那个人,这里没有写前端界面控制, 上面就是不间断+多线程聊天的代码

36:python远程操作linux使用paramiko库函数

    import paramiko

    #创建一个ssh对象--帮助操作linux的,linux必须要有ssh服务才能操作linux
    ssh =paramiko.SSHClient()                   #SSHClient是一个类,创建ssh对象

    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())   #设置连接方式,热键策略,密钥策略--paramiko.AutoAddPolicy自动添加的策略

    #ssh连接需要远程主机ip地址,端口号(ssh服务默认的端口号22),传输协议,在远程主机已经存在的用户名和密码
    ssh.connect("192.168.10.199",22,'worker1','123456')

    #执行命令
    stdin,stdout,stderr=ssh.exec_command('pwd')  #函数返回值,return stdin, stdout, stderr 标准输入,标准输出,标准错误


            #stdin = chan.makefile_stdin("wb", bufsize)     #返回值stdin是wb二进制的,
            #stdout = chan.makefile("r", bufsize)
            #stderr = chan.makefile_stderr("r", bufsize)

    print(stdin)                    
                                    #返回的是一个对象,<paramiko.ChannelFile from <paramiko.Channel 0 (open) window=2097152 -> 
                                    #<paramiko.Transport at 0x3b3cd90 (cipher aes128-ctr, 128 bits) (active; 1 open channel(s))>>>
                                    #需要加read(),返回的是二进制需要decode解码
    print(stdout.read().decode("utf8"))
    print(stderr.read())

    ssh.close()

    #代码进入linux当中默认存在的目录就是用户登录进去的home的用户目录


二:每次执行ssh.exec_command命令。相当于打开了新的终端,所以多个命令一起运行需要写在一个函数里面,分号隔开,如下: 
    import paramiko

    #创建一个ssh对象--帮助操作linux的,linux必须要有ssh服务才能操作linux
    ssh =paramiko.SSHClient()                   #SSHClient是一个类,ssh对象

    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())   #设置连接方式,热键策略,密钥策略--paramiko.AutoAddPolicy自动添加的策略

    #ssh连接需要远程主机ip地址,端口号(ssh服务默认的端口号22),传输协议,在远程主机已经存在的用户名和密码
    ssh.connect("192.168.10.199",22,'worker1','123456')




    stdin,stdout,stderr=ssh.exec_command('cd ./Desktop;touch a.txt')     #cd ./Desktop再pwd,但是还在原来的/home/worker1目录--因为每次执行ssh.exec_command命令。相当于打开了新的终端
                                                    #一个函数传多个命令才能实现进入目录再进行其他操作,多个命令之间用分号进行分隔
            
    print(stdin)                    

    print(stdout.read().decode("utf8"))
    print(stderr.read())

    ssh.close()

    #代码进入linux当中默认存在的目录就是用户登录进去的home的用户目录

37:把本地文件传到linux,linux文件下载到本地,远程linux文件上传和下载

1:window文件传递给linux
    import paramiko    
    ssh =paramiko.SSHClient()

    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())


    ssh.connect("192.168.10.199",22,'worker1','123456')

    #传文件需要创建一个新的对象,如下sftp对象
    sftp = ssh.open_sftp()                                 #ftp文本传输协议

    #将本地文件传输到远程机器
    #第一个参数是本地路径,第二个参数是远程机器想要存放这个文件的路径
    sftp.put("C:\\Users\\ywt\\PycharmProjects\\pythonAdvanced\\day5\\11.py"
             ,"/home/worker1/Desktop/女神.py")

    ssh.close()
    
2:linux文件下载到winodws机器
    import paramiko
    ssh =paramiko.SSHClient()

    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect("192.168.10.199",22,'worker1','123456')

    #传文件需要创建一个新的对象,如下sftp对象
    sftp = ssh.open_sftp()                                 #ftp文本传输协议

    #将远程机器文件下载到本地
    #sftp.get,也接收两个参数1:远程机器文件路径  2:本地想要存放这个文件的路径
    sftp.get("/home/worker1/Desktop/a.txt",
             "C:\\Users\\ywt\\Desktop\\a.txt")
    ssh.close()

28:练习

    '''
    下载附件cfiles.zip(见附件cfiles.zip)

    解压该压缩包,里面 包含了两个文件。 一个叫 'gbk编码.txt',
    该文件是gbk编码的。
    另一个文件叫 'utf8编码.txt', 该文件是utf8编码的。
    两个文件里面的内容都包含中文。

    要求大家编写一个python程序,该程序做到以下2点

    1. 将两个文件内容读出, 合并内容到一个字符串中,
       并能用print语句将合并后的内容正确显示

    2. 然后,程序用中文提示用户“请输入 新文件的名称”,
       用户输入文件名可以包含中文
       将上面合并后的内容存储到一个新文件中,以utf8格式编码。
       新文件的文件名就是上面用户输入的名字。
    '''

    newstr=""
    pathgbk="./gbk编码.txt"
    pathutf8="./utf8编码.txt"
    with open(pathgbk,"r",encoding="gbk") as f:
        str1=f.read()
        newstr=newstr+str1

    with open(pathutf8,"r",encoding="utf8") as f:
        str2=f.read()
        newstr=newstr+str2
    print(newstr)

    file_name=input("请输入>>>")
    file_path=f"./{file_name}.txt"
    with open(file_path,"w",encoding="utf8") as f:
        f.write(newstr)
day2:
    '''
    #现已实现以下功能my. log、my. name. my shopping. _mall函数
    #要求编写装饰器,为my_log. my_ name. my. shoping mall 函数加上认证的功能要求登灵成功次,后续的面数都无需再输入户名和密码
    #账户名密码来自字典变量
    #补充知识: token
    #token的意思是“令牌”,是服务端生成的一串字符串(这里我们可以随意指定一个字符串), 作为客户端进行请求的一个标识
    #当用户第一次登录后,服务器生成一个token并将此token返回给客户端,以后客户端只需带上这个token来请求数据即可,无需再次带上用户名和密码
    user dict={ #存用户名和密码
        "user1": "123""user2": "123",
        }

    def my_log()
        print("this is my_ log')

    def my_name(name):
        print("欢迎登陆",name)

    def my_ shopping. mal):
        priny("购物商城")
    while True:
        choose_num=input("\n\n1:查看日志 \n2:我的信息 \n3:我的商城\n请输入操作选项(输入q退出)>>")
        if choose_num=="q" or choose_num=="Q":
            break
        elif choose_num=="1":
            my_log()
        elif choose_num == "2":
            my_name()
        elif choose_num=="3":
            my_
            shopping.mal()

    '''
    #需求:要求编写装饰器,为my_log. my_ name. my. shoping mall 函数加上认证的功能要求登录成功一次,后续的操作都无需再输入户名和密码
    #装饰器检查是否登录,如果未登录就登录,如果已登录就不需要登录  token一般存储到redis当中,也是数据库---非关系型数据库,键值对形式数据库,基于缓存的,速度很快
    user_dict={ #存用户名和密码
        "user1": "123",
        "user2": "123"
        }
    token=""                        #token默认为空

    def  auth(func):
        def inner(*args):       #有的函数传一个参数,有的被装饰的函数不传参数,使用不定长传参
            global token        #修改全局变量需要global一下
            if token:           #如果token还有效,直接调用原函数提供服务
                func(*args)
            else:               #如果token无效就登录,输入用户名和密码
                name=input("请输入用户名>>>").strip()
                psw=input("请输入密码>>>").strip()
                if name in user_dict and user_dict[name]==psw:     #如果用户名存在,键在字典当中,且密码正确--提供服务且设置token
                    func(*args)
                    token="10086"           #服务器设置token值
                else:   #如果用户名或密码输入错误
                    print("用户名或密码错误")
        return inner

    @auth
    def my_log():
        print("this is my_log")
    @auth
    def my_name(name):
        print("欢迎登陆",name)
    @auth
    def my_shopping_mal():
        print("购物商城")

    while True:
        choose_num=input("\n\n1:查看日志 \n2:我的信息 \n3:我的商城\n请输入操作选项(输入q退出)>>")
        if choose_num=="q" or choose_num=="Q":
            break
        elif choose_num=="1":
            my_log()
        elif choose_num == "2":
            my_name("张三")
        elif choose_num=="3":
            my_shopping_mal()
day3:
    '''
    先阅读下面关于Python requests 库的文章 ,了解 使用它去获取一个网页内容的方法。
    http://cn.python-requests.org/zh_CN/latest/user/quickstart.html
    然后编写一个python程序,创建两个子线程,分别到下面的网址获取文本内容
    http://mirrors.163.com/centos/6/isos/x86_64/README.txt
    http://mirrors.163.com/centos/7/isos/x86_64/0_README.txt
    主线程等待这个两个子线程获取到信息后,将其内容依次合并后存入名为 readme89.TXT 的文件中
    '''
    import threading
    import requests
    newstr=""           #初始化的字符串用来存储两个网页的内容
    lock=threading.Lock()       #上锁,两个线程对这个newstr数据操作,可能一个没写完另外一个又写,乱了,所以上锁

    def foo(url):
        global newstr
        lock.acquire()
        a=requests.get(url).text
        newstr= newstr + a
        lock.release()

    #创建两个线程
    t1=threading.Thread(target=foo,args=["http://mirrors.163.com/centos/6/isos/x86_64/README.txt"])
    t2=threading.Thread(target=foo,args=["http://mirrors.163.com/centos/6/isos/x86_64/README.txt"])

    t1.start()
    t2.start()
    t1.join()
    t2.join()
    with open("./readme89.txt","w",encoding="utf8") as f:
        f.write(newstr)
        
day4: 
    1:server服务器代码
        '''
        通过 socket 模块开发远程命令客户端
        需求:linux 运行服务端代码,本地运行客户端代码
        服务端代码启动后,客户端进行连接
        连接成功后,客户端向服务端发送命令
        服务端收到命令,执行, 
        客户端接收执行结果,并打印
        '''
        import subprocess           #调用外部程序执行命令
        import socket

        #获取soket实例
        server=socket.socket()
        server.bind(("127.0.0.1",13000))        #socket绑定ip地址和端口号
        server.listen()                         #开始监听

        while True:                     #一重循环监听多个客户端的请求
            print("等待客户端连接")
            #接收并建立与客户端的连接
            conn,addr=server.accept()
            print("新连接:",addr)
            while True:                 #二重循环开始一对一的聊天了
                data=conn.recv(1024).decode("utf8")
                if not data:
                    print("客户端断开了....")
                    conn.close()
                    break
                print("收到的命令",data)
                res=subprocess.Popen(data,shell=True,stdout=subprocess.PIPE).stdout.read()         #这里read的数就是bytes类型    subprocess-相当于cmd里面执行命令
                if len(res) ==0:
                    res="命令执行成功,没有输出".encode("utf8")
                else:       #发送数据之前,先告诉客户端要发送多少数据给他,res长度是int类型,int转成str再转成bytes
                    conn.sendall(str(len(res)).encode("utf8"))      #告诉发的长度,然后循避免粘包,等待客户端响应
                    conn.recv(1024)                                 #接收数据

                    conn.sendall(res)                                #发送数据,sendall这里没有分成1024发送,sendall会把数据循环发送直到把全部数据发完
                                                                    #发送一次都发过去没有问题,但是接收1024,不知道发过来是多少,服务器可以把全部的
                                                                    #都发过来,但是客户端不知道服务发送的数据多大,有可能1g,2g,3g,所以这种情况下默认
                                                                    #全盘接受数据,对客户端的系统是很严峻的考验,是不安全的,所以指定1024接受
                                                                    

    2:client客户端的代码
        import socket

        client=socket.socket()

        client.connect(("127.0.0.1",13000))      #连接服务器

        while True:
            msg=input("请输入>>>").strip()
            if len(msg) ==0:
                continue        #如果没有数据,不做任何操作,结束这次循环
            client.sendall(msg.encode("utf8"))    #如果有数据发过去,命令发给服务端

            #接收这条命令的执行结果的大小
            res_return_size=client.recv(1024)
            total_recv_size=int(res_return_size.decode("utf8"))
            cmd_res=b""
            client.sendall(b"ok")   #收到数据后发送ok

            while total_recv_size>0:
                data=client.recv(1024)
                cmd_res=cmd_res+data
                total_recv_size=total_recv_size-1024
            else:
                print("数据收完了")
                print(cmd_res.decode("gbk"))        #windows cmd执行的命令是gbk的编码

 

posted @ 2021-03-02 14:58  至高无上10086  阅读(308)  评论(0编辑  收藏  举报