软件的开发规范

第十八章

一. 软件的开发规范

什么是开发规范?为什么要有开发规范呢?

​ 你现在包括之前写的一些程序,所谓的'项目',都是在一个py文件下完成的,代码量撑死也就几百行,

真正的后端开发的项目,系统等,少则几万行代码,多则十几万,几十万行代码,

你全都放在一个py文件中行么?当然你可以说,只要能实现功能即可。

​ 软件开发,规范 项目 目录结构,代码规范,遵循PEP8规范等等, 更加清晰,合理的开发。

博客园系统的作业举例,

将我们之前在一个py文件中的所有代码,整合成规范的开发。

目录结构(简化版):

image-20190808184433407

py文件的具体代码如下:

status_dic = {
    'username': None,
    'status': False,
}
flag = True

def login():
    i = 0
    with open('register', encoding='utf-8') as f1:
        dic = {i.strip().split('|')[0]: i.strip().split('|')[1] for i in f1}
    while i < 3:
        username = input('请输入用户名:').strip()
        password = input('请输入密码:').strip()
        if username in dic and dic[username] == password:
            print('登录成功')
            return True
        else:
            print('用户名密码错误,请重新登录')
            i += 1


def register():
    with open('register', encoding='utf-8') as f1:
        dic = {i.strip().split('|')[0]: i.strip().split('|')[1] for i in f1}
    while 1:
        print('33[1;45m 欢迎来到注册页面 33[0m')
        username = input('请输入用户名:').strip()
        if not username.isalnum():
            print('33[1;31;0m 用户名有非法字符,请重新输入 33[0m')
            continue
        if username in dic:
            print('33[1;31;0m 用户名已经存在,请重新输入 33[0m')
            continue
        password = input('请输入密码:').strip()
        if 6 <= len(password) <= 14:
            with open('register', encoding='utf-8', mode='a') as f1:
                f1.write(f'
{username}|{password}')
            status_dic['username'] = str(username)
            status_dic['status'] = True
            print('33[1;32;0m 恭喜您,注册成功!已帮您成功登录~ 33[0m')
            return True
        else:
            print('33[1;31;0m 密码长度超出范围,请重新输入 33[0m')


def auth(func):
    def inner(*args, **kwargs):
        if status_dic['status']:
            ret = func(*args, **kwargs)
            return ret
        else:
            print('33[1;31;0m 请先进行登录 33[0m')
            if login():
                ret = func(*args, **kwargs)
                return ret

    return inner


@auth
def article():
    print(f'33[1;32;0m 欢迎{status_dic["username"]}访问文章页面33[0m')


@auth
def diary():
    print(f'33[1;32;0m 欢迎{status_dic["username"]}访问日记页面33[0m')


@auth
def comment():
    print(f'33[1;32;0m 欢迎{status_dic["username"]}访问评论页面33[0m')


@auth
def enshrine():
    print(f'33[1;32;0m 欢迎{status_dic["username"]}访问收藏页面33[0m')


def login_out():
    status_dic['username'] = None
    status_dic['status'] = False
    print('33[1;32;0m 注销成功 33[0m')


def exit_program():
    global flag
    flag = False
    return flag


choice_dict = {
    1: login,
    2: register,
    3: article,
    4: diary,
    5: comment,
    6: enshrine,
    7: login_out,
    8: exit_program,
}


while flag:
    print('''
    欢迎来到博客园首页
    1:请登录
    2:请注册
    3:文章页面
    4:日记页面
    5:评论页面
    6:收藏页面
    7:注销
    8:退出程序''')

    choice = input('请输入您选择的序号:').strip()
    if choice.isdigit():
        choice = int(choice)
        if 0 < choice <= len(choice_dict):
            choice_dict[choice]()
        else:
            print('33[1;31;0m 您输入的超出范围,请重新输入 33[0m')

    else:
        print('33[1;31;0m 您您输入的选项有非法字符,请重新输入 33[0m')

此时我们是将所有的代码都写到了一个py文件中,

如果代码量多且都在一个py文件中,那么对于代码结构不清晰,不规范,运行起来效率也会非常低。

所以我们接下来一步一步的修改:

  1. 程序配置.

image-20190808183553328

你项目中所有的有关文件的操作出现几处,都是直接写的register相对路径,

如果说这个register注册表路径改变了,或者你改变了register注册表的名称,那么相应的这几处都需要一一更改,这样其实你就是把代码写死了,

那么怎么解决?

我要统一相同的路径,也就是统一相同的变量,在文件的最上面写一个变量指向register注册表的路径,代码中如果需要这个路径时,直接引用即可。

image-20190808183612322

  1. 划分文件

image-20190808183637363

一个项目的函数不能只是这些,我们只是举个例子,这个小作业函数都已经这么多了,那么要是一个具体的实际的项目,函数会非常多,所以我们应该将这些函数进行分类, 然后 分文件而治。在这里我划分了以下几个文件:

settings.py: 配置文件,

就是放置一些项目中需要的静态参数,比如文件路径,数据库配置,软件的默认设置等等

类似于我们作业中的这个:

image-20190808183653259

common.py:公共组件文件,

这里面放置一些我们常用的公共组件函数,并不是我们核心逻辑的函数,而更像是服务于整个程序中的公用的插件,程序中需要即调用。比如我们程序中的装饰器auth,有些函数是需要这个装饰器认证的,但是有一些是不需要这个装饰器认证的,它既是何处需要何处调用即可。比如还有密码加密功能,序列化功能,日志功能等这些功能都可以放在这里。

image-20190808183706876

src.py:这个文件主要存放的就是核心逻辑功能,你看你需要进行选择的这些核心功能函数,都应该放在这个文件中。

image-20190808183722810

start.py:项目启动文件。你的项目需要有专门的文件启动,而不是在你的核心逻辑部分进行启动的,有人对这个可能不太理解,

为什么还要设置一个单独的启动文件呢?

  #### 目的就是放在显眼的位置,方便开启。

你想想你的项目这么多py文件,如果src文件也有很多,那么到底哪个文件启动整个项目,你还得一个一个去寻找,太麻烦了,这样我把它单独拿出来,就是方便开启整个项目。

那么我们写的项目开启整个项目的代码就是下面这段:

image-20190808183736591

你把这些放置到一个文件中也可以,但是没有必要,我们只需要一个命令或者一个开启指令就行,就好比我们开启电视只需要让人很快的找到那个按钮即可,对于按钮后面的一些复杂的线路板,我们并不关心,所以我们要将上面这个段代码整合成一个函数,

开启项目的''按钮''就是此函数的执行即可。

image-20190808183749056

这个按钮要放到启动文件start.py里面。

除了以上这几个py文件之外还有几个文件,也是非常重要的:

类似于register文件:这个文件文件名不固定,register只是我们项目中用到的注册表,但是这种文件就是存储数据的文件,类似于文本数据库,那么我们一些项目中的数据有的是从数据库中获取的,有些数据就是这种文本数据库中获取的,总之,你的项目中有时会遇到将一些数据存储在文件中,与程序交互的情况,所以我们要单独设置这样的文件。

log文件:log文件顾名思义就是存储log日志的文件。日志我们一会就会讲到,日志主要是供开发人员使用。比如你项目中出现一些bug问题,

比如开发人员对服务器做的一些操作都会记录到日志中,以便开发者浏览,查询。

至此,我们将这个作业原来的两个文件,合理的划分成了6个文件,但是还是有问题的,如果我们的项目很大,你的每一个部分相应的你一个文件存不下的,比如你的src主逻辑文件,函数很多,你是不是得分成:src1.py src2.py?

你的文本数据库register这个只是一个注册表,如果你还有个人信息表,记录表呢? 如果是这样,你的整个项目也是非常凌乱的:

image-20190808183851177

3. 划分具体目录

​ 上面看着就非常乱了,那么如何整改呢?

所以我们这可以整多个文件夹,分别管理不同的物品,那么标准版本的目录结构就来了:

为什么要设计项目目录结构?

"设计项目目录结构",就和"代码编码风格"一样,属于个人风格问题。对于这种风格上的规范,一直都存在两种态度:

  1. 一类同学认为,这种个人风格问题"无关紧要"。理由是能让程序work就好,风格问题根本不是问题。
  2. 另一类同学认为,规范化能更好的控制程序结构,让程序具有更高的可读性。

我是比较偏向于后者的,因为我是前一类同学思想行为下的直接受害者。我曾经维护过一个非常不好读的项目,其实现的逻辑并不复杂,但是却耗费了我非常长的时间去理解它想表达的意思。

从此我个人对于提高项目可读性、可维护性的要求就很高了。"项目目录结构"其实也是属于"可读性和可维护性"的范畴,我们设计一个层次清晰的目录结构,就是为了达到以下两点:

  1. 可读性高: 不熟悉这个项目的代码的人,一眼就能看懂目录结构,知道程序启动脚本是哪个,测试目录在哪儿,配置文件在哪儿等等。从而非常快速的了解这个项目。
  2. 可维护性高: 定义好组织规则后,维护者就能很明确地知道,新增的哪个文件和代码应该放在什么目录之下。这个好处是,随着时间的推移,代码/配置的规模增加,项目结构不会混乱,仍然能够组织良好。

所以,我认为,保持一个层次清晰的目录结构是有必要的。更何况组织一个良好的工程目录,其实是一件很简单的事儿。

image-20190808183906562

上面那个图片就是较好的目录结构。

二. 按照项目目录结构,规范博客园系

  1. 配置start.py文件

我们首先要配置启动文件,启动文件很简答就是将项目的启动执行放置start.py文件中,运行start.py文件可以成功启动项目即可。 那么项目的启动就是这个指令run() 我们把这个run()放置此文件中不就行了?

image-20190808184513416

这样你能执行这个项目么?肯定是不可以呀,你的starts.py根本就找不到run这个变量,肯定是会报错的。

NameError: name 'run' is not defined 本文件肯定是找不到run这个变量也就是函数名的,不过这个难不倒我们,我们刚学了模块, 另个一文件的内容我们可以引用过来。但是你发现import run 或者 from src import run 都是报错的。为什么呢? 骚年,遇到报错不要慌!

我们说过你的模块之所以可以引用,那是因为你的模块肯定在这个三个地方:内存,内置,sys.path里面,那么core在内存中肯定是没有的,也不是内置,而且sys.path也不可能有,因为sys.path只会将你当前的目录(bin)加载到内存,所以你刚才那么引用肯定是有问题的,那么如何解决?内存,内置你是左右不了的,你只能将core的路径添加到sys.path中,这样就可以了。

import sys
sys.path.append(r'D:lnh.pythonpy project	eaching_showlogcore')
from src import run
run()

这样虽然解决了,但是你不觉得有问题么?你现在从这个start文件需要引用src文件,那么你需要手动的将src的工作目录添加到sys.path中,那么有没有可能你会引用到其他的文件?比如你的项目中可能需要引用conf,lib等其他py文件,那么在每次引用之前,或者是开启项目时,全部把他们添加到sys.path中么?

sys.path.append(r'D:lnh.pythonpy project	eaching_showlogcore')
sys.path.append(r'D:lnh.pythonpy project	eaching_showlogconf')
sys.path.append(r'D:lnh.pythonpy project	eaching_showlogdb')
sys.path.append(r'D:lnh.pythonpy project	eaching_showloglib')

这样是不是太麻烦了? 我们应该怎么做?我们应该把项目的工作路径添加到sys.path中,用一个例子说明:你想找张三,李四,王五,赵六等人,这些人全部都在一栋楼比如在汇德商厦,那么我就告诉你汇德商厦的位置:北京昌平区沙河镇汇德商厦。 你到了汇德商厦你在找具体这些人就可以了。所以我们只要将这个blog项目的工作目录添加到sys.path中,这样无论这个项目中的任意一个文件引用项目中哪个文件,就都可以找到了。所以:

import sys
sys.path.append(r'D:lnh.pythonpy project	eaching_showlog')
from core.src import run
run()

上面还是差一点点,你这样写你的blog的路径就写死了,你的项目不可能只在你的电脑上,项目是共同开发的,你的项目肯定会出现在别人电脑上,那么你的路径就是问题了,在你的电脑上你的blog项目的路径是上面所写的,如果移植到别人电脑上,他的路径不可能与你的路径相同, 这样就会报错了,所以我们这个路径要动态获取,不能写死,所以这样就解决了:

import sys
import os
# sys.path.append(r'D:lnh.pythonpy project	eaching_showlog')
print(os.path.dirname(__file__))
# 获取本文件的绝对路径  # D:/lnh.python/py project/teaching_show/blog/bin
print(os.path.dirname(os.path.dirname(__file__)))
# 获取父级目录也就是blog的绝对路径  # D:/lnh.python/py project/teaching_show/blog
BATH_DIR = os.path.dirname(os.path.dirname(__file__))
sys.path.append(BATH_DIR)
from core.src import run
run()

那么还差一个小问题,这个starts文件可以当做脚本文件进行直接启动,如果是作为模块,被别人引用的话,按照这么写,也是可以启动整个程序的,这样合理么?这样是不合理的,作为启动文件,是不可以被别人引用启动的,所以我们此时要想到 __name__了:

import sys
import os
# sys.path.append(r'D:lnh.pythonpy project	eaching_showlog')
# print(os.path.dirname(__file__))
# 获取本文件的绝对路径  # D:/lnh.python/py project/teaching_show/blog/bin
# print(os.path.dirname(os.path.dirname(__file__)))
# 获取父级目录也就是blog的绝对路径  # D:/lnh.python/py project/teaching_show/blog
BATH_DIR = os.path.dirname(os.path.dirname(__file__))
sys.path.append(BATH_DIR)
from core.src import run

if __name__ == '__main__':
    run()

这样,我们的starts启动文件就已经配置成功了。以后只要我们通过starts文件启动整个程序,它会先将整个项目的工作目录添加到sys.path中,然后在启动程序,这样我整个项目里面的任何的py文件想引用项目中的其他py文件,都是你可以的了。

  1. 配置settings.py文件。

接下来,我们就会将我们项目中的静态路径,数据库的连接设置等等文件放置在settings文件中。

我们看一下,你的主逻辑src中有这样几个变量:

status_dic = {
    'username': None,
    'status': False,
}
flag = True
register_path = r'D:lnh.pythonpy project	eaching_showlog
egister'

我们是不是应该把这几个变量都放置在settings文件中呢?不是!setttings文件叫做配置文件,其实也叫做配置静态文件

什么叫静态? 静态就是一般不会轻易改变的,但是对于上面的代码status_dic ,flag这两个变量,由于在使用这个系统时会时长变化,所以不建议将这个两个变量放置在settings配置文件中,只需要将register_path放置进去就可以。

register_path = r'D:lnh.pythonpy project	eaching_showlog
egister'

image-20190808185147957

但是你将这个变量放置在settings.py之后,你的程序启动起来是有问题,为什么?

with open(register_path, encoding='utf-8') as f1:
NameError: name 'register_path' is not defined

因为主逻辑src中找不到register_path这个路径了,所以会报错,那么我们解决方式就是在src主逻辑中引用settings.py文件中的register_path就可以了。

image-20190808185220737

这里引发一个问题:为什么你这样写就可以直接引用settings文件呢?我们在starts文件中已经说了,刚已启动blog文件时,我们手动将blog的路径添加到sys.path中了,这就意味着,我在整个项目中的任何py文件,都可以引用到blog项目目录下面的任何目录:bin,conf,core,db,lib,log这几个,所以,刚才我们引用settings文件才是可以的。

  1. 配置common.py文件

接下来,我们要配置我们的公共组件文件,在我们这个项目中,装饰器就是公共组件的工具,我们要把装饰器这个工具配置到common.py文件中。先把装饰器代码剪切到common.py文件中。这样直接粘过来,是有各种问题的:

image-20190808185244851

所以我们要在common.py文件中引入src文件的这两个变量。

image-20190808185313895

可是你的src文件中使用了auth装饰器,此时你的auth装饰器已经移动位置了,所以你要在src文件中引用auth装饰器,这样才可以使用上

image-20190808185333682

image-20190808185348147

OK,这样你就算是将你之前写的模拟博客园登录的作业按照规范化目录结构合理的完善完成了,最后还有一个关于readme文档的书写。

关于readme的内容

每个项目都应该有的一个文件,目的是能简要描述该项目的信息,让读者快速了解这个项目。

它需要说明以下几个事项:

  1. 软件定位,软件的基本功能。
  2. 运行代码的方法: 安装环境、启动命令等。
  3. 简要的使用说明。
  4. 代码目录结构说明,更详细点可以说明软件的基本原理。
  5. 常见问题说明。

在软件开发初期,由于开发过程中以上内容可能不明确或者发生变化,并不是一定要在一开始就将所有信息都补全。但是在项目完结的时候,是需要撰写这样的一个文档的。

第十九章

一.re模块

1.什么是正则?

 正则就是用一些具有特殊含义的符号组合到一起(称为正则表达式)来描述字符或者字符串的方法。或者说:正则就是用来描述一类事物的规则。(在Python中)它内嵌在Python中,并通过 re 模块实现。正则表达式模式被编译成一系列的字节码,然后由用 C 编写的匹配引擎执行。

元字符 匹配内容
w 匹配字母(包含中文)或数字或下划线
W 匹配非字母(包含中文)或数字或下划线
s 匹配任意的空白符
S 匹配任意非空白符
d 匹配数字
D 匹配非数字
A 从字符串开头匹配
z 匹配字符串的结束,如果是换行,只匹配到换行前的结果
匹配一个换行符
匹配一个制表符
^ 匹配字符串的开始
$ 匹配字符串的结尾
. 匹配任意字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符。
[...] 匹配字符组中的字符
[...](https://guobaoyuan.gitee.io/book/19-0 re.html#fn_...) 匹配除了字符组中的字符的所有字符
* 匹配0个或者多个左边的字符。
+ 匹配一个或者多个左边的字符。
匹配0个或者1个左边的字符,非贪婪方式。
{n} 精准匹配n个前面的表达式。
{n,m} 匹配n到m次由前面的正则表达式定义的片段,贪婪方式
ab 匹配a或者b
() 匹配括号内的表达式,也表示一个组

------------------------------------------------匹配模式----------------------------------------------------

我们现在配合着正则表达式来进行测试

1.字符串的常用操作:一对一匹配

s1 = 'meet郭宝元'
print(s1.find('宝元'))

2.正则匹配

w 匹配中文,字母,数字,下划线

import re
name = "宝元-meet_123 "
print(re.findall("w",name))
#结果
['宝', '元', 'm', 'e', 'e', 't', '_', '1', '2', '3']

W 不匹配中文,字母,数字,下划线

import re
name = "宝元-meet_123 "
print(re.findall("W",name))
# 结果
['-',' ']

s 匹配任意的空白符

import re
name = "宝元-meet_123 "
print(re.findall("s",name))
# 结果
[' ']

S 匹配不是任意的空白符

import re
name = "宝元-meet_123 "
print(re.findall("s",name))
# 结果
['宝', '元', '-', 'm', 'e', 'e', 't', '_', '1', '2', '3']

d 匹配数字

import re
name = "宝元-meet_123 "
print(re.findall("d",name))
# 结果
['1', '2', '3']

D 匹配非数字

import re
name = "宝元-meet_123 "
print(re.findall("D",name))
# 结果
['宝', '元', '-', 'm', 'e', 'e', 't', '_', ' ']

A 与 ^ 从字符串开头匹配

import re
name = "宝元-meet_123 "
print(re.findall("A宝元",name))
# 结果
['宝元']

import re
name = "宝元-meet_123 "
print(re.findall("A宝元",name))
# 结果
['宝元']

 与 z 与 $ 字符串结尾匹配

import re
name = "宝元-meet_123 "
print(re.findall("123 ",name))
# 结果
['123 ']

import re
name = "宝元-meet_123 "
print(re.findall("123 ",name))
# 结果
['123 ']

import re
name = "宝元-meet_123 "
print(re.findall("123 $",name))
# 结果
['123 ']

匹配换行符合制表符

import re
name = "宝元-meet_123	 
"
print(re.findall("
",name))
# 结果
['
']

import re
name = "宝元-meet_123	 
"
print(re.findall("	",name))
# 结果
['	']

------------------------------------------------匹配方式----------------------------------------------------

. 匹配任意字符(换行符除外)

import re
name = "宝元-meet_123	 
"
print(re.findall(".",name))
# 结果
['宝', '元', '-', 'm', 'e', 'e', 't', '_', '1', '2', '3', '	', ' ']

. 匹配任意字符

import re
name = "宝元-meet_123	 
"
print(re.findall(".",name,re.DOTALL))
# 结果
['宝', '元', '-', 'm', 'e', 'e', 't', '_', '1', '2', '3', '	', '
']

? 匹配?前元素0个或1个

import re
name = "m-e-me-meet-meet_123	 
"
print(re.findall("me?",name))
# 结果
['m', 'me', 'me', 'me']

***** 匹配 * 前面元素0个或多个 [贪婪匹配]

import re
name = "m-e-me-meet-meet_123	 
"
print(re.findall("*",name))
# 结果
['m', 'me', 'mee', 'mee']

+ 匹配 +前面元素1个或多个 [贪婪匹配]

import re
name = "m-e-me-meet-meet_123	 
"
print(re.findall("me+",name))
# 结果
['me', 'mee', 'mee']

{n,m} 匹配n到m个元素

import re
name = "m-e-me-meet-meet_123	 
"
print(re.findall("e{1,2}",name))
# 结果
['e', 'e', 'ee', 'ee']

.* 任意内容0个或多个

import re
name = "m-e-me-meet-meet_123	 
"
print(re.findall(".*",name))
# 结果
['m-e-me-meet-meet_123', '']

.*? 任意内容0个或1个

import re
name = "m-e-me-meet-meet_123"
print(re.findall("m.*?e",name))
# 结果
['m-e', 'me', 'mee', 'mee']

import re
name = "m-e-me-meet-meet_123"
print(re.findall("m.?e",name))
# 结果
['m-e', 'me', 'mee', 'mee']

[] 获取括号中的内容

import re
name = "m-e-me-meet-meet_123"
print(re.findall("[1-9]",name))
# 结果
['1', '2', '3']
# []中的-是什么至什么不会匹配-

import re
name = "m-e-me-meet-meet_123"
print(re.findall("[a-z]",name))
# 结果
['m', 'e', 'm', 'e', 'm', 'e', 'e', 't', 'm', 'e', 'e', 't']

import re
name = "m-e-me-meet-meet_123"
print(re.findall("[A-z]",name))
# 结果
['m', 'e', 'm', 'e', 'm', 'e', 'e', 't', 'm', 'e', 'e', 't', '_']
# 是按照ascii码表位进行匹配的

import re
name = "m-e-me-meet-meet_123"
print(re.findall("[a-zA-Z]",name))
# 结果
['m', 'e', 'm', 'e', 'm', 'e', 'e', 't', 'm', 'e', 'e', 't']

import re
name = "m-e-me-meet-meet_123"
print(re.findall("[^A-z]",name))
# 结果
['-', '-', '-', '-', '1', '2', '3']
# [^A-z] 有上尖号就是取反,获取不是字母和特定的几个字符

如果想要匹配到-,就需要进行如下操作(将-号放到最前面)
import re
name = "m-e-me-meet-meet_123"
print(re.findall("[-+*/]",name))
# 结果
['-', '-', '-', '-']

练习

有如下字符串:'alex_sb ale123_sb wu12sir_sb wusir_sb ritian_sb' 的 alex wusir '

找到所有带_sb的内容

() 分组 定制一个匹配规则

import re
print(re.findall('(.*?)_sb', 'alex_sb wusir_sb 日天_sb'))
# 结果
['alex', ' wusir', ' 日天']

# 应用举例:
print(re.findall('href="(.*?)"','<a href="http://www.baidu.com">点击</a>')
# 结果
['http://www.baidu.com']

| 匹配 左边或者右边

import re
print(re.findall('alex|宝元|wusir', 'alex宝元wusiraleeeex宝宝元odlb'))
# 结果
['alex', '宝元', 'wusir', '宝元']

import re
print(re.findall('compan(day|morrow)','Work harder today than yesterday, and the day after tomorrow will be better'))
# 结果
['day', 'morrow']

import re
print(re.findall('compan(?:day|morrow)','Work harder today than yesterday, and the day after tomorrow will be better'))
# 结果
['today', 'tomorrow']
# 分组() 中加入?: 表示将整体匹配出来而不只是()里面的内容。

------------------------------------------------常用方法----------------------------------------------------

findall 全部找到返回一个列表

import re
print(re.findall("alex","alexdsb,alex_sb,alexnb,al_ex"))
# 结果
['alex', 'alex', 'alex']

search 从字符串中任意位置进行匹配查找到一个就停止了,返回的是一个对象. 获取匹配的内容必须使用.group()进行获取

import re
print(re.search("sb|nb","alexdsb,alex_sb,alexnb,al_ex").group())
# 结果
sb

match 从字符串开始位置进行匹配

import re
print(re.match('meet', 'meet alex wusir 日天').group())
# 结果
meet

import re
print(re.match('alex', 'meet alex wusir 日天'))
# 结果
None

split 分隔 可按照任意分隔符进行分隔

import re
print(re.split('[ ::,;;,]','alex wusir,日天,太白;女神;肖锋:吴超'))
# 结果
['alex', 'wusir', '日天', '太白', '女神', '肖锋', '吴超']

sub 替换

import re
print(re.sub('barry', 'meet', 'barry是最好的讲师,barry就是一个普通老师,请不要将barry当男神对待。'))
# 结果
meet是最好的讲师,meet就是一个普通老师,请不要将meet当男神对待。

compile 定义匹配规则

import re
obj = re.compile('d{2}')
print(obj.findall("alex12345"))
# 结果
['12', '34']

import re
['12', '34']
obj = re.compile('d{2}')
print(obj.search("alex12345").group())
# 结果
12

finditer 返回一个迭代器

import re
g = re.finditer('al',"alex_alsb,al22,aladf")
print(next(g).group())
print([i.group() for i in g])
# 结果
al
['al','al','al']

给分组起名字

import re
ret = re.search("<(?P<tag_name>w+)>w+</w+>","<h1>hello</h1>")
print(ret.group("tag_name"))
print(ret.group())
# 结果
h1
<h1>hello</h1>

import re
ret = re.search(r"<(w+)>w+</1>","<h1>hello</h1>")
print(ret.group(1))
print(ret.group())

相关练习:

1 "1-2*(60+(-40.35/5)-(-4*3))"
1.1 匹配所有的整数
print(re.findall('d+',"1-2*(60+(-40.35/5)-(-4*3))"))
1.2 匹配所有的数字(包含小数)
print(re.findall(r'd+.?d*|d*.?d+', "1-2*(60+(-40.35/5)-(-4*3))"))
1.3 匹配所有的数字(包含小数包含负号)
print(re.findall(r'-?d+.?d*|d*.?d+', "1-2*(60+(-40.35/5)-(-4*3))"))

2,匹配一段你文本中的每行的邮箱
http://blog.csdn.net/make164492212/article/details/51656638 匹配所有邮箱

3,匹配一段你文本中的每行的时间字符串 这样的形式:'1995-04-27'

s1 = '''
时间就是1995-04-27,2005-04-27
1999-04-27 老男孩教育创始人
老男孩老师 alex 1980-04-27:1980-04-27
2018-12-08
'''
print(re.findall('d{4}-d{2}-d{2}', s1))

4 匹配 一个浮点数
print(re.findall('d+.d*','1.17'))

5 匹配qq号:腾讯从10000开始:
print(re.findall('[1-9][0-9]{4,}', '2413545136'))

s1 = '''
<div id="cnblogs_post_body" class="blogpost-body"><h3><span style="font-family: 楷体;">python基础篇</span></h3>
<p><span style="font-family: 楷体;">&nbsp; &nbsp;<strong><a href="http://www.cnblogs.com/guobaoyuan/p/6847032.html" target="_blank">python 基础知识</a></strong></span></p>
<p><span style="font-family: 楷体;"><strong>&nbsp; &nbsp;<a href="http://www.cnblogs.com/guobaoyuan/p/6627631.html" target="_blank">python 初始python</a></strong></span></p>
<p><span style="font-family: 楷体;"><strong>&nbsp; &nbsp;<strong><a href="http://www.cnblogs.com/guobaoyuan/articles/7087609.html" target="_blank">python 字符编码</a></strong></strong></span></p>
<p><span style="font-family: 楷体;"><strong><strong>&nbsp; &nbsp;<a href="http://www.cnblogs.com/guobaoyuan/articles/6752157.html" target="_blank">python 类型及变量</a></strong></strong></span></p>
<p><span style="font-family: 楷体;"><strong>&nbsp; &nbsp;<a href="http://www.cnblogs.com/guobaoyuan/p/6847663.html" target="_blank">python 字符串详解</a></strong></span></p>
<p><span style="font-family: 楷体;">&nbsp; &nbsp;<strong><a href="http://www.cnblogs.com/guobaoyuan/p/6850347.html" target="_blank">python 列表详解</a></strong></span></p>
<p><span style="font-family: 楷体;"><strong>&nbsp; &nbsp;<a href="http://www.cnblogs.com/guobaoyuan/p/6850496.html" target="_blank">python 数字元祖</a></strong></span></p>
<p><span style="font-family: 楷体;">&nbsp; &nbsp;<strong><a href="http://www.cnblogs.com/guobaoyuan/p/6851820.html" target="_blank">python 字典详解</a></strong></span></p>
<p><span style="font-family: 楷体;"><strong>&nbsp; &nbsp;<strong><a href="http://www.cnblogs.com/guobaoyuan/p/6852131.html" target="_blank">python 集合详解</a></strong></strong></span></p>
<p><span style="font-family: 楷体;"><strong>&nbsp; &nbsp;<a href="http://www.cnblogs.com/guobaoyuan/articles/7087614.html" target="_blank">python 数据类型</a>&nbsp;</strong></span></p>
<p><span style="font-family: 楷体;"><strong>&nbsp; &nbsp;<a href="http://www.cnblogs.com/guobaoyuan/p/6752169.html" target="_blank">python文件操作</a></strong></span></p>
<p><span style="font-family: 楷体;"><strong>&nbsp; &nbsp;<a href="http://www.cnblogs.com/guobaoyuan/p/8149209.html" target="_blank">python 闭包</a></strong></span></p>
<p><span style="font-family: 楷体;"><strong>&nbsp; &nbsp;<a href="http://www.cnblogs.com/guobaoyuan/articles/6705714.html" target="_blank">python 函数详解</a></strong></span></p>
<p><span style="font-family: 楷体;"><strong>&nbsp; &nbsp;<a href="http://www.cnblogs.com/guobaoyuan/articles/7087616.html" target="_blank">python 函数、装饰器、内置函数</a></strong></span></p>
<p><span style="font-family: 楷体;"><strong>&nbsp; &nbsp;<a href="http://www.cnblogs.com/guobaoyuan/articles/7087629.html" target="_blank">python 迭代器 生成器</a>&nbsp;&nbsp;</strong></span></p>
<p><span style="font-family: 楷体;"><strong>&nbsp; &nbsp;<a href="http://www.cnblogs.com/guobaoyuan/articles/6757215.html" target="_blank">python匿名函数、内置函数</a></strong></span></p>
</div>
'''
1,找到所有的span标签的内容
ret = re.findall('<span(.*?)>', s1)
print(ret)

2,找到所有a标签对应的url
print(re.findall('<a href="(.*?)".*?</a>',s1))

第二十章

一.模块和包

包 是程序中一种组织文件的形式.

只要文件夹下含有__init__.py文件就 是一个包

包是干什么的呢?

为了能够充分的将某个功能进行重用 我们使用了模块,但是慢慢的模块就会越来越多.我们想提高程序的结构性可维护性,就使用包将模块进行统一管理

包能够管理多个模块,我们想要使用包里的模块怎么办呢?

使用import 和from xx import xx 现有如下结构

bake            

    ├── __init__.py       

    ├── api               

        ├── __init__.py

        ├── policy.py

        └── versions.py

  ├── cmd             

    ├── __init__.py

    └── manage.py

  └── db                

      ├── __init__.py

      └── models.py

我们在bake同级创建一个test.py进行导入policy.py 我们使用模块的import的时候只能将api添加到sys.path的路劲中,我们来看看包使用import导入

import bake.api.policy
bake.api.policy.get()

导入的太长了下边使用的时候还需要在重复写一遍,我们可以使用as起别名

import bake.api.policy as p
p.get()

这样的操作只支持包,普通的文件夹无效,有人一定在想我把bake拿过来然后一层一层的打开那拿工具就可以了

import bake
bake.api.policy.get()

不好使,这样导入是只将policy导入了,有人想怎么将api包下的模块全部导入不要急,先说单独导入的方式

咱们能够使用import进行导入,在来看看from的导入方式

from bake.api import policy
policy.get()
from bake import api
print(api.versions.name)

还是不好使,通过这两个我们能够感觉都导入的时候指定要导入的内容,不能再导入后在进行开箱子

我们现在说了单独导入一个模块,现在来说道说道怎么导入某个包下的所有模块,想要导入某个包下的所有的模块 我们就需要在包中的__init__.py做点手脚

bake包下的__init__.py
from . import api

.是当前路径,因为from的时候不能空着

api包下的__init__.py
from . import policy

我们将包下的__init__配置好,然后在test.py进行导入

import bake
bake.api.policy.get()

又好使了,这是为什么呢?我们import导入bake这个包,因为bake是一个文件夹不能进行任何操作,就让__init__.py代替它 去将api这包中的模块导入,api也是一个文件夹不能操作就需要让api下边的__init__.py去找api下边的两个模块

这个和公司的上下级关系一样,打比方现在test.py就是一个ceo要和policy这个小员工谈话,ceo先把这个想法人事经理,人事经理就是 bake这个包,人事经理通知人事让人事查找一下policy在那个部门,人事查到后通知部门的负责人,部门的负责人在通知部门的主管,主管告诉policy这个员工, 说ceo要找你,部门的主管带着policy去找人事,人事带着policy,人事然后在带着policy去找ceo.最后成功的和ceo进行了一番交流

如果在传达的时候中间一个环节忘记传递了,policy就不知道ceo在找他,ceo等了好久不来ceo就生气报错了

使用的时候需要注意: 有的同学,想在policy文件中导入versions就是直接使用import,在policy文件使用没有问题,很美,很高兴.但是在test.py执行的时候就会报错 因为我们在test.py中执行的import versions 相当于在test.py文件进行查找,肯定不会找到,我们需要在policy文件中向sys.path添加了当前的路劲就可以了 具体操作如下:

import os
import sys
sys.path.insert(os.path.dirname(__file__)

file__获取的是当前文件的路径,这样我们就能在test中正常使用了,我们使用from也能够将某个包下所有的模块全都导入 比如我们现在想将cmd包下的所有的模块导入需要在bake包下的__init.py进行设置

from . import *

我们需要在api包下设置__init__.py

from . import policy
from . import versions

我们需要在db包下设置__init__.py

from . import models

我们需要在cmd包下设置__init__.py

from . import manage

test.py调用如下:

from bake.api import *
print(versions.name)
policy.get()

from bake.db import *
models.register_models(123)

from bake.cmd import *
print(manage.name)

在使用import有个注意点,python2中如果import包,没有__init__.py文件就会报错 python3 import没有__init__.py文件的包不会报错 from 包 import 包或者模块(在import后边不能在进行.操作)

路径:

绝对路径:从最外层(bake)包.查找的就是绝对路径 相对路径:.就是相对路径, ..是上一级目录 例如:我们在bake/api/version.py中想要导入bake/cmd/manage.py

# 绝对路径:
from bake.cmd import manage
manage.main()

#相对路径:
from ..cmd import manage
manage.main()

注意在使用相对路径的时候一定要在于bake同级的文件中测试 我们需要在和bake同级的test.py中测试

from bake.cmd import manage

二 . logging模块

我们来说一下这个logging模块,这个模块的功能是记录我们软件的各种状态,你们现在和我一起找到红蜘蛛的那个图标,然后右键找一找是不是有个错误日志.其实每个软件都是有错误日志的,开发人员可以通过错误日志中的内容对他的程序进行修改

这只是一种应用场景,有的还会将日志用于交易记录.比如你给我转账应该做记录吧,

我们使用的信用卡,每消费的一笔都会记录,我们来看看这个日志怎么用?

我们先来看一下函数式简单配置

import logging  
logging.debug('debug message')  
logging.info('info message')  
logging.warning('warning message')  
logging.error('error message')  
logging.critical('critical message')

默认情况下Python的logging模块将日志打印到了标准输出中,且只显示了大于等于WARNING级别的日志,这说明默认的日志级别设置为WARNING

(日志级别等级CRITICAL > ERROR > WARNING > INFO > DEBUG),

默认的日志格式为日志级别:Logger名称:用户输出消息。

我们自己用函数写的这个可以正常使用但是不够灵活,我们看看这个灵活的

灵活配置日志级别,日志格式,输出位置:

import logging  
logging.basicConfig(level=logging.DEBUG,  
                    format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',  
                    datefmt='%a, %d %b %Y %H:%M:%S',  
                    filename='/tmp/test.log',  
                    filemode='w')  

logging.debug('debug message')  
logging.info('info message')  
logging.warning('warning message')  
logging.error('error message')  
logging.critical('critical message')

basicConfig()函数中可通过具体参数来更改logging模块默认行为,可用参数有:

  • filename:用指定的文件名创建FiledHandler,这样日志会被存储在指定的文件中。
  • filemode:文件打开方式,在指定了filename时使用这个参数,默认值为“a”还可指定为“w”。
  • format:指定handler使用的日志显示格式。
  • datefmt:指定日期时间格式。
  • level:设置记录日志的级别
  • stream:用指定的stream创建StreamHandler。可以指定输出到
  • sys.stderr,sys.stdout或者文件(f=open(‘test.log’,’w’)),默认为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用户输出的消息

logger对象配置

import logging

logger = logging.getLogger()
# 创建一个handler,用于写入日志文件
fh = logging.FileHandler('test.log',encoding='utf-8') 

# 再创建一个handler,用于输出到控制台 
ch = logging.StreamHandler() 
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

fh.setLevel(logging.DEBUG)

fh.setFormatter(formatter) 
ch.setFormatter(formatter) 
logger.addHandler(fh) #logger对象可以添加多个fh和ch对象 
logger.addHandler(ch) 

logger.debug('logger debug message') 
logger.info('logger info message') 
logger.warning('logger warning message') 
logger.error('logger error message') 
logger.critical('logger critical message')

logging库提供了多个组件:Logger、Handler、Filter、Formatter。Logger对象提供应用程序可直接使用的接口,Handler发送日志到适当的目的地,Filter提供了过滤日志信息的方法,Formatter指定日志显示格式。另外,可以通过:logger.setLevel(logging.Debug)设置级别,当然,也可以通过

fh.setLevel(logging.Debug)单对文件流设置某个级别。

第二十一章

一.面向对象初识

1.1 面向过程编程vs函数式编程

我们在没有学习函数的时候,写的代码都是面向过程式编程

# 面向过程编程 测量对象的元素的个数。
s1 = 'fjdsklafsjda'
count = 0
for i in s1:
    count += 1


l1 = [1,2,3,4]
count = 0
for i in l1:
    count += 1

在我们学习函数后就是在面向函数编程

def func(s):
    count = 0
    for i in s:
        count += 1
    return count
func('fdsafdsa')
func([1,2,3,4])

通过对比可知:函数编程较之面向过程编程最明显的两个特点:

1,减少重复的代码。

2,增强代码的可读性。

二.面向对象初识

2.1 函数式编程vs面向对象编程

函数式编程

# 函数式编程

# auth 认证相关
def login():
    pass

def regisgter():
    pass

# account 账户相关
def func1():
    pass

def func2():
    pass


# 购物车相关
def shopping(username,money):
    pass
def check_paidgoods(username,money):
    pass
def check_unpaidgoods(username,money):
    pass
def save(username,money):
    pass

面向对象编程

class LoginHandler:
    def login(self):
        pass

    def regisgter(self):
        pass

class Account:
    def func1(self):
        pass

    def func2(self):
        pass

class ShoppingCar:
    def shopping(username,money):
        pass
    def check_paidgoods(username,money):
        pass
    def check_unpaidgoods(username,money):
        pass
    def save(username,money):
        pass

通过对比可以看出面向对象第一个优点:

面向对象编程:是一类相似功能函数的集合,使你的代码更清晰化,更合理化。

说第二个优点之前,先看看什么是面向对象。

面向对象的程序设计的核心是对象(上帝式思维),要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。

问题来了,那什么是类?什么又是对象?

类:就是具有相同属性和功能的一类事物

对象:就是类的具体表现形式

具体一些:先解释解释什么是⻋? 有轱辘, 有⽅向盘, 有发动机, 会跑的是⻋. 好. 在解释⼀个. 什么是⼈. 有名字, 年龄, 爱好, 会唱歌跳舞思考的是⼈.那么广义上 车,人就是类:但是具体的我的车,你这个人这是一个对象。

猫,是一类,你们家养的 大橘。

狗,是一类,隔壁家养的那只二哈就是对象。

⾯向对象思维, 要⾃⼰建立对象. ⾃⼰建立场景. 你是就是⾯向对象世界中的上帝. 你想让⻋⼲嘛就⼲嘛. 你想让⼈⼲嘛⼈就能⼲嘛。

再说第二个优点:面向对象,要拥有上帝的视角看问题,类其实就是一个公共模板(厂房),对象就从具体的模板实例化出来(慢慢体会)。

2.2 类的结构

class Human:
    """
    此类主要是构建人类
    """
    mind = '有思想'  # 第一部分:静态属性 属性 静态变量 静态字段
    dic = {}
    l1 = []
    def work(self): # 第二部分:方法 函数 动态属性
        print('人类会工作')
class 是关键字与def用法相同,定义一个类。
Human是此类的类名,类名使用驼峰(CamelCase)命名风格,首字母大写,私有类可用一个下划线开头。
类的结构从大方向来说就分为两部分:
静态变量。
动态方法。

三. 从类名的角度研究类

3.1 类名操作静态属性

第一种,查看类中的所有内容:类名.__dict__方式。

class Human:
    """
    此类主要是构建人类
    """
    mind = '有思想'  # 第一部分:静态属性 属性 静态变量 静态字段
    dic = {}
    l1 = []
    def work(self): # 第二部分:方法 函数 动态属性
        # print(self)
        print('人类会工作')

print(Human.__dict__)
以下了解:
print(Human.__dict__['mind'])
Human.__dict__['mind'] = '无脑'  # 错误
#通过这种方式只能查询,不能增删改.
print(Human.__dict__)

第二种:万能的点.

class Human:
    """
    此类主要是构建人类
    """
    mind = '有思想'  # 第一部分:静态属性 属性 静态变量 静态字段
    dic = {}
    l1 = []
    def work(self): # 第二部分:方法 函数 动态属性
        # print(self)
        print('人类会工作')
print(Human.mind)  # 查
Human.mind = '无脑'  # 改
print(Human.mind)
del Human.mind  # 删
Human.walk = '直立行走'
print(Human.walk)
# 通过万能的点 可以增删改查类中的单个属性

  对以上两种做一个总结:如果想查询类中的所有内容,通过 第一种__dict__方法,如果只是操作单个属性则用万能的点的方式。

2.2 类名操作动态方法

  前提:除了两个特殊方法:属性,类方法之外,一般不会通过类名操作一个类中的方法。

class Human:
    """
    此类主要是构建人类
    """
    mind = '有思想'  # 第一部分:静态属性 属性 静态变量 静态字段
    dic = {}
    l1 = []
    def work(self): # 第二部分:方法 函数 动态属性
        # print(self)
        print('人类会工作')
    def tools(self):
        print('人类会使用工具')

Human.work(111)
Human.tools(111)
下面可以做,但不用。
Human.__dict__['work'](111)

四. 从对象的角度研究类

4.1 什么是对象

对象是从类中出来的,只要是类名加上(),这就是一个实例化过程,这个就会实例化一个对象。

执行下列代码会发生什么事情?

class Human:
    mind = '有思想'

    def work(self): 
        print('人类会工作')

    def tools(self):
        print('人类会使用工具')
obj = Human() # 实例化对象
print(obj)  # <__main__.Human object at 0x00000191508AA828>

其实实例化一个对象总共发生了三件事:

  1,在内存中开辟了一个对象空间。

  2,自动执行类中的__init__方法,并将这个对象空间(内存地址)传给了__init__方法的第一个位置参数self。

  3,在__init__ 方法中通过self给对象空间添加属性。

示例:

class Human:
    mind = '有思想'
    language = '使用语言'
    def __init__(self,name,sex,age,hobby):
        # self 和 obj 指向的是同一个内存地址同一个空间,下面就是通过self给这个对象空间封装四个属性。
        self.n = name
        self.s = sex
        self.a = age
        self.h = hobby

obj = Human('meet','男',18,'运动')

3.2 对象操作对象空间属性

对象查询对象中所有属性。 对象.dict

class Human:

    mind = '有思想'
    language = '实用语言'
    def __init__(self,name,sex,age,hobby):
        # self 和 obj 指向的是同一个内存地址同一个空间,下面就是通过self给这个对象空间封装四个属性。
        self.n = name
        self.s = sex
        self.a = age
        self.h = hobby

obj = Human('meet','男',18,'运动')
print(obj.__dict__)  # {'n': 'meet', 'h': '运动', 's': '男', 'a': 18}

对象操作对象中的单个属性。 万能的点 .

class Human:

    mind = '有思想'
    language = '实用语言'
    def __init__(self,name,sex,age,hobby):
        # self 和 obj 指向的是同一个内存地址同一个空间,下面就是通过self给这个对象空间封装四个属性。
        self.n = name
        self.s = sex
        self.a = age
        self.h = hobby

obj = Human('meet','男',18,'运动')
obj.job = 'IT'  # 增
del obj.n  # 删
obj.s = '女' # 改
print(obj.s)  # 查
print(obj.__dict__)

4.3 对象查看类中的属性

class Human:

    mind = '有思想'
    language = '实用语言'
    def __init__(self,name,sex,age,hobby):
        self.n = name
        self.s = sex
        self.a = age
        self.h = hobby

obj = Human('meet','男',18,'运动')
print(obj.mind)
print(obj.language)
obj.a = 666
print(obj.a)

4.4 对象操作类中的方法

class Human:

    mind = '有思想'
    language = '实用语言'
    def __init__(self,name,sex,age,hobby):
        self.n = name
        self.s = sex
        self.a = age
        self.h = hobby

    def work(self):
        print(self)
        print('人类会工作')

    def tools(self):
        print('人类会使用工具')

obj = Human('meet','男',18,'运动')
obj.work()
obj.tools()

  类中的方法一般都是通过对象执行的(除去类方法,静态方法外),并且对象执行这些方法都会自动将对象空间传给方法中的第一个参数self.

4.5 self 是什么?

self其实就是类中方法(函数)的第一个位置参数,只不过解释器会自动将调用这个函数的对象传给self。所以咱们把类中的方法的第一个参数约定俗成设置成self, 代表这个就是对象.这个self可以进行改变但是不建议大家进行修改

传参分为隐式传参和显示传参,self这种方式就是隐式传参

4.6 一个类可以实例化多个对象

obj1= Human('小胖','男',20,'美女')
obj2= Human('相爷','男',18,'肥女')
print(obj1,obj2)
print(obj1.__dict__)
print(obj2.__dict__)

我们每实例化一个对象都开辟一个空间,并且这写空间之间是独立的.

第二十二章

一.Python 类的空间问题

1.1 何处可以添加对象属性

class A:
    def __init__(self,name):
        self.name = name

    def func(self,sex):
        self.sex = sex
# 类外面可以:
obj = A('meet')
obj.age = 18
print(obj.__dict__)  # {'name': 'meet', 'age': 18}

# 类内部也可以:
obj = A('meet') # __init__方法可以。
obj.func('男')  # func 方法也可以。

总结:对象的属性不仅可以在__init__里面添加,还可以在类的其他方法或者类的外面添加。

1.2 何处可以添加类的静态属性

class A:
    def __init__(self,name):
        self.name = name

    def func(self,sex):
        self.sex = sex

    def func1(self):
        A.bbb = 'ccc'

# 类的外部可以添加

A.aaa = 'baoyuan'
print(A.__dict__)

# 类的内部也可以添加。

A.func1(111)
print(A.__dict__)

总结:类的属性不仅可以在类内部添加,还可以在类的外部添加。

1.3 对象如何找到类的属性

之前咱们都学习过,实例化一个对象,可以通过点的方式找到类中的属性,那么他为什么可以找到类中的属性呢?

通过图解说明:

image-20190812190947526

对象查找属性的顺序:先从对象空间找 ------> 类空间找 ------> 父类空间找 ------->.....

类名查找属性的顺序:先从本类空间找 -------> 父类空间找--------> ........

上面的顺序都是单向不可逆,类名不可能找到对象的属性。

二. 类与类之间的关系

⼤千世界, 万物之间皆有规则和规律. 我们的类和对象是对⼤千世界中的所有事物进⾏归类. 那事物之间存在着相对应的关系. 类与类之间也同样如此. 在⾯向对象的世界中. 类与类中存在以下关系:我们会使用这个关系就行,不用扣这写名词

  1. 依赖关系
  2. 组合关系
  3. 继承关系(类的三大特性之一:继承。)

2.1 依赖关系

⾸先, 我们设计⼀个场景. 夏天到了大象很热,大象想到冰箱中. 注意. 在这个场景中, 其实是存在了两种事物的. ⼀个是⼤象, ⼤象负责整个事件的掌控者, 还有⼀个是冰箱, 冰箱负责被⼤象操纵. 通过这个我们要分出主次, 大象就是主 冰箱就是次

⾸先, 写出两个类, ⼀个是⼤象类, ⼀个是冰箱类

class Elphant:
    def __init__(self, name):
        self.name = name

    def open(self):
        '''
        开⻔
        '''
        pass

    def close(self):
        '''
        关⻔
        '''
        pass


class Refrigerator:

    def open_door(self):
        print("冰箱⻔被打开了")

    def close_door(self):
        print("冰箱⻔被关上了")

  冰箱的功能非常简单, 只要会开⻔, 关⻔就⾏了. 但是⼤象就没那么简单了. 想想. ⼤象开⻔和关⻔的时候是不是要先找个冰箱啊. 然后呢? 打开冰箱⻔. 是不是打开刚才找到的那个冰箱⻔. 然后装⾃⼰. 最后呢? 关冰箱⻔, 注意, 关的是刚才那个冰箱吧. 也就是说. 开⻔和关⻔⽤的是同⼀个冰箱. 并且. ⼤象有更换冰箱的权利, 想进那个冰箱就进那个冰箱. 这时, ⼤象类和冰箱类的关系并没有那么的紧密. 因为⼤象可以指定任何⼀个冰箱. 接下来. 我们把代码完善⼀下

class Elphant:
    def __init__(self, name):
        self.name = name

    def open(self,obj1):
        '''
        开⻔

        '''
        print('大象要开门了,默念三声,开')
        obj1.open_door()

    def close(self):
        '''
        关⻔
        '''
        print('大象要关门了,默念三声,关')


class Refrigerator:

    def open_door(self):
        print("冰箱⻔被打开了")

    def close_door(self):
        print("冰箱⻔被关上了")


elphant1 = Elphant('大象')
haier = Refrigerator()
elphant1.open(haier)

通过上边的代码可以发现,将一个类名或对象当做参数传递给另一个函数被使用就是依赖关系

2.2 组合关系

这个最简单. 也是最常⽤的⼀种关系. 比如. ⼤家都有男女朋友. 男⼈关联着女朋友. 女⼈关联着男朋友. 这种关系可以是互相的, 也可以是单⽅⾯的.

定义类

class Boy:
    def __init__(self,name,girlFriend=None):
        self.name = name
        self.girlFriend = girlFriend

    def have_a_diner(self):
        if self.girlFriend:
            print('%s 和 %s 一起晚饭'%(self.name,self.girlFriend.name))
        else:
            print('单身狗,吃什么饭')


class Girl:
    def __init__(self,name):
        self.name = name

实例(创建)日天对象

b = Boy('日天')
b.have_a_diner() # 此时是单身狗

# 突然有一天,日天牛逼了
b.girlFriend = '如花'
b.have_a_diner()  #共进晚餐

实例(创建)wusir对象

# wusir 生下来就有女朋友 服不服
gg = Girl('小花')
bb = Boy('wusir', gg)
bb.have_a_diner()

# 结果嫌他有点娘,不硬,分了
bb.girlFriend = None
bb.have_a_diner()

我们了解了依赖关系和组合关系,现在来对比一下

依赖关系,将一个类或对象传递给另一个类的方法中

组合关系,将一个类的对象传递到另一个类的对象属性中

像这样的关系有很多很多. 比如. 学校和老师之间的关系.

学校和老师示例:

# 老师属于学校,必须有学校才可以工作
class School:

    def __init__(self,name,address):
        self.name = name
        self.address = address


class Teacher:

    def __init__(self,name,school):
        self.name = name
        self.school = school

s1 = School('北京校区','美丽的沙河')
s2 = School('上海校区','上海迪士尼旁边')
s3 = School('深圳校区','南山区')

t1 = Teacher('武大',s1)
t2 = Teacher('海峰',s2)
t3 = Teacher('日天',s3)

print(t1.school.name)
print(t2.school.name)
print(t3.school.name)

但是学校也是依赖于老师的,所以老师学校应该互相依赖。

class School:

    def __init__(self,name,address):
        self.name = name
        self.address = address
        self.teacher_list = []

    def append_teacher(self,teacher):
        self.teacher_list.append(teacher)

class Teacher:

    def __init__(self,name,school):
        self.name = name
        self.school = school

s1 = School('北京校区','美丽的沙河')
s2 = School('上海校区','上海迪士尼旁边')
s3 = School('深圳校区','南山区')

t1 = Teacher('武大',s1)
t2 = Teacher('海峰',s2)
t3 = Teacher('日天',s3)

s1.append_teacher(t1)
s1.append_teacher(t2)
s1.append_teacher(t3)

# print(s1.teacher_list)
# for teacher in s1.teacher_list:
#     print(teacher.name)

组合:将一个类的对象封装到另一个类的对象的属性中,就叫组合。

咱们设计一个游戏人物类,让实例化几个对象让这几个游戏人物实现互殴的效果。

class Gamerole:
    def __init__(self,name,ad,hp):
        self.name = name
        self.ad = ad
        self.hp = hp
    def attack(self,p1):
        p1.hp -= self.ad
        print('%s攻击%s,%s掉了%s血,还剩%s血'%(self.name,p1.name,p1.name,self.ad,p1.hp))
gailun = Gamerole('盖伦',10,200)
yasuo= Gamerole('亚索',50,80)

#盖伦攻击亚索
gailun.attack(yasuo)
# 亚索攻击盖伦
yasuo.attack(gailun)

但是这样互相攻击没有意思,一般游戏类的的对战方式要借助武器,武器是一个类,武器类包含的对象很多:刀枪棍剑斧钺钩叉等等,所以咱们要写一个武器类。

class Gamerole:
    def __init__(self,name,ad,hp):
        self.name = name
        self.ad = ad
        self.hp = hp
    def attack(self,p1):
        p1.hp -= self.ad
        print('%s攻击%s,%s掉了%s血,还剩%s血'%(self.name,p1.name,p1.name,self.ad,p1.hp))

class Weapon:
    def __init__(self,name,ad):
        self.name = name
        self.ad = ad
    def weapon_attack(self,p1,p2):
        p2.hp = p2.hp - self.ad - p1.ad
        print('%s 利用 %s 攻击了%s,%s还剩%s血' %(p1.name,self.name,p2.name,p2.name,p2.hp))

接下来借助武器攻击对方:

pillow = Weapon('绣花枕头',2)
pillow.weapon_attack(meet,panky)
# 但是上面这么做不好,利用武器攻击也是人类是动作的发起者,所以不能是pillow武器对象,而是人类利用武器攻击对方

所以,对代码进行修改。

class Gamerole:
    def __init__(self,name,ad,hp):
        self.name = name
        self.ad = ad
        self.hp = hp
    def attack(self,p1):
        p1.hp -= self.ad
        print('%s攻击%s,%s掉了%s血,还剩%s血'%(self.name,p1.name,p1.name,self.ad,p1.hp))

    def equip_weapon(self,wea):
        self.wea = wea  # 组合:给一个对象封装一个属性改属性是另一个类的对象
class Weapon:
    def __init__(self,name,ad):
        self.name = name
        self.ad = ad
    def weapon_attack(self,p1,p2):
        p2.hp = p2.hp - self.ad - p1.ad
        print('%s 利用 %s 攻击了%s,%s还剩%s血'
              %(p1.name,self.name,p2.name,p2.name,p2.hp))


# 实例化三个人物对象:
meet = Gamerole('太白',10,200)
panky = Gamerole('金莲',20,50)
pillow = Weapon('绣花枕头',2)

# 给人物装备武器对象。
meet.equip_weapon(pillow)

# 开始攻击
meet.wea.weapon_attack(meet,panky)

上面就是组合,只要是人物.equip_weapon这个方法,那么人物就封装了一个武器对象,再利用武器对象调用其类中的weapon_attack方法

python
原文地址:https://www.cnblogs.com/bky20061005/p/12121494.html