zoukankan      html  css  js  c++  java
  • Python3.5学习之旅五

    模块初识

    一、定义

    在python中,模块是用来实现某一特定功能的代码集合。其本质上就是以‘.py’结尾的python文件。例如某文件名为test.py,则模块名为test。

    二、导入方法

    我们在这一节通过举例来向大家简单介绍模块的导入方法。

    我们在这里创建一个自定义模块‘module_test’,如下(module_test.py):

    name='kobe'
    def say_hello():
        print('hello kobe!')

    另新建'import_test.py'并在其中导入模块module_test,方法如下:

    import module_test
    print(module_test.name)
    print(module_test.say_hello())

    代码执行结果如下:

    通过上例,我们可以看出import_test.py通过导入模块module_test,取出了module_test中‘name’变量的值,并且运行了函数say_hello。

    若想导入导入多个模块,则可‘import module1,module2...’

    >>>import module1, module2,...

    若想导入某个模块中某个功能或全部功能

    >>>from module import *    #导入module模块中的全部功能
    >>>from module import (功能名)  #导入module模块中的某功能

    还以上例说明:

    from module_test import say_hello as say   #导入module_test模块中的say_hello函数功能,并用‘say’来代替函数名
    say()

    代码执行结果如下:

    通过上述例子我们简单总结一下,即导入模块的本质就是把模块python文件解释一遍。

    三、‘包’的定义及导入

    ‘包’本质就是一个目录(创建好之后会自动其目录下生成一个‘_init_.py’文件)。若包下无其他文件,则导入这个包则本质上即是导入包下的‘_init_.py’文件。

    例如,若我们创建一个包‘package_age’,并在其下的‘_init_.py’文件中写入:

    print('123456')

    再创建一个Python文件‘p_test.py’并导入包‘package_age’:

    import package_test

    则结果为:

    四、非同级目录下的模块导入

    若主代码与想要导入的Python文件不在同一级目录下,则不能导入,在导入时会优先在当前路径下去寻找相应的python模块。

    例如我们想在目录‘module_test’下的python文件‘main.py’中导入目录‘test4’下的'p_test.py'的python文件(如下图)。

    程序实现如下 :

    #main.py
    
    from module_kobe import *
    logger()
    #module_kobe.py
    
    name='kobe bryant'
    def say_hello():
        print('hello kobe!')
    def logger():
        print('in the module')

    代码执行结果如下:

    即显示差找不到模块‘module_kobe’。

    那么若我们想导入不在同一目录下的模块怎么做呢?这就需要使用‘os’和‘sys’模块来将想要导入模块所在的路径加载到主程序中,还以上述为例,代码编写如下:

    import sys,os
    print(sys.path)     #以列表的形式打印当前文件所在的路径
    print(os.path.abspath(__file__))   #打印当前文件所在的绝对路径
    print(os.path.dirname(os.path.abspath(__file__)))   #打印当前文件所在目录
    print(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))    #打印当前文件所在目录的上一级目录
    sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))    #将当前文件所在目录的上一级目录加载过来
    
    from module_kobe import *
    logger()

    代码执行结果如下:

    可以看到我们实现了导入非同级目录下的python自定义模块功能,其中关于‘os’和‘sys’模块我们在后面会有详细介绍。

     五、模块分类

    python中常用的模块主要分为三种:a. 开源模块,b. 自定义模块,c. 标准库(内置模块)。

    其中前两种都是程序员自己编写的模块,其中开源模块可以供他人使用,自定义模块一般就是自己使用,上述章节介绍的就是自定义模块。而标准库(内置模块)是python自带的模块,我们写程序时可以直接拿来使用的。

    接下来我们简单介绍几个常用的内置模块。

    一、时间模块——time与datetime

    在python中,通常有这几种方式来表示时间——a. 格式化的时间字符串,b. 时间戳,和表示时间的元组(struct_time)。

    首先,格式化的字符串即我们通常意义下的用来表示时间的形式,形如:‘2019-08-29 20:39:40’表示2019年8月29日20时39分40秒。

    其次,关于时间戳,我们首先要向大家介绍一下几个概念:

    UTC(Coordinated Universal Time,世界协调时间),即格林威治天文时间、世界标准时间,在中国为UTC+8(即比标准时间早八个小时 )。

    DST(Daylight Saving Time,夏时制,夏时令):又称“日光节约时制”和“夏令时间”,是一种为节约能源而人为规定地方时间的制度,在这一制度实行期间所采用的统一时间称为“夏令时间”。

    时间戳(time stamp):通常来说,时间戳表示的是从1970年1月1日00:00:00开始按秒计算的偏移量,我们可以用‘time.time()’来获取当前的时间戳,如下:

    >>>import time
    >>>time.time()
    1567083047.565318

    还有一个是元组(struct_time),获取struct_time的方法如下:

    >>>import time
    >>> time.localtime()
    time.struct_time(tm_year=2019, tm_mon=8, tm_mday=29, tm_hour=20, tm_min=53, tm_sec=2, tm_wday=3, tm_yday=241, tm_isdst=0)

    如上,元组struct_time内共有九个元素,依次为:年;月;日;时;分钟;秒;一周的第几天(周一为0,周二为1,以此类推);一年的第几天;是否是夏令时。

     接下来向大家简单介绍一下time模块的几个常用方法:

    >>> import time
    >>> time.time()     #获取当前时间戳
    1567168188.3943985    
    >>> time.localtime()     #获取当前当地时间的struct_time元组
    time.struct_time(tm_year=2019, tm_mon=8, tm_mday=30, tm_hour=20, tm_min=30, tm_sec=0, tm_wday=4, tm_yday=242, tm_isdst=0)
    >>> time.timezone   #获取当地的时区,以秒为单位
    -28800
    >>> time.altzone     #UTC时间与夏令时的差值
    -32400
    >>> time.sleep(2)    #睡眠2秒,括号内为可设置的睡眠的秒数
    >>> time.gmtime(1567168188.3943985)   #将括号内的时间戳转换成UTC下的struct_time元组格式
    time.struct_time(tm_year=2019, tm_mon=8, tm_mday=30, tm_hour=12, tm_min=29, tm_sec=48, tm_wday=4, tm_yday=242, tm_isdst=0)
    >>> time.localtime(1567168188.3943985)      #将括号内的时间戳转换成当地的struct_time元组格式(注意与time.gmtime()的差别)
    time.struct_time(tm_year=2019, tm_mon=8, tm_mday=30, tm_hour=20, tm_min=29, tm_sec=48, tm_wday=4, tm_yday=242, tm_isdst=0)
    >>> time.asctime()
    'Fri Aug 30 20:56:58 2019'

    若想把struct_time元组的格式转换为格式化的字符串的形式:

    >>> x=time.localtime()
    >>> time.strftime('%Y-%m-%d %H:%M:%S',x)  #将x以%年-%月-%日 %时:%分钟:%秒的格式输出
    '2019-08-30 20:48:52'

    同样的,也可以将格式化的字符串装换为struct_time元组格式:

    >>> time.strptime('2019-08-30 20:48:52','%Y-%m-%d %H:%M:%S')
    time.struct_time(tm_year=2019, tm_mon=8, tm_mday=30, tm_hour=20, tm_min=48, tm_sec=52, tm_wday=4, tm_yday=242, tm_isdst=-1)

    若想取出struct_time元组格式时间中的某个元素,如下例:

    import time
    x=time.localtime()
    print(x)
    print(x.tm_year)
    print('%d' %x.tm_yday)

    代码执行结果如下:

     接下来为大家介绍一下datetime模块的使用方法,具体如下:

    import datetime
    print(datetime.datetime.now())   #获取当前时间
    print(datetime.datetime.now()+datetime.timedelta(3))    #当前时间+3天
    print(datetime.datetime.now()+datetime.timedelta(-3))    #当前时间-3天
    print(datetime.datetime.now()+datetime.timedelta(hours=3))   #当前时间+3小时
    print(datetime.datetime.now()+datetime.timedelta(minutes=3))   #当前时间+3分钟
    c_time=datetime.datetime.now()
    print(c_time.replace(minute=3,hour=2))  #修改时间,将分钟改为3,小时改为2

    代码执行结果如下:

     二、random模块

     random模块即随机数模块,它用来生成随机数。具体介绍如下:

    >>> import random
    >>> random.random()    #在0到1之间随机取出一个浮点数
    0.9835265154729393
    >>> random.randint(1,3)  #在[1,3]之间随机取出一个整数(包含1和3)
    1
    >>> random.randint(1,3)
    3
    >>> random.randrange(1,3)   #从range(1,3)即[0,1,2]中随机取出一个整数
    1
    >>> random.choice('hello')    #从字符串‘hello’中随机取出一个字符
    'l'
    >>> random.choice([1,3,5])   #从列表[1,3,5]中随机取出一个数字
    5
    >>> random.sample('hello',2)    #从字符串‘hello’中随机取出2个字符
    ['h', 'l']
    >>> random.uniform(1,3)   #在1到3之间随机取出一个浮点数
    1.2706200726679393
    >>> l=[1,2,3,4,5,6]
    >>> random.shuffle(l)   #对l进行重新排序
    >>> l
    [6, 4, 2, 5, 3, 1]

    那么有了上面的铺垫,我们就可以试着写出一个随机输出验证码的代码如下:

    '''
    设置一个有4位数字的验证码
    '''
    import random
    checkcode=''
    for i in range (4):    #将验证码设置成四位
        current=random.randint(0,9)    #从0到9之间随机取出一位整数
        checkcode += str(current)
    print(checkcode)

    代码执行结果如下:

     上例实现了一个比较简单的四位数字的验证码,接下来我们可以尝试写出可以输出含有数字和字母的验证码,且数字和字母的位置数量不固定。

    代码实现如下:

    '''
    设置一个4位的验证码,包含数字和字母
    '''
    import random
    checkcode=''
    for i in range (4):   #设置验证码长度为4位
        current=random.randint(0,4)   #从[0,1,2,3]中随机选择一位整数
        if current==i:     #若current==i,则输出子母
            tmp=chr(random.randint(65,90))   #65到90为26个英文字母对应的ASCII码数字
        else:   #若current!=i,则输出数字
            tmp=random.randint(0,9)
        checkcode += str(tmp)
    print(checkcode)

    代码实现结果如下:

     可以看到我们实现了输出包含数字和字母的四位验证码。

    三、os模块

    os模块即操作系统模块,它提供对操作系统进行调用的接口。

    主要调用方式如下:

     import os
    >>> os.getcwd()   #查看当前目录
    'C:\Users\pc'   
    >>> os.chdir(r'C:Program Files')    #切换路径到C:Program Files
    >>> os.getcwd()   #查看当前目录
    'C:\Program Files'
    >>> os.curdir   #获取当前目录
    '.'       #‘.’代表当前目录
    >>> os.pardir    #获取当前目录的父目录
    '..'   #'..' 代表上一级目录
    >>> os.makedirs(r'D:acd')      #递归的创建目录
    >>> os.removedirs(r'D:acd')    #递归地删除目录
    >>> os.chdir(r'D:')    #切换路径到D盘
    >>> os.mkdir('a')   #生成单级目录
    >>> os.rmdir('a')   #删除单级目录
    >>> os.listdir('.')   #列出当前目录下的子目录
    ['.idlerc', '.jssc', '.LSC', '.packettracer', '.PyCharm40', '.QtWebEngineProcess', '3D Objects', 'AppData', 'Application Data', 'Cisco Packet Tracer 6.2sv', 
    'Contacts', 'Cookies', 'Desktop', 'Documents', 'Downloads', 'Favorites', 'gsview32.ini', 'IntelGraphicsProfiles', 'Links', 'Local Settings', 'MicrosoftEdgeBackups', 
    'Music', 'My Documents', 'NetHood', 'NTUSER.DAT', 'ntuser.dat.LOG1', 'ntuser.dat.LOG2', 'NTUSER.DAT{1b051980-cfce-11e9-b42b-005056c00001}.TM.blf', 
    'NTUSER.DAT{1b051980-cfce-11e9-b42b-005056c00001}.TMContainer00000000000000000001.regtrans-ms', 
    'NTUSER.DAT{1b051980-cfce-11e9-b42b-005056c00001}.TMContainer00000000000000000002.regtrans-ms', 'ntuser.ini', 'OneDrive', 'Pictures', 
    'PrintHood', 'PycharmProjects', 'q.log', 'Recent', 'Roaming', 'Saved Games', 'Searches', 'SendTo', 'Templates', 'Videos', 'WebpageIcons.db', '「开始」菜单']
    >>> os.remove( ) #删除一个文件
    >>> os.rename('oldname','newname')    重命名文件,目录
    >>> os.stat('D:')   #获取D盘的信息,括号内可为其他文件名或目录名
    os.stat_result(st_mode=16895, st_ino=1407374883553285, st_dev=12081982, st_nlink=1, st_uid=0, st_gid=0, st_size=12288, st_atime=1567682975, st_mtime=1567682975, 
    st_ctime=1495248147) >>> os.sep #输出操作系统特定的路径分隔符,win为‘\’,Linux为‘/’ '\' >>> os.linesep #输出当前平台使用的行终止符,win为‘ ’,Linux为‘ ’ ' ' >>> os.pathsep #输出用于分隔文件路径的字符串 ';' >>> os.environ #显示环境变量 environ({'COMSPEC': 'C:\WINDOWS\system32\cmd.exe', 'CCPKPATH': 'D:\CTEX\CTeX\fonts\pk\modeless\cct\dpi$d', 'NUMBER_OF_PROCESSORS': ...... >>> os.name #输出字符串指示当前的使用平台,win为‘nt’,Linux为‘posix’ 'nt' >>> os.system('dir') #运行shell命令 >>> os.path.abspath('d:') #输出括号内目录或文件的规范化的绝对路径 'D:\' >>> os.path.dirname(path) #返回path所在的目录 >>> os.path.exits(path) #判断path是否存在 >>> os.path.isabs(path) #判断是否是绝对路径(以根目录开头) >>> os.path.isfile(path) #判断是否是文件 >>> os.path.getatime(path) #返回path所指向的文件或目录的最后存取时间 >>> os.path.getmtime(path) #返回path所指向的文件或目录的最后修改时间

    四、sys模块

     接下来简单介绍一下sys模块的功能,如下:

    >>> import sys
    >>> sys.argv     #运行命令行参数list,第一个元素是程序本身路径
    ['']   
    >>> sys.exit()    #退出程序
    >>> sys.version    #获取python解释程序的版本信息
    '3.5.2 (v3.5.2:4def2a2901a5, Jun 25 2016, 22:18:55) [MSC v.1900 64 bit (AMD64)]'

     五、shutil模块

     shutil模块是一个高级的文件、文件夹、压缩包处理模块。

    接下来简单介绍一下shutil模块的功能。

    首先我们可以用shutil模块来复制文件的内容,具体如下:

    import shutil
    f1=open('本节笔记',encoding='utf-8')       
    f2=open('本节笔记2','w',encoding='utf-8')
    shutil.copyfileobj(f1,f2)   #将'本节笔记'内的内容复制到'本节笔记2'中

    同样可以起到复制文件功能的还有shutil.copyfile(),具体如下:

    import shutil
    shutil.copyfile('笔记','笔记2')   #将文件'笔记'的内容复制到'笔记2'中

    同时,shutil模块还可以实现压缩文件的功能,介绍如下:

    >>>shutil.make_archive(basename,format,root_dir)

    其中,basename为压缩包的文件名,也可以是压缩包的路径。format为压缩包的种类,可以是‘zip’,'tar','bztar','gztar'。'root_dir'为要压缩的文件夹路径。

    举例如下:

    import shutil
    import os
    print(os.getcwd())   #获取当前路径
    shutil.make_archive('shutil_archive_test','zip','D:python3.5day5')   #将文件'D:python3.5day5'以zip的形式压缩为'shutil_archive_test'

    同时,shutil模块还可以其他的一些简单功能,介绍如下:

    >>>shutil.copy(src,dst)   #拷贝文件和权限
    >>>shutil.copy2(src,dst)   #拷贝文件和权限
    >>>shutil.copytree(src,dst)   #递归地拷贝文件
    >>>shutil.rmtree(src)   #递归删除目录
    >>>shutil.move(src,dst)   #递归地移动文件

     五、xml处理模块

    xml是实现不同语言或程序之间进行数据交换的协议,跟jason差不多,但jason使用起来更加简单。不过在jason没诞生之前,大家只能选择用xml,至今仍有许多公司如金融行业的很多系统的接口还主要

    是xml。

    xml是通过< >节点来区别数据结构的,格式如下:

    <data>
        <country name="Liechtenstein">
            <rank updated="yes">2</rank>
            <year updated_by="han">2010</year>
            <gdppc>141100</gdppc>
            <neighbor direction="E" name="Austria" />
            <neighbor direction="W" name="Switzerland" />
        </country>
        <country name="Singapore">
            <rank updated="yes">5</rank>
            <year updated_by="han">2013</year>
            <gdppc>59900</gdppc>
            <neighbor direction="N" name="Malaysia" />
        </country>
        <country name="Panama">
            <rank updated="yes">69</rank>
            <year updated_by="han">2013</year>
            <gdppc>13600</gdppc>
            <neighbor direction="W" name="Costa Rica" />
            <neighbor direction="E" name="Colombia" />
        </country>
    </data>

    上述代码描述了三个国家的GDP和邻国等信息,我们可以看到这有点类似于字典。那么我们如何对xml进行处理呢?

    下面我们尝试着把xml中的节点及其属性打印出来:

    import xml.etree.ElementTree as ET
    
    tree = ET.parse("xmltest.xml")
    root = tree.getroot()
    print(root)    #打印根节点
    print(root.tag)
    
    #遍历xml文档
    for child in root:
        print(child.tag, child.attrib)   #attrib表示属性
        for i in child:
            print(i.tag,i.text,i.attrib)
    
    #只遍历year 节点
    for node in root.iter('year'):
        print(node.tag,node.text)

    代码执行结果如下:

     那么如何修改xml中的内容呢?

    import xml.etree.ElementTree as ET
    
    tree = ET.parse("xmltest.xml")
    root = tree.getroot()
    
    #修改
    for node in root.iter('year'):
        new_year = int(node.text) + 2   #year += 2
        node.text = str(new_year)
        node.set("updated_by","kobe")  #添加属性
    
    tree.write("xmltest.xml")

    可以看到修改后的xml文件如下:

    <data>
        <country name="Liechtenstein">
            <rank updated="yes">2</rank>
            <year updated_by="kobe">2012</year>
            <gdppc>141100</gdppc>
            <neighbor direction="E" name="Austria" />
            <neighbor direction="W" name="Switzerland" />
        </country>
        <country name="Singapore">
            <rank updated="yes">5</rank>
            <year updated_by="kobe">2015</year>
            <gdppc>59900</gdppc>
            <neighbor direction="N" name="Malaysia" />
        </country>
        <country name="Panama">
            <rank updated="yes">69</rank>
            <year updated_by="kobe">2015</year>
            <gdppc>13600</gdppc>
            <neighbor direction="W" name="Costa Rica" />
            <neighbor direction="E" name="Colombia" />
        </country>
    </data>

    同样的,我们也可以自己创建一个xml文件如下:

    import xml.etree.ElementTree as ET
    
    
    new_xml = ET.Element("personinfolist") #根节点
    personinfo = ET.SubElement(new_xml,"personinfo",attrib={"enrolled":"yes"})  #创建new_xml的子节点,子节点名为‘name’,子节点属性为"enrolled":"yes"
    name = ET.SubElement(personinfo,"name")
    name.text='han s'
    age = ET.SubElement(personinfo,"age",attrib={"checked":"no"})   #age是name的子节点
    sex = ET.SubElement(personinfo,"sex")
    age.text = '33'   #赋值
    personinfo2 = ET.SubElement(new_xml,"personinfo",attrib={"enrolled":"no"})
    name = ET.SubElement(personinfo2,"name")
    name.text='duan y'
    age = ET.SubElement(personinfo2,"age")
    age.text = '19'
    
    et = ET.ElementTree(new_xml) #生成文档对象
    et.write("test.xml", encoding="utf-8",xml_declaration=True)   #将创建的xml文件命名为"test.xml"
    
    ET.dump(new_xml) #打印生成的格式

     生成的xml文件如下:

    <personinfolist>
        <personinfo enrolled="yes">
            <age checked="no">33</age>
            <sex />
        </personinfo>
        <personinfo enrolled="no">
            <name>duan y</name>
            <age>19</age>
        </personinfo>
    </personinfolist>

    六、configparser模块

     configparser模块常用来生成和修改常见配置文件。

    如常用的配置文件示例如下:

    [DEFAULT]
    ServerAliveInterval = 45
    Compression = yes
    CompressionLevel = 9
    ForwardX11 = yes
    
    [bitbucket.org]
    User = hg
    
    [topsecret.server.com]
    Port = 50022
    ForwardX11 = no

    那么如何用configparser模块来创建这样一个配置文件呢?

    如下:

    import configparser
    
    config = configparser.ConfigParser()
    
    config["DEFAULT"] = {'ServerAliveInterval': '45',
                          'Compression': 'yes',
                         'CompressionLevel': '9'}
    
    config['bitbucket.org'] = {}
    config['bitbucket.org']['User'] = 'hg'
    
    config['topsecret.server.com'] = {}
    topsecret = config['topsecret.server.com']
    topsecret['Host Port'] = '50022'     # mutates the parser
    topsecret['ForwardX11'] = 'no'  # same here
    
    config['DEFAULT']['ForwardX11'] = 'yes'
    with open('example.ini', 'w') as configfile:
       config.write(configfile)

    那么,如果我们想要读取其中的某些内容该如何去做呢?

    import configparser
    
    conf=configparser.ConfigParser()
    conf.read('example.ini')
    
    print(conf.defaults())   #打印默认
    print(conf.sections())  #打印节点
    
    print(conf['bitbucket.org']['User'])
    
    sec=conf.remove_section('bitbucket.org')
    conf.write(open('example.cfg','w'))

    代码实现结果如下:

     七、hashlib模块

    hashlib模块用于实现加密相关的操作,主要提供SHA1,SHA224,SHA256,SHA384,SHA512,MD5算法。

    下面的例子实现了对某句话的加密。

    import hashlib
    m=hashlib.md5()  #md5加密
    m.update(b'hello')
    print(m.hexdigest())   #十六进制加密
    
    m.update(b'it is me')
    print(m.hexdigest())
    
    m2=hashlib.md5()
    m2.update(b'helloit is me')
    print(m2.hexdigest())
    
    s2=hashlib.sha1()
    s2.update(b'helloit is me')
    print(s2.hexdigest())

    代码运行结果如下:

     我们可以看到用SHA1加密算法加密后的结果要比用MD5加密要复杂。

     八、hmac模块

     hmac模块可使内部通过对我们创建key和内容再进行处理后加密。举例如下:

    import hmac
    h=hmac.new(b'123','长亭外,古道边,芳草碧连天'.encode(encoding='utf-8'))
    print(h.hexdigest())

    代码执行结果如下:

     我们可以看到这实现了加密的效果。

    九、re模块

     re模块用做正则表达式,一些常用的正则表达式符号可参见https://www.cnblogs.com/alex3714/articles/5161349.html

    我们在这里简单演示演示一下一些常见的匹配语法:

    >>> import re
    >>> re.match('^han','hanshuo')
    <_sre.SRE_Match object; span=(0, 3), match='han'>
    >>> re.match('^han','hanshuo').group()
    'han'
    >>> re.match('.','123ko').group()
    '1'
    >>> re.search('R[a-zA-Z]+a','chen123RongAhuaRonghua##').group()
    'RongAhuaRonghua'
    >>> re.search('#.+#','123#hello#').group()
    '#hello#'
    >>> re.search('(?P<id>[0-9]+)(?P<name>[a-zA-Z]+)','abcd1234daf@').group()
    '1234daf'
    >>> re.search('(?P<id>[0-9]+)(?P<name>[a-zA-Z]+)','abcd1234daf@').groupdict()
    {'id': '1234', 'name': 'daf'}
    >>> re.split('[0-9]','abc12de3fr45gh')   #分割
    ['abc', '', 'de', 'fr', '', 'gh']
    >>> re.sub('[0-9]+','1','abc52de3fr45gh',count=2)   #替换,count是替换次数
    'abc1de1fr45gh'
    
    
    
  • 相关阅读:
    ASP.NET 2.0 X64的奇怪问题
    【分享】从网上爬的WPF学习资料
    大家一起学习less 5:字符串插值
    大家一起学习less 3:命名空间
    我的模块加载系统 v18
    大家一起学习less 2:自带函数
    “计算机之子”的MVVM框架源码学习笔记
    我的MVVM框架 v0.1发布
    大家一起学习less 6:一些有用的混合函数
    less源码学习
  • 原文地址:https://www.cnblogs.com/sunny0824/p/11420452.html
Copyright © 2011-2022 走看看