zoukankan      html  css  js  c++  java
  • python常用模块

    1.1 时间模块time() 与 datetime()

      1、 time()模块中的重要函数

    函数

    描述

    asctime([tuple])

    将时间元组转换为字符串

    localtime([secs])

    将秒数转换为日期元组(转换成本国时区而不是utc时区)

    mktime(tuple)

    将时间元组转换为本地时间

    sleep(secs)

    休眠(不做任何事情)secs

    strptime(string[, format])

    将字符串解析为时间元组

    time()

    获取当前时间戳

    time.gmtime()

    将时间转换成utc格式的元组格式

       2、time()模块时间格式转换

                

      3、time()模块时间转换

        1. 时间戳               1970年1月1日之后的秒,     即:time.time()

        2. 格式化的字符串    2014-11-11 11:11,           即:time.strftime('%Y-%m-%d')

        3. 结构化时间          元组包含了:年、日、星期等... time.struct_time    即:time.localtime()

    import time
    print(time.time())                              # 时间戳:1511166937.2178104
    print(time.strftime('%Y-%m-%d'))                 # 格式化的字符串: 2017-11-20
    print(time.localtime())                         # 结构化时间(元组): (tm_year=2017, tm_mon=11...)
    print(time.gmtime())                            # 将时间转换成utc格式的元组格式: (tm_year=2017, tm_mon=11...)
    
    #1. 将结构化时间转换成时间戳: 1511167004.0
    print(time.mktime(time.localtime()))
    
    #2. 将格字符串时间转换成结构化时间 元组: (tm_year=2017, tm_mon=11...)
    print(time.strptime('2014-11-11', '%Y-%m-%d'))
    
    #3. 结构化时间(元组) 转换成  字符串时间  :2017-11-20
    print(time.strftime('%Y-%m-%d', time.localtime()))  # 默认当前时间
    
    #4. 将结构化时间(元组) 转换成英文字符串时间 : Mon Nov 20 16:51:28 2017
    print(time.asctime(time.localtime()))
    
    #5. 将时间戳转成 英文字符串时间 : Mon Nov 20 16:51:28 2017
    print(time.ctime(time.time()))
    time()模块时间转换

      4、ctime和asctime区别

              1)ctime传入的是以秒计时的时间戳转换成格式化时间

              2)asctime传入的是时间元组转换成格式化时间

    import time
    t1 = time.time()
    print(t1)               #1483495728.4734166
    print(time.ctime(t1))   #Wed Jan  4 10:08:48 2017
    t2 = time.localtime()
    print(t2)               #time.struct_time(tm_year=2017, tm_mon=1, tm_mday=4, tm_hour=10, print(time.asctime(t2)) #Wed Jan  4 10:08:48 2017
    ctime和asctime区别

       5、datetime

    import datetime
    #1、datetime.datetime获取当前时间
    print(datetime.datetime.now())
    #2、获取三天后的时间
    print(datetime.datetime.now()+datetime.timedelta(+3))
    #3、获取三天前的时间
    print(datetime.datetime.now()+datetime.timedelta(-3))
    #4、获取三个小时后的时间
    print(datetime.datetime.now()+datetime.timedelta(hours=3))
    #5、获取三分钟以前的时间
    print(datetime.datetime.now()+datetime.timedelta(minutes = -3))
    
    import datetime
    print(datetime.datetime.now())                                   #2017-08-18 11:25:52.618873
    print(datetime.datetime.now().date())                            #2017-08-18
    print(datetime.datetime.now().strftime("%Y-%m-%d %H-%M-%S"))    #2017-08-18 11-25-52
    datetime获取时间
    #1、datetime对象与str转化
    # datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    '2018-03-09 10:08:50'
    
    # datetime.datetime.strptime('2016-02-22',"%Y-%m-%d")
    datetime.datetime(2016, 2, 22, 0, 0)
    
    #2、datetime对象转时间元组
    # datetime.datetime.now().timetuple()
    time.struct_time(tm_year=2018, tm_mon=3, tm_mday=9,
    
    #3、时间戳转换成datetime对象
    # datetime.datetime.fromtimestamp(1520561646.8906238)
    datetime.datetime(2018, 3, 9, 10, 14, 6, 890624)
    datetime时间转换

      6、本地时间与utc时间相互转换

    # -*- coding: utf-8 -*-
    
    import time
    import datetime
    
    def utc2local(utc_st):
        ''' 作用:将UTC时间装换成本地时间
        :param utc_st: 传入的是utc时间(datatime对象)
        :return:  返回的是本地时间 datetime 对象
        '''
        now_stamp = time.time()
        local_time = datetime.datetime.fromtimestamp(now_stamp)
        utc_time = datetime.datetime.utcfromtimestamp(now_stamp)
        offset = local_time - utc_time
        local_st = utc_st + offset
        return local_st
    
    def local2utc(local_st):
        ''' 作用:将本地时间转换成UTC时间
        :param local_st: 传入的是本地时间(datatime对象)
        :return: 返回的是utc时间 datetime 对象
        '''
        time_struct = time.mktime(local_st.timetuple())
        utc_st = datetime.datetime.utcfromtimestamp(time_struct)
        return utc_st
    
    utc_time = datetime.datetime.utcfromtimestamp(time.time())
    # utc_time = datetime.datetime(2018, 5, 6, 5, 57, 9, 511870)        # 比北京时间晚了8个小时
    local_time = datetime.datetime.now()
    # local_time = datetime.datetime(2018, 5, 6, 13, 59, 27, 120771)    # 北京本地时间
    
    utc_to_local = utc2local(utc_time)
    local_to_utc = local2utc(local_time)
    print utc_to_local       # 2018-05-06 14:02:30.650270     已经转换成了北京本地时间
    print local_to_utc       # 2018-05-06 06:02:30            转换成北京当地时间
    本地时间与utc时间相互转换
    from django.utils import timezone
    from datetime import datetime
    
    utc_time = timezone.now()
    local_time = datetime.now()
    
    #1、utc时间装换成本地时间
    utc_to_local = timezone.localtime(timezone.now())
    
    #2、本地时间装utc时间
    local_to_utc = timezone.make_aware(datetime.now(), timezone.get_current_timezone())
    django的timezone时间与本地时间转换

      7、Python计算两个日期之间天数

    import datetime
    d1 = datetime.datetime(2018,10,31)   # 第一个日期
    d2 = datetime.datetime(2019,2,2)     # 第二个日期
    interval = d2 - d1                   # 两日期差距
    print(interval.days)                 # 具体的天数
    Python计算两个日期之间天数

    1.2 random()模块

       1、random()模块常用函数

    函数

    描述

    random()

    返回0<n<=1

    getrandbits(n)

    以长整形形式返回n个随机位

    uniform(a, b)

    返回随机实数n,其中a<=n<=b

    randrange([start], stop, [step])

    返回range(start,stop,step)中的随机数

    choice(seq)

    从序列seq中返回随意元素

    shuffle(seq[, random])

    原地指定序列seq(将有序列表变成无序的:洗牌)

    sample(sea, n)

    从序列seq中选择n个随机且独立的元素

        2、random常用函数举例

    import random
    #⒈ 随机整数:
    print(random.randint(0,99))             # 随机选取0-99之间的整数
    print(random.randrange(0, 101, 2))      # 随机选取0-101之间的偶数
    
    #⒉ 随机浮点数:
    print(random.random())                   # 0.972654134347
    print(random.uniform(1, 10))             # 4.14709813772
    
    #⒊ 随机字符:
    print(random.choice('abcdefg'))         # c
    print(random.sample('abcdefghij',3))    # ['j', 'f', 'c']
    random()函数使用举例

      3、使用random实现四位验证码

    import random
    checkcode = ''
    for i in range(4):
        current = random.randrange(0,4)
        if current == i:
            tmp = chr(random.randint(65,90))    #65,90表示所有大写字母
        else:
            tmp = random.randint(0,9)
        checkcode += str(tmp)
    print(checkcode)                            #运行结果: 851K
    使用for循环实现
    import random
    import string
    str_source = string.ascii_letters + string.digits
    str_list = random.sample(str_source,7)
    
    #['i', 'Q', 'U', 'u', 'A', '0', '9']
    print(str_list)
    str_final = ''.join(str_list)
    
    #iQUuA09
    print(str_final)            # 运行结果: jkFU2Ed
    使用random.sample实现
    >>> string.digits
    '0123456789'
    >>> string.ascii_lowercase
    'abcdefghijklmnopqrstuvwxyz'

    1.3 os模块

    import os
    #1 当前工作目录,即当前python脚本工作的目录路径
    print(os.getcwd())    # C:UsersadminPycharmProjectss14Day5	est4
    
    #2 当前脚本工作目录;相当于shell下cd
    os.chdir("C:\Users\admin\PycharmProjects\s14")
    os.chdir(r"C:UsersadminPycharmProjectss14")
    print(os.getcwd())    # C:UsersadminPycharmProjectss14
    
    #3 返回当前目录: ('.')
    print(os.curdir)        # ('.')
    
    #4 获取当前目录的父目录字符串名:('..')
    print(os.pardir)        # ('..')
    
    #5 可生成多层递归目录
    os.makedirs(r'C:aaabb')         # 可以发现在C盘创建了文件夹/aaa/bbb
    
    #6 若目录为空,则删除,并递归到上一级目录,如若也为空,则删除,依此类推
    os.removedirs(r'C:aaabb')    # 删除所有空目录
    
    #7 生成单级目录;相当于shell中mkdir dirname
    os.mkdir(r'C:bb')        # 仅能创建单个目录
    
    #8 删除单级空目录,若目录不为空则无法删除,报错;相当于shell中rmdir dirname
    os.rmdir(r'C:aaa')        # 仅删除指定的一个空目录
    
    #9 列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印
    print(os.listdir(r"C:UsersadminPycharmProjectss14"))
    
    #10 删除一个文件
    os.remove(r'C:bb	est.txt')        # 指定删除test.txt文件
    
    #11 重命名文件/目录
    os.rename(r'C:bb	est.txt',r'C:bb	est00.bak')
    
    #12 获取文件/目录信息
    print(os.stat(r'C:bb	est.txt'))
    
    #13 输出操作系统特定的路径分隔符,win下为"\",Linux下为"/"
    print(os.sep)                # 
    
    #14 输出当前平台使用的行终止符,win下为"
    ",Linux下为"
    "
    print(os.linesep)
    
    #15 输出用于分割文件路径的字符串
    print(os.pathsep)                # ;  (分号)
    
    #16 输出字符串指示当前使用平台。win->'nt'; Linux->'posix'
    print(os.name)                # nt
    
    #17 运行shell命令,直接显示
    os.system("bash command")
    
    #18 获取系统环境变量
    print(os.environ)                # environ({'OS': 'Windows_NT', 'PUBLIC': ………….
    
    #19 返回path规范化的绝对路径
    print(os.path.abspath(r'C:/bbb/test.txt'))    # C:bb	est.txt
    
    #20 将path分割成目录和文件名二元组返回
    print(os.path.split(r'C:/bbb/ccc'))    # ('C:/bbb', 'ccc')
    
    #21 返回path的目录。其实就是os.path.split(path)的第一个元素
    print(os.path.dirname(r'C:/bbb/ccc'))    # C:/bbb
    
    #22 返回path最后的文件名。如何path以/或结尾,那么就会返回空值。即os.path.split(path)的第二个元素
    print(os.path.basename(r'C:/bbb/ccc/ddd'))    # ddd
    
    #23 如果path存在,返回True;如果path不存在,返回False
    print(os.path.exists(r'C:/bbb/ccc/'))    # True
    
    #24 如果path是绝对路径,返回True        # True
    print(os.path.isabs(r"C:UsersadminPycharmProjectss14Day5	est4"))
    
    #25 如果path是一个存在的文件,返回True。否则返回False
    print(os.path.isfile(r'C:/bbb/ccc/test2.txt'))        # True
    
    #26 如果path是一个存在的目录,则返回True。否则返回False
    print(os.path.isdir(r'C:/bbb/ccc'))            # True
    
    #28 返回path所指向的文件或者目录的最后存取时间
    print(os.path.getatime(r'C:/bbb/ccc/test2.txt'))        # 1483509254.9647143
    
    #29 返回path所指向的文件或者目录的最后修改时间
    print(os.path.getmtime(r'C:/bbb/ccc/test2.txt'))        # 1483510068.746478
    
    #30 无论linux还是windows,拼接出文件路径
    put_filename = '%s%s%s'%(self.home,os. path.sep, filename)
    #C:UsersadminPycharmProjectss14day10select版FTPhome
    os模块常用方法
    import os
    
    os.makedirs('C:/aaa/bbb/ccc/ddd',exist_ok=True)         # exist_ok=True:如果存在当前文件夹不报错
    path = os.path.join('C:/aaa/bbb/ccc','ddd',)
    f_path = os.path.join(path,'file.txt')
    
    with open(f_path,'w',encoding='utf8') as f:
        f.write('are you ok!!')
    os命令创建文件夹: C:/aaa/bbb/ccc/ddd并写入文件file1.txt
    import os,sys
    print(os.path.dirname( os.path.dirname( os.path.abspath(__file__) ) ))
    BASE_DIR = os.path.dirname( os.path.dirname( os.path.abspath(__file__) ) )
    sys.path.append(BASE_DIR)
    
    # 代码解释:
    # 要想导入其他目录中的函数,其实就是将其他目录的绝对路径动态的添加到pyhton的环境变量中,这样python解释器就能够在运行时找到导入的模块而不报错:
    # 然后调用sys模块sys.path.append(BASE_DIR)就可以将这条路径添加到python环境变量中
    将其他目录的绝对路径动态的添加到pyhton的环境变量中

      1、os.popen获取脚本执行结果

    data = {'name':'aaa'}
    import json
    print json.dumps(data)
    data.py
    #! /usr/bin/env python
    # -*- coding: utf-8 -*-
    import os,json
    
    ret = os.popen('python data.py')
    data = ret.read().strip()
    ret.close()
    data = json.loads(data)
    print data  # {'name':'aaa'}
    get_data.py

    1.4 sys模块

      1、 sys基本方法

        sys.argv          返回执行脚本传入的参数

        sys.exit(n)                       退出程序,正常退出时exit(0)

        sys.version                           获取Python解释程序的版本信息

        sys.maxint                            最大的Int值

        sys.path                                返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值

        sys.platform                           返回操作系统平台名称

        sys.stdout.write('please:')

        val = sys.stdin.readline()[:-1]

      2、使用sys返回运行脚本参数

    import sys
    # C:Users	omPycharmProjectss14Reviewday01>  python test01.py 1 2 3
    print(sys.argv)         # 打印所有参数                 ['test01.py', '1', '2', '3']
    print(sys.argv[1:])     # 获取索引 1 往后的所有参数     ['1', '2', '3']
    sys.argv返回执行脚本传入的参数

    1.5 tarfile用于将文件夹归档成 .tar的文件

    import tarfile
    # 将文件夹Day1和Day2归档成your.rar并且在归档文件夹中Day1和Day2分别变成bbs2.zip和ccdb.zip的压缩文件
    tar = tarfile.open('your.tar','w')
    tar.add(r'C:UsersadminPycharmProjectss14Day1', arcname='bbs2.zip')
    tar.add(r'C:UsersadminPycharmProjectss14Day2', arcname='cmdb.zip')
    tar.close()
    
    # 将刚刚的归档文件your.tar进行解压解压的内容是bbs2.zip和cmdb.zip压缩文件而不是变成原有的文件夹
    tar = tarfile.open('your.tar','r')
    tar.extractall()  # 可设置解压地址
    tar.close()
    tarfile使用

    1.6 shutil 创建压缩包,复制,移动文件

      注 :   shutil 对压缩包的处理是调用 ZipFile 和 TarFile 两个模块来进行的
        作用: shutil 创建压缩包并返回文件路径(如:zip、tar),并且可以复制文件,移动文件

    import shutil
    #1 copyfileobj()  将文件test11.txt中的内容复制到test22.txt文件中
    f1 = open("test11.txt",encoding="utf-8")
    f2 = open("test22.txt",'w',encoding="utf-8")
    shutil.copyfileobj(f1,f2)
    
    #2  copyfile()  直接指定文件名就可进行复制
    shutil.copyfile("test11.txt",'test33.txt')
    
    #3  shutil.copymode(src, dst) 仅拷贝权限。内容、组、用户均不变
    
    #4  shutil.copystat(src, dst)  拷贝状态的信息,包括:mode bits, atime, mtime, flags
    shutil.copystat('test11.txt','test44.txt')
    
    #5  递归的去拷贝目录中的所有目录和文件,这里的test_dir是一个文件夹,包含多级文件夹和文件
    shutil.copytree("test_dir","new_test_dir")
    
    #6  递归的去删除目录中的所有目录和文件,这里的test_dir是一个文件夹,包含多级文件夹和文件
    shutil.rmtree("test_dir")
    
    #7 shutil.move(src, dst)  递归的去移动文件
    shutil.move('os_test.py',r'C:\')
    
    #8  shutil.make_archive(base_name, format,...) 创建压缩包并返回文件路径,例如:zip、tar
    '''
       1. base_name: 压缩包的文件名,也可以是压缩包的路径。只是文件名时,则保存至当前目录,否则保存至指定路径,
            如:www                        =>保存至当前路径
            如:/Users/wupeiqi/www =>保存至/Users/wupeiqi/
       2.  format: 压缩包种类,“zip”, “tar”, “bztar”,“gztar”
       3. root_dir: 要压缩的文件夹路径(默认当前目录)
       4. owner: 用户,默认当前用户
       5. group: 组,默认当前组
       6. logger: 用于记录日志,通常是logging.Logger对象
    
    '''
    #将C:UsersadminPycharmProjectss14Day4 的文件夹压缩成 testaa.zip
    shutil.make_archive("testaa","zip",r"C:UsersadminPycharmProjectss14Day4")
    shutil使用

    1.7 zipfile将文件或文件夹进行压缩

    import zipfile
    #将文件main.py和test11.py压缩成day5.zip的压缩文件
    z = zipfile.ZipFile('day5.zip', 'w')
    z.write('main.py')
    z.write("test11.txt")
    z.close()
    
    #将刚刚压缩的day5.zip文件进行解压成原文件
    z = zipfile.ZipFile('day5.zip', 'r')
    z.extractall()
    z.close()
    zipfile使用

    1.8 shelve 模块

      作用:shelve模块是一个简单的k,v将内存数据通过文件持久化的模块,可以持久化任何pickle可支持的python数据格式

    import shelve
    import datetime
    #1 首先使用shelve将.py中定义的字典列表等读取到指定文件shelve_test中,其实我们可不必关心在文件中是怎样存储的
    d = shelve.open('shelve_test')   #打开一个文件
    info = {"age":22,"job":"it"}
    name = ["alex","rain","test"]
    d["name"] = name                  #持久化列表
    d["info"] = info
    d["date"] = datetime.datetime.now()
    d.close()
    
    #2 在这里我们可以将刚刚读取到 shelve_test文件中的内容从新获取出来
    d = shelve.open('shelve_test')   # 打开一个文件
    print(d.get("name"))             # ['alex', 'rain', 'test']
    print(d.get("info"))             # {'job': 'it', 'age': 22}
    print(d.get("date"))             # 2017-11-20 17:54:21.223410
    shelve持久化

    1.9 json和pickle序列化

      1、json序列化

        1. 序列化 (json.dumps) :是将内存中的对象存储到硬盘,变成字符串

        2. 反序列化(json.loads) : 将刚刚保存在硬盘中的内存对象从新加载到内存中

        json.dumps( data,ensure_ascii=False, indent=4)

    #json序列化代码
    import json
    info = {
        'name':"tom",
        "age" :"100"
    }
    f = open("test.txt",'w')
    # print(json.dumps(info))
    f.write(json.dumps(info))
    f.close()
    json序列化
    #json反序列化代码
    import json
    f = open("test.txt","r")
    data = json.loads(f.read())
    f.close()
    print(data["age"])
    json反序列化

      2、pickle序列化

        1. python的pickle模块实现了python的所有数据序列和反序列化。基本上功能使用和JSON模块没有太大区别,方法也同样是dumps/dump和loads/load

        2. 与JSON不同的是pickle不是用于多种语言间的数据传输,它仅作为python对象的持久化或者python程序间进行互相传输对象的方法,因此它支持了python所有的数据类型。

    #pickle序列化代码
    import pickle
    info = {
        'name':"tom",
        "age" :"100"
    }
    f = open("test.txt",'wb')
    f.write(pickle.dumps(info))
    f.close()
    pickle序列化
    #pickle反序列化代码
    import pickle
    f = open("test.txt","rb")
    data = pickle.loads(f.read())
    f.close()
    print(data["age"])
    pickle反序列化

      3、解决JSON不可以序列化datetime类型

    import json,datetime
    
    class JsonCustomEncoder(json.JSONEncoder):
        def default(self, field):
            if isinstance(field, datetime.datetime):
                return field.strftime('%Y-%m-%d %H:%M:%S')
            elif isinstance(field, datetime.date):
                return field.strftime('%Y-%m-%d')
            else:
                return json.JSONEncoder.default(self, field)
    
    t = datetime.datetime.now()
    
    print(type(t),t)
    f = open('ttt','w')                              #指定将内容写入到ttt文件中
    f.write(json.dumps(t,cls=JsonCustomEncoder))      #使用时候只要在json.dumps增加个cls参数即可
    解决json无法序列化时间格式

        4、JSON和pickle模块的区别

        1. JSON只能处理基本数据类型。pickle能处理所有Python的数据类型。

        2. JSON用于各种语言之间的字符转换。pickle用于Python程序对象的持久化或者Python程序间对象网络传输,但不同版本的Python序列化可能还有差异

    1.10 hashlib 模块

      1、用于加密相关的操作,代替了md5模块和sha模块,主要提供 SHA1, SHA224, SHA256, SHA384, SHA512 ,MD5 算法

    import hashlib
    
    #1 ######## md5 ########
    # 目的:实现对b"HelloIt's me" 这句话进行md5加密
    m = hashlib.md5()            # 1)生成一个md5加密对象
    m.update(b"Hello")          # 2)使用m对 b"Hello" 加密
    m.update(b"It's me")        # 3) 使用m对 b"It's me"加密
    print(m.hexdigest())         # 4) 最终加密结果就是对b"HelloIt's me"加密的md5值:5ddeb47b2f925ad0bf249c52e342728a
    
    #2 ######## sha1 ########
    hash = hashlib.sha1()
    hash.update(b'admin')
    print(hash.hexdigest())
    
    #3 ######## sha256 ########
    hash = hashlib.sha256()
    hash.update(b'admin')
    print(hash.hexdigest())
    
    #4 ######## sha384 ########
    hash = hashlib.sha384()
    hash.update(b'admin')
    print(hash.hexdigest())
    
    #5 ######## sha512 ########
    hash = hashlib.sha512()
    hash.update(b'admin')
    print(hash.hexdigest())
    五种简单加密方式

      2、以上加密算法虽然依然非常厉害,但时候存在缺陷,即:通过撞库可以反解。所以,有必要对加密算法中添加自定义key再来做加密。

    ######### hmac ########
    import hmac
    h = hmac.new(b"123456","真实要传的内容".encode(encoding="utf-8"))
    print(h.digest())
    print(h.hexdigest())
    # 注:hmac是一种双重加密方法,前面是加密的内容,后面才是真实要传的数据信息
    hmac添加自定义key加密

    1.11 subprocess 模块

       1、subprocess原理以及常用的封装函数

        1.  运行python的时候,我们都是在创建并运行一个进程。像Linux进程那样,一个进程可以fork一个子进程,并让这个子进程exec另外一个程序

        2. 在Python中,我们通过标准库中的subprocess包来fork一个子进程,并运行一个外部的程序。

        3. subprocess包中定义有数个创建子进程的函数,这些函数分别以不同的方式创建子进程,所以我们可以根据需要来从中选取一个使用

        4.  另外subprocess还提供了一些管理标准流(standard stream)和管道(pipe)的工具,从而在进程间使用文本通信。

    #1、返回执行状态:0 执行成功
    retcode = subprocess.call(['ping', 'www.baidu.com', '-c5'])
    
    #2、返回执行状态:0 执行成功,否则抛异常
    subprocess.check_call(["ls", "-l"])
    
    #3、执行结果为元组:第1个元素是执行状态,第2个是命令结果
    >>> ret = subprocess.getstatusoutput('pwd')
    >>> ret
    (0, '/test01')
    
    #4、返回结果为 字符串 类型
    >>> ret = subprocess.getoutput('ls -a')
    >>> ret
    '.
    ..
    test.py'
    
    
    #5、返回结果为'bytes'类型
    >>> res=subprocess.check_output(['ls','-l'])
    >>> res.decode('utf8')
    '总用量 4
    -rwxrwxrwx. 1 root root 334 11月 21 09:02 test.py
    '
    subprocess常用函数
    subprocess.check_output(['chmod', '+x', filepath])
    subprocess.check_output(['dos2unix', filepath])
    将dos格式文件转换成unix格式

      2、subprocess.Popen()

        1. 实际上,上面的几个函数都是基于Popen()的封装(wrapper),这些封装的目的在于让我们容易使用子进程

        2. 当我们想要更个性化我们的需求的时候,就要转向Popen类,该类生成的对象用来代表子进程

        3. 与上面的封装不同,Popen对象创建后,主程序不会自动等待子进程完成。我们必须调用对象的wait()方法,父进程才会等待 (也就是阻塞block)

        4. 从运行结果中看到,父进程在开启子进程之后并没有等待child的完成,而是直接运行print。

    #1、先打印'parent process'不等待child的完成
    import subprocess
    child = subprocess.Popen(['ping','-c','4','www.baidu.com'])
    print('parent process')
    
    #2、后打印'parent process'等待child的完成
    import subprocess
    child = subprocess.Popen('ping -c4 www.baidu.com',shell=True)
    child.wait()
    print('parent process')
    child.wait()等待子进程执行

        child.poll()                                 # 检查子进程状态
        child.kill()                                  # 终止子进程
        child.send_signal()                   # 向子进程发送信号
        child.terminate()                       # 终止子进程

       3、subprocess.PIPE 将多个子进程的输入和输出连接在一起

        1. subprocess.PIPE实际上为文本流提供一个缓存区。child1的stdout将文本输出到缓存区,随后child2的stdin从该PIPE中将文本读取走

        2. child2的输出文本也被存放在PIPE中,直到communicate()方法从PIPE中读取出PIPE中的文本。

        3. 注意:communicate()是Popen对象的一个方法,该方法会阻塞父进程,直到子进程完成

    import subprocess
    #下面执行命令等价于: cat /etc/passwd | grep root
    child1 = subprocess.Popen(["cat","/etc/passwd"], stdout=subprocess.PIPE)
    child2 = subprocess.Popen(["grep","root"],stdin=child1.stdout, stdout=subprocess.PIPE)
    out = child2.communicate()               #返回执行结果是元组
    print(out)
    #执行结果: (b'root:x:0:0:root:/root:/bin/bash
    operator:x:11:0:operator:/root:/sbin/nologin
    ', None)
    分步执行cat /etc/passwd | grep root命
    import subprocess
    
    list_tmp = []
    def main():
        p = subprocess.Popen(['ping', 'www.baidu.com', '-c5'], stdin = subprocess.PIPE, stdout = subprocess.PIPE)
        while subprocess.Popen.poll(p) == None:
            r = p.stdout.readline().strip().decode('utf-8')
            if r:
                # print(r)
                v = p.stdout.read().strip().decode('utf-8')
                list_tmp.append(v)
    main()
    print(list_tmp[0])
    获取ping命令执行结果 

    1.12 logging模块

      1、logging模块的作用

        1. 很多程序都有记录日志的需求,并且日志中包含的信息即有正常的程序访问日志,还可能有错误、警告等信息输出

        2. python的logging模块提供了标准的日志接口,你可以通过它存储各种格式的日志

        3. logging的日志可以分为 debug()info()warning()error() and critical() 5个级别

    Level

    When it’s used

    DEBUG

    Detailed information, typically of interest only when diagnosing problems.

    INFO

    Confirmation that things are working as expected.

    WARNING

    An indication that something unexpected happened, or indicative of some problem in the near future (e.g. ‘disk space low’). The software is still working as expected.

    ERROR

    Due to a more serious problem, the software has not been able to perform some function.

    CRITICAL

    A serious error, indicating that the program itself may be unable to continue running.

      2、日志格式

    %(name)s

    Logger的名字

    %(levelno)s

    数字形式的日志级别

    %(levelname)s

    文本形式的日志级别

    %(pathname)s

    调用日志输出函数的模块的完整路径名,可能没有

    %(filename)s

    调用日志输出函数的模块的文件名

    %(module)s

    调用日志输出函数的模块名

    %(funcName)s

    调用日志输出函数的函数名

    %(lineno)d

    调用日志输出函数的语句所在的代码行

    %(created)f

    当前时间,用UNIX标准的表示时间的浮 点数表示

    %(relativeCreated)d

    输出日志信息时的,自Logger创建以 来的毫秒数

    %(asctime)s

    字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒

    %(thread)d

    线程ID。可能没有

    %(threadName)s

    线程名。可能没有

    %(process)d

    进程ID。可能没有

    %(message)s

    用户输出的消息

      3、打印日志和将日志写入文件小例子

    import logging
    #1 将日志信息打印到屏幕中
    logging.debug('debug info')
    logging.info('info')
    logging.warning('warning info')
    logging.error('error info')
    logging.critical('critical info')
    将日志打印到屏幕
    import logging
    logging.basicConfig(filename='test.log',level=logging.INFO,format='%(levelname)s  %(asctime)s %(message)s',datefmt='%Y-%m-%d %H:%M-%S')
    logging.info('I am info')
    # 记录的文件的日志格式:   INFO  2017-11-26 12:54-04 I am info
    将日志记录到文件

      4、同时把log打印在屏幕和文件日志里

        1. Python 使用logging模块记录日志涉及四个主要类

          1. logger提供了应用程序可以直接使用的接口;

          2. handler将(logger创建的)日志记录发送到合适的目的输出;

          3. filter提供了细度设备来决定输出哪条日志记录;

          4. formatter决定日志记录的最终输出格式

        2. 第一个主要类:logger

          1. 每个程序在输出信息之前都要获得一个Logger。

          2. Logger通常对应了程序的模块名,比如聊天工具的图形界面模块可以这样获得它的Logger:LOG=logging.getLogger(”chat.gui”)

          3. 而核心模块可以这样:LOG=logging.getLogger(”chat.kernel”)

          4. Logger.setLevel(lel):指定最低的日志级别,低于lel的级别将被忽略。debug是最低的内置级别,critical为最高

          5. Logger.addFilter(filt)、Logger.removeFilter(filt):添加或删除指定的filter

          6. Logger.addHandler(hdlr)、Logger.removeHandler(hdlr):增加或删除指定的handler

          7. Logger.debug()、Logger.info()、Logger.warning()、Logger.error()、Logger.critical():可以设置的日志级别

        3. 第二个主要类:handler

          1. handler对象负责发送相关的信息到指定目的地,Python的日志系统有多种Handler可以使用。

          2. Handler可以把信息输出到控制台,有些Logger可以把信息输出到文件,还有些 Handler可以把信息发送到网络上

          3. 如果觉得不够用,还可以编写自己的Handler,可以通过addHandler()方法添加多个多handler

            1Handler.setLevel(lel):指定被处理的信息级别,低于lel级别的信息将被忽略

            2Handler.setFormatter():给这个handler选择一个格式

            3Handler.addFilter(filt)、Handler.removeFilter(filt):新增或删除一个filter对象

        4. 每个Logger可以附加多个Handler,这里介绍一些常用的

          1. logging.StreamHandler

            1. 使用这个Handler可以向类似与sys.stdout或者sys.stderr的任何文件对象(file object)输出信息。

            2. 它的构造函数是: StreamHandler([strm]) 其中strm参数是一个文件对象。默认是sys.stderr

          2. logging.FileHandler

            1. 和StreamHandler类似,用于向一个文件输出日志信息,不过FileHandler会帮你打开这个文件

            2. 它的构造函数是:

                                       ① FileHandler(filename[,mode])

                                       ② filename是文件名,必须指定一个文件名

                                       ③ mode是文件的打开方式。参见Python内置函数open()的用法。默认是’a',即添加到文件末尾。

          3. logging.handlers.RotatingFileHandler

            1. 这个Handler类似于上面的FileHandler,但是它可以管理文件大小。

            2. 当文件达到一定大小之后,它会自动将当前日志文件改名,然后创建 一个新的同名日志文件继续输出

            3. 比如日志文件是chat.log。当chat.log达到指定的大小之后,RotatingFileHandler自动把文件改名为chat.log.1

            4. 不过,如果chat.log.1已经存在,会先把chat.log.1重命名为chat.log.2。。。最后重新创建 chat.log,继续输出日志信息

            5. 它的构造函数是:RotatingFileHandler( filename[, mode[, maxBytes[, backupCount]]])

                                       其中filename和mode两个参数和FileHandler一样

                                       maxBytes用于指定日志文件的最大文件大小,如果maxBytes为0,意味着日志文件可以无限大这时上面描述的重命名过程就不会发生

                                       backupCount用于指定保留的备份文件的个数。比如,如果指定为2,当上面描述的重命名过程发生时,原有的chat.log.2并不会被更名,而是被删除。

          4. logging.handlers.TimedRotatingFileHandler

            1. 这个Handler和RotatingFileHandler类似,不过,它没有通过判断文件大小来决定何时重新创建日志文件,而是间隔一定时间就 自动创建新的日志文件。

            2. 过新的文件不是附加数字,而是当前时间。它的构造函数是:

                                       TimedRotatingFileHandler( filename [,when [,interval [,backupCount]]])

                                       其中filename参数和backupCount参数和RotatingFileHandler具有相同的意义。

                                        interval是时间间隔。

                                       when参数是一个字符串。表示时间间隔的单位,不区分大小写。它有以下取值:

                                 注: S 秒;M 分;H 小时;D 天;W 每星期(interval==0时代表星期一);midnight 每天

    import logging
    #1、 create logger
    logger1 = logging.getLogger('TEST-LOG')      #这里的logger1是创建的logger实例名称
    logger1.setLevel(logging.DEBUG)                #这里设置只有在DEBUG及其以上级别才会记录
    
    #2、 create console handler and set level to debug
    ch = logging.StreamHandler()               #StreamHandler括号内无参数表示默认是sys.stderr
    ch.setLevel(logging.DEBUG)                   #指定只有在DEBUG级别及以上的才会打印到屏幕
    
    #3、 create file handler and set level to warning
    fh = logging.FileHandler("access.log",encoding="utf-8")    #与StreamHandler类似但是必须指定记录日志文件名
    fh.setLevel(logging.WARNING)                                 #指定在WARNING级别及以上的才会记录到文件中
    
    #4、 create formatter                    #指定记录日志的格式
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    
    #5、 add formatter to ch and fh
    ch.setFormatter(formatter)                #ch为刚刚创建的StreamHandler方法这里指定使用formatter的格式
    fh.setFormatter(formatter)                #fh为刚刚创建的FileHandler方发使用刚创建的formatter日志格式
    
    #6、 add ch and fh to logger
    logger1.addHandler(ch)                    #将刚刚创建的StreamHandler方法添加到刚刚新建的日志实例logger1中
    logger1.addHandler(fh)                    #将刚刚创建的FileHandler方法添加到刚刚新建的日志实例logger1中
    
    #7、 'application' code
    logger1.debug('debug message')
    logger1.info('info message')
    logger1.warn('warn message')
    logger1.error('error message')
    logger1.critical('critical message')
    
    # 运行结果:打印在屏幕中的信息是下面五条
    # 2017-01-07 09:32:08,478 - TEST-LOG - DEBUG - debug message
    # 2017-01-07 09:32:08,479 - TEST-LOG - INFO - info message
    # 2017-01-07 09:32:08,479 - TEST-LOG - WARNING - warn message
    # 2017-01-07 09:32:08,479 - TEST-LOG - ERROR - error message
    # 2017-01-07 09:32:08,480 - TEST-LOG - CRITICAL - critical message
    # 记录到access.log的文件内容是这里的三条
    # 2017-01-08 12:21:26,398 - TEST-LOG - WARNING - warn message
    # 2017-01-08 12:21:26,398 - TEST-LOG - ERROR - error message
    # 2017-01-08 12:21:26,398 - TEST-LOG - CRITICAL - critical message
    同时把log打印在屏幕和文件日志里
    import logging
    
    from logging import handlers
    
    logger = logging.getLogger(__name__)
    
    log_file = "timelog.log"
    
    #1、根据日志文件大小切分:当文件大于10字节就会创建一个timelog.log.1 这样名字的文件存放后续日志
    # fh = handlers.RotatingFileHandler(filename=log_file,maxBytes=10,backupCount=3)
    
    #2、根据时间间隔切分: 每次间隔5秒就会重新创建一个timelog.log.2017-11-21_15-09-46 这样名字的文件存放后续日志
    fh = handlers.TimedRotatingFileHandler(filename=log_file,when="S",interval=5,backupCount=3)
    
    formatter = logging.Formatter('%(asctime)s %(module)s:%(lineno)d %(message)s')
    
    fh.setFormatter(formatter)
    
    logger.addHandler(fh)
    
    logger.warning("test1")
    logger.warning("test1")
    
    # 运行结果:
    #1. 根据日志文件大小切分:当存放日志的文件大于10字节就会从新创建新文件存放
    #2. 根据时间间隔切分: 每隔5秒就会自动创建一个新文件存放
    log日志自动截断代码展示

      5、项目使用举例:记录错误日志和运行日志

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    
    import os
    import logging
    from config import settings
    
    
    class Logger(object):
        __instance = None
    
        def __init__(self):
            self.run_log_file = settings.RUN_LOG_FILE
            self.error_log_file = settings.ERROR_LOG_FILE
            self.run_logger = None
            self.error_logger = None
    
            self.initialize_run_log()
            self.initialize_error_log()
    
        def __new__(cls, *args, **kwargs):
            if not cls.__instance:
                cls.__instance = object.__new__(cls, *args, **kwargs)
            return cls.__instance
    
        @staticmethod
        def check_path_exist(log_abs_file):
            log_path = os.path.split(log_abs_file)[0]
            if not os.path.exists(log_path):
                os.mkdir(log_path)
    
        def initialize_run_log(self):
            self.check_path_exist(self.run_log_file)
            file_1_1 = logging.FileHandler(self.run_log_file, 'a', encoding='utf-8')
            fmt = logging.Formatter(fmt="%(asctime)s - %(levelname)s :  %(message)s")
            file_1_1.setFormatter(fmt)
            logger1 = logging.Logger('run_log', level=logging.INFO)
            logger1.addHandler(file_1_1)
            self.run_logger = logger1
    
        def initialize_error_log(self):
            self.check_path_exist(self.error_log_file)
            file_1_1 = logging.FileHandler(self.error_log_file, 'a', encoding='utf-8')
            fmt = logging.Formatter(fmt="%(asctime)s  - %(levelname)s :  %(message)s")
            file_1_1.setFormatter(fmt)
            logger1 = logging.Logger('run_log', level=logging.ERROR)
            logger1.addHandler(file_1_1)
            self.error_logger = logger1
    
        def log(self, message, mode=True):
            """
            写入日志
            :param message: 日志信息
            :param mode: True表示运行信息,False表示错误信息
            :return:
            """
            if mode:
                self.run_logger.info(message)
            else:
                self.error_logger.error(message)
    lib/log.py
    # 错误日志
    ERROR_LOG_FILE = os.path.join(BASEDIR, "log", 'error.log')
    # 运行日志
    RUN_LOG_FILE = os.path.join(BASEDIR, "log", 'run.log')
    settings.py
    from lib.log import Logger
    Logger().log('成功创建',True)   # 运行日志
    Logger().log('创建失败',False)  # 错误日志
    run.py 记录日志

    1.13 paramiko使用

        在windows中安装paramiko:   pip3 install paramiko

      1、linuxscp命令的使用

           ssh root@10.1.0.51            #ssh远程登录

           scp -rp aa.txt  root@10.1.0.50:/tmp/                  #将本地aa.txt文件复制到10.1.0.50的/tmp文件夹中

      2、Paramiko模块作用

        1)如果需要使用SSH从一个平台连接到另外一个平台,进行一系列的操作时,

          比如:批量执行命令,批量上传文件等操作,paramiko是最佳工具之一。

        2)paramiko是用python语言写的一个模块,遵循SSH2协议,支持以加密和认证的方式,进行远程服务器的连接

        3)由于使用的是python这样的能够跨平台运行的语言,所以所有python支持的平台,如Linux, Solaris, BSD,MacOS X, Windows等,paramiko都可以支持

        4)如果需要使用SSH从一个平台连接到另外一个平台,进行一系列的操作时,paramiko是最佳工具之一

        5)现在如果需要从windows服务器上下载Linux服务器文件:

          a. 使用paramiko可以很好的解决以上问题,它仅需要在本地上安装相应的软件(python以及PyCrypto)
          b. 对远程服务器没有配置要求,对于连接多台服务器,进行复杂的连接操作特别有帮助。

      3、paramiko基于用户名密码连接

    import paramiko
    
    # 1 创建SSH对象
    ssh = paramiko.SSHClient()
    # 2 允许连接不在know_hosts文件中的主机
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    # 3 连接服务器
    ssh.connect(hostname='1.1.1.3', port=22, username='root', password='chnsys@2016')
    
    # 4 执行命令                                         #stdin标准输入: 自己输入的命令
    stdin, stdout, stderr = ssh.exec_command('pwd')      # stdout标准输出:  命令执行结果
    # 5 获取命令结果                                     #stderr标准错误:  命令执行报错的结果
    res, err = stdout.read(), stderr.read()
    result = res if res else err
    print(result.decode())                              #运行结果: /root
    
    # 6 关闭连接
    ssh.close()
    远程执行命令
    import paramiko
    
    #1 连接客户端
    transport = paramiko.Transport(('10.1.0.50',22))
    transport.connect(username='root',password='chnsys@2016')
    
    #2 定义与客户端交互    将刚刚定义的transport当参数传递给他
    sftp = paramiko.SFTPClient.from_transport(transport)
    #3 将location.py 上传至服务器 /tmp/test.py
    sftp.put(r'C:bbfile.txt', '/tmp/file.txt')
    
    #4 将remove_path 下载到本地 local_path
    sftp.get('/tmp/file.txt',r'C:bbfile.txt')
    
    #5 关闭连接
    transport.close()
    SFTPClient实现对Linux服务器上传和下载

      4、在两台Linux中演示无密码ssh登陆对方

        1、使用ssh-copy-id命令将公钥copy到被管理服务器中(法1:简单)

          1)操作目的:在10.1.0.50的tom用户下生成秘钥对,将生成的私钥用ssh-copy-id拷贝给10.1.0.51的root用户,那么root用户就可以使用ssh root@10.1.0.51 远程登陆了

    [tom@localhost .ssh]$ ssh-keygen 
    Generating public/private rsa key pair.
    Enter file in which to save the key (/home/tom/.ssh/id_rsa): 
    Enter passphrase (empty for no passphrase): 
    Enter same passphrase again: 
    Your identification has been saved in /home/tom/.ssh/id_rsa.
    Your public key has been saved in /home/tom/.ssh/id_rsa.pub.
    ssh-keygen生成秘钥

          2)执行完上面命令后再 /home/tom/.ssh 文件夹下生成了公钥私钥连个文件:  id_rsa  id_rsa.pub

          3)将在10.1.0.50中生成的公钥复制到10.1.0.51的root加目录下:

             ssh-copy-id root@10.1.0.51

          4)执行完成后就会在10.1.0.51中生成 /home/zhangsan/.ssh/ authorized_keys 文件

          5)此时输入: ssh root@10.1.0.51       就可以直接不用密码登陆了

        2、手动创建秘钥并手动copy到被管理服务器(法2:较复杂)

          1)使用10.1.0.51在不输入密码的情况下ssh链接到10.1.0.50,使用10.1.0.50的tom用户身份进行登录

          2)在 10.1.0.51上创建用于认证的秘钥对

    [root@localhost /]# ssh-keygen 
    Generating public/private rsa key pair.
    Enter file in which to save the key (/root/.ssh/id_rsa): 
    Enter passphrase (empty for no passphrase): 
    Enter same passphrase again: 
    Your identification has been saved in /root/.ssh/id_rsa.        #存放私钥 的路径
    Your public key has been saved in /root/.ssh/id_rsa.pub.        #存放公约 的路径
    注:将10.1.0.51上生成的密钥对中的公钥放到10.1.0.50的服务器tom用户家目录/home/tom/.ssh/authorized_keys 中,就可以在10.1.0.51中无密码登陆10.1.0.50了
    ssh-keygen生成秘钥对

          3)在10.1.0.51中生成的私钥路径:cat  ~/.ssh/id_rsa.pub

          4)在被登录服务器中创建用户tom,将刚刚在10.1.0.51上生成的私钥内容放到10.1.0.50的/home/tom/.ssh/authorized_keys中,

          5)新创建用户没用ssh登录过时没有,可以手动创建

    1、mkdir /home/tom/.ssh                                               #创建/home/tom/.ssh目录
    
    2、chmod 700 /home/tom/.ssh/                                     #将目录权限改为 700
    
    3、touch /home/tom/.ssh/authorized_keys                              #创建/home/tom/.ssh/authorized_keys文件
    
    4、chmod 600 /home/tom/.ssh/authorized_keys                     #将文件权限改为600
    
    5、将10.1.0.51的公钥文件粘贴到10.1.0.50的/home/tom/.ssh/authorized_keys中
    手动创建.ssh中的文件

          6)完成上面几步后就可以在10.1.0.51上无密码登陆10.1.0.50了

          7)登陆命令:  ssh tom@10.1.0.50

      5、paramiko基于公钥密钥连接:(ssh_rsa)

        1.  操作目的:在10.1.0.50中生成公钥和私钥,然后将生成的公钥放到10.1.0.51的winuser的/home/zhangsan/.ssh/ authorized_keys 目录下

        2.  第一步:在10.1.0.50中创建anyuser,在10.1.0.51中创建winuser

        3.  在10.1.0.50中使用anyuser登陆,然后生成公钥和私钥

    [anyuser@localhost ~]$ ssh-keygen 
    Generating public/private rsa key pair.
    
    Enter file in which to save the key (/home/anyuser/.ssh/id_rsa): Created directory '/home/anyuser/.ssh'.
    Enter passphrase (empty for no passphrase): 
    Enter same passphrase again: 
    Your identification has been saved in /home/anyuser/.ssh/id_rsa.
    Your public key has been saved in /home/anyuser/.ssh/id_rsa.pub.
    anyuser生成秘钥对

        4. 将在10.1.0.50中生成的公钥复制到10.1.0.51的winuser目录下:    ssh-copy-id winuser@10.1.0.51

        5. 执行完ssh-copy-id命令后就可以看到在10.1.0.51中新加了这样的目录和文件/home/zhangsan/.ssh/ authorized_keys   # authorized_keys就是刚刚在10.1.0.50中生成的私钥

        6. 将10.1.0.50中生成秘钥文件内容放到windows的PyCharm运行文件同目录,随便命名为:id_rsa50.txt

    import paramiko
    
    # 1 指定公钥所在本地的路径
    private_key = paramiko.RSAKey.from_private_key_file('id_rsa50.txt')
    
    # 2 创建SSH对象
    ssh = paramiko.SSHClient()
    # 3 允许连接不在know_hosts文件中的主机
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    # 4 连接服务器
    ssh.connect(hostname='10.1.0.51', port=22, username='winuser', pkey=private_key)
    
    # 5 执行命令
    stdin, stdout, stderr = ssh.exec_command('pwd')  # stdout标准输出:  命令执行结果
    # 6 获取命令结果                                          #stderr标准错误:  命令执行报错的结果
    res, err = stdout.read(), stderr.read()
    result = res if res else err
    print(result.decode())
    # 7 关闭连接
    ssh.close()
    用PyCharm基于公钥密钥执行命令
    import paramiko
    #1 指定公钥所在本地的路径
    private_key = paramiko.RSAKey.from_private_key_file('id_rsa50.txt')
    
    #2 连接客户端
    transport = paramiko.Transport(('10.1.0.51', 22))
    transport.connect(username='winuser', pkey=private_key )
    #3 定义与客户端交互
    sftp = paramiko.SFTPClient.from_transport(transport)
    
    #4上传 将本地test1.py 上传至服务器 /tmp/test1.py
    sftp.put('test1.py', '/tmp/test1.py')
    
    #5下载 将服务器/tmp/test1.py文件 下载到本地C:bb	est1.txt
    sftp.get('/tmp/test1.py', r'C:bb	est1.txt')
    
    transport.close()
    用PyCharm基于公钥密钥上传下载

    1.14 re模块

       1、常用正则表达式符号

        通配符( . 

          作用:点(.)可以匹配除换行符以外的任意一个字符串

          例如:‘.ython’ 可以匹配‘aython’ ‘bython’ 等等,但只能匹配一个字符串

        转义字符(  

          作用:可以将其他有特殊意义的字符串以原本意思表示

          例如:‘python.org’ 因为字符串中有一个特殊意义的字符串(.)所以如果想将其按照普通意义就必须使用这样表示:  ‘python.org’ 这样就只会匹配‘python.org’ 了

          注:如果想对反斜线()自身转义可以使用双反斜线(\)这样就表示 ’’

        字符集

          作用:使用中括号来括住字符串来创建字符集,字符集可匹配他包括的任意字串

            ①‘[pj]ython’ 只能够匹配‘python’  ‘jython’

             ‘[a-z]’ 能够(按字母顺序)匹配a-z任意一个字符

            ‘[a-zA-Z0-9]’ 能匹配任意一个大小写字母和数字    

            ‘[^abc]’ 可以匹配任意除a,b和c 之外的字符串

        管道符

          作用:一次性匹配多个字符串

          例如:’python|perl’ 可以匹配字符串‘python’ 和 ‘perl’

        可选项和重复子模式(在子模式后面加上问号?)

          作用:在子模式后面加上问号,他就变成可选项,出现或者不出现在匹配字符串中都是合法的

          例如:r’(aa)?(bb)?ccddee’ 只能匹配下面几种情况

             ‘aabbccddee’

             ‘aaccddee’

             ‘bbccddee’

             ‘ccddee’

        字符串的开始和结尾

                ‘w+’ 匹配以w开通的字符串

                ‘^http’ 匹配以’http’ 开头的字符串

              ‘ $com’ 匹配以‘com’结尾的字符串

        7.最常用的匹配方法

              d     匹配任何十进制数;它相当于类 [0-9]。
              D     匹配任何非数字字符;它相当于类 [^0-9]。
              s     匹配任何空白字符;它相当于类 [ fv]。
              S     匹配任何非空白字符;它相当于类 [^ fv]。
              w     匹配任何字母数字字符;它相当于类 [a-zA-Z0-9_]。
              W     匹配任何非字母数字字符;它相当于类 [^a-zA-Z0-9_]。

              w*    匹配所有字母字符

              w+    至少匹配一个字符

     1 '.'         默认匹配除
    之外的任意一个字符,若指定flag DOTALL,则匹配任意字符,包括换行
     2 '^'         匹配字符开头,若指定flags MULTILINE,这种也可以匹配上(r"^a","
    abc
    eee",flags=re.MULTILINE)
     3 '$'         匹配字符结尾,或e.search("foo$","bfoo
    sdfsf",flags=re.MULTILINE).group()也可以
     4 '*'         匹配*号前的字符0次或多次,re.findall("ab*","cabb3abcbbac")  结果为['abb', 'ab', 'a']
     5 '+'         匹配前一个字符1次或多次,re.findall("ab+","ab+cd+abb+bba") 结果['ab', 'abb']
     6 '?'         匹配前一个字符1次或0次
     7 '{m}'       匹配前一个字符m次
     8 '{n,m}'     匹配前一个字符n到m次,re.findall("ab{1,3}","abb abc abbcbbb") 结果'abb', 'ab', 'abb']
     9 '|'         匹配|左或|右的字符,re.search("abc|ABC","ABCBabcCD").group() 结果'ABC'
    10 '(...)'     分组匹配,re.search("(abc){2}a(123|456)c", "abcabca456c").group() 结果 abcabca456c
    11  
    12 'A'       只从字符开头匹配,re.search("Aabc","alexabc") 是匹配不到的
    13 ''       匹配字符结尾,同$
    14 'd'       匹配数字0-9
    15 'D'       匹配非数字
    16 'w'       匹配[A-Za-z0-9]
    17 'W'       匹配非[A-Za-z0-9]
    18 's'        匹配空白字符、	、
    、
     , re.search("s+","ab	c1
    3").group() 结果 '	'
    19            匹配一个单词边界,也就是指单词和空格间的位置,如,“er”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”
    20 B           匹配非单词边界。“erB”能匹配“verb”中的“er”,但不能匹配“never”中的“er”
    re模块更详细表达式符号

      2、re模块常用函数

    compile(pattern[, flags])

    根据正则表达式字符串创建模式对象

    search(pattern, string[, flags])

    在字符串中寻找模式

    match(pattern, 常用模块[, flags])

    在字符串的开始处匹配模式

    split(pattern, string[, maxsplit=0])

    根据模式的匹配项来分割字符串

    findall(pattern, string)

    列出字符串中模式的所有匹配项并以列表返回

    sub(pat, repl, string[, count=0])

    将字符串中所有pat的匹配项用repl替换

    escape(string)

    将字符串中所有特殊正则表达式字符转义

         re.compile(pattern[, flags])

            1)把一个正则表达式pattern编译成正则对象,以便可以用正则对象的match和search方法

            2)用了re.compile以后,正则对象会得到保留,这样在需要多次运用这个正则对象的时候,效率会有较大的提升

    import re
    mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
    ret = re.match(mobile_re,'18538762511')
    print(ret)            # <_sre.SRE_Match object; span=(0, 11), match='18538652511'>
    re.compile使用

         search(pattern, string[, flags])  match(pattern, string[, flags])

            1)match :只从字符串的开始与正则表达式匹配,匹配成功返回matchobject,否则返回none;

            2)search :将字符串的所有字串尝试与正则表达式匹配,如果所有的字串都没有匹配成功,返回none,否则返回matchobject;

    import re
    a =re.match('www.bai', 'www.baidu.com')
    b = re.match('bai', 'www.baidu.com')
    print(a.group())                                # www.bai
    print(b)                                        # None
    
    # 无论有多少个匹配的只会匹配一个
    c = re.search('bai', 'www.baidubaidu.com')
    print(c)                                        # <_sre.SRE_Match object; span=(4, 7), match='bai'>
    print(c.group())                                # bai
    match与search使用比较

         split(pattern, string[, maxsplit=0])

            作用:将字符串以指定分割方式,格式化成列表

    import re
    text = 'aa 1bb###2cc3ddd'
    print(re.split('W+', text))        # ['aa', '1bb', '2cc3ddd']
    print(re.split('W', text))          # ['aa', '1bb', '', '', '2cc3ddd']
    print(re.split('d', text))           # ['aa ', 'bb###', 'cc', 'ddd']
    print(re.split('#', text))            # ['aa 1bb', '', '', '2cc3ddd']
    print(re.split('#+', text))          # ['aa 1bb', '2cc3ddd']
    split使用

         findall(pattern, string)

            作用:正则表达式 re.findall 方法能够以列表的形式返回能匹配的子串

    import re
    p = re.compile(r'd+')
    print(p.findall('one1two2three3four4'))             # ['1', '2', '3', '4']
    print(re.findall('o','one1two2three3four4'))        # ['o', 'o', 'o']
    print(re.findall('w+', 'he.llo, wo#rld!'))         # ['he', 'llo', 'wo', 'rld']
    findall使用

         sub(pat, repl, string[, count=0])

            1)替换,将string里匹配pattern的部分,用repl替换掉,最多替换count次然后返回替换后的字符串

            2)如果string里没有可以匹配pattern的串,将被原封不动地返回

            3)repl可以是一个字符串,也可以是一个函数

            4) 如果repl是个字符串,则其中的反斜杆会被处理过,比如 会被转成换行符,反斜杆加数字会被替换成相应的组,比如 6 表示pattern匹配到的第6个组的内容

    import re
    test="Hi, nice to meet you where are you from?"
    print(re.sub(r's','-',test))          # Hi,-nice-to-meet-you-where-are-you-from?
    print(re.sub(r's','-',test,5))        # Hi,-nice-to-meet-you-where are you from?
    print(re.sub('o','**',test))           # Hi, nice t** meet y**u where are y**u fr**m?
    sub使用

         escape(string)

            1)   re.escape(pattern) 可以对字符串中所有可能被解释为正则运算符的字符进行转义的应用函数。

            2)  如果字符串很长且包含很多特殊技字符,而你又不想输入一大堆反斜杠,或者字符串来自于用户(比如通过raw_input函数获取输入的内容),

                                        且要用作正则表达式的一部分的时候,可以用这个函数

    import re
    print(re.escape('www.python.org'))
    escape使用

      3、re模块中的匹配对象和组 group()

          1)group方法返回模式中与给定组匹配的字符串,如果没有给定匹配组号,默认为组0

          2)m.group() == m.group(0) == 所有匹配的字符

    import re
    a = "123abc321efg456"
    print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(0))    # 123abc321
    print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).groups())    # ('123', 'abc', '321')
    print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(1))    # 123
    print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(2))    # abc
    print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(3))    # 321
    
    
    import re
    m = re.match('(..).*(..)(..)','123456789')
    print(m.group(0))              # 123456789
    print(m.group(1))              # 12
    print(m.group(2))              # 67
    print(m.group(3))              # 89
    group(0)与group(1)区别比较
    import re
    m = re.match('www.(.*)..*','www.baidu.com')
    print(m.group(1))           # baidu
    print(m.start(1))             # 4
    print(m.end(1))             # 9
    print(m.span(1))            # (4, 9)
    group()匹配之返回匹配索引
    import re
    test = 'dsfdf 22 g2323  GigabitEthernet0/3        10.1.8.1        YES NVRAM up eee'
    # print(re.match('(w.*d)s+(d{1,3}.d{1,3}.d{1,3}.d{1,3})s+YESs+NVRAMs+(w+)s+(w+)s*', test).groups())
    
    ret = re.search(  r'(w*/d+).*s(d{1,3}.d{1,3}.d{1,3}.d{1,3}).*(s+ups+)',test ).groups()
    print(ret)          # 运行结果: ('GigabitEthernet0/3', '10.1.8.1', ' up ')
    
    #1. (w*d+/d+)      匹配结果为:GigabitEthernet0/3
    #1.1   w*: 匹配所有字母数字     
    #1.2   /d+:匹配所有斜杠开头后根数字 (比如:/3 )
    
    #2. (d{1,3}.d{1,3}.d{1,3}.d{1,3})  匹配结果为:10.1.8.1
    
    #3. s+ups+   匹配结果为: up 这个单词,前后都为空格
    group()匹配ip,状态以元组返回

       4、re模块其他知识点

    import re
    #匹配时忽略大小写
    print(re.search("[a-z]+","abcdA").group())                #abcd
    print(re.search("[a-z]+","abcdA",flags=re.I).group())            #abcdA
    #连同换行符一起匹配:
    #'.'默认匹配除
    之外的任意一个字符,若指定flag DOTALL,则匹配任意字符,包括换行
    print(re.search(r".+","
    aaa
    bbb
    ccc").group())            #aaa
    print(re.search(r".+","
    aaa
    bbb
    ccc",flags=re.S))            
            #<_sre.SRE_Match object; span=(0, 12), match='
    aaa
    bbb
    ccc'>
    print(re.search(r".+","
    aaa
    bbb
    ccc",flags=re.S).group())
                                                                                       aaa
                                                                                       bbb
                                                                                       ccc
                                                        
    re匹配忽略大小写,匹配换行
    1)init_l=[i for i in re.split('(-d+.*d*)',expression) if i]
            a. 按照类似负数的字符串分割成列表
            b. -d+.*d*是为了可以匹配浮点数(比如:3.14)
            c. (if i)是为了去除列表中的空元素
            d. 分割结果:['-1', '-2', '*((', '-60', '+30+(',
    2)re.search('[+-*/(]$',expression_l[-1])
            a. 匹配expression_l列表最后一个元素是 +,-,*,/,( 这五个符号就是负数
    3)new_l=[i for i in re.split('([+-*/()])',exp) if i]
            a. 将字符串按照+,-,*,/,(,)切分成列表(不是正真的负数就切分)
    4)print(re.split('([+-])','-1+2-3*(2*2+3)'))            #按照加号或者减号分割成列表
    运行结果: ['', '-', '1', '+', '2', '-', '3*(2*2', '+', '3)']
    计算器用到的几个知识点

    1.15  xml处理模块

       1、xml模块的作用

          1.  xml是实现不同语言或程序之间进行数据交换的协议,跟json差不多

          2. 但json使用起来更简单,不过,古时候,在json还没诞生的黑暗年代,大家只能选择用xml呀

          3. 至今很多传统公司如金融行业的很多系统的接口还主要是xml

       2、手动创建xmltest.xml文件,并使用python读取xml文件中的数据

    <?xml version="1.0"?>                    <!--#版本号-->
    <data>                                    <!--#data是一个标签随便写,data中包含三组数据,每组数据都是country标签-->
        <country name="Liechtenstein">    <!--#name是给country标签取的名字-->
            <rank updated="yes">2</rank>    <!--#两个</rank>中的2是数据排名,updated="yes"是属性可随便写-->
            <year>2008</year>                <!--#在2008年-->
            <gdppc>141100</gdppc>            <!--#人均gdp是141100-->
            <neighbor name="Austria" direction="E"/>          <!--#他有个邻居有两个属性 name 和 direction-->
            <neighbor name="Switzerland" direction="W"/>
        </country>
        <country name="Singapore">
            <rank updated="yes">5</rank>
            <year>2011</year>
            <gdppc>59900</gdppc>
            <neighbor name="Malaysia" direction="N"/>
        </country>
        <country name="Panama">
            <rank updated="yes">69</rank>
            <year>2011</year>
            <gdppc>13600</gdppc>
            <neighbor name="Costa Rica" direction="W"/>
            <neighbor name="Colombia" direction="E"/>
        </country>
    </data>
    xmltest.xml文件内容
    import xml.etree.ElementTree as ET
    
    tree = ET.parse("xmltest.xml")  # 写上要处理的文件明智
    root = tree.getroot()
    print(root)  # 打印的是内存地址信息
    print(root.tag)  # root.tag = data其实就是标签的名字
    
    # 遍历xml文档
    for child in root:  # 循环每个子标签中的内容 其中root是一个内存对象,在这里是所有的文件对象
        print(child.tag, child.attrib)  # 1 child.tag其实就是data数据中的标签名在这里都是country
        for i in child:  # 2  child.attrib其实就是data数据中的属性
            # 3 比如:属性 {'name': 'Liechtenstein'}  {'name': 'Singapore'}
            print(i.tag, i.text, i.attrib)  # 1 比如在数据中 <rank updated="yes">2</rank>
        # 2 打印的结果就是 rank 2 {'updated': 'yes'}
        # 3 其中 i.tag = rank  表示内层标签名
        # 4  i.text = 2  表示这个标签的数据
        # 5 i.attrib = {'updated': 'yes'} 表示标签属性
    # 只遍历year 节点
    for node in root.iter('year'):
        print(node.tag, node.text)
    创建python文件读取xmltest.xml内容
    #1、运行第一个for循环的结果
    <Element 'data' at 0x00A3A510>
    data
    country {'name': 'Liechtenstein'}
    
    
    rank 2 {'updated': 'yes'}
    year 2008 {}
    gdppc 141100 {}
    neighbor None {'name': 'Austria', 'direction': 'E'}
    neighbor None {'name': 'Switzerland', 'direction': 'W'}
    country {'name': 'Singapore'}
    rank 5 {'updated': 'yes'}
    year 2011 {}
    gdppc 59900 {}
    neighbor None {'name': 'Malaysia', 'direction': 'N'}
    country {'name': 'Panama'}
    rank 69 {'updated': 'yes'}
    year 2011 {}
    gdppc 13600 {}
    neighbor None {'name': 'Costa Rica', 'direction': 'W'}
    neighbor None {'name': 'Colombia', 'direction': 'E'}
    
    
    #2、 运行第二个for循环的结果
    year 2008
    year 2011
    year 2011
    
    
    下面以xmltest.xml中一组数据为例解释:
    <data>
        <country name="Liechtenstein">
            <rank updated="yes">2</rank>
            <year>2008</year>
            <gdppc>141100</gdppc>
            <neighbor name="Austria" direction="E"/>
            <neighbor name="Switzerland" direction="W"/>
        </country>
    
    #3、第一个for循环各项的意义:
    root.tag = data
    child.tag = country
    child.attrib = {'name': 'Liechtenstein'}
    i.tag = rank, year, gdppc, neighbor, neighbor
    i.text = 2, 2008,141100, None, None
    i.attrib = {'updated': 'yes'}, {}, {}
    {'direction': 'E', 'name': 'Austria'}
    {'direction': 'W', 'name': 'Switzerland'}
    #4、第二个for循环各项的意义
    node.tag = year      #标签
    node.text = 2008       #数据
    对python读取xml文件解释

      3、python修改和删除xmltest.xml文档内容

    import xml.etree.ElementTree as ET
    
    tree = ET.parse("xmltest.xml")
    root = tree.getroot()
    
    #修改
    for node in root.iter('year'):        #循环data中三组数据,找到标签名为year的内容
        new_year = int(node.text) + 1        # node.text表示year标签中的数据部分 将其加1
        node.text = str(new_year)        # 重新赋值node.text 即给year种数据部分赋值
        node.set("updated","yes")        #并且在year标签的字段中添加新属性:year updated="yes
    tree.write("xmltest.xml")
    '''
    #1、这里仅取出data中的一组数据:
    <country name="Liechtenstein">
        <rank updated="yes">2</rank>
        <year>2008</year>
        <gdppc>141100</gdppc>
        <neighbor name="Austria" direction="E"/>
        <neighbor name="Switzerland" direction="W"/>
    </country>
    '''
    
    '''
    #2、这里是修改后的数据:
    <country name="Liechtenstein">
        <rank updated="yes">2</rank>
        <year updated="yes">2009</year>
        <gdppc>141100</gdppc>
        <neighbor direction="E" name="Austria" />
        <neighbor direction="W" name="Switzerland" />
    </country>
    '''
    python修改xml文件
    import xml.etree.ElementTree as ET                    
    
    tree = ET.parse("xmltest.xml")
    root = tree.getroot()
    
    #删除node
    for country in root.findall('country'):        #在文件中寻找所有标签名为'country'的所有组数据
       rank = int(country.find('rank').text)        #在'country'数据组中找出所有标签名为'rank'中的数据
       if rank > 50:                    #如果这个数据大于50
         root.remove(country)                #将这个'country'数据组从xml文件中删除
    
    tree.write('output.xml')
    
    注:执行结束后发现原来的xml文件的data数据中有三组'country'数据,现在只有两组了
    删除指定xml文档

      4、使用python创建xml文档

    import xml.etree.ElementTree as ET
    
    new_xml = ET.Element("personinfolist")           #personinfolist表示根节点
    
    #1 根节点"personinfolist"的第一个子节点"personinfo"
    personinfo = ET.SubElement(new_xml,"personinfo",attrib={"enrolled":"yes"})
            #指定在new_xml根节点中创建子节点名字为"personinfo" 属性为  enrolled="yes"
    name = ET.SubElement(personinfo,"name")    #在personinfo子节点中创建一个子节点标签是"name"
    name.text = 'Alex Li'            #给刚刚创建的name标签加入一个数据 'Alex Li'
    age = ET.SubElement(personinfo,"age",attrib={"checked":"no"})
    age.text = '33'    #在personinfo子节点中创建一个子节点标签是age 属性是 checked":"no"
    
    #2 根节点"personinfolist"的第一个子节点"personinfo2"
    personinfo2 = ET.SubElement(new_xml,"personinfo",attrib={"enrolled":"no"})
    name = ET.SubElement(personinfo2,"name")
    name.text = 'Oldboy Ran'
    age = ET.SubElement(personinfo2,"age")
    age.text = '19'
    
    et = ET.ElementTree(new_xml) #生成文档对象
    et.write("test.xml", encoding="utf-8",xml_declaration=True)
    
    ET.dump(new_xml) #打印生成的格式
    
    #1、说明
    "test.xml" : 生成的文档名称
    encoding="utf-8" : 指定字符编码
    xml_declaration=True :指定文档格式是xml
    
    #2、运行结果
    <?xml version='1.0' encoding='utf-8'?>
    <personinfolist>
        <personinfo enrolled="yes">
            <name>Alex Li</name>
            <age checked="no">33</age>
        </personinfo>
    
        <personinfo enrolled="no">
            <name>Oldboy Ran</name>
            <age>19</age>
        </personinfo>
    </personinfolist>
    使用python创建xml文档

    1.16  PyYAML模块

      1. 安装PyYAML

          1)Download the source package PyYAML-3.08.tar.gz and unpack it.

          2)Go to the directory PyYAML-3.08 and run 

          3)PyYAML下载网址 http://pyyaml.org/wiki/PyYAML

                      $ python setup.py install

      2. 安装LibYAML

          1)If you want to use LibYAML bindings, which are much faster than the pure Python version,

          2)you need to download and install LibYAML.

          3)Then you may build and install the bindings by executing.

             $ python setup.py --with-libyaml install

      3. 使用的一个小例子

    import yaml
    document = """
      a: 1
      b:
        c: 3
        d: 4
    """
    print(yaml.dump(yaml.load(document)))
    '''#1、将YAML格式文件转换成python格式文件
    a: 1
    b: {c: 3, d: 4}
    '''
    
    
    print(yaml.dump(yaml.load(document), default_flow_style=False))
    '''#2、将python格式文件转换成YAML格式文件
    a: 1
    b:
      c: 3 
      d: 4
    '''
    YAML使用举例

    1.17 ConfigParser模块

         作用: 用于生成和修改常见配置文档,当前模块的名称在 python 3.x 版本中变更为 configparser。

       1、来看一个好多软件的常见文档格式如下

    [DEFAULT]
    ServerAliveInterval = 45
    Compression = yes
    CompressionLevel = 9
    ForwardX11 = yes
    
    [bitbucket.org]
    User = hg
    
    [topsecret.server.com]
    Port = 50022
    ForwardX11 = no
    
    
    #1、文件中内容包含三个节点:和python中字典效果一样
    '''
    [DEFAULT]
    [bitbucket.org]
    [topsecret.server.com]
    '''
    好多软件的常见文档格式事例

      2、用python生成一个这样的文档

    import configparser
    
    config = configparser.ConfigParser()         #生成一个configparser处理的对象赋值为config
    
    #1 创建第一个节点"DEFAULT"
    config["DEFAULT"] = {'ServerAliveInterval': '45',
                          'Compression': 'yes',
                         'CompressionLevel': '9'}
    
    #2 创建第二个节点'bitbucket.org'
    config['bitbucket.org'] = {}
    config['bitbucket.org']['User'] = 'hg'
    
    #3 创建第三个节点'topsecret.server.com'
    config['topsecret.server.com'] = {}
    config['topsecret.server.com']['Host Port'] = '50022'     # mutates the parser
    config['topsecret.server.com']['ForwardX11'] = 'no'  # same here
    
    config['DEFAULT']['ForwardX11'] = 'yes'            #在"DEFAULT"中添加一个内容
    
    with open('example.ini', 'w') as configfile:        #将生成的内容写入'example.ini'文件中
       config.write(configfile)
    用python生成一个这样的文档
    import configparser
    
    config = configparser.ConfigParser()
    config.read('example.ini')
    
    
    #1 这里打印的是所有的节点,但是"DEFAULT"节点是不会打印出来
    print(config.sections())
    # 运行结果: ['bitbucket.org', 'topsecret.server.com']
    
    #2 这里打印的是"DEFAULT"节点中的所有内容,变成一个字典打印出来
    print(config.defaults())
    # 运行结果:OrderedDict([('compressionlevel', '9'), ('serveraliveinterval', '45'), ('compression', 'yes'), ('forwardx11', 'yes')])
    
    #3 打印出具体节点中的某一项内容
    print(config['bitbucket.org']['user'])
    # 运行结果: hg
    
    #4 遍历指定节点中的所有key值,如果在default模块有单自己模块没有的也会打印出来
    for key in config["topsecret.server.com"]: print(key)
    '''
    运行结果:
    host port
    forwardx11
    compressionlevel
    serveraliveinterval
    compression
    '''
    
    #5 删除example.ini文件中'bitbucket.org'节点中的所有内容
    sec = config.remove_section('bitbucket.org')
    # config.write(open('example.cfg', "w"))    #这里是不修改原文件将内容写到新文件中
    config.write(open('example.ini', "w"))        #这里是将改变的文件写入原文件中
    用python读取刚刚生成的文档 
     
  • 相关阅读:
    关于bootstrap的css样式总结
    SpringCloudConfig分布式配置中心
    static 关键字的作用-------王志亭
    java 学习当中我遇到的第一个设计模式-----王志亭
    在java 多态 中 父类作为参数列表的方法
    强悍的蒙古人---王志亭
    蒙古人--巴特尔
    乌兰巴托----王志亭
    不一样的插入方法-------王志亭
    乌兰巴托的思念--------------王志亭
  • 原文地址:https://www.cnblogs.com/jiaxinzhu/p/11785098.html
Copyright © 2011-2022 走看看