• day06_20170604_常用模块/面向对象程序设计


    一、上节补充

    之前讲正则的时候涉及到的方法都是findall,也可以用search、match、split、sub等!

    search:

    import re
    print(re.search('e','alex like play'))#输出<_sre.SRE_Match object; span=(2, 3), match='e'>,是一个对象
    print(re.search('e','alex like play').group())#查看匹配的结果
    #注意:search...group和findall的使用对比:
    print(re.search('al(e)xslik(e)','alex like play').group())#输出结果:alex like,当group为0或者为空,代表默认匹配的全部
    print(re.findall('al(?:e)xslik(?:e)','alex like play'))#用findall默认匹配所有的,输出结果:['alex like']
    print(re.search('al(e)xslik(e)','alex like play').group(1))#输出结果为e,group里面写1,代表查第一个分组里面的e,如果写2,代表查第二个分组里面的e
    print(re.findall('al(e)xslik(e)','alex like play'))#就相当于findall的这种用法,返回: [('e', 'e')]

    match:

    import re
    print(re.match('e','alex like play'))#显示none,因为match默认是从头开始找,找不到就不往后找了,故报none
    print(re.match('a','alex like play'))#<_sre.SRE_Match object; span=(0, 1), match='a'>返回的是一个对象
    print(re.match('a','alex like play').group())#查看匹配的结果

    split:

    import re
    print(re.split('[ab]','abcd'))#输出结果:['', '', 'cd'] 以a或者b为分隔符,对abcd进行分割

    sub:

    import re
    print(re.sub('^a','A','alex like play'))#输出结果:Alex like play 把首字母换成大写,如果不加^,就是找到所有的a都换成大写的
    print(re.sub('^(w+)(s)(w+)(s)(w+)',r'52341','alex like play'))#输出结果:play like alex
    print(re.sub('^(w+)(s+)(w+)(s+)(w+)',r'52341','alex    like      play'))#输出结果:play    like      alex  多个空格的情况颠倒顺序输出,w+指的是至少匹配一个字母,s+指的是至少一个空格
    print(re.sub('^(w+)(W+)(w+)(W+)(w+)',r'52341','alex "  + = like  ----==    play'))#输出结果:play "  + = like  ----==    alex 含非字母字符

    注意:

    import re
    print(re.search('companies|company','my company is already done,all companies will be done').group())
    #“或”这种匹配,只要前面的匹配成功,那么就不会匹配下面的了。这个返回结果是company ,原因是 拿着companies和company和后面的每一个单词进行匹配,哪个先匹配,就不会再继续匹配下去了

    补充:

    import re
    #找出所有的数字
    print(re.findall(r'-?d+.?d*',"1-12*(60+(-40.35/5)-(-4*3))"))#输出结果:['1', '-12', '60', '-40.35', '5', '-4', '3']
    #匹配所有的整数
    print(re.findall(r'-?d+.d+|(-?d+)',"1-2*(60+(-40.35/5.3+1.2)-(-4*3))"))#输出结果:['1', '-2', '60', '', '', '', '-4', '3']

     二、常用模块

    1、时间模块

    time :时间分成三种时间-----1)时间戳  2)结构化的时间  3)格式化的字符串

    下面来看代码解说:

    时间戳:

    import time
    print(time.time())#输出结果:1501732321.0198536 1970年一直到今天经历过的秒数,通常给计算机用的
    

    结构化的时间(这种调的是对象的一种形式):

    import time
    print(time.localtime())#输出结果:time.struct_time(tm_year=2017, tm_mon=8, tm_mday=3, tm_hour=11, tm_min=54, tm_sec=42, tm_wday=3, tm_yday=215, tm_isdst=0)
    #如果是取具体的时间呢
    print(time.localtime().tm_year)
    print(time.gmtime())#跟time.localtime()的区别是:跟标准时间差了8小时,用法一样
    

    格式化的字符串  

    print(time.strftime('%Y-%m-%d %H:%M:%S'))#注意这个分隔符可以任意指定
    print(time.strftime('%Y-%m-%d %X'))
    

    这三种格式之间存在着一种转换的关系:因为你有可能拿到从计算机读出来的时间戳,时间戳对我来说又看不懂,所以有这种需要转换成你所需要的看得懂的格式的需求!

    如果是时间戳要想转成结构化的时间可以用localtime或者gmtime

    print(time.localtime(13213123))#随便输入的秒,以对象的形式输出,转成了结构化形式的时间
    print(time.localtime(time.time()))#把当前的时间戳转换成结构化的时间

    结构化的时间转换成时间戳

    print(time.mktime(time.localtime()))#把当前的结构化时间转换成时间戳,最后以秒的形式输出

    结构化的时间怎么转换成格式化的字符串

    print(time.strftime('%Y %X',time.localtime()))

    把格式化的字符串转换成结构化的时间

    print(time.strptime('2017-06-04 11:59:59','%Y-%m-%d %X'))

    注意:你在Linux里面看到过这种时间格式:Thu Aug  3 14:31:53 2017

    print(time.asctime())

    把时间戳转换成 %a %b %d %H %M %S %Y串

    print(time.ctime(123123123)) #输出结果:Mon Nov 26 08:52:03 1973

    把结构化的时间转换成%a %b %d %H %M %S %Y

    print(time.asctime(time.localtime()))#输出结果:Thu Aug  3 14:43:40 2017
    

    2、random模块

    做运维的你会发现,当你看日志的时候会有很多很多的ip,你会尝试着把ip禁掉。根据某一个时间点之内,他访问的次数超过一定次数,会把它加到IPtable里面,就是把他禁掉,这就是禁止别人爬虫你网站的行为!被你禁止掉之后,过一段时间,又有大量ip来了,他换了一个代理的ip地址,他是怎么做到的呢?首先他要有多个ip地址。看下面的代码,每次可以随机取一个ip:

    import random
    proxy_ip = [
        '1.1.1.1',
        '1.1.1.2',
        '1.1.1.3',
        '1.1.1.4',
    ]
    print(random.choice(proxy_ip))

    下面是random的一些方法简介:

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    import random
    print(random.random())#(0,1)----float  大于0且小于1之间的小数
    print(random.randint(1,3)) #[1,3]大于等于1且小于等于3之间的整数
    print(random.randrange(1,3))#[1,3)大于等于1且小于3之间的整数
    print(random.choice([1,'23',[4,5]]))#1或者23或者[4,5]
    print(random.sample([1,'23',[4,5]],2))#sample的意思是每次随机选出两个,拼成一个列表(列表元素任意2个组合)
    print(random.uniform(1,3))#大于1小于3的小数,如1.927109612082716
    
    item = [1,3,5,7,9]
    random.shuffle(item)#打乱item的顺序,相当于"洗牌"
    print(item)

    完成下面的需求,生成随机验证码:

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    import random
    def v_code(n=5):#定义一个验证码 比方说有五位
        res = ''#首先要有个空串,将循环的内容加进去
        for i in range(n):#有五位,for要循环五次,每一次要么选择一个数字要么选择一个字母
            num = random.randint(0,9)#每一次都要从字母或者数字里面随机拿出一个来
            s = chr(random.randint(65,90))#从26个英文字母里面随机拿出一个来
            add = random.choice([num,s])#每一次循环要么选择一个数字要么选择一个字母
            res+=str(add)#每循环一次添加一次
        return res
    print(v_code(6))#随意指定验证码的位数

     3、os模块

    我们在初始学习Python的时候,经常有同学将Python和shell联想到一起,怎么用Python完成shell脚本的那种功能呢?shell写脚本直接写的都是操作操作系统的命令!Python里面怎么样去操作你系统的命令呢?os模块可以帮你达到这种效果!

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    import os
    os.getcwd() #获取当前工作目录,即当前python脚本工作的目录路径
    os.chdir("dirname")  #改变当前脚本工作目录;相当于shell下cd
    os.curdir  #返回当前目录: ('.')
    os.pardir  #获取当前目录的父目录字符串名:('..')
    os.makedirs('dirname1/dirname2')    #可生成多层递归目录,相当于make -p
    os.removedirs('dirname1')    #若目录为空,则删除,并递归到上一级目录,如若也为空,则删除,依此类推
    os.mkdir('dirname')   # 生成单级目录;相当于shell中mkdir dirname
    os.rmdir('dirname')    #删除单级空目录,若目录不为空则无法删除,报错;相当于shell中rmdir dirname
    os.listdir('dirname')    #列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印
    os.remove()  #删除一个文件
    os.rename("oldname","newname")  #重命名文件/目录
    os.stat('path/filename') # 获取文件/目录信息
    os.sep    #输出操作系统特定的路径分隔符,win下为"\",Linux下为"/"
    os.linesep    #输出当前平台使用的行终止符,win下为"	
    ",Linux下为"
    "
    os.pathsep    #输出用于分割文件路径的字符串 win下为;,Linux下为:
    os.name    #输出字符串指示当前使用平台。win->'nt'; Linux->'posix'
    os.system("bash command") # 运行shell命令,直接显示
    os.environ  #获取系统环境变量

    有关路径:

    # os.path.abspath(path)  #返回path规范化的绝对路径
    print(os.path.abspath('a/b/c.txt'))#输出结果:D:python201720170423s17_zcday06ac.txt
    print(os.path.abspath('/a/b/c.txt'))#输出结果:D:ac.txt
    # os.path.split(path)  #将path分割成目录和文件名二元组返回
    print(os.path.split('E:\a\c.txt'))#输出结果:('E:\a', 'c.txt')
    # os.path.dirname(path)  #返回path的目录。其实就是os.path.split(path)的第一个元素
    print(os.path.dirname('E:\a'))#输出结果:E:
    # os.path.basename(path)  #返回path最后的文件名。如何path以/或结尾,那么就会返回空值。即os.path.split(path)的第二个元素
    print(os.path.basename('E:\a\a.txt'))#输出结果:a.txt
    # os.path.exists(path)  #如果path存在,返回True;如果path不存在,返回False
    print(os.path.exists('E:\a'))#输出结果:False
    print(os.path.exists('D:\python2017\20170423\s17_zc'))#输出结果:True
    # os.path.isabs(path)  #如果path是绝对路径,返回True
    print(os.path.isabs('E:\day06'))#输出结果:True
    print(os.path.isabs('day06'))#输出结果:False
    # os.path.isfile(path)  #如果path是一个存在的文件,返回True。否则返回False
    # os.path.isdir(path)  #如果path是一个存在的目录,则返回True。否则返回False
    # os.path.join(path1[, path2[, ...]]) # 将多个路径组合后返回,第一个绝对路径之前的参数将被忽略
    print(os.path.join('a','E://b','c.txt'))#输出结果:E://bc.txt
    print(os.path.join('a','b','c.txt'))#输出结果:ac.txt
    #注意:如果是Linux下,print(os.path.join('a','/b','c.txt')),输出 /b/c.txt
    # os.path.getatime(path)  #返回path所指向的文件或者目录的最后存取时间
    # os.path.getmtime(path)  #返回path所指向的文件或者目录的最后修改时间
    # os.path.getsize(path) #返回path的大小
    print(os.path.getsize(r'D:\python2017\20170423\s17_zc\day06\os模块.py'))#单位是字节,只能查看文件大小,不能查看目录大小

     有关路径规划:

    #在Linux和Mac平台上,该函数会原样返回path,在windows平台上会将路径中所有字符转换为小写,并将所有斜杠转换为饭斜杠。
    print(os.path.normcase('c:/windows\system32\'))#输出结果:'c:\windows\system32\'
    #规范化路径,如..和 /
    print(os.path.normpath('c://windows\System32\../Temp/'))#输出结果:'c:\windows\Temp'
    a = '/Users/jieli/test1/\a1/\\aa.py/../..'
    print(os.path.normpath(a))#输出结果:#/ Users / jieli / test1

    完成下面的需求:如何把项目目录加到环境变量里面去?比如你平时安装软件包的时候,下载下来的ngnix,安装完了之后会有个ngnix目录,ngnix算是你的项目目录,ngnix下面还有bin目录等文件!day06是项目路径,他下面有子文件spam.py,目录结构如图!在spam.py这个文件中实现把day06项目目录加入到环境变量中,怎么实现呢?以spam.py这个文件为基准,找到他的上级目录及上上级目录,然后把它变成一个绝对路径!

    代码实现:

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    import os,sys
    BASE_DIR=os.path.dirname(os.path.dirname((os.path.abspath(__file__))))
    sys.path.append(BASE_DIR)

    可不可以用其他方式实现呢?

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    import os,sys
    BASE_DIR = os.path.normpath(
        os.path.join(__file__,
        os.pardir,
        os.pardir)
    )
    print(BASE_DIR) #输出结果:D:python201720170423s17_zcday06

    4、sys模块

    sys模块.py里面代码如下:

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    import sys
    print(sys.argv)

    在命令行执行:

    python D:python201720170423s17_zcday06sys模块.py
    #输出结果:['D:\python2017\20170423\s17_zc\day06\sys模块.py']
    C:Userszhaichao.DS>python D:python201720170423s17_zcday06sys模块.py --host 192.168.1.3 --port 8080
    #输出结果:['D:\python2017\20170423\s17_zc\day06\sys模块.py', '--host', '192.168.1.3','--port', '8080']

    这样argv就可以根据索引取元素了:print(sys.argv[0])

    sys模块的其他使用:

    1 sys.argv           命令行参数List,第一个元素是程序本身路径
    2 sys.exit(n)        退出程序,正常退出时exit(0)
    3 sys.version        获取Python解释程序的版本信息
    4 sys.maxint         最大的Int值
    5 sys.path           返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值
    6 sys.platform       返回操作系统平台名称

    进度条实现:

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    import sys
    import time
    for i in range(100):
        sys.stdout.write('
    %s' %('#'*i))#
    表示跳到行首,第一次打印了一个※,不换行,跳到行首,第二次打印的※覆盖第一次的※,有打印两个※
        sys.stdout.flush()#把打印的结果刷出来
        time.sleep(0.5)
    #注意:如果打印不出来,在终端执行

    5、shutil模块

     主要对文件进行处理,比如拷贝、压缩等!

     1)将文件内容拷贝到另一个文件中

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    import shutil
    shutil.copyfileobj(open('test.py','r'), open('test1.py', 'w'))

    另一种拷贝方式:

    import shutil
    shutil.copyfile('test.py', 'test2.py')#目标文件无需存在,如果存在,执行之后,内容会覆盖

    2)仅拷贝权限。内容、组、用户均不变

    import shutil
    shutil.copymode('f1.log', 'f2.log') #目标文件必须存在

    3)仅拷贝状态的信息,包括:mode bits, atime, mtime, flags

    import shutil
    shutil.copystat('f1.log', 'f2.log') #目标文件必须存在

    4)拷贝文件和权限

     import shutil
     shutil.copy('f1.log', 'f2.log')

    5)拷贝文件和状态信息

    import shutil
    shutil.copy2('f1.log', 'f2.log')

    6)递归的去拷贝文件夹

    import shutil
    shutil.copytree('folder1', 'folder2', ignore=shutil.ignore_patterns('*.pyc', 'tmp*')) #目标目录不能存在,注意对folder2目录父级目录要有可写权限,ignore的意思是排除 
    import shutil
    shutil.copytree('f1', 'f2', symlinks=True, ignore=shutil.ignore_patterns('*.pyc', 'tmp*'))
    #通常的拷贝都把软连接拷贝成硬链接,即对待软连接来说,创建新的文件
    拷贝软连接

    7)递归的去删除文件

     import shutil
     shutil.rmtree('folder1')

    8)递归的去移动文件,它类似mv命令,其实就是重命名。

    import shutil
    shutil.move('folder1', 'folder3')

    9)创建压缩包并返回文件路径,例如:zip、tar

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    import shutil
    #将D:Python201720170423s17_zcday06bb下的文件打包放置当前程序目录 shutil.make_archive(
    "data_bak", 'gztar', root_dir=r'D:python201720170423s17_zcday06bb')#data_bak打完包之后的包名,gztar指的是压缩的格式,tar包,用gz压缩的结果,root_dir指要压缩的路径

    注意:如果将 D:python201720170423s17_zcday06bb下的文件打包放置 /tmp/目录,怎么操作?

    import shutil
    shutil.make_archive("/tmp/data_bak", 'gztar', root_dir=r'D:python201720170423s17_zcday06bb')

    base_name: 压缩包的文件名,也可以是压缩包的路径。只是文件名时,则保存至当前目录,否则保存至指定路径,

    如 data_bak =>保存至当前路径
    如:/tmp/data_bak =>保存至/tmp/

    format: 压缩包种类,“zip”, “tar”, “bztar”,“gztar”

    root_dir: 要压缩的文件夹路径(默认当前目录)

    owner: 用户,默认当前用户

    group: 组,默认当前组

    logger: 用于记录日志,通常是logging.Logger对象

    10)shutil 对压缩包的处理是调用 ZipFile 和 TarFile 两个模块来进行的,详细:

    tarFile归档和解压缩

    #归档
    import tarfile
    t = tarfile.open('egon.tar','w')
    t.add(r'D:python201720170423s17_zcday06os模块.py',arcname='a.bak')
    t.add(r'D:python201720170423s17_zcday06
    andom模块.py',arcname='b.bak')
    t.close()
    
    #解压缩
    import tarfile
    t = tarfile.open('data_bak.tar.gz','r')
    t.extractall('extract_dir')
    t.close()

    为什么要加arcname呢?如下面加与不加的对比图,如果不加那么在解压的时候连根目录就解压出来了,如果加了,显示如下红框:

    zipfile压缩与解压

    import zipfile
    #压缩
    z = zipfile.ZipFile('laxi.zip','w')
    z.write('a.log')
    z.write('data.data')
    z.close()
    
    #解压
    z = zipfile.ZipFile('laxi.zip','r')
    z.extractall(path='.')
    z.close()

    6、json&pickle模块----这两个模块是用来进行序列化的。

    什么是序列化?

      程序的运行是在内存中运行的!内存有什么特点?断电数据就没了。下次再运行还得重头再来,所以要把内存的数据存下来,永久的存下来只能网硬盘里面存,那就涉及到一个问题:把内存的数据网硬盘里面存,躺在硬盘上的都是二进制,但是你操作硬盘都是以什么单位来操作的!在程序级别,你以什么操作硬盘---文件处理!文件是操作系统给你提供的一个抽象的概念,来帮你操作硬盘,那文件里面存的又是什么---字符串!也就是说你内存里面的数据要想保存到文件里面,文件里面存得又是字符串,那内存里面有没有可能有字典、列表,这些东西都保存成字符串的形式,你在取出来会有什么问题?再取出来还是字符串的形式,就没有原来的数据结构的概念了!所以你要存一定要把数据结构存下来,那现在就矛盾了。内存当中是数据结构,文件里面是字符串,最终肯定还是要保存成字符串,那怎么解决这个问题?这就需要加一个中间过程!在内存的数据往硬盘转的过程来一个中间环节,把内存中的数据按照某种固定的格式转换成字符串,取的时候再按照这种固定的格式转换成我想要的字典、字符串、列表!

      你玩过vmware么?里面有个做快照的功能:当你虚拟机运行到一定状态的时候,里面有大量的数据肯定不只字符串,你做快照实际上就是把他们保存到硬盘中,他是按照某种固定的格式转成字符串的,读的时候也是按照某种格式转成字典等我想要看的格式。----这就叫序列化!

    为什么要序列化?

    1、持久保存状态  2、跨平台数据交互: 比如你们公司是可能全用Python么,这种状况非常少,你用Python单独做了一个功能,那你怎样和其他的Java程序对接呢?一个语言写的程序与另外一种语言写的程度对接,对接的只能是数据,我程序处理完的数据交给你,数据一定是有结构的,比如列表、元组、字典,Python的字典Java能识别么?不能!所以需要把Python的字典转成一种通用的格式,Java跟Python都认识,转完这格式之后就可以把他交给Java了,Java在按照同样的格式在转成Java能认识的字典!

    如何序列化之json和pickle:如果要跨平台要用Python,如果想Python自己玩,就用pickle

    有同学会有疑问,这种不是可以用我们之前学过的一个函数eval来实现么?比如下面的场景,写入文件之前是字典,读出来还是字典:

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    dic = {
        'name':'alex',
        'age':9000,
        'height':'150cm',
    }
    # with open('a.txt','w') as f:
    #     f.write(str(dic))
    with open('a.txt','r') as f:
        res = eval(f.read())
        print(res,type(res))

    再看下面的代码:

    x = "[null,true,false,1]"#比方说从文件里面已经读出字符串
    eval(x)#用eval进行转化,结果报NameError: name 'null' is not defined

    改成如下 呢?

    x = "[None,True,1]"
    res = eval(x)
    print(res,type(res))
    #eval是把这个字符串里内容读出来相当于在当前位置执行一下,得到的就是一个列表,不能读出我想要的结果
    #我能不能用他根据第几个值来取值?----是可以的?
    print(res[1],type(res))

    思考:那为什么上面[null,true,false,1]的不行?因为Python里面没有null这个类型!说明eval不支持统一的这种格式!那我们可以用json这个模块来实现!

    import json
    x = "[null,true,false,1]"
    res = json.loads(x)
    print(res)
    输出结果:
    [None, True, False, 1]

    [null,true,false,1]---->[None, True, False, 1]把Python不认识的格式转换成了Python识别的格式!如下是json对应的Python的类型:

    上面的json达到的目的是否可以用picked完成呢?不能----说明pickle无法读出此种类型,因为他只识别Python类型

    import pickle
    x = "[null,true,false,1]"
    res = pickle.loads(x)
    print(res)
    #输出结果:TypeError: a bytes-like object is required, not 'str' 要求是bytes的,不能是字符串的

    那下面我们看看这两个模块是怎么玩的?

    首先来Jason序列化的过程:dic ----->res=json.dumps(dic)---->f.write(res)

    import json
    dic = {
        'name':'alex',
        'age':9000,
        'height':'150cm'
    }
    res = json.dumps(dic)
    print(res,type(res))#直接加上你要序列化的对象
    #输出结果:{"height": "150cm", "age": 9000, "name": "alex"} <class 'str'>
    #json.dumps实际上就是把字典转换成一个字符串,但这个字符串是json能识别的字符串
    #拿到这个字符串我就可以写到文件里面去了!
    with open('a.json','w') as f:
        f.write(res)

    下面怎么把数据读出来呢?反序列话的过程:f.read()---->res=json.loads(res)---->dic=res

    with open('a.json','r') as f:
        dic = json.loads(f.read())
        print(dic,type(dic))
    #输出结果:{'height': '150cm', 'age': 9000, 'name': 'alex'} <class 'dict'>
        print(dic['name'])
    #输出结果:alex

     上面的过程比较麻烦,我们还可以用下面的json便捷操作(转换的同时往文件里面写):

    import json
    dic = {
        'name':'alex',
        'age':9000,
        'height':'150cm'
    }
    json.dump(dic,open('b.json','w'))
    #以上把dic存入到文件,下面对文件内容的输出
    import  json
    res = json.load(open('b.json','r'))
    print(res,type(res))

    7、pickle模块

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    import pickle
    dic = {
        'name':'alex',
        'age':13
    }
    print(pickle.dumps(dic))
    #输出结果:b'x80x03}qx00(Xx04x00x00x00nameqx01Xx04x00x00x00alexqx02Xx03x00x00x00ageqx03K
    u.'

    与json不同的是,他转换成了bytes类型,要是往文件里面写该怎么写?

    with open('a.pkl','wb') as f:
        f.write(pickle.dumps(dic))#查看写进去的内容都是bytes格式的

    写完之后如何读出来呢?

    with open('a.pkl','rb') as f:
        d = pickle.loads(f.read())
        print(d,type(d))
        #输出结果:{'age': 13, 'name': 'alex'} <class 'dict'>

    跟json一样,是否存在更简单的dump和load方法呢?

    import pickle
    dic = {
        'name':'alex',
        'age':13
    }
    pickle.dump(dic,open('b.pkl','wb'))
    #以上为写文件,以下为读文件
    
    res = pickle.load(open('b.pkl','rb'))
    print(res,type(res))
    #输出结果:{'name': 'alex', 'age': 13} <class 'dict'>

    pickle的特殊之处在于可以序列化Python任意的对象。

    比如我想把下面的函数给序列化:

    def func():
        print('from func')

    首先来看json是否能实现?---说明不能实现,json不支持Python的函数类型

    import json
    def func():
        print('from func')
    json.dumps(func)
    #报错TypeError: <function func at 0x00A36738> is not JSON serializable

    那来看看pickle呢?---说明支持

    import pickle
    def func():
        print('from func')
    f = pickle.dumps(func)
    print(f)#输出结果:b'x80x03c__main__
    func
    qx00.'

    既然支持,那我们就可以完成下列对函数func序列化操作:

    def func():
        print('from func')
    import pickle
    pickle.dump(func,open('c.pkl','wb'))

    进行反序列化:

    def func():
        print('from func')
    import pickle
    res = pickle.load(open('c.pkl','rb'))
    res()#可以进行执行,输出结果:from func
    print(res)#获取函数的地址:<function func at 0x005B6738>

    如果我单独创建Python文件,进行反序列化会有什么问题呢?----报错

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    
    import pickle
    pickle.load(open('c.pkl','rb'))
    #报错:AttributeError: Can't get attribute 'func' on <module '__main__' from 'D:/python2017/20170423/s17_zc/day06/pickle模块/pickle反序列化.py'>

    如果我反序列化的是字典、元组等,是不会有问题的。但是我现在序列化的是函数,那现在就有问题了!为什么?

      序列化的时候从这句pickle.dump(func,open('c.pkl','wb'))开始序列化的,func是内存地址,这个内存地址指向内存一段函数,我的值是那个函数,现在序列化的是函数名,相当于把内存地址给dump下来了,现在文件一运行完,内存地址确实是保存下来了,但函数的数据从内存中已经释放掉了,然后你在另外一个文件中反序列化,你是往内存中加载了一个内存地址,这个内存地址试图要找到他对应的那个数据,但那块数据已经没了。所以现在出错了。那在原文件为什么没问题呢?实际上也是有问题的,比如我把func函数定义去掉,也是报相同的问题。

      对于其他普通的数据类型为什么没问题呢?因为类似于字典、元组,你序列化的是他们本身的数据类型,所以在反序列化的时候相当于在内存中重新定义了一下数据类型。

    8、shelve模块

    这个模块也和序列化有关,和pickle是一样的,可以序列化所有的Python类型,但是他只是换了一种形式进行序列化。

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    
    import shelve
    f = shelve.open(r'sheve.txt')
    f['student1'] = {'name':'egon','age':18,'height':'180cm'}
    print(f['student1'])#输出结果:{'age': 18, 'height': '180cm', 'name': 'egon'}
    print(f['student1']['name'])#输出结果:egon
    f.close()

    9、xml模块

    xml是实现不同语言或程序之间进行数据交换的协议,跟json差不多,但json使用起来更简单,不过,古时候,在json还没诞生的黑暗年代,大家只能选择用xml呀,至今很多传统公司如金融行业的很多系统的接口还主要是xml。

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

    <?xml version="1.0"?>
    <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>
        <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>

    xml协议在各个语言里的都 是支持的,在python中可以用以下模块操作xml:

    # print(root.iter('year')) #全文搜索,不考虑层级结构
    # print(root.find('country')) #在root的子节点找,只找一个
    # print(root.findall('country')) #在root的子节点找,找所有

    首先来看iter,把所有的year遍历出来

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    import xml.etree.ElementTree as ET
    
    tree = ET.parse("a")#先解析一下这个文件
    root = tree.getroot()#获取根节点,就是a.xml里面的 data
    #print(root.iter('year'))#返回的是一个迭代器<_elementtree._element_iterator object at 0x010F8ED0>
    for i in root.iter('year'):
        #print(i)#全文搜索year
        print(i.tag,i.text,i.attrib)
        #tag指的是标签 text指的是标签中间加的内容 attrib指的是标签中可以设置的属性,最后返回的结果是字典格式的,没有属性所以返回空字典
    输出结果:
    year 2008 {}
    year 2011 {}
    year 2011 {}

    来看find:

    import xml.etree.ElementTree as ET
    
    tree = ET.parse("a")#先解析一下这个文件
    root = tree.getroot()#获取根节点,就是a.xml里面的 data
    print(root.find('year'))#输出结果:None 因为他是在root下面的子节点找
    print(root.find('country'))
    #输出结果:<Element 'country' at 0x00868EA0>
    #这么多country,怎么证明输出的是第一个呢?
    print(root.find('country').attrib)#输出结果:{'name': 'Liechtenstein'} 看到这个属性和a.xml对比,发现是第一个

    最后看findall:

    import xml.etree.ElementTree as ET
    
    tree = ET.parse("a")#先解析一下这个文件
    root = tree.getroot()#获取根节点,就是a.xml里面的 data
    print(root.findall('country'))
    输出结果:
    [<Element 'country' at 0x00768EA0>, <Element 'country' at 0x00766030>, <Element 'country' at 0x00766120>]

    怎么样来遍历整个xml文档:---遍历是指把每个节点及子节点都取出来

    import xml.etree.ElementTree as ET
    
    tree = ET.parse("a")#先解析一下这个文件
    root = tree.getroot()#获取根节点,就是a.xml里面的 data
    
    for country in root:#遍历根节点下的子节点country
        print('======>',country.attrib['name'])
        for item in country:#遍历country下面的子节点
            #print(item)
            print(item.tag,item.text,item.attrib)

    需求:把所有国家的年加1,同时加上属性update=yes ,最终达到效果:<year updated="yes">2011</year>

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    import xml.etree.ElementTree as ET
    
    tree = ET.parse("a")#先解析一下这个文件
    root = tree.getroot()#获取根节点,就是a.xml里面的 data
    for year in root.iter('year'):
        year.text=str(int(year.text)+1)
        year.set('update','yes')
    #注意执行之后只是在内存中进行了修改,要是写到文件里面得加上下面的:
    tree.write('b.xml')#这样就会新生出b.xml文件,如果想在原文件改,

    如何删除大于2010年的year,怎么操作呢?

    import xml.etree.ElementTree as ET
    
    tree = ET.parse("a")#先解析一下这个文件
    root = tree.getroot()#获取根节点,就是a.xml里面的 data
    for country in root:#首先找到year的父节点
        print(country)
        print('=====>',country.find('year'))#找到每个country节点下面的year
        year = country.find('year')
        if int(year.text) > 2010:
            country.remove(year)
    tree.write('d.xml')

    如何添加一个节点呢?比如添加如下节点:

    <year2 update="yes">2012</year2></country>
    import xml.etree.ElementTree as ET
    
    tree = ET.parse("a")#先解析一下这个文件
    root = tree.getroot()#获取根节点,就是a.xml里面的 data
    for country in root:#首先找到year的父节点
        year = country.find('year')
        if int(year.text) > 2010:
            year2 = ET.Element('year2')
            year2.text = str(int(year.text)+1)
            year2.set('update','yes')
            country.append(year2)
    tree.write('e.xml')

    自己创建xml文档:

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    import xml.etree.ElementTree as ET
    
    new_xml = ET.Element("namelist")#获取初始节点,拿到对象new_xml
    name = ET.SubElement(new_xml, "name", attrib={"enrolled": "yes"})#往new_xml这个对象里面添加子节点,名称为name,属性为"enrolled": "yes"
    age = ET.SubElement(name, "age", attrib={"checked": "no"})
    sex = ET.SubElement(name, "sex")
    sex.text = '33'
    name2 = ET.SubElement(new_xml, "name", attrib={"enrolled": "no"})
    age = ET.SubElement(name2, "age")
    age.text = '19'
    
    et = ET.ElementTree(new_xml)  # 生成文档对象
    et.write("test.xml", encoding="utf-8", xml_declaration=True)
    
    ET.dump(new_xml)  # 打印生成的格式

    10、hashlib模块

     哈希模块主要是做文本校验的。说一下他的根本的用法:

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    import hashlib
    m = hashlib.md5()
    m.update('23fdsfafdfdf你愁啥啊'.encode('utf-8'))
    print(m.hexdigest())#输出结果:798b394282d42108c05afb576bc589a4

    哈希的特点:

    1.只要你校验的内容相同则hash运算结果相同,内容稍微改变则hash值则变
    2.不可逆推
    3.相同算法:无论校验多长的数据,得到的哈希值长度固定。

    怎么样来校验一个文件呢?如果文件内容变了,最后的校验结果也 变了

    import hashlib
    m = hashlib.md5()
    with open(r'D:python201720170423s17_zcday06data_bak.tar.gz','rb') as f:
        for line in f:
            m.update(line)
        md5_num = m.hexdigest()
    print(md5_num)#输出结果:c9e35048b2ca373718df07af3ad1a7d5

    以上加密算法虽然依然非常厉害,但有时候存在缺陷,即:通过撞库可以反解。所以,有必要对加密算法中添加自定义key再来做加密。 下面的代码比md5更安全一些。但是也有方法将其破解掉--撞库

    import hashlib
    s = hashlib.sha256()
    s.update('helloword'.encode('utf-8'))
    print(s.hexdigest())#输出结果:0b322d15ea034793a8646baa1744ffacbdf7f959b66c68970f032c4ac8b9c8cb

    模拟撞库破解密码

    import hashlib
    #首先得有个密码列表
    passwds=[
        'alex3714',
        'alex1313',
        'alex94139413',
        'alex123456',
        '123456alex',
        'a123lex',
        ]
    def make_passwd_dic(passwds):
        dic={}
        for passwd in passwds:
            m=hashlib.md5()
            m.update(passwd.encode('utf-8'))
            dic[passwd]=m.hexdigest() #以密码作为key,以最后校验的结果做为value
        return dic#返回密码字典
    
    def break_code(cryptograph,passwd_dic):
        for k,v in passwd_dic.items():#for循环这个字典
            if v == cryptograph:#用value和这个校验结果走比对
                print('密码是===>33[46m%s33[0m' %k)#比对成功就把字典的key返回,就是铭文密码
    
    cryptograph='aee949757a2e698417463d47acac93df'
    break_code(cryptograph,make_passwd_dic(passwds))

    python 还有一个 hmac 模块,它内部对我们创建 key 和 内容 进行进一步的处理然后再加密:

    import hmac
    h = hmac.new('egon123456'.encode('utf-8'))
    h.update('alex3714'.encode('utf-8'))
    print(h.hexdigest())#这个密码的加密是由egon123456和alex3714共同加密而成,所以如果撞库解密的话也得先解出egon123456,在解出alex3714

    密码加盐的方式,提高了校验的安全性。

    11、subprocess模块

    之前我们学习了一个可以执行命令的模块是os。但是os有问题:

    执行完os直接就给我返回结果了!那你执行命令直接就在当前位置显示么?xshell是远程连到服务器,执行命令是把命令传给服务端了,命令是在服务端执行!如果像这种形式直接在服务端执行了,那结果就拿不到了直接显示在服务端了,在哪执行结果就在哪显示,但xshell在服务端执行,把结果返回给你,在当前显示。也就是说你必须要有一种处理手段,可以执行命令,然后把结果保存下来,然后控制往任意位置发送,这就用到了subprocess模块

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    import subprocess
    res = subprocess.Popen('dir',shell=True,stdout=subprocess.PIPE)#第一个参数写上你的命令,第二个你要写上shell=True,第三个stdout表示标准输出结果
    #stdout=subprocess.PIPE指的是:我把结果不往终端扔了,扔到一个管道里面去,放到那里暂存着
    #print(res)#输出结果;<subprocess.Popen object at 0x00265C50> 是一个对象
    #放到管道里面了,怎么拿出来呢?
    #print(res.stdout.read())#从管道里面把结果拿出来然后打印到pycharm终端,是bytes格式,所以得decode
    print(res.stdout.read().decode('gbk'))
    输出结果:
     驱动器 D 中的卷没有标签。
     卷的序列号是 46A8-3AFF
    
     D:python201720170423s17_zcday06subprocess模块 的目录
    
    2017/08/09  16:48    <DIR>          .
    2017/08/09  16:48    <DIR>          ..
    2017/08/09  16:48               650 test.py
                   1 个文件            650 字节
                   2 个目录 62,803,075,072 可用字节

    一旦我输入的是个错误的命令呢?

    import subprocess
    res = subprocess.Popen('dirweewf',shell=True,stderr=subprocess.PIPE,stdout=subprocess.PIPE)
    #print(res.stderr.read())#输出的是bytes
    #stderr=subprocess.PIPE指的是一旦命令出错,就把结果扔到这个错误的管道里面去
    print(res.stderr.read().decode('gbk'))
    输出结果:
    'dirweewf' 不是内部或外部命令,也不是可运行的程序
    或批处理文件。

    在window上实现找到day06下面的txt文件

    pycharm实现:

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    import subprocess
    res = subprocess.Popen(r'dir D:python201720170423s17_zcday06 | findstr txt*',shell=True,stderr=subprocess.PIPE,stdout=subprocess.PIPE)
    print(res.stdout.read().decode('gbk'))
    #输出结果:2017/08/07  17:26                48 a.txt

    上面还可以这样实现:

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    import subprocess
    res1 = subprocess.Popen(r'dir D:python201720170423s17_zcday06',shell = True,stdout=subprocess.PIPE)
    #print(res1.stdout.read())
    res = subprocess.Popen(r'findstr txt*',shell=True,stdin = res1.stdout,stderr=subprocess.PIPE,stdout=subprocess.PIPE)
    print(res.stdout.read().decode('gbk'))
    #输出结果:2017/08/07  17:26                48 a.txt
    print(res.stdout.read().decode('gbk'))#在重复执行,已经取不到内容。注意,你把内容丢到管道里,取出过一次,管道里面已经没有了,你在取也是空的。管道只能取一次内容
     

    应用场景:爬虫是从网上不断的爬网页,网页就是一堆代码,把静态内容拿下来,进行正则表达式匹配,从这一堆内容中拿出你想要的结果。那会了这个过程以后,可以把这个过程设计成这种形式:专门一个程序负责爬,爬的结果stdout交给第二个程序的stdin。这两个程序交互型:一程序负责不断的爬,把爬出的结果交到他自己的输出管道里面,另一个程序负责从他的管道里取出来自己进行处理,模拟的就是这个管道的过程!

     12、logging模块

    ======》介绍

    日志级别对应的数字:

    CRITICAL = 50
    FATAL = CRITICAL
    ERROR = 40
    WARNING = 30
    WARN = WARNING
    INFO = 20
    DEBUG = 10
    NOTSET = 0
    可在logging.basicConfig()函数中可通过具体参数来更改logging模块默认行为,可用参数有
    filename:用指定的文件名创建FiledHandler(后边会具体讲解handler的概念),这样日志会被存储在指定的文件中。
    filemode:文件打开方式,在指定了filename时使用这个参数,默认值为“a”还可指定为“w”。
    format:指定handler使用的日志显示格式。
    datefmt:指定日期时间格式。
    level:设置rootlogger(后边会讲解具体概念)的日志级别
    stream:用指定的stream创建StreamHandler。可以指定输出到sys.stderr,sys.stdout或者文件,默认为sys.stderr。若同时列出了filename和stream两个参数,则stream参数会被忽略。
    
    
    format参数中可能用到的格式化串:
    %(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用户输出的消息

    ======》使用

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    import logging
    logging.basicConfig(filename='access.log',#指定filename就是把日志写到文件里去,如果不指定,就是直接往终端输出
                        format='%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s',#format指的是输出日志的格式
                        datefmt='%Y-%m-%d %H:%M:%S %p',#3datefmt指定时间格式
                        level=10)#level指的是日志的级别,10代表debug 除了数字代表,还可以写成 level = logging.DEBUG
    logging.debug('debug')
    logging.info('info')
    logging.warning('warning')
    logging.error('error')
    logging.critical('critical')

    输出结果如图:

     

    总结:

    一:如果不指定filename,则默认打印到终端
    二:指定日志级别:
        指定方式:
            1:level=10
            2:level=logging.ERROR
    
        日志级别种类:
            CRITICAL = 50
            FATAL = CRITICAL
            ERROR = 40
            WARNING = 30
            WARN = WARNING
            INFO = 20
            DEBUG = 10
            NOTSET = 0
    
    三:指定日志级别为ERROR,则只有ERROR及其以上级别的日志会被打印

    日志格式

    %(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

    用户输出的消息

    
    

    13、configparser模块

    你平时可能遇到如下格式的配置文件:a.ini ,内容如下

    # 注释1
    ; 注释2
    
    [section1]
    k1 = v1
    k2:v2
    user=egon
    age=18
    is_admin=true
    salary=31
    db = pymysql+mysql://egon:123@192.168.2.3/db1
    enable = false [section2] k1
    = v1

    这个文件,我要读他的内容,该怎么读呢?

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    import configparser
    config = configparser.ConfigParser()
    config.read('a.ini')
    print(config.sections())#把标题输出来['section1', 'section2']
    print(config.options('section1'))#输出第一个标题下面的key['k1', 'k2', 'user', 'age', 'is_admin', 'salary']
    print(config.items('section1'))#输出第一个标题下面的key和value[('k1', 'v1'), ('k2', 'v2'), ('user', 'egon'), ('age', '18'), ('is_admin', 'true'), ('salary', '31')]

    现在只想取a.ini中db的值,取出age的整数值,取enable的布尔值,该怎么取呢?

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    import configparser
    config = configparser.ConfigParser()
    config.read('a.ini')
    print(config.get('section1','db'))#输出结果:pymysql+mysql://egon:123@192.168.2.3/db1
    print(config.getint('section1','age'))#输出结果:18 取出的直接是整型
    print(config.getboolean('section1','enable'))#输出结果:False 布尔值类型

    如何对a.ini进行改写

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    import configparser
    
    config=configparser.ConfigParser()
    config.read('a.cfg')
    
    
    #删除整个标题section2,删除之后这个标题下面所有的配置就都没了
    config.remove_section('section2')
    
    #删除标题section1下的某个k1和k2
    config.remove_option('section1','k1')
    config.remove_option('section1','k2')
    
    #判断是否存在某个标题
    print(config.has_section('section1'))
    
    #判断标题section1下是否有user
    print(config.has_option('section1','enable'))
    
    
    #添加一个标题
    config.add_section('egon')
    
    #在标题egon下添加name=egon,age=18的配置
    config.set('egon','name','egon')
    config.set('egon','age',18) #报错,必须是字符串
    
    
    #最后将修改的内容写入文件,完成最终的修改,否则只是在内存中进行修改
    config.write(open('a.cfg','w'))

    三、软件开发规范

    一般写一个程序,目录结构如图:

    start.py内容:

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    import os,sys
    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    sys.path.append(BASE_DIR)
    from core import main
    if __name__ == '__main__':
        main.run()

    settings.py内容:

    x = 1
    y = 2

    main.py

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    
    import sys
    print(sys.path)#查看的是start.py里面定义的sys path
    from conf import settings#是否可以引用settings.py的配置
    def run():
        print('from main')
        print('配置文件内容',settings.x,settings.y)

    四、类与对象

    1、什么是面向对象的程序设计及为什么要有它?

    面向过程的程序设计:核心是过程二字,过程指的是解决问题的步骤,即先干什么再干什么......面向过程的设计就好比精心设计好一条流水线,是一种机械式的思维方式。

    优点是:复杂度的问题简单化、流程化

    缺点是:一套流水线或者流程就是用来解决一个问题,生产汽水的流水线无法生产汽车,即便是能,也得是大改,改一个组件,牵一发而动全身。

    应用场景:一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以及Apache HTTP Server等。

    面向对象的程序设计:核心是对象二字,(要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。面向对象的程序设计好比如来设计西游记,如来要解决的问题是把经书传给东土大唐,如来想了想解决这个问题需要四个人:唐僧,沙和尚,猪八戒,孙悟空,每个人都有各自的特征和技能(这就是对象的概念,特征和技能分别对应对象的数据属性和方法属性),然而这并不好玩,于是如来又安排了一群妖魔鬼怪,为了防止师徒四人在取经路上被搞死,又安排了一群神仙保驾护航,这些都是对象。然后取经开始,师徒四人与妖魔鬼怪神仙交互着直到最后取得真经。如来根本不会管师徒四人按照什么流程去取),对象是特征与技能的结合体,基于面向对象设计程序就好比在创造一个世界,你就是这个世界的上帝,存在的皆为对象,不存在的也可以创造出来,比较面向过程,面向对象更加注重对现实世界的模拟,是一种“上帝式”的思维方式。

    优点是:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。

    缺点:

    1. 一些扩展性要求低的场景使用面向对象会徒增编程难度,比如管理linux系统的shell脚本就不适合用面向对象去设计,面向过程反而更加适合。

    2.可控性差, 无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法预测最终结果。于是我们经常看到一个游戏人某一参数的修改极有可能导致阴霸的技能出现,一刀砍死3个人,这个游戏就失去平衡。

    应用场景:需求经常变化的软件,一般需求的变化都集中在用户层(跟人有关的软件需求经常变化),互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方,面向对象的程序设计并不是全部。对于一个软件质量来说,面向对象的程序设计只是用来解决扩展性。

    2、类和对象

    1)什么是对象,什么是类

    基于面向对象设计一款游戏:英雄联盟,每个玩家选一个英雄,每个英雄都有自己的特征和技能,特征即数据属性,技能即方法属性,特征与技能的结合体就是一个对象。 

    从一组对象中提取相似的部分就是类,类是所有对象都具有的特征和技能的结合体。 

    2)在现实生活当中,是先有对象,还是先有类?

    先有的一个个具体存在的对象(比如一个具体存在的人),还是先有的人类这个概念,这个问题需要分两种情况去看

    在现实世界中:先有对象,再有类------------->世界上肯定是先出现各种各样的实际存在的物体,然后随着人类文明的发展,人类站在不同的角度总结出了不同的种类,如人类、动物类、植物类等概念。也就说,对象是具体的存在,而类仅仅只是一个概念,并不真实存在

    在程序中:务必保证先定义类,后产生对象------------->这与函数的使用是类似的,先定义函数,后调用函数,类也是一样的,在程序中需要先定义类,后调用类。不一样的是,调用函数会执行函数体代码返回的是函数体执行的结果,而调用类会产生对象,返回的是对象

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    #定义类的语法
    class Foo:
        '''文档注释'''
        pass
    class Chinese:
        country = 'China'
        def talk(self):
            print('talking')
    p = Chinese()#如果程序中没有类,是无法产生对象的
    print(p)#<__main__.Chinese object at 0x0078A7F0>产生了一个Chinese的object

    类里面可以定义变量名,函数名,实际上还可以是一切可执行的代码,这个以后再讲。定义完了类,怎么用呢?有两种用法:1)属性的引用 2)实例化

    提示:Python的class术语与c++有一定区别,与modula-3更像。

    类的属性引用

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    
    class Chinese:
        country = 'China'
        def talk(self):
            print('talking',self)
    #属性的引用
    print(Chinese.country)#输出China
    print(Chinese.talk)#输出结果:<function Chinese.talk at 0x007A1150>
    Chinese.talk('123')#输出结果:talking 123 形参传值
    Chinese.x =1 #往名称空间里面放了一个x
    print(Chinese.x)#输出结果:1
    Chinese.country=123233333333333#改一下country的结果
    print(Chinese.country)#输出结果:123233333333333

    实例化

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    
    class Chinese:
        country = 'China'
        def talk(self):
            print('talking',self)
    #实例化
    p1 = Chinese()
    print(p1)#得到具体的中国人
    #对象的使用:只有一种,就是属性引用,属性引用包含调用类的变量,调用类的函数,找到函数可以执行函数,这都是属性引用,类还有一个特别的地方,就是创建一个具体存在的对象,但是对象不能再继续实例化,创建出别的对象,

    每一个中国人都会有自己的特性,可以用def __init__()

    class Chinese:
        country = 'China'
        def __init__(self,name,age,sex):# Chinese.__init__(p1,'egon','18','male'),把对象本身当成参数传给自己
            # p1.Name = name;p1.Age=age,p1.Sex=sex
            self.Name = name
            self.Age = age
            self.Sex = name
        def talk(self):
            print('talking',self)
    #实例化
    p1 = Chinese('egon','18','male')#实际上调的是Chinese.__init__(),所以需要三个参数,否则报错
    p2 = Chinese('alex','9000','female')
    #print(p1)#得到具体的中国人
    #调属于自己独有的特征
    print(p1.Name)
    print(p1.Age)
    print(p1.Sex)
    print(p2.Name)
    print(p2.Age)
    print(p2.Sex)
    #调用共有的特征
    print(p1.country)
    print(p2.country)
    p1.talk()

    3、类与对象的名称空间以及绑定方法

    class Chinese:
        country = 'China'
        def __init__(self,name,age,sex):
            self.country=123123#print(p1.country)先找自己的,后找类里面的
            self.Name = name
            self.Age = age
            self.Sex = name
        def talk(self):
            print('%s is talking'%self.Name)
    #类名称空间
    print(Chinese.__init__)#就是一个普通函数
    #怎么查看类的名称空间呢?
    print(Chinese.__dict__)#内置方法__dict__
    #对象的空间
    p1 = Chinese('egon','18','male')
    print(p1.__dict__)#属性分为数据属性和函数属性,对象本身只有数据属性;而类拥有数据属性和函数属性
    print(p1.Age)#p1.__dict__['Age'] p1.Age是以字符串形式当做key,去调用他对应的值
    print(p1.country)#自己的字典里面有就先找自己的,如果没有就找类里面定义的,如果类定义的也没有,就不在找全局的了

    绑定方法:

    class Chinese:
        country = 'China'
        def __init__(self,name,age,sex):
            self.country=123123#print(p1.country)先找自己的,后找类里面的
            self.Name = name
            self.Age = age
            self.Sex = name
        def talk(self):
            print('%s is talking'%self.Name)
    
    p1 = Chinese('egon','18','male')
    p2 = Chinese('alex','180','female')
    #发现调用类的数据属性时候,返回的地址空间是一样的
    print(p1,id(p1.country))#返回<__main__.Chinese object at 0x0062B070> 6226720
    print(p2,id(p2.country))#返回<__main__.Chinese object at 0x0062B090> 6226720
    #调用类的函数属性时候,返回的地址空间是不一样的,谁调用的就绑定谁
    print(Chinese.talk)#返回<function Chinese.talk at 0x00621150>
    print(p1.talk)#返回<bound method Chinese.talk of <__main__.Chinese object at 0x0062B070>>
    print(p2.talk)#返回<bound method Chinese.talk of <__main__.Chinese object at 0x0062B090>>

    但凡在类内部定义的,没有装饰器的函数,对于类来说是函数属性,对于对象来说都是绑定方法,类调用的时候就是普通函数,有几个参数传几个参数,而对象调用的时候就是绑定方法,把自己当做参数传进去。

    ----------over

  • 相关阅读:
    29Mybatis_整合ehcache以及应用场景
    28Mybatis_查询缓存-二级缓存-二级缓存测试-
    27Mybatis_一级缓存的实际应用场景
    解析分布式锁之Zookeeper实现(一)
    程序员的十年工作创业血泪史,万字长文,仔细读完,受益匪浅
    2018 Java线程热门面试题,你知道多少?
    看阿里P9架构师如何向你定义架构及架构师
    程序员30 岁之后:如何实现质的突破?
    强者自救,圣者渡人
    干货:通过双十一等项目实战看架构技术
  • 原文地址:https://www.cnblogs.com/zhaic/p/7234182.html
Copyright © 2020-2023  润新知