Python学习之路-Python2-30天学习笔记
这里的学习笔记来自2016年当初系统化学习python2.7的笔记,过分资料已经过时,由于还有很多可以参考查询价值,所以这里保留。
每一章的作业代码见以下git地址:https://github.com/lizexiong/python2-learning-notes
1.L001
1.1 Python详细介绍
讲解Python的起源以及Python的分类,本章可以略过。
1.2 Python环境的搭建
Python环境的搭建,本章可以略过。
1.3 Python的第一次和内部执行流程以编译器声明
输出Hello world ,本章也可以略过。
1.4 Python的编码声明
默认Python的编码为ASCII,默认为8位,表示不了中文,而uncode最少为16位,这时就会出现一个问题,中文一般默认用3个字节,24位表示,这个时候就会出现内存资源的浪费,因为这个时候uncode可以写英文,英文用ASCII就可以写出来,但是还是占用了那么多的内存(使用Uncode)。所以这里做了一个优化,就是uncode的子分类,UTF8,所有的英文还是ASCII表示,中文就是该用24位表示。
Python的官方声明,Python3.0默认编码就是UTF8.
这里作为开发人员,如果linux底层系统版本最好使用ubuntu,因为CentOS的版本对比比较文档,但是一些库和软件比较旧,如Python在CentOS6上面的版本为2.6,而Ubuntu就是2.7了。
对于python的版本,python2.4之后直接跳跃到了3.0,那时候没有2.6,但是3.0好多底层都改变了,2.4不兼容,所以在后期又除了过渡版本2.6。Python2.6兼容很多2.4的特性,又兼容很多3.0的特性。在过渡的过程中,现在2017年,已经除了Python2.7,2.7已经不支持兼容很多2.4版本的内容了。
1.5 Python注释和脚本参数
这里演示单行注释
这里演示多行注释
现在讲解脚本参数
这里类似shell的$1类型。
现在参数是没有意义的,因为代码没有写,脚本没有捕获到,只是做了一个简单的输出。
这里就调用了Python的默认模块sys,而argv就是作为捕获参数的。
1.6 Python字节码一
上节课程中模块讲了三种,一种是下载的,一种是导入的,还有一种是自己写的
以下演示写一个简单的模块。
现在创建一个简单的m.py的模块
现在查看hello.py的脚本
导入自己写的m.py模块,当然,在同级目录下
现在运行hello.py
这里结果不重要,查看文件是否有改变
这里多出了一个pyc文件。
打开pyc文件,发现里面全是python解释器才能看懂的文件,这里就是python的字节码,
这个字节码是经过m.py编译成的,后期python拿这个pyc和机器调度。这里m.py经过编译是可以变成m.pyc的,当然m.pyc是可以通过反编译变成m.py的。
这里说一个题外话
安卓是基于java写的,java编译成字节码之后就是上图上的文件,然后再次经过混淆,在拿给客户用,但是不过经过怎么编译和混淆,都是可以经过反编译回来的,只是相对安全而以。
1.7 Python字节码二
本章为理论讲解。
接上节。编译m.py后生成m.pyc,那么如果删除m.py,那么能够执行吗?答案是可以。
这里关系到python的执行顺序,python首先会找m.py,如果这里m.py内容和m.pyc内是相同的,那么就不管m.py,直接找m.pyc,这里也给python做了一个加速,如果m.py和m.pyc的内容不同,那么就回重新编译m.py生成m.pyc.
1.8 Python变量声明
变量理论
变量相当于一个昵称。指代指内存里某个地址中保存的内容
例如 name=lizexiong
name 就是一个昵称,而lizexiong保存在内存里面,通过name可以调出来。
声明变量有3个要求,这里只说出第三个,就是跳过关键字,变量名不能为以下字符。
这里有第二种赋值方式,第一种不在阐述。
这里直接将name1的地址直接给了name2,那么现在,修改name1,name2会发生变化吗?
答案是不会变化。
现在更改,发现name2并没有发生变化。
这里是原理图
因为对于字符串修改的时候,注意,这里是字符串。给name1重新赋值的时候,相当于在内存里重新开辟了一块空间,而name2还是只想原来的内存地址,所以,name1改变,name2不改变。
1.9 Python字符串内部原理一
Python是用C写的,但是这里C语言里面没有字符串这个概念, 但是有字符,
如一个hello,在java,Python叫一个字符串,但是这个hello是由5个字符组成的。
Python是使用字符数组里面伪造的。
字符串一旦修改,重新创建。
为什么要这么做,如有一个字符串
[‘h’,’e’,….’o’] [‘l’,’I’]
我现在要修改hello这个字符穿如果不是重新开辟空间,就会出现这种情况。
把后面的字符串位置给占住了
[‘h’,’e’,….’o’,’o’,’o’] [‘l’,’I’]
所以,字符串一旦修改,必须重新创建。
这里如果字符串相加,在内存里面怎么分配
这如果使用+号,hello分配一块空间,sb分配一块空间,这样就浪费了,而且性能不高。
这里在很多编程语言里面叫做万恶的+号。
这里如果是C语言,那么开发人员就要自己去释放内存,但是python,java等高级变成语言,是不需要关心内存的释放和关闭的,有自动的垃圾回收机制。Python虚拟机自己来回收。
这里还有一种,在其它变成语言也适用,数据类型分为2种,第一种是值类型,第二种是引用类型。第一种值类型只要修改,重新开辟空间,第二种引用类型,就可以在原始上直接修改了,可以在不同的内存地址,但是通过链接过来。
1.10 Python字符串内部原理二
现在还有,变量值相同,查看内存地址,结果是相同的
其实应该不同,在一定的可控范围之内,内存地址才能指向一个地方,这是python的一个优化,目的就是为了重复使用同一个字符串,没必要在内存里面重复开辟一个空间。
字符串是一个缓存池,会将常用的一些,简要的一些给放进去,那么就不用重新开辟空间,
而int类型的范围是257就不相同了
1.11 Python字符串内部原理三
Python解释器经过了哪些阶段:
- 加载内存、此法分析,语法分析、编译—》执行字节码、转换机器码。
- ASCII、UNiCODE、UTF8
剩下的就是一些总结,这里可以略过。
1.12 Python输入和输出
本章讲解用户自定义的输入,如用户名和隐藏密码。
隐藏密码
1.13 Python流程控制一
本章简单演示if语句,实现条件,用户输入用户和密码如果成功就显示登录成功。
本章只有一点需要注意,就是if 后面 == 和 is ,这里==只要是值相同就行,而is必需内存地址相同。
1.14 Python流程控制二
接上一章,只是简单的if 嵌套,本章可略过。
1.15 Python流程控制三
本章讲解if elif ,本章可略过。
1.16 Python初始基本数据类型之数字
大体来说,数据类型分为以下两类
1.17 Python初始基本数据类型之字符串一
字符串的格式化分为2种。%s是字符串的占位符,%d是int的占位符
现在传递2个值。如果出现2个,那么按照顺序写参数。
这种传递值是python特有的,而java就不同,java如下。
1.18 Python初始基本数据类型之字符串二
Python表示字符串有三种方式
''这种只能表示单行的字符串。
“”这种也是表示单行字符串。这里和单引号没有任何区别。
“”” “””可以表示多行字符串,此方法可以利用在如登录什么信息,提示信息等。
现在讲解通过下标获取字符串。为列表讲解打基础。
还有一个-1代表数组的最后一个。
为什么是-1,因为下标从0开始,-1就是减1,得到索引的长度减去1。
如
name = ‘lizexiong’
print len(name)
print name[-1] == name[len(name)-1]
这里还有一个解决去除字符串两边的空格
1.19 Python初始基本数据类型之集合和for循环
现在讲解字符串分割的方法,在讲解分割之前,这里先讲解列表。
定义列表为2种方式。上面那种方式本质是调用下面的方式执行的。
简单的索引就不讲解。
这里直接讲解字符串的分割。
这里按照,号分割开,分割后就变成了列表。
如果要通过空格来分割,就使用\t来代替
现在讲解增加值
这里内存地址也没有变。修改之后不会重新分配内存空间。修改之后,值也会跟着改变。
删除列表元素
把列表变为字符串,json方法
这里通过_来把列表变成字符串,并且按照自定义的连接方式
判断值是否在不在列表里,得到的是布尔值
现在讲解元祖
列表可修改,元祖不能修改。
循环列表及元祖,太过简单,不做讲解。
现在总计一下,字符串、列表、元祖的区别
for循环。
太过简单,直接贴出演示代码
这里需要注意的是,遇到continue就会跳出这次循环,进入下一次循环。
1.20 Python初始基本数据类型之while循环
本章过于简单,直接贴出代码。
1.21 Python初始基本数据类型之字典
字典称为键值对,一个键对应一个值。
简要获取一个字典键对应的值
这是使用for循环拿出键的值,但是没有值
如果这里要拿出键值,这里要使用items的方式来拿。
字典的保存顺序是无序的。
这里还有两种方法,拿出所有的key和所有的values
1.22 Python运算符
Python是有直接计算器的
本章也是讲解的一些基本运算符,可略过。
1.23 Python初始文件操作
本章实现将在git作业里有详细解释。
下列还是贴出作业代码,也可以在git中查看
1.24 Python作业
作业在GIT里有详细表示,参考本人GIT,本章可省略。
2.L002
2.1 本节内容预览
一些基础结构功能,这些功能怎么来的,如int的什么的方法,编码,算法还有数据结构,集合等。
2.2 作业总结
本章全是废话,可以略过
2.3 上周作业讲解
本章课程只讲了实现思想,参考本人git。
2.4 上周内容回顾
回顾上节课程。也可略过。
2.5 变量的管理
作用域
这里讲解就是变量的作用域在哪里使用。总结就是以上结论可能在其它语言使用,但是python不适用,python总结就是只要在内存里存在,就能使用。
2.6 三元运算
三元运算简单来说就是对if else的变种,
可以看出,if在中间,如果条件成立,就把用值赋给前面的name,否则就是后面的name。
这就是三元运算。
2.7 各种进制间运算方法
就是进制间转换,本章省略。
2.8 变量的作用域
本章就是做了一句话的补充,就是变量名对大小写敏感。
2.9 Pychram断点调试
讲解IDE的使用,本章省略。
2.10 Python之一切皆对象
比如,我创建一个列表,这个列表就是一个对象,对象有很多方法,都在类里面,如果创建第二个列表,那么第二个列表还是一个对象,它也有追加,清除等方法,但是创建的时候不会把这些方法都给它,这样就不会照成内存浪费,需要的时候直接在类里面找就可以了。
现在找类的方法,有这么几种方法。
首先没有IDE的时候的查找办法。
首先创建一个对象,然后在一步一步查找到方法。
现在想看一个详细的help。
现在查看一个方法的详细信息
现在使用IDE来查看
打出要查看的类名称,按住crtl,就可以查看了
总结以下查看类及功能的方法
2.11 数据类型的内置方法
这里查看list的方法的时候有带__的,有没有带的,带__叫暂时叫做内置方法。没带__,不是内置方法。非内置方法,只有一个表示,只能表示一种,如append只能往后面添加,只有一种执行方式。而内置方法,可能有一种,可能有多种。
使用以下数据的内置方法
普通加减。
查看__abs__绝对值的用法
可以看出,有两种执行方式
现在讲解创建字符串的几种方式
以及创建的的时候以几进制来表示。
常用的就是前面2种,后面的基本用不上,但是要知道有这种方式。
这里讲解怎么查看源码
刚才的执行方式还记得,这里<==> 等价与x&y,解释的很清楚了。
这里在讲解__cmp__的用法。做比较的,注意查看结果。
也可以这么做比较
现在讲解强制生成一个元祖的方法
现在讲解divmod
这里的用法是,比如访问一些网页时的分页,这个网页共有多少页码。
如现在有99也条数据,每个页面10条数据,有多少个页面
现在讲解float转换浮点类型
2.12 数据类型的内置方法2
__format__方法使用。加空格变成字符串的方法,一般很少用到。
__hash__做对比。
__hex__,返回16进制表示
__init__方法
这里在python里面,如果写了
i = 10
i = int(10)
那么就会自动调用__init__方法,这里暂时做到了解即可。
__pow__ 幂次方计算
记住有__str__和__repr__两种方法,暂时不需要多了解。知道有此种方法。
2.13 字符串的内置方法
数字级别的内置方法差不多了,但是一大堆都不常用,现在讲解字符串的内置方法。
首先回顾创建字符串的两种方式是什么
capitalize首字母大写的方法。
Center内容居中
或者让两边填充为*号也可以
Count 自序列的个数。就是lizexiong这个名字,l的出现个数。
也可以指定起始位置
2.14 解码与编码
编码就是由unicode变成utf8,utf8由解码变成unicode
可以看出unicode是最原始的编码,utf8和gbk都是优化过的,不过是utf8还是gbk想变成unicode都要经过解码。
例如以下示例演示由gbk变成utf8是否能在gbk的终端正常显示。
2.15 字符串的内置方法后续
__endswith__是否以 … 结尾。
__expandtables__,将tab转换为空格,默认一个tab转为8个空格。
或者让一个tab键变成一个空格。
__find__,寻找下标,没有找到使用-1表示
字符串格式化
这里只是一种方式,还有一个自定义方式,不用指定{0}或者{1}
2.16 字符串的格式化方法
截止现在字符串的格式化已经讲解了2种方式了。
现在讲解的第三种是上面两种变种而来。
使用的是把列表里面的数据放进来。
这里把一个列表传送进去了。
现在把一个字典传送进去。
这里可以总计出,如果是列表括号里面是一个*,
如果是字典,需要2个*,且字典的数据类型必须相对。
Find 和index区别
Find找不到返回-1,而index直接报错
Isalnum,判断是否是数字和字母。
Isalpha,判断是否是字母,这个就不做演示。
Istitle,判断字符串是不是标题。
title,把字符串变成标题的格式。
转换成标题,首字母全部大写。自定义的首字母大写就会变成标题。
如下,将标题赋值后判断一下。
Join方法,前文讲解过。
ljust ,内容左对齐,右侧填充
这里填充只能填写一个符号,不要填写多个。
Swapcase,大写变小写,小写变大写。
Partition,分割,通过已有指定字符来分割。
replace,注意,这里替换是替换所有的
rsplit,前文学过split,是通过什么分割,这里出现的作用就是,如果分割符不唯一,那么就从右边第一个开始。
这里还有一个特例的方法,没有使用过,但还是贴出来
2.17 列表基础
append方法,添加一个值到列表最后面。
注意这里使用的不是方括号。
count,统计出现值的次数。
extend,这个列表扩展里面还可以增加列表,把原列表扩展,扩展后内存地址不变。
也可以自己扩展自己,但是字典就不会,字典会报错。
Index,返回出现第一个出现的下标,如果不存在,返回一个异常。
Insert,在指定的下标位置插入数据。
pop,删除并且返回指定下标的值,如果没有指定下标,返回最后一个。
这种方式就像子弹一样,打出一发,掉出来一发。
也可以把返回的值存储起来。
remove删除列表的值,删除第一个找到的值。Del才是标注下标。
Reverse,反转,列表里面的数据颠倒过来。
Sort,排序,数字按照阿拉伯,如果是字符串,默认按照ASCII,如果是中文,按照unicode。
2.18 列表与元组的常用方法
元祖的元素不能被修改
元祖的元素的元素可以被修改,如元祖的元素是列表,这个列表可以修改。
2.19 字典及常用方法
简单取出字典的值
还有一种判断的方法。
如果字典里面没有这个值就返回OK
不能把列表当字典的key,因为列表是可变的,但是元祖可以
现在打印当前时间,日期也可以当成key,但是没有必要
现在看看字典有什么方法。
Fromkeys,所有值相同时可以这么复制
has_key,检查字典里有没有这样的key,这个方法要被去掉了。
Itesm,以列表的方式显示出字典的键值。
这种取值的方式最好不要使用,因为在数据量很大的时候,要把字典先转成列表。
使用以下方式来最好
popitems,直接删除键和值
现在回顾以下字典删除的2种方式。
上面的del是全局性的,可以删除很多东西。
现在使用range生成一个大的字典来做测试。
使用popitem
这里注意,看到是有序的删除,其实是无序的,做一些其它操作后可能就改变了
setdefault
如果有就返回值,没有就创建一个none
现在给700赋值,还是none,因为这个值已经在了,就不会创建了,除非这个值不存在
update
可以将2个字典整合起来,如果存在相同的键,那么后面的会覆盖前面的值。
现在讲解copy
现在首先尝试往字典里面加一个字典,当然这里现在生成的字典值需要是列表了。
现在要找b[0]下面的字典b怎么找?
这里首先b[0]找到的是第一级字典的键“0”
b[0][0] 找到的是第一级字典下列表下标为0的值
b[0][0][‘b’]是最后的字典的键。
现在将第三级字典赋值测试
现在测试字典的copy
在此之前,这里要强调,使用fromkeys非常吭,因为发现,我所有的值都变成一样的了,
因为formkeys给的值全部指向了一个内存地址,更改一个,其余的全部改变。
现在用另一种方式来使用。
使用for循环,那么值的内存地址就不一样了。
现在生成实验数据
现在简单的copy,然后把b的值改掉,查看c的值是否改变
值发生改变,这是肯定的
所以这里提供了一个copy的方法
现在删除c的值,查看c和d有什么变化。
结果,d没有随着c的删除而删除
但是这里,还有更好玩的
现在再次改动c。
结果还是发生了改变,这是为什么?
这里的意思是浅拷贝,只拷贝第一层,在往深入的层次就copy了。
现在来一个深copy
要借助第三方的一个模块
2.20 集合及关系运算
集合是一种数据类型。
把相同是数据取出来,也就是取交集。
要用集合计算,必须把上面的列表变成集合。
从上面可以看出,集合的功能就是去重。
还有一个是交叉集的运算,同时,集合是无序的。
现在开始比较,首先把b也变成集合d。
现在集合c和集合d比较。
查看两个集合的交集
查看并集
查看不相交的值
现在取出c里面有,d里面没有的
查看一个集合是不是另一个集合的子集。
现在判断是不是父集
集合是否能修改
结果,pop只能随机删除,但是还有一个方法。
集合小总结
集合在哪些地方会用,如资产管理,如资产的自动更新,如:这里有一台server,server自动汇报我有2块硬盘,硬盘会有唯一的键值,如插槽,如果有一天,一块硬盘坏了,就放在了一个新的插槽上面。
集合就是这样的
这种情况下只有把broken的值和new的值做一个对比。然后把新的数据写到数据库。
现在又比如加了4块硬盘,怎么就知道4块硬盘是新的
通过集合对比判断哪些是新的,哪些是旧的
2.21 计算器作业讲解
作业2
作业2
3.L003
3.1 上节内容回顾
本章只是简单回顾,可略过。
3.2 课上练习之元素分类
总结对字典和列表赋值。
然后做一个练习
或者使用这种方式
个人写出代码
这里在需要注意的是在else语句里面,新创建键的时候,一定要统一加上,号。
第二个练习
3.3 collections系列之计数器
collections是对字典的补充。
查看一下collections的其它方法
如果看到一个类后面加了(dict)就表示当前这个类是扩展的dict。(list)就是扩展的列表。
most_common()里面如果没有值就是打印出所有出现多少次,如果打一个3,那么就是至少出现了3次。
update
相当于只是简单的相加了
返回没有的值测试
most_common,下图解释是有故障的,相当于mysql的limit,数字是几,就显示几个值。
elements(),但是如果直接拿值的话拿到的就是迭代器,要使用for来拿,这就是所说的迭代器,迭代器只有循环的时候才能拿出来。
现在测试传入一个列表,计算列表数值出现的次数。
3.4 collections系列之有序字典
有序字典和字典在人使用的时候是一样的,但内部结构自己做了一个处理。
如下述这样
比如,我定义一个有序字典
a[‘k1’]=1
a[‘k2’]=2
这个时候python内部就回维护一个列表,因为列表有序,且有下标,相当于
维护了一个字典key的列表
li=[‘k1’,’k2’]
如果字典有嵌套字典,那么就没有有序了,因为有序字典只管第一层。
现在定义一个有序字典测试
3.5 collections系列之默认字典
现在讲解如果字典的键为空,那么能否append。
是不可以append的,因为dic[‘k1’]=None,None是没有append方法的,条件不成立。除非改掉None.
默认情况下,字典的value都是none,append是不能用的,要判断如dict[‘k1’]是不是列表,如果是,才能append。
这里做一个传统字典与默认字典的比对
如创建给传统字典赋值
dict ={} if ‘k1’ in dict.keys(): dict[‘k1’].append(1) else: dict[‘k1’] = [1,]
显然传统字典需要判断是否有值,如果没有值,要先创建一个。
而默认字典如下
dict={‘k1’:[]} dict= collections.defaultdict(list) dict[‘k1’].append(1)
默认字典已经设置了value的默认类型,所以不需要在if判断了。
演示默认字典。
3.6 collections系列之可命名元组
可命名元祖也可以理解成为给元祖加了一个坐标。
如传统创建元祖的时候步骤是这样的。
直接使用类创建对象。
使用对象
如:li=list(1,2,3,4)
而可命名元祖是这样的。
li=list(1,2,3,4)
x=1,y=2,z=3
创建步骤是这样的
创建有三个元素可命令类。(这个根据实际情况来顶,可命名元祖是动态的类。)
根据类创建对象。
使用对象
这种又类似像字典一样
li={‘x’:1,’y’:2,’z’:3}
这里演示以下创建可命令元祖
但是根据自己创建的类的元祖数据不能越界,否则会报错。
现在查看一下自己创建的mytuple这个类有哪些可以使用的方法
子类里面会继承父类里面的所有。这句话意思就是有一个默认的tuple类,它的方法我自己创建的mytunple都可以用,而且,我自己创建的mytuple也有很多方法。
这里只是简单讲解,详细使用方法百度或者留意后期课程。
3.7 collections系列之双向队列
队列的线程安全,比如双向队列,两边可以插入数据也可以写入数据,但是如果来1000个人来拿一个数据,那么线程安全就回锁住这个数据,这个人上玩了,下个人才可以上。
查看以下双向队列有哪些方法。
使用最简单的pop
3.8 单向队列
单向队列只能从一边操作,不管是往里面放数据,还是往外面拿数据。
队列就是生产者和消费者中间的东西。如果没有生产,那么就等着。
且,单向队列不在collections里面,在queue模块里面。
创建一个单向队列,里面最多能放10条数据。
单向队列没有往左加数据和右加数据,只有加和取。队列数据是先进先出,数据谁先进来谁就先出去。(FIFO)
这里补充一个知识点,队列和栈的区别,它们是相反的。
队列是先进先出一个,而棧是从最后一个弹出。
演示往单向队列里插入数据在拿出数据
可以看到,是按照进入的顺序拿出的,而且,没有数据之后,终端一直处于等待状态。
3.9 迭代器和生成器原理
迭代器
使用地方在有一些值直接取取不出来,只有通过for循环迭代才能出来。
这里如果使用列表测试,列表是没有elements方法的,会直接报错,但是列表有如下方法
这里会判断里面还会不会有数据,有的话把下一个拿给你,next
方法,还有一些自己定义类迭代器的,这个后期还会深入讲解。
生成器
因为range()的值是可以查看,且通过其它方式,如下标可以取出来的
下面这个例子就可以看出迭代器和生成器的关系
至于自己写生成器和本章知识,只是一个概念,后期会详细讲解。
3.10 下标式循环以及冒泡算法一
本章讲解主要是一个简单概念,就是通过下标就行循环,和值的替换。
第一种,简单循环,循环数字。
第二种,循环列表,通过下标循环。
第三种,通过列表的长度来循环
第四种,冒泡排序
3.11 内置函数一
函数分类:
- 内置函数
- 自定义函数
- 导入其它模块
函数作用:
如,列表的append,提供了一个添加功能,一个函数相当于一个功能块。
内置函数就是把一些内置的常用的类和方法给简化的提取出来了,做了一个快捷方式,放在了内置函数里面,为了让我们使用方便。
之前如果要使用某些功能,要经过几个阶段。如abs,创建对象 i=6,如果要求abs,那么就需要i.__abs__(),这样就求出绝对值了。
如果abs非常常用,就把abs放到内置函数里面,就要这么执行abs(6),这么abs就会自己找到__abs__()类方法然后自己去执行。
如现在有一个vars()函数,是所有变量的意思。
Vars意思就是这个文件里面有哪些变量,我全部都可以拿到。
查看以下vars打印的文字版
{'name': 'lizexiong', '__builtins__': <module '__builtin__' (built-in)>, '__file__': 'E:/Python/code/day3/collections.py', '__package__': None, '__name__': '__main__', '__doc__': None}
name=lizexiong是我自己定义的变量,下面从module开始是它自己的变量。
如__file__打印出脚本当前路径,每一个py的文件都有这么一个变量。
__doc__ “”” “”” 打印出注释中的内容,也可以说出现”””调用这个函数。
__name__,这里拿c语言比较,如果执行
print __name__
void main(){
}
那么就会默认调用这个主函数void main。
对于python,这个main就不太一样,如果执行了python test.py,那么就会把赋值成这样
__name__ = ‘__main__’
但是如果此时,我导入import collections模块,它也有__name__模块,但是这个模块不等于__main___.只有被执行的脚本的__name___等于__main__,其余的即使有,也等于__main__.
__package__后期讲解。
本章只是简单了解以下函数而已。
3.12 内置函数二
本章讲解内置函数的使用方法,具体方法可自行百度。可略过。
3.13 内置函数三
本章主要了解以下以下函数,因为现在没有深入理解函数,现在只是做到过一遍,以下的参数要学会传参才能使用。
3.14 函数式编程和面向过程对比
函数式编程,还没有到面向对象
本文讲了定义函数的简单用法,可粗略浏览。
3.15 函数式编程之发送邮件
传递内容参数的修改
3.16 函数之返回值
函数中重要的是4.5点。
现在先理解返回值。
如,出现问题就发报警邮件,但是发送告警成功了没有,没人知道。即使失败,我也要写入日志记录一下。
简单就是函数体执行完成之后会返回一个结果,我会把结果赋值给一个变量。
假设现在邮件发送成功了。
返回值就是告诉他的调用者,我这个函数到底成功否?
如果正常,什么都不管,如果返回flase,那么我邮件在重新发送一次,或者写一条日志。
题外:如果我没有写return,但是定义了变量接收函数,那么返回的就是None
返回值总结:
1.未明确指定返回值,返回None。
2.返回值可以赋值给某个变量。
3.返回值返回什么,定义的变量就是什么类型。
3.17 函数之普通参数一
这里将形式参数,简称形参,没有实际的值,只是一个指针。
还有实际参数,叫实参,是调用这个函数的时候来的。这个时候就回在内存开辟一块空间。
3.18 函数之普通参数二
函数可以传多个参数
3.19 函数之默认参数
3.20 函数之动态参数一
这里首先定义个函数,传入一个动态参数,如果什么值都不传入,会输出一个空元祖。
现在传入一个元祖可以看到
如果要取消内部构造元祖,那么就在参数前面加一个*
3.21 函数之动态参数二
字典传参。
这里形参里面传递2个**,那么传递的就是字典。
直接通过命令行传递字典,注意这里键不能加引号。
现在直接传送一个字典。
3.22 函数之动态参数三
两个动态参数结合一下。
直接混搭输入测试,但是要先传单值,然后传字典,否则会出错。
以上图自动将元祖和字典分开了,当然数据类型要要紧贴着。
这里,在前文中讲解过一个format参数,这里看看它的帮助信息。
回顾以下字符串的格式方法。
第一种方式。
第二种,直接变量等于
3.23 文件操作之打开文件一
打开文件拓展讲解。
打开文件两种方式
Open模式内部调用的就是file,推荐使用open,因为file在3.0以后,file就不能单独使用了,被Python内部集成了,可能到另一个模块里面了,使用open,python会自己去找file,而人为找的就很麻烦。
在L001的课程里已经讲解了文件打开的课程,本章主要讲解两个文件打开的2个函数。
seek() 控制指针的起始打开位置
tell() 标识指针位置
首先测试标识指针的位置。
测试文件内容为
测试指定指针的起始位置
3.24 文件操作之打开文件二
这里有一个补充点讲解,在L001的作业中,明明没有设置指针的位置,而且文件打开模式是r+,为什么文件内容会追加到了最后,因为读完了这些文件后,指针就到了最后,所以r+就相当于追加,如果是w,那么文件就是先清除在写入了。(可以说文件读出到哪,指针都在哪)
还有,如果是a模式,那么指针一定会在最后,即使你把指针给调走,那么在执行的时候,还是会调回最后。
如下演示设置指针位置设置在5然后在写入
演示r+开头,我不读取文件,直接写入,指针为0,那么就是从开头替换写入。
现在不更改模式情况下,使用truncate方法截断模式来只保留前面的内容,后面的全部清除,指针在哪,就从哪截断。
还有一个补充讲解,因为在一些其它语言中,换行符有的是\r,有的是\n,这里在打开模式的时候将所有的\r换成\n。
这个r+U只能在r模式的时候用。
首先不加U,查看一下输出
结果输出
加上U查看
结果一样
这里在做简单演示,查看是否/r转换为/n,直接读出来查看以下。
不加U
加U查看
3.25 文件操作之操作文件
在python,对一个文件操作完成后立即释放,否则别人就不能用
本章主要将讲解一些方法使用,但没演示。
3.26 with管理文件操作
With在2.7之后支持同时打开多个文件
以下讲解的是两个文件,然后把第一个文件包括改写的内容写进去。
3.27 本节作业
可略过,或者查看下一章
4.L004
4.1 今日课程介绍
本略过,补充一些基础用法。
4.2 冒泡排序一
冒泡排序前文已经讲解,略过。
这里讲解第二种方法。
4.3 haproxy配置文件之获取数据一
本章只是演示过程,最后会单独总结一章,本章主要是查找,并且有flag这个标志位的思想。
4.4 haproxy配置文件之获取数据二
接上一章,ha会在最后一章节来总结。主要在此解惑获取数据的流程。这里讲解不多说,自己看代码体会。
4.5 haproxy配置文件之添加数据一
添加文件的思想。
- 在哪个backend下面添加一个什么样的记录。
4.6 haproxy配置文件之添加数据二
添加数据实践
4.7 haproxy配置文件之添加数据二(补充一)
本章主要讲解如果输入的记录不存在,那么怎么把数据写进去
4.8 haproxy配置文件之添加数据二(补充二)
4.9 haproxy配置文件功能演示
演示代码可用性,可略过。
4.10 haproxy配置文件之添加数据三
本章因为录像原因,本章是重复的内容。
4.11 lambda表达式一
简单来说就是对函数的简化操作而以。
以下是代码演示
4.12 lambda表达式二
lambda接收多个参数。
也可以接收动态参数
4.13 内置函数之filter
以上结论可以看出,如果filter只传一个None,filter只取结果为真的数据。
也可以传一个函数,现在测试只要大与30的数
这里相当于函数里面有个a>33的条件,当大于33的时候为True,才返回,其余的为false。
4.14 内置函数之map
结果为
现在查看以下内部怎么运行的
当然,这里不止可以做加减,只要看函数指定的是什么操作,乘除也可以啊。
这里map也可以接收多个参数
如这里有2个列表,让
li1=[11,22,33]
li2=[3,4,5]
这里让11+3 22+4 …形成一个新的列表
这里当然也可以传递多个值,但是这里有一点要注意,列表的下标要有值,否则,map遍历的时候会报错,找不到这个值。
如果真有这种,如a3没有值的这种情况,可以在函数里面加一个if判断,并且给空下标设置默认值
这里如果用lambda可以一句解决
4.15 内置函数之reduce
简单来说会接收一些数据,如,一个列表,一个元祖,进行计算然后得到一个元祖
比如做一个累加,计算出1-10的和。
也可以改成乘法。
4.16 yield生成器
yield,记住上一次操作,下次再执行时,继续执行。
普通函数里面,只要发现一个return,生命周期就结束运行。下面的return就不在运行。
如果这里一个函数内有多个return,那怎么办?
所以这里可以使用yield。这里使用yield一定不要误解,因为在函数里面碰到循环之后就跳出了,但是yield记录了上一次的return,跳出了这个函数,并没有完全结束,只是冻结了,所以就执行了第二个return。
就是这种结果,你看,如果是returun,那么永远返回第一个碰到的return
且不能通过for循环来拿。
看到这里,是不是有点在哪里见过此种方式,range()和xrange()
这里模拟我们自己写一个xrange,分析debug流程。
这里知道,只要return,那么永远返回的第一个值,这里的函数可以看出,在seed小于10的时候,执行yield seed,比如返回一个1之后,那么就回冻结这个函数,由for来输出第一个值,然后函数再次开始,由于记录了上一次的位置,所以,第二个值返回的就是2,依次类推,可以看出,yield就是记录上一个位置的,而return是做不到的。
4.17 内置函数总结
这里对map,filter,reduce做了一个小总结,还有对reduce做了一个设置基本起始基数的补充。
4.18 装饰器(alex)
本章在git代码库里有详细解释。下一章也有详细解释,这里没有讲清楚,参考后面的笔记。
4.19 本节作业
预习课程
5.L005
5.1 Day1至今内容回顾
回顾,可略过。
5.2 Python装饰器原理一
学习装饰器必备知识
如果这里定义多个函数,查看执行顺序
这里个人补充一下,函数加上()才能执行函数里面的代码,被函数调用也一样。
需求
这里解释原理,按照步骤拆分。
1. 例如 f1是开发好的无法再次更改的模块。
2. 函数 auth(func)执行顺序
首先auth(func)传一个参数f1,那么函数开始读取,auth(f1) ,那么进入函数,
发现有一个函数inner(),函数inner会打印一个字符串“f2”,然后再次打印函数
func(),由于传了一个值f1,那么func()==f1(),但是此时,函数没有被调用,那么什么都不会输出,在最后返回函数inner体,这是返回的函数inner由于没有调用,所以执行auth(f1)函数返回值为空。
3. 这里如果需要函数来返回值,那么就需要在后面加上函数的标识,表示调用了这个函数
4. 最关键,如果这里我返回的函数inner等于现在的f1,那么根据函数的执行顺序,那么就回自动去找前世的f1。
相当于以下代码
a=auth(f1)
f1=a
这个时候就算去执行f1函数,那么根据装饰器设置的语法,那么也是经过代码流程处理后的f1.
5. 而装饰器原理呢,就如下图
@auth ,那么就会自动找到下一行的的函数名来自动执行。
也可以说@auth = f1=auth(f1)
5.3 Python装饰器原理二
装饰器其实就是函数加上python的语法堂。
本章是对不懂的同学一个补充讲解。
还补充了一个import来执行的方式。
5.4 Python装饰器去装饰含参数的函数一
现在讲解,关于装饰调用的函数里面有参数的讲解。
之前的f1,函数里面是没有参数的,如果,这时,本来的f1里面有参数那怎么办?
在
执行auth(f1)的时候,f1是没有参数的,如果这里有参数,那么auth函数下inner的func也必须有参数。
如,现在定义f2是有参数的。
那么此时只能在定义个装饰器。
给inner和func传送一个参数即可,这里可能不能理解inner为什么也要参数,因为执行了auth1函数后,然后调用下面的时候,inner必须也要有参数。因为inner要接收它里面func的参数的长度,所以要与auth保持一致。这种说法可能不准确,更有可能的是因为inner要返回给auth,所以,inner的值个数要和auth保持一直。
5.5 Python装饰器去装饰含参数的函数二
上篇讲解了如果函数有参数的处理办法,但是那仅是一个参数,如果一个函数要传多个参数呢?可以使用*,**来表示万能。
且到时候装饰器只用定义一个即可。
5.6 Python装饰器去装饰含返回值的函数
补充一句,一个函数没有return,默认返回None
代码解释
可以看出如果auth(func) == auth auth(f3),那么 inner就是新的f3函数,但是inner是没有返回值的,所以,无法返回原f3的值,因为现在原f3有一个返回值,那么直接将这个结果交给一个变量,然后由新的f3函数(也就是inner)来返回即可。
以下是课堂的代码解释,大致意思就是原f3变成新的inner,新的f3(inner)没有返回值
5.7 Python装饰器实现登陆验证原理
本章实现的就是再次新建一个login函数,然后在装饰器里面调用,但是不能值是固定的。
更先进的办法在下一章。
5.8 Python装饰器实现token验证
这里使用通过传入一个值来验证对比,以下是实现思路。
补充讲解, k = j[‘token’]是给字典赋值,如果不删除,给func()的时候就多了一个参数,就会报错,因为f3()函数是没有带参数的。
5.9 Python装饰器之多装饰器
多个装饰器装饰一个函数。
原理也很简单,仔细看输出结果,就是外层函数和和内层函数执行顺序的区别。
5.10 Python装饰器之装饰器加参数
第10分钟开始,这篇是2个视频合1,前面10分钟没人听懂。
本章听的是懂非懂,以后章节再次补充。
以下是个人理解
1. 代码会根据执行顺序往下执行。
首先把def a()载入内存不执行
然后把def b()载入内存不执行
然后把there(aa,bb)载入内存不执行
2. 到了@there的时候,这个时候因为是第三层函数,
首先抛弃掉@符号,执行there(a,b),这个时候便执行了a,b两个函数。
3.def there里面有两个值,便把a,b两个函数载入there(aa,bb),
这时,便再回返回一个函数two,此时,two便是一个简单的装饰器了。
4.这时,@there(a,b)变成了 two()普通装饰器,two()开始把 def index()载入进来,获取返回值inner,这是inner也就是index
5.此时,最外层函数定义了there(aa,bb),那么在inner里面,也就可以调用最外层的函数了。
6. index()开始调用的时候,不管输入什么值其实都没关系,仔细看
5.11 Python递归原理
主要注意,自己调用自己,并且换一个参数
利用函数编写如下数列:
斐波那契数列指的是这样一个数列 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368
仔细看,是否是每两个值相加,知道此方式,是不是就方便了很多。
5.12 Python递归返回值
这里返回的是none,明明是大于50000就会返回,为什么返回none,这里加入返回50000是第10回,那么就回把c返回给调用者,出现大于50000的调用者就是第9次func(),func这时是没有值返回的,所以是none,其实还有后续,第8次返回给第7次,第7次返回给第6次,但是都是没有值的。
举一个例子,10个人开外报数1,报到10之后,10的人手里有一个手机,如果要给第一个人,那么只有1.2.3一个一个给他。
如果想返回值,应该在把值返回给前一次就可以了,为什么return func(b,c)在后面还说是第9次?因为只有第九次func(b,c)之后,retrun c 第10次才回大于50000
5.13 Python递归执行流程剖析
这里用一个简单的标志位来表示,为什么没有返回值,
个人理解,总之,这里要记住,这里是return,而不是print,返回值是需要接收的,为什么出现下图的现象,因为只有当c>50000的时候,才回返回C,这时,整个函数定义就是只返回C,只有在上一次执行后(也就是func(b,c)),c才可能大于50000,把值返回给上一次之后便打印出来,但是在上一次之后,条件便没有满足大于50000,所以都是None了。
5.14 Python模块介绍
本章简单介绍了一下模块的强大。
5.15 Python模块的导入一
导入模块的2种方式
第一种
import sys
sys.argv
但是由于第一种导入的东西太多,这里可以指定。
form sys import argv
那么这里就可以直接引用了。
argv
下面附上图解。
在很多情况下,如果要导入多个模块,如果出现模块重名,
那么就可以给模块取别名
这里讲解为什么我import模块的时候,python就可以找到我的py模块呢?是因为python在安装的时候就已经设置了很多默认的路径,每当你import的时候,python就会根据默认的路径去找模块。
这时,这里又出现一个问题,我不想把模块放到默认路径里面,那么就可以在代码的开头指定我需要使用的模块路径在哪里,然后把这个路径加到python的路径列表里就可以了。
5.16 Python模块的导入二
模块导入的补充章节
如果我的模块过多,就放在了一个文件夹下面,但是我直接调用这个文件夹,那么它就是一个普通的文件夹,只有在文件夹下面有__init__文件的时候,python才认为你是一个模块包,才会根据这个文件去找你想要的。
5.17 Python内置模块os和sys
讲解最基本模块,如果需要使用,请百度。
5.18 Python内置模块ConfigParser
此模块较简单,这里还是过一遍
本章因为源码比较长,所以这里直接查看git代码。
但是这里补充一点,为什么我模块自动过滤的是[],而不是其它符号,这里就是查看源码了,源码里写的固定就是[],如果是其它符号,自己把源码复制改一份就行了。(这里就是看的源码,这是其中一个代码,不看源码是不知道这个[]是干啥的)
5.19 Python内置模块hashlib
对密码进行加密
这里有md5,sha加密的模块,但是现在全部整个在了hashlib模块里面,而且,md5,sha的单独的方法在以后会删除,当然,也有全文加密,这里就不重点讲解。但是还是演示一下简单的加密。
5.20 Python今日作业
6.L006
6.1 JSON序列化
Json在不同语言之间进行数据交换。也可以进行不同程序之间的内存交换。
为什么不同语言之间要交换,因为有时候一个项目不可能全是一个语言写的。
有时候需要交互。相当于一个翻译的角色。
首先我们看看json怎么转的。
6.2 JSON序列化2
这里举一个最简单的例子。
就以文件打开为例,如果是别的语言或者程序,是不可能直接识别我语言的格式的,如果这里打开一个文件,然后存入到硬盘,硬盘只能存储字符串,所以,通过json转换成字符串存入进入,然后通过固定json固定格式在读取出来。
这里有以下代码,如果不通过json来转换的化,直接将字典存入到硬盘是会报错的。
现在通过json来转换,发现可以存储到硬盘了。
现在假如通过另一个程序,也就是qq来打开
之前学习过,通过read的方式可以把文本以字符串的方式读取进来,这里直接使用json来然后观察结果。
发现如果不用json转,文件都读取不进来,因为这是文件是字符串。
现在使用json来转换
可以看出,现在虽然类型还是字符串,还是已经可以和字典一样使用键来获取值了。
当然json也不是万能,所以json只转最常用的数据类型。
这种转换叫做序列化,序列化成字符串是要固定类型的
如果是一些复杂的类型,如date这种格式,json就处理不了。
6.3 JSON序列化3
前文把内存中的数据写入到硬盘的时候还有一种简化方式就是以下图。
在dump的时候,json有dump和dumps两种方式,这里说说两种方式的区别。
两种方式在执行运行上完全没有区别。
区别在于,dump直接将转换好的数据写入硬盘文件。
而dumps先需要声明一个值接收需要dumps的值,然后在写入硬盘文件,其它地方没有任何区别。
Dump
可以看到,dump直接将a的值写入到硬盘文件了。
Dumps
需要将文件接收然后在写入进去。
6.4 Subprocess模块
可以用这个执行系统命令。
现在首先在windows上使用演示os.system,发现根本无法存储返回值,只能执行命令。
当然也是有可以存储返回值的,但是之上的所有用法都将被废弃,没有必要在去上面浪费时间。以上执行shell命令的相关的模块和函数的功能均在 subprocess 模块中实现,并提供了更丰富的功能
Subprocess包含了以上的所有功能,可以统一使用subprocess来执行。
在windows上演示
call
需要注意,这里有一个shell等于ture,shell = True ,允许 shell 命令是字符串形式,简单来说,就是予许将值给原生的shell来处理,python没有处理识别这种字符串的能力,当python处理不了,只能交给原生shell。
还有一点注意,执行成功,终端返回了0,如果执行错误呢?那么就是非0了。
check_call
执行命令,如果执行状态码是 0 ,则返回0,否则抛异常
这里留一个疑问,这里的解释和call不一样,但是执行结果和call一模一样,所以不做演示,以后在来解答。
check_output
区别就是返回执行结果,不返回执行码,也就是用这种方式来存储返回值的。
6.5 Subprocess模块2
由于在windows上演示命令太少,而且很多功能无法演示,这里直接使用linux在做记录。
Call 和 check_call
check_output
再次强调,这种方式可以存储返回值
这里还有一点需要强调,如果是字符串的格式,就要加shell,否者就要用python的格式,建议不要使用字符串,这样会有高危bug,bug在于如果是用户输入的时候,如果用户输入rm,那么整个系统都危险了。相当于shell=True就是只能输入字符串,然后转换。
首先使用查看字符串的格式,那么一定要加shell=True
现在如果不用shell等于Ture,那么使用这种方式。但是这种方式一些命令根本无法使用。
总之,如果使用python的格式如果也处理不了,那么就加上shell=True
当然这里还有一种更复杂的执行方式
subprocess.Popen(...)
参数:
- args:shell命令,可以是字符串或者序列类型(如:list,元组)
- bufsize:指定缓冲。0 无缓冲,1 行缓冲,其他 缓冲区大小,负值 系统缓冲
- stdin, stdout, stderr:分别表示程序的标准输入、输出、错误句柄
- preexec_fn:只在Unix平台下有效,用于指定一个可执行对象(callable object),它将在子进程运行之前被调用
- close_sfs:在windows平台下,如果close_fds被设置为True,则新创建的子进程将不会继承父进程的输入、输出、错误管道。所以不能将close_fds设置为True同时重定向子进程的标准输入、输出与错误(stdin, stdout, stderr)。
- shell:同上
- cwd:用于设置子进程的当前目录
- env:用于指定子进程的环境变量。如果env = None,子进程的环境变量将从父进程中继承。
- universal_newlines:不同系统的换行符不同,True -> 同意使用 \n
- startupinfo与createionflags只在windows下有效。将被传递给底层的CreateProcess()函数,用于设置子进程的一些属性,如:主窗口的外观,进程的优先级等等
如这里我要启动一个交互式的环境,如python,这里就需要指定一个输入输出和错误信息了。
当然以上的后面的部分如果不是交互式的是可以不用加的,因为如果是交互式的,他就会在python在起一个python,那么就麻烦了。
尽然是交互式的,并且已经开启了,那么就可以给那个终端传递信息了,并且通过stdout来读取信息
现在输入错误信息查看
这里当然也有关闭这个进程,但是如果直接使用kill()关闭,那么有些进程就会变成僵尸进程,所以,这里kill()之后最好在加一个wait
这里有一个更加强大的方法,不需要我们指定的在去读错误输出,错误输入了
6.6 Subprocess模块3
一些补充讲解,可以略过。
6.7 Shutil模块
本章就是用python模块来实现复制,移动,压缩等操作,并没有什么卵用。
copyfile( src, dst) | 从源src复制到dst中去。当然前提是目标地址是具备可写权限。抛出的异常信息为IOException. 如果当前的dst已存在的话就会被覆盖掉 |
copymode( src, dst) | 只是会复制其权限其他的东西是不会被复制的 |
copystat( src, dst) | 复制权限、最后访问时间、最后修改时间 |
copy( src, dst) | 复制一个文件到一个文件或一个目录 |
copy2( src, dst) | 在copy上的基础上再复制文件最后访问时间与修改时间也复制过来了,类似于cp –p的东西 |
copy2( src, dst) | 如果两个位置的文件系统是一样的话相当于是rename操作,只是改名;如果是不在相同的文件系统的话就是做move操作 |
copytree(olddir,newdir,True/Flase) | 把olddir拷贝一份newdir,如果第3个参数是True,则复制目录时将保持文件夹下的符号连接,如果第3个参数是False,则将在复制的目录下生成物理副本来替代符号连接 |
这里只贴出一条演示
在顺便补充一下压缩等功能
shutil 对压缩包的处理是调用 ZipFile 和 TarFile 两个模块来进行的
shutil.make_archive(base_name, format,...)
创建压缩包并返回文件路径,例如:zip、tar
- base_name: 压缩包的文件名,也可以是压缩包的路径。只是文件名时,则保存至当前目录,否则保存至指定路径,
如:www =>保存至当前路径
如:/Users/wupeiqi/www =>保存至/Users/wupeiqi/
- format: 压缩包种类,“zip”, “tar”, “bztar”,“gztar”
- root_dir: 要压缩的文件夹路径(默认当前目录)
- owner: 用户,默认当前用户
- group: 组,默认当前组
- logger: 用于记录日志,通常是logging.Logger对象
压缩
解压
Tar.gz也是有专用模块的,可以直接使用shutil来解压,也可以直接import那个tar的模块,所以,最好使用zip方式压缩,这里需要用的时候在来补充
这里贴出示例
6.8 日期模块
结构化时间的优势
以下是转换字符串的图片
这里需要注意3点
- 怎么打印日期。
- 怎么打印时间戳
- 怎么把日期打印成python的格式。
打印时间戳
这是从1970年开始的。
打印日期
转换成我们认识的格式
转换成统计的格式
还可以单独取值
把日期转为时间戳
时间可以加减,但是加减的单位最大只能为天。
6.9 Logging日志模块
这里所有的解释都在代码里面标注了
查看输入结果
日志级别大小关系为:CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET,当然也可以自己定义日志级别。
对于等级:
1 2 3 4 5 6 7 8 |
CRITICAL = 50 FATAL = CRITICAL ERROR = 40 WARNING = 30 WARN = WARNING INFO = 20 DEBUG = 10 NOTSET = 0 |
logging.basicConfig函数各参数:
filename: 指定日志文件名
filemode: 和file函数意义相同,指定日志文件的打开模式,'w'或'a'
format: 指定输出的格式和内容,format可以输出很多有用信息,如上例所示:
%(levelno)s: 打印日志级别的数值
%(levelname)s: 打印日志级别名称
%(pathname)s: 打印当前执行程序的路径,其实就是sys.argv[0]
%(filename)s: 打印当前执行程序名
%(funcName)s: 打印日志的当前函数
%(lineno)d: 打印日志的当前行号
%(asctime)s: 打印日志的时间
%(thread)d: 打印线程ID
%(threadName)s: 打印线程名称
%(process)d: 打印进程ID
%(message)s: 打印日志信息
datefmt: 指定时间格式,同time.strftime()
level: 设置日志级别,默认为logging.WARNING
stream: 指定将日志的输出流,可以指定输出到sys.stderr,sys.stdout或者文件,默认输出到sys.stderr,当stream和filename同时指定时,stream被忽略
6.10 Logging日志模块2
到这里,已经实现了日志写入到文件,那么现在实现日志输入在屏幕,同时输入到文件。
这里深入讲解,首先要理解4个概念。
logger:提供日志接口,供应用代码使用。logger最长用的操作有两类:配置和发送日志消息。可以通过logging.getLogger(name)获取logger对象,如果不指定name则返回root对象,多次使用相同的name调用getLogger方法返回同一个logger对象。简单来说就是配置和发送日志消息。
handler:将日志记录(log record)发送到合适的目的地(destination),简单来说就是将日志输入到哪里?屏幕?还是日志文件?
filter:过滤日志文件
formatter:指定日志记录输出的具体格式
这里先给出简单的解释。
相信上面的讲解,已经很清楚了。
现在进入更加深入的讲解。
由于代码过长,如果需要代码,直接上git查找
首先查看打印信息 2017-04-09 21:15:23,265 - root - WARNING - logger warning message 2017-04-09 21:15:23,265 - root - ERROR - logger error message 2017-04-09 21:15:23,266 - root - CRITICAL - logger critical message 2017-04-09 21:15:23,266 - mylogger - INFO - logger1 info message 2017-04-09 21:15:23,266 - mylogger - INFO - logger1 info message 2017-04-09 21:15:23,266 - mylogger - WARNING - logger1 warning message 2017-04-09 21:15:23,266 - mylogger - WARNING - logger1 warning message 2017-04-09 21:15:23,266 - mylogger - ERROR - logger1 error message 2017-04-09 21:15:23,266 - mylogger - ERROR - logger1 error message 2017-04-09 21:15:23,266 - mylogger - CRITICAL - logger1 critical message 2017-04-09 21:15:23,266 - mylogger - CRITICAL - logger1 critical message 2017-04-09 21:15:23,266 - mylogger - INFO - logger2 info message 2017-04-09 21:15:23,266 - mylogger - INFO - logger2 info message 2017-04-09 21:15:23,266 - mylogger - WARNING - logger2 warning message 2017-04-09 21:15:23,266 - mylogger - WARNING - logger2 warning message 2017-04-09 21:15:23,266 - mylogger - ERROR - logger2 error message 2017-04-09 21:15:23,266 - mylogger - ERROR - logger2 error message 2017-04-09 21:15:23,266 - mylogger - CRITICAL - logger2 critical message 2017-04-09 21:15:23,266 - mylogger - CRITICAL - logger2 critical message 2017-04-09 21:15:23,266 - mylogger.child1 - WARNING - logger3 warning message 2017-04-09 21:15:23,266 - mylogger.child1 - WARNING - logger3 warning message 2017-04-09 21:15:23,266 - mylogger.child1 - WARNING - logger3 warning message 2017-04-09 21:15:23,266 - mylogger.child1 - ERROR - logger3 error message 2017-04-09 21:15:23,266 - mylogger.child1 - ERROR - logger3 error message 2017-04-09 21:15:23,266 - mylogger.child1 - ERROR - logger3 error message 2017-04-09 21:15:23,267 - mylogger.child1 - CRITICAL - logger3 critical message 2017-04-09 21:15:23,267 - mylogger.child1 - CRITICAL - logger3 critical message 2017-04-09 21:15:23,267 - mylogger.child1 - CRITICAL - logger3 critical message 2017-04-09 21:15:23,267 - mylogger.child1.child2 - DEBUG - logger4 debug message 2017-04-09 21:15:23,267 - mylogger.child1.child2 - DEBUG - logger4 debug message 2017-04-09 21:15:23,267 - mylogger.child1.child2 - DEBUG - logger4 debug message 2017-04-09 21:15:23,267 - mylogger.child1.child2 - DEBUG - logger4 debug message 2017-04-09 21:15:23,267 - mylogger.child1.child2 - INFO - logger4 info message 2017-04-09 21:15:23,267 - mylogger.child1.child2 - INFO - logger4 info message 2017-04-09 21:15:23,267 - mylogger.child1.child2 - INFO - logger4 info message 2017-04-09 21:15:23,267 - mylogger.child1.child2 - INFO - logger4 info message 2017-04-09 21:15:23,267 - mylogger.child1.child2 - WARNING - logger4 warning message 2017-04-09 21:15:23,267 - mylogger.child1.child2 - WARNING - logger4 warning message 2017-04-09 21:15:23,267 - mylogger.child1.child2 - WARNING - logger4 warning message 2017-04-09 21:15:23,267 - mylogger.child1.child2 - WARNING - logger4 warning message 2017-04-09 21:15:23,269 - mylogger.child1.child2 - ERROR - logger4 error message 2017-04-09 21:15:23,269 - mylogger.child1.child2 - ERROR - logger4 error message 2017-04-09 21:15:23,269 - mylogger.child1.child2 - ERROR - logger4 error message 2017-04-09 21:15:23,269 - mylogger.child1.child2 - ERROR - logger4 error message 2017-04-09 21:15:23,269 - mylogger.child1.child2 - CRITICAL - logger4 critical message 2017-04-09 21:15:23,269 - mylogger.child1.child2 - CRITICAL - logger4 critical message 2017-04-09 21:15:23,269 - mylogger.child1.child2 - CRITICAL - logger4 critical message 2017-04-09 21:15:23,269 - mylogger.child1.child2 - CRITICAL - logger4 critical message 2017-04-09 21:15:23,269 - mylogger.child1.child2.child3 - DEBUG - logger5 debug message 2017-04-09 21:15:23,269 - mylogger.child1.child2.child3 - DEBUG - logger5 debug message 2017-04-09 21:15:23,269 - mylogger.child1.child2.child3 - DEBUG - logger5 debug message 2017-04-09 21:15:23,269 - mylogger.child1.child2.child3 - DEBUG - logger5 debug message 2017-04-09 21:15:23,269 - mylogger.child1.child2.child3 - DEBUG - logger5 debug message 2017-04-09 21:15:23,269 - mylogger.child1.child2.child3 - INFO - logger5 info message 2017-04-09 21:15:23,269 - mylogger.child1.child2.child3 - INFO - logger5 info message 2017-04-09 21:15:23,269 - mylogger.child1.child2.child3 - INFO - logger5 info message 2017-04-09 21:15:23,269 - mylogger.child1.child2.child3 - INFO - logger5 info message 2017-04-09 21:15:23,269 - mylogger.child1.child2.child3 - INFO - logger5 info message 2017-04-09 21:15:23,269 - mylogger.child1.child2.child3 - WARNING - logger5 warning message 2017-04-09 21:15:23,269 - mylogger.child1.child2.child3 - WARNING - logger5 warning message 2017-04-09 21:15:23,269 - mylogger.child1.child2.child3 - WARNING - logger5 warning message 2017-04-09 21:15:23,269 - mylogger.child1.child2.child3 - WARNING - logger5 warning message 2017-04-09 21:15:23,269 - mylogger.child1.child2.child3 - WARNING - logger5 warning message 2017-04-09 21:15:23,270 - mylogger.child1.child2.child3 - ERROR - logger5 error message 2017-04-09 21:15:23,270 - mylogger.child1.child2.child3 - ERROR - logger5 error message 2017-04-09 21:15:23,270 - mylogger.child1.child2.child3 - ERROR - logger5 error message 2017-04-09 21:15:23,270 - mylogger.child1.child2.child3 - ERROR - logger5 error message 2017-04-09 21:15:23,270 - mylogger.child1.child2.child3 - ERROR - logger5 error message 2017-04-09 21:15:23,270 - mylogger.child1.child2.child3 - CRITICAL - logger5 critical message 2017-04-09 21:15:23,270 - mylogger.child1.child2.child3 - CRITICAL - logger5 critical message 2017-04-09 21:15:23,270 - mylogger.child1.child2.child3 - CRITICAL - logger5 critical message 2017-04-09 21:15:23,270 - mylogger.child1.child2.child3 - CRITICAL - logger5 critical message 2017-04-09 21:15:23,270 - mylogger.child1.child2.child3 - CRITICAL - logger5 critical message
这里再次补充一下logger库。logging库提供了多个组件:Logger、Handler、Filter、Formatter。Logger对象提供应用程序可直接使用的接口,Handler发送日志到适当的目的地,Filter提供了过滤日志信息的方法,Formatter指定日志显示格式。
Logger
Logger是一个树形层级结构,输出信息之前都要获得一个Logger(如果没有显示的获取则自动创建并使用root Logger,如第一个例子所示)。
logger = logging.getLogger()返回一个默认的Logger也即root Logger,并应用默认的日志级别、Handler和Formatter设置。
当然也可以通过Logger.setLevel(lel)指定最低的日志级别,可用的日志级别有logging.DEBUG、logging.INFO、logging.WARNING、logging.ERROR、logging.CRITICAL。
Logger.debug()、Logger.info()、Logger.warning()、Logger.error()、Logger.critical()输出不同级别的日志,只有日志等级大于或等于设置的日志级别的日志才会被输出。
而,我们的代码中
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')
只输出了
2017-04-09 21:15:23,265 - root - WARNING - logger warning message 2017-04-09 21:15:23,265 - root - ERROR - logger error message 2017-04-09 21:15:23,266 - root - CRITICAL - logger critical message
这是因为,从这个输出可以看出logger = logging.getLogger()返回的Logger名为root。这里没有用logger.setLevel()显示的为logger设置日志级别,所以使用默认的日志级别WARNIING,故结果只输出了大于等于WARNIING级别的信息
另外,我们明明通过logger1.setLevel(logging.DEBUG)将logger1的日志级别设置为了DEBUG,为何显示的时候没有显示出DEBUG级别的日志信息,而是从INFO级别的日志开始显示呢?
原来logger1和logger2对应的是同一个Logger实例,只要logging.getLogger(name)中名称参数name相同则返回的Logger实例就是同一个,且仅有一个,也即name与Logger实例一一对应。在logger2实例中通过logger2.setLevel(logging.INFO)设置mylogger的日志级别为logging.INFO,所以最后logger1的输出遵从了后来设置的日志级别。可以说,后面的把前面的给覆盖了。
logger1 = logging.getLogger('mylogger') logger1.setLevel(logging.DEBUG) logger2 = logging.getLogger('mylogger') logger2.setLevel(logging.INFO)
看第4行代码,logger2的配置把logger1的配置给覆盖了。
这里还有一个问题
为什么logger1、logger2对应的每个输出分别显示两次,logger3对应的输出显示3次,logger4对应的输出显示4次......呢?
是因为我们通过logger = logging.getLogger()显示的创建了root Logger,而logger1 = logging.getLogger('mylogger')创建了root Logger的孩子(root.)mylogger,logger2同样。
logger3 = logging.getLogger('mylogger.child1')创建了(root.)mylogger.child1
logger4 = logging.getLogger('mylogger.child1.child2')创建了(root.)mylogger.child1.child2
logger5 = logging.getLogger('mylogger.child1.child2.child3')创建了(root.)mylogger.child1.child2.child3
而孩子,孙子,重孙……既会将消息分发给他的handler进行处理也会传递给所有的祖先Logger处理。
试着注释掉如下一行程序,观察程序输出
#logger.addHandler(fh)
而在文件输出中每条记录对应一行(因为我们注释掉了logger.addHandler(fh),没有对root Logger启用FileHandler)
2017-04-09 21:28:52,746 - mylogger - INFO - logger1 info message 2017-04-09 21:28:52,746 - mylogger - WARNING - logger1 warning message 2017-04-09 21:28:52,746 - mylogger - ERROR - logger1 error message 2017-04-09 21:28:52,746 - mylogger - CRITICAL - logger1 critical message 2017-04-09 21:28:52,746 - mylogger - INFO - logger2 info message 2017-04-09 21:28:52,746 - mylogger - WARNING - logger2 warning message
所以它们的重复从 logger1以后开始了。
孩子,孙子,重孙……可逐层继承来自祖先的日志级别、Handler、Filter设置,也可以通过Logger.setLevel(lel)、Logger.addHandler(hdlr)、Logger.removeHandler(hdlr)、Logger.addFilter(filt)、Logger.removeFilter(filt)。设置自己特别的日志级别、Handler、Filter。若不设置则使用继承来的值。
Handler
上述例子的输出在标准输出和指定的日志文件中均可以看到,这是因为我们定义并使用了两种Handler。
fh = logging.FileHandler('apache.log') ch = logging.StreamHandler()
Handler对象负责发送相关的信息到指定目的地,有几个常用的Handler方法:
Handler.setLevel(lel):指定日志级别,低于lel级别的日志将被忽略
Handler.setFormatter():给这个handler选择一个Formatter
Handler.addFilter(filt)、Handler.removeFilter(filt):新增或删除一个filter对象。
可以通过addHandler()方法为Logger添加多个Handler:
logger.addHandler(fh)
logger.addHandler(ch)
有多种可用的handler,这里只是简单的过一遍
logging.StreamHandler 可以向类似与sys.stdout或者sys.stderr的任何文件对象(file object)输出信息
logging.FileHandler 用于向一个文件输出日志信息
logging.handlers.RotatingFileHandler 类似于上面的FileHandler,但是它可以管理文件大小。当文件达到一定大小之后,它会自动将当前日志文件改名,然后创建一个新的同名日志文件继续输出
logging.handlers.TimedRotatingFileHandler 和RotatingFileHandler类似,不过,它没有通过判断文件大小来决定何时重新创建日志文件,而是间隔一定时间就自动创建新的日志文件
logging.handlers.SocketHandler 使用TCP协议,将日志信息发送到网络。
logging.handlers.DatagramHandler 使用UDP协议,将日志信息发送到网络。
logging.handlers.SysLogHandler 日志输出到syslog
logging.handlers.NTEventLogHandler 远程输出日志到Windows NT/2000/XP的事件日志
logging.handlers.SMTPHandler 远程输出日志到邮件地址
logging.handlers.MemoryHandler 日志输出到内存中的制定buffer
logging.handlers.HTTPHandler 通过"GET"或"POST"远程输出到HTTP服务器
各个Handler的具体用法可查看参考书册:https://docs.python.org/2/library/logging.handlers.html#module-logging.handlers
Formatter
Formatter对象设置日志信息最后的规则、结构和内容,默认的时间格式为%Y-%m-%d
%H:%M:%S。
#定义Formatter formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') #为Handler添加Formatter fh.setFormatter(formatter) ch.setFormatter(formatter)
Filter
做过滤用的,这里就先跳过。
6.11 Re正则表达式
这里需要自己练习
6.12 面向对象介绍
Self就是代表实例本身,
这里只是简单介绍,可以略过。
7.L007
7.1 上节内容回顾
回顾的内容已经在L006进行补充,这里没有多余笔记,如果想回顾L006内容,只看本节补充即可。
7.2 计算器之处理表达式括号
首先补充一点,re.split(变量,1),这里这个1是找到第一个进行分割。
首先定义个函数,然后通过正则表达把(+-*/)和.过滤出来,类似9+1 9.1*11.2
结果分割的时候,中间的明显不是我们想要的,现在使用search就是我们想要的了
首先只计算content里面的数据,然后定一个一个函数,然后进行拼接
然后一直不断处理上面的内容
7.3 random模块创建验证码
首先查看一下random的方法
这里直接贴出代码,本节比较简单。
7.4 面向对象三大特性之封装一
所有高级语言三大特性,封装,继承和多态。但在python中多台表现的不突出。
Class foo:
Def __init__
Def之前叫做函数,这里这叫做构造方法。
面向对象封装形式之一。如,这里有一个连接数据库的要求,那么就可以把主机IP,端口,用户名,密码封装的对象里面,需要用的时候去拿就可以了。
如以下代码
class foo(): def __init__(self,hostname,port,username,password): self.hostname=hostnname self.port=port self.username=username def create(self): pass def remove(self): pass obj=foo(1.1.1.1,3306,lizexiong,huawei) obj.create()
这里创建一个对象obj,在类初始对象的的时候传的值封装的对象里面了,就比如
self.hostname=hostname ==》 obj.hostname = hostname
在下面方法里面的时候,def create(self):这里的self就是obj,方法执行的时候调用初始封装的值。
这里只有是一种情况,封装分为2节。
7.5 面向对象三大特性之封装二
本讲主要讲解封装的
封装方式二,可以在里面调用方法计算。
7.6 面向对象三大特性之继承一
这里三讲合为一讲。
主要是讲解经典类和新式类的区别,只要集成了object的类都是新式类。不管是直接集成还是间接继承,只要集成了object,那么都是新式类。在3.0之后默认都是新式类了。
这里新式类和经典类有什么区别?
继承:
首先对于其它语言,如,java,不支持多继承。相当于说class test(class1,class2)
Python支持多继承。
如:
主要还有了解广度优先(新式类)和深度优先(经典类)的区别。
所谓的广度就是有如下几个类
Class A(object)
Class B
Class C(A)
Class D(C,B)
如果C里面没有我们要用的方法,而B和A都有,那么首先会去B里面找,
如果是经典类,那么找完了C,那么就回直接去找A了。
7.7 面向对象三大特性总结
这里是总结,就是下图一些问答,博客里也很详细
7.8 面向对象类成员之字段
注:所有成员中,只有普通字段的内容保存对象中,即:根据此类创建了多少对象,在内存中就有多少个普通字段。而其他的成员,则都是保存在类中,即:无论对象的多少,在内存中只创建一份。
字段包括:普通字段和静态字段,他们在定义和使用中有所区别,而最本质的区别是内存中保存的位置不同,
- 普通字段属于对象
- 静态字段属于类
静态字段为什么要存在?因为如果多个对象里面有相同的值,那么就不需要普通字段一个一个封装了,直接在内存里创建一份就可以了。
如果方法加上一个property装饰器后,那么就变成了一个属性。这里提一笔,为什么要有属性这个东西,就是为了把方法伪造成一个字段,变成一个人的属性。也就是我访问一个属性的方式和访问一个字段是一样的。
7.9 面向对象类成员之方法
方法包括:普通方法、静态方法和类方法,三种方法在内存中都归属于类,区别在于调用方式不同。
- 普通方法:由对象调用;至少一个self参数;执行普通方法时,自动将调用该方法的对象赋值给self;
- 类方法:由类调用; 至少一个cls参数;执行类方法时,自动将调用该方法的类复制给cls;
- 静态方法:由类调用;无默认参数;
这三种方法什么时候用呢?其实静态方法就差不多等于一个函数,当你的程序全部在面向对象编程的时候,那么就有用了。 类方法其实就是对静态方法进行了一个约束,这个约束就是在传值的时候自动把当前的类给传进来。类方法和静态方法的访问甚至无需创建对象就可以访问,节省内存开销。
7.10 面向对象类成员之属性一
首先python的属性用的是很少,但是还是要了解。
如果你已经了解Python类中的方法,那么属性就非常简单了,因为Python中的属性其实是普通方法的变种。
对于属性,有以下三个知识点:
- 属性的基本使用
- 属性的两种定义方式
由属性的定义和调用要注意一下几点:
- 定义时,在普通方法的基础上添加 @property 装饰器;
- 定义时,属性仅有一个self参数
- 调用时,无需括号
方法:foo_obj.func()
属性:foo_obj.prop
注意:属性存在意义是:访问属性时可以制造出和访问字段完全相同的假象
属性由方法变种而来,如果Python中没有属性,方法完全可以代替其功能。
属性的定义有两种方式:
- 装饰器 即:在方法上应用装饰器
- 静态字段 即:在类中定义值为property对象的静态字段
装饰器方式:在类的普通方法上应用@property装饰器
经典类,具有一种@property装饰器
新式类,具有三种@property装饰器
经典类中的属性只有一种访问方式,其对应被 @property 修饰的方法
新式类中的属性有三种访问方式,并分别对应了三个被@property、@方法名.setter、@方法名.deleter修饰的方法
静态字段方式,创建值为property对象的静态字段
当使用静态字段的方式创建属性时,经典类和新式类无区别
属性就是把方法伪造成了字段。这里代码就不贴出了,需要查看在git上。
7.11 面向对象类成员修饰符
类的所有成员在上一步骤中已经做了详细的介绍,对于每一个类的成员而言都有两种形式:
- 公有成员,在任何地方都能访问
- 私有成员,只有在类的内部才能方法
私有成员和公有成员的定义不同:私有成员命名时,前两个字符是下划线。(特殊成员除外,例如:__init__、__call__、__dict__等)
class C: def __init__(self): self.name = '公有字段' self.__foo = "私有字段"
私有成员和公有成员的访问限制不同:
静态字段
- 公有静态字段:类可以访问;类内部可以访问;派生类中可以访问
- 私有静态字段:仅类内部可以访问;
普通字段
- 公有普通字段:对象可以访问;类内部可以访问;派生类中可以访问
- 私有普通字段:仅类内部可以访问;
方法、属性的访问于上述方式相似,即:私有成员只能在类内部使用
ps:非要访问私有属性的话,可以通过 对象._类__属性名
7.12 今日内容总结
7.13 补充之类特殊方法
就是一些内置的变态方法,需要去熟悉。
7.14 今日作业
8.L008
8.1 上节内容回顾
就是回顾类和对象。
8.2 内置函数isinstance和issubclass
isinstance(obj, cls)
检查是否obj是否是类 cls 的对象
此方法也可以检测就是是对应的类型,如,检测字符串,数据类型,一般可以用在录入用户输入的是否为指定类型,通过if来判断,然后通过强制转换。
issubclass(sub, super)
检查是否类为另一个类的派生类,用的比较少
8.3 异常处理应用场景
这里首先使用万能接收器,exception,这样就可以接收到所有异常。
可以看到,上述的异常信息明显没有红字提示,并且已经将提示输出了。
代码的异常信息种类有非常多,每个异常专门用于处理某一项异常。
这里就不全部举例说明了,就比如IndexError
这里明显列表越界
KeyError
Value
这里value直接报错了
因为对于上述实例,异常类只能用来处理指定的异常情况,如果非指定异常则无法处理。
所以,在写程序中要考虑try代码中可能出现的任意异常,可以这样写。
当然,在文章开篇的时候,有一个万能异常,它可以捕获任意异常,即:
接下来,是否有个疑问,既然有了万能异常,其它异常是否就可以忽略了!
答案:当然不是,对于特殊或提醒的异常需要先定义,最后定义Exception来确保程序正常运行。
8.4 异常处理基本使用
这里异常还有如if的其它结构。
上述可以看到,还支持else等逻辑判断,且finally不管什么情况下,都会执行,而finally一般应用在,连接数据库,关闭和退出等场景可以使用到。
这里当然还有主动触发异常,代码主动出现异常如同,我创建一个模块,里面提供了很多方法,如:发邮件,发短信等功能。
主动触发异常呢,会把Exception捕获到的错误信息传给后面的i。这个主动触发异常,在后期大项目里面会使用到。
这里可以看到,如果输出正常了,那么就一直报主动异常,且,except里面什么都没有输出。
而现在报错了之后,i就接收到了异常,把异常传送给了i,而i是接收到的是try代码里面的异常,所以在上上图中,若没有异常,那么就主动触发了一个异常给i。
不信?可以看看以下代码,如果n成立,就直接主动异常,否则,直接把报错信息传送给i.
这里还有一个概念,为什么在前期IndexError什么的都可以输出信息,这里呢,就先不看源码,因为,IndexError什么的都是Exception的派生类,所以Exception能捕获到其它的异常。
8.5 异常处理自定义异常
这里讲解自定义异常,这里最主要的是知道为什么能获取异常,是因为里面类里面有一个__str__的方法。
这里,我们创建一个自定义的异常来看。
可以看到Exception已捕获到异常而且传给i了。
那么i是怎么拿到异常的呢?
这里i是对象,i是怎么来的?是Exception类创建的,那么print的是一个对象。
假设这里有一个对象
这里得到一个内存地址,为什么前面print i可以拿到异常呢?因为__str__方法
所以现在自定义一个异常,继承Exception类
那么我现在定一个异常,并且主动触发一个异常。
但是这里的异常就写死了,现在让异常动态起来。
分析自定义异常代码,应用场景,自定义返回某些特定字符串以及网页。
如果执行lizexiongError()这个类,会返回__str__里面的字符串,所以,这里我直接给LizexiongError里面创建初始方法,然后传送参数。在使用__str__判断返回什么,由于
LizexiongError继承Exception类,所以,将值复制给i,并打印出来。
8.6 异常处理之断言
此方式了解即可。运用在二个地方
第一, 一般在调试的时候使用;
第二,有些代码里面只有满足某些条件才能用这个程序。
8.7 反射和普通方式对比(模拟Web框架)
首先讲解一些常见的web框架。所谓的框架就是在一个文件夹里面放哪些内容。如登录、认证等。
MVC框架。
Mvc框架,就是规定了某一个文件夹里面放什么东西
Mvc框架组成就是
models:数据库信息
views:html模块信息
controllers:逻辑信息
Mtv框架组成就是
models
template
views
本章讲解的是,在一些常见的web框架里,有登录,有认证,有退出,这里写出每个模块,然后通过用户输入的url来判断是否有这个值,有的话把值返回出来。
以下是一种逻辑简单的书写方式。
现在还有一种升级的方式,如果URL有100个,那不可能写100个elif来判断,所以这里使用到getattr来去某个容器(模块)找函数,字符串函数名,如果有,就把函数获取到。
8.8 反射操作模块中的成员
此篇深入了解,本机用程序开放一个端口,并且优化一些语法。且讲解了许多其它用法,如setattr,delattr.
旁白,因为是在一个文件里面写,首先注释上面的代码。
可以看出上述代码做了优化,首先导入一个类,模拟开启本机8000端口,然后在获取到用户输入的url下标为1的值复制给后面的if判断。
直接返回函数的值,没有指定模块,可以访问。
如果没有此成员,那么一定是404
现在讲解其它用法,setattr,delattr
首先查看以下home模块有哪些成员,函数在没有执行,只把函数名给放入到内存里面的 。
可以看到home模块里面的成员。
也可以直接查看内存地址。
这里就可以setattr了,这里set一个值查看set前和set后的区别
这里setattr也可以传一个函数进去,这里可以使用lambda的方式,相当于在home模块里面添加一个成员。
这里还有一个删除的方法,来查看删除前和删除后的区别。
这里的删除只删除内存里面,文件里的不删。
这里笔记不删除,就是为了保留两种方式。
或者
8.9 反射操作类和对象中的成员
本章讲解对set,get,del,has进行了细化讲解。
这些方式目的就是操作内存中某个容器中的元素。
比如定义一个类,里面有类方法,静态方法,都可以一一找出来。
就是去需找一些东西,没有难点,记住。
可以看到类里面输出了很多已有的成员,已字典的方式显示出来。
现在查看单独查看keys,可以查看到已有的成员key。
上面查看了类,现在看看创建的对象有哪些成员
看一下对象又哪些成员
现在使用hasattr看看能否获取到。
现在获取一个对象里面没有的成员。
这里发现还是True,因为对象找的时候现在自己的内存里面找,如果没找到,还会去创建它的类里面去找,因为有一个类对象指针。
8.10 反射操作多层嵌套成员
如果类放在别的文件里面,那怎么找?可以使用两个getattr来找。
首先在home.py里面创建一个类。
然后在index 里面 import home,然后见以下代码
对于一个文件来说,有变量,函数,类等成员,对于类来说,它也有自己的成员。尽然找到了类,那么就可以根据类来查找类里面的成员。
上述查找的类里面的静态字段,若将类实例化呢?类实例化会执行类__init__的内容,所以这里看以下代码。
8.11 动态模块导入
这算一个补充小章节,与getattr关系不大了。
在之前中,如果在import 模块的时候如果有模块重名怎么办?可以使用as给模块起别名,
这里__module__类似与模块改名as ,但是__module__将导入的模块名变为字符串变量了。
8.12 完善Web框架
本章是对上一章的完善,相当于模块以及后面的内容都由用户自定义输入。
很多web框架就是用反射来实现的。
8.13 设计模式之单例模式一
什么是单例模式?
就是对于一些常见的类,有__init__, self.name=name类似这种,如果每次都实例化对象,那么每次在内存中都会创建同样的内存,造成内存浪费。那么问题来了...如果并发量大的话,内存里就会存在非常多功能上一模一样的对象。存在这些对象肯定会消耗内存,对于这些功能相同的对象可以在内存中仅创建一个,需要时都去调用,也是极好的!!!
针对面向对象来做的,函数式编程没有这个说法。
这里还是沿用反射的代码,重新创建一个userinfo.py
userinfo.py
这里webdemo有一些要注意。
取消了录入url,直接网页录入;
由于地址是localhost/userinfo/get_user,所以模块和方法的下标变为1,2
且不知为何,不需要import userinfo
这里开启8000端口,通过web访问userinfo/get_user 可以得到返回值。且内存地址肯定不一样,因为这里获取方法,每一次都在内存里面重新生成一个,问题在于,在内存里面,值都是一样,内存地址不一样,这样就浪费了。如果在内存里面只存一个。每个人来调用的时候就用那一个,那么就会好很多。
不信?可以自己打印出内存地址来查看是否这样(这里由于浏览器,会有报错,但可以无视)
这里开始改造升级使用一个内存地址。
首先创建一个不创建实例就可以访问的方法,类方法或者静态方法。
这里选择类方法。
这里代码没有完善,只用执行userinfo
这里首先创建了一个私有字段,然后一个类方法(这里一定要记得类方法的调用方式),
首先调用了类方法,首先判断私有字段是否为真,默认为None,那么就是False,就回else判断,就回把Sqlhelper() 实例化__init__的对象复制给私有字段cls.static_instance,就把实例返回回去了,第二次访问,私有字段有值,那么就不会重新实例化对象了,一直调用那么一个.
那么,这里继续优化,我每次get_user时候是否就可以不用每次去获取类的方法了呢?
我每次调用方法的时候,就可以直接将类的方法传给我。所以也可以说直接调用类对象就可以了。
如果不是直接调用类对象?那么还是创建2个内存地址,相当于实例化2次一样。
Web访问测试
此篇为userinfo.py和webdeam.py两个代码,在个git上都有详细代码。
8.14 设计模式之单例模式二
第二章节就是把第一章节的内容给整理一下。
代码:danli.py
首先回顾普通的类调用,查看内存地址,肯定是不一样的。
这样一直创建类实例化就会在内存里面创建多个地址。
而如果想要一个值,第一次有,第二次把已经有的值给他,这就是单例。
相当于第一种普通模式的时候,直接类初始化,每一次调用,在内存里面重新开辟一块空间,而单例就是直接调用类对象,在类对象里面判断是否有数据。
8.15 socket原理和WEB框架的实现
socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求。
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用【打开】【读写】【关闭】模式来操作。socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)
socket和file的区别:
file模块是针对某个指定文件进行【打开】【读写】【关闭】
socket模块是针对 服务器端 和 客户端Socket 进行【打开】【读写】【关闭】
这里设想一下,我们平常的http请求,有多个用户来请求,这时,一个用户请求数据,服务器返回数据,然后断开,这个就叫做短链接。有http,当然还有tcp,http基于tcp创建的,这个过程就是首先创建一个tcp连接。怎么创建一个tcp连接呢?在这里叫做socket。
以下首先说说web的最原始socket的框架,简单解释以下执行流程。
代码: socket_demo.py
补充一句,代码client_send”HTTP/1.1 200OK\r\n\r\n”)暂时还没发现用处。
执行
查看程序结果
下章将详细解析代码含义。
8.16 socket服务端和客户端实现
这里自己写服务端和客户端。其实,在本章,只要流程搞清楚了,本章也就清楚了。
首先解释以下语法的作用。
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0)
参数一:地址簇
socket.AF_INET IPv4(默认)
socket.AF_INET6 IPv6
socket.AF_UNIX 只能够用于单一的Unix系统进程间通信
参数二:类型
socket.SOCK_STREAM 流式socket , for TCP (默认)
socket.SOCK_DGRAM
数据报式socket , for UDP
socket.SOCK_RAW 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。
socket.SOCK_RDM 是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。
socket.SOCK_SEQPACKET 可靠的连续数据包服务
参数三:协议
0 (默认)与特定的地址家族相关的协议,如果是 0 ,则系统就会根据地址格式和套接类别,自动选择一个合适的协议
总结就是指定IP协议类型和tcp等。
这里默认值就是ipv4和tcp,所以参数可以保持默认不写
sk.bind(address)
要绑定的地址,以元组(host,port)的形式表示地址。
sk.listen(backlog)
开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。也就是连接最大数
sk.connect(address)
连接到address处的套接字。一般,address的格式为元组(hostname,port),如果连接出错,返回socket.error错误
相当于土电话里面传输的电话线。
sk.recv(bufsize[,flag])
指定最大接收1024字节,超过了分次接收
sk.send(string[,flag])
将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。即:可能未将指定内容全部发送。
sk.sendall(string[,flag])
将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
内部通过递归调用send,将所有内容发送出去。
简单来说就是sendall会先往缓冲区里面发,发完之后马上发到对面。
这里来详细解释一下流程
首先讲解服务端
首先要创建一个socket地址簇
Socket.socket()保持默认参数,然后指定绑定的IP和端口,
bind.(ip_port),然后设置最大监听数
listen(5),然后就要开阻塞监听来自客户端的请求
conn,addr = accept()
conn为电话线,addr为地址,然后将conn的数据给recv来接收
client_data =conn.revc(1024)
到这里,因为是最简单的框架,内部没有任何处理,但是接下来,
服务器该将数据给返回给客户端了
conn.sendall(“内容”)
然后断开连接。
接下来讲解客户端
这里客户端也可以说是浏览器,但是这里自己写一个。按照正规流程,如下
首先我也需要一个地址簇,
socket.socket(),然后就是我要连接哪个地址的哪个端口,
connect(IP_PORT),然后发送一些请求数据过去
send(‘请求数据’),这时,服务器应该收到了我的请求数据,并做了处理,把我想要的数据给我返回回来。
然后关闭连接。
现在启动两个py文件,在客户端执行查看结果。
8.17 socket详细方法
本章讲解socket的一些方法功能,在以后用的时候回来查阅。
8.18 socket实现单线程聊天机器人
这里就是模拟客户端发送请求,服务器端判断是什么请求然后返回给客户端相应值,但是这里是单线程的,只允许一个客户端发送请求。
代码:socket_service.py socket_client.py
执行结果
这里有一个疑问,在server里面为什么有2个while,这里得出的结果验证,在二次while的时候,代码执行到sk.accpet()的时候就不在接收客户端的请求了,所以要待二次while将请求禁锢在第二次while里面,这里也是一种代码思路。
8.19 SocketServer使用多线程实现Socket服务端
本节两张合成一章
现在首先只实现基本框架
代码: thread_socket_server.py thread_socket_client.py
Server
Client
现在测试一下多线程
现在解释一下服务端代码:
首先不说父类
1.就class MyServer(SocketServer.BaseRequestHandler),一定要自定义一个类,并且继承以下类。
2. 类中必须定义一个名称为 handle 的方法,注意,这里是必须.
3.启动ThreadingTCPServer,这里才是实现真正的多线程。
这里描述一下执行步骤:
1.启动服务端程序,每一个客户但进程过来的时候,SocketServer.ThreadingTCPServer就回把MyServer实例化,但是MyServer实例化没有__init__参数,那么就会去父类里面找
看到这,大概就知道为什么函数名称为什么要为handle了,因为在实例化的时候,执行的就是handle名的函数,实例化过后就会拿着这个实例化的线程去和客户端交互。Server_foreve()相当于一个死循环,接收到一个来自客户端的请求时,就会实例化MyServer这个类,没一个请求来了,就创建一个Myserver的对象,就这么实现了多线程。
如果想要更官方的说法,这里可以去参考源码
内部调用流程为:
- 启动服务端程序
- 执行 TCPServer.__init__ 方法,创建服务端Socket对象并绑定 IP 和 端口
- 执行 BaseServer.__init__ 方法,将自定义的继承自SocketServer.BaseRequestHandler 的类 MyRequestHandle赋值给self.RequestHandlerClass
- 执行 BaseServer.server_forever 方法,While 循环一直监听是否有客户端请求到达 ...
- 当客户端连接到达服务器
- 执行 ThreadingMixIn.process_request 方法,创建一个 “线程” 用来处理请求
- 执行 ThreadingMixIn.process_request_thread 方法
- 执行 BaseServer.finish_request 方法,执行 self.RequestHandlerClass() 即:执行 自定义 MyRequestHandler 的构造方法(自动调用基类BaseRequestHandler的构造方法,在该构造方法中又会调用 MyRequestHandler的handle方法)
Socket只会干两个事情,一个是收,一个是发。他是网络协议的基础。
当然,这里只能收发,在handle函数里面干什么随我们的意,这里,实现执行远程命令,并且返回。
Server
这里先讲解服务端代码
这里没有sk.accept来接收用户请求,而是self.request来接收用户请求。
还有执行系统命令使用os模块的popen方法,可以返回系统命令,这里Self.request.send(str(len(res_cmd)))将客户端的长度先发送给客户端,这里等会在客户有判定。然后在把数据全部发送给客户端。
这里为什么要现将发送的长度发送给客户端?
因为一次接收1024个包,但是这时如果来了2000个包,sendall会将所有数据放入缓冲区,客户端第一次循环会接收1024个包,剩下的900多个会在第二次不管输入什么首先返回给客户端,所以这里就出现了问题,但是我服务器的包都发送完毕了,我服务端就没什么关系了,剩下的到客户端来处理。
首先定义一个变量,用来接收服务器返回给我的长度res_size,长度类型为字符串。
然后在将这个值设置为总长度total_size,然后在设置一个已经接收到的长度received_size=0,然后while正式开始接收数据,没接收一次数据长度,就将已经接收的长度变化。if 已接收的长度 == 总长度的时候,那么就是最后一次接收了,那么就回将缓存区里面的数据最后一次打印出来并退出。这样就实现一次将数据接收完成。
现在输出结果查看
中间部分省略
这里完成了,但是还有一个问题,在这里多输入几次ipconfig,会概率出现以下情况。
这种情况叫做socket的粘包,为什么会出现粘包?
上面说我int了一个不合法的数据。但是我int了ipconfig的结果,那当然报错了。
这是因为服务端发送数据过来的时候,发过来了长度和结果,但是别误会,并不是发送过了2次,而是当缓冲区满了之后才回发送
发送端和接收端都有缓存区,2端都是等都满了才会发出去,缓冲区还有超时时间,时间到了即使没有满,也发出去,但是时间在1秒之下。如果没有这种情况,首先长度到了缓冲区,长度没有发送出去,结果又来到了缓冲区,这种情况,长度和结果一起发给了客户端,就出现了粘包。
解决这种办法的方式就是在服务器发送长度后是单独的,也就是说想办法将发送长度和法从返回结果隔开就可以了。
在服务端将长度发送给客户端后,客户端发送一个我要开始接收了,然后客户端在开始接收数据。
完整代码
Server
Client
执行结果
9.L009
本天内容无需关注,全部在第10天详细讲解。
9.1 服务器端处理客户端请求
从现在开始,就要认真的写一个项目了,何为认真写一个项目,就是哪些代码应该归类到哪些目录下,到时候直接调用,给人一种清晰的感觉。
首先bin目录放FTPserver的执行程序
- Conf放的是配置文件.
- Modules放置的是主程序.
- Var下面放置log和用户空间.
首先写一些初始代码
首先,如果目录写成这个样子,那么按照一贯打牌的方式,那么该程序就只有一个入口,那就是bin下的py,然后通过bin下的py去调用其它的文件,这时就出现了一个问题,bin和conf和modules是同级目录,那么怎么去调用呢?
这个时候就要去main里面找这个类,然后把sys.argv传给main的ArgvHandler类。
这个时候CrazyFTPClient就可以调用main.py了吗?
不行,因为都是同级目录,结果肯定找不到。
这里就可以使用sys.path把总目录加入到下面。所以os这里就有用了。
首先这里添加一个__file__看看结果。
打印除了当前程序所在的路径名。
这里看到的是绝对路径,是因为使用pycharm调用的,其实是一个相对路径。
如果linux用脚本执行,是一个相对路径。
这里既然可以打印我程序在哪个路径,那么我就可以知道,对于总程序,我在那一级。
首先找到第一级。
这里已经去了一级了,现在再去一级呢?
那么在加一个os.path.dirname就可以了
这个时候打印的是相对路径,那么怎么变成绝对路径呢?
os.path.abspath就可以了。
这里还不够,将这个路径加入到环境变量才算成功。
9.2 服务器端读取配置文件
上一篇,入口写完了,那么所有的东西都在main里面,比如,我传来一个start,那么就start。
本章主要实现启动ftp服务,且监听的端口号及IP地址为配置文件的方式。
首先写Main 程序
然后写需要调用的启动多线程的程序
这里只写一个多线程,等会主程序会来直接调用的。
最后写配置文件
然后就是程序启动的入口,直接把sys.argv参数传到main的类里面去,然后按照顺序执行。
现在启动测试
9.3 用户认证
讲解的及其垃圾,但本课主要要明确开发的思路以及开发的规范,代码有空后续在做研究。
9.4 三层架构介绍和后结开发思路
所谓三层架构简单来说就是把目录结构功能分成而以。
9.5 线程进程及CPU工作原理介绍
百度吧。
9.6 线程及线程锁
这里也略过,在第10天全部会详细讲解。
10.L010
10.1 IO多路复用介绍
这里先忘掉socket.server.
本章讲解相当于,几个客户端来请求,我们不用socket.server,然后服务器内部自己决定先处理谁的请求,注意,这里是单线程,没有使用socket.server.
I/O多路复用,通过一种机制,监听多个描述符,一旦某个描述符就绪,能通知程序进行读写操作。
原生的socket没有能里监听多个描述符。
什么是描述符?
Sk = socket.socket()
这个sk就是描述符。
现在说linux的python的select模块,其中提供了select,epool、pool三个方法,分别调用系统的select,poll,epool从而实现多路复用。
这里简单描述以下select,poll,epoll的区别,
- Select 限制监听描述付的个数为1024,
- Poll没有限制。
- 而epoll是linux系统底层的更新。一旦一个请求过来,那么我系统自动感知到了,而上面的是比如手动的。Python只是调用而以。
10.2 select监视终端输入
python在windows上普通文件的变化,通过I/O是检测不到的。所以python在上只有终端的输入输出和socket的输入输出。
Select 监听到stdin的变化就输出一个操作。
这里来一个实际案例。
本章代码io.py
第9行: [sys.stdin,] 这里是一个列表,相当于 f=file()的句柄f,obj=socket()的obj,而sys.stdin=终端输入的内容。
Select.select()监听用户的输入,如果用户输入内容,select感知后,sys.stdin改变。将改变的文件句柄保存至列表,并将列表作为select第一个参数返回,如果用户没有输入内容,那么select第一个参数也就是readable=[].
这里后面还有一个1,这里代表等待时间,为1秒。
而[sys.stdin,]这个逗号是书写规范,让人一眼就可以看出这是列表、元祖还是函数。
10.3 select实现socket服务端一
通过select监听多个句柄。
下面一步一步来,先来一个简单的例子。
还记得前文中的setblocking(False)吗?这里如果设置为False,那么accept和recv如果没有数据过来,那么就会报错。
本章代码io_server_1.py
sk.setblocking为False之后,没有收到客户端的信息,那么就报错了。
现在如果不想让它报错怎么解决呢?
那么使用select
以上,我只监听了一个端口,如果我要监听2个端口呢?
那么我把2个端口号的句柄一起放入select监听就可以了。
这里补充一点,for循环里面的i.accept是什么?
I就是sk,sk.accept是会接收到2个值的,这里i.accept()配合for循环,表示一直在监听,并且有值了就把i.accept的address打印出来。
Sk是服务端的socket句柄,通过accept()接收后,如 conn,address=i.accept(),这个conn就是客户端的句柄了
10.4 select实现socket服务端二
上一章实现了不使用socket.socket来实现监听多个请求,现在实现不使用socket.socket实现与客户端交互。
本章代码 io_server_2.py,io_client_2.py
Io_server_2.py
Io_client_2.py
模拟两个客户端连接
代码讲解
服务端
首先select.select会监听句柄变化,这里句柄已经使用列表input替代了,首先这里要搞清楚一个概念,sk是我服务器端的句柄,当有客户端连接进来的时候,sk服务端句柄会发生变化,select会扑捉到,然后第21行的for循环就回获取客户端句柄,并将客户端句柄添加进入input的监听列表,此时,sk服务器端句柄就不会在发生变化,后期通信,只有客户端句柄会发生变化,但,当第二个用户接入进来的时候,sk又发生变化,又扑捉到了一个客户端的连接,又把客户端句柄添加进列表,此时,次要是新用户连接进来,sk服务器端句柄才回发生变化(22行开始的if判断),然后把客户端句柄添加至列表。就这样实现了多线程。
10.5 select实现soekct服务端三(tornado源码)
tornado是一个web框架,上一章的东西会使用在哪?就是这里。
以上是tornado的源码,打开源码,找到poll方法,发现,和上一章写的东西是一样的,但是源码是把值放入一个字典里面了,而且,他还使用了其它参数,下面更加深入讲解。
10.6 select实现socket服务端四(监视服务端和客户端socket句柄)
前文讲解了
rlist , w ,x = select.select(input, [] , [] , 1)
input里面有变化,select感知到,并赋值给rlist,而input后面的[]同理会复制给w。但还是有不通,只要[]不是空列表就可以感知到。
而x 代表的是错误,当监听的所有句柄里面有错误的时候就会把这个值复制给x。
而最后第4个参数1,代表的是阻塞时间,当代码走到这里的时候,如果没有客户端发来数据,阻塞一秒后代码就往下执行,如果没有设置这个值,那么就会一直阻塞,直到有请求过来。
本章还是沿用上一章代码来讲解。
首先这里只图例讲解w的值。
当客户端连接进来的以后,有数据后,一直监听ouput的值,这里会在后期详细讲解。
当然,整体程序还是有个小bug,就是在客户端断开连接后,select还在监听退出的客户端句柄,这里当然不合理,当客户端断掉之后,应该立即删除监听的这个句柄。
思路如下,
客户端连接断开了会给服务器发送一个空的消息,这样,我们就可以通过if来判断了。
让客户端断掉连接查看结果。这里不能直接关闭客户端,直接关闭客户端是没有数据发送,服务端会报错的。
这里的客户端代码需要小小的修改。
直接让客户端接收一个值,然后超时没有接收到断开就可以了,这里也要注意服务器端的睡眠时间,一定要让服务器在if判断的时候能收到数据,即使是个空数据,否则,服务端报错。
这里还是贴出所有代码,关注标红的地方。
查看结果,是否客户端超时退出后就自动删除了。
10.7 select实现socket服务端五(接收和返回数据)
第二个参数output的使用方式。也可以说控制读写,也可以说是接收和返回数据。
第二个参数叫法存在争议。
本章目的,若文件描述符可读,rList有变化,感知
若文件描述符可写,wList,只要有,就感知
在前面几章也讲解了,如果wList里面如果有值,就一直感知,但是没有客户端发送连接数据过来的时候,那么wList就一直是空,只有客户端连接或者发送数据过来,就一直感知,这里要注意。
本章代码
io_server_2_1.py io_client_2.py
首先查看服务器端代码
当有客户端连接过来的时候,将客户端连接句柄添加进去output列表,在下一个循环将数据发送后,马上删除数据,这样就不会导致重复给客户端发送数据。
客户端端代码没有改变。
10.8 select实现socket服务端六(Queue的基本使用)
本章节应该在下一章讲解,但是这里首先讲解方便下章节学习。
Queue,遵循先进先出原则,若没有数据,那么就一直等待。
可以看出Queue是一个类。
首先演示如若Queue没有数据会出现什么情况。
若没有数据就一直卡在那里。
这里当然有一个参数nowait,若没有数据就不等待,直接报错。
这里可以使用异常处理来避免此次报错。
这样就避免了等待和报错。
当然,还有,我限制了队列的大小,然后上传数据,会出现什么情况。
还是等待,这里当然也有nowait参数。
10.9 select实现socket服务端七(分离读写操作原理及实现)
作为一个web服务器,一般流程是接收数据→处理数据→返回数据,现在接收到的数据就是client_data。
本章代码 io_server_queue.py io_client_2.py
Client
执行结果
为什么这个client_data能用?如果1秒中之内,来了2个客户端同时发了信息,服务端能感知到客户端是2个。那么服务端的rList可以监听到2个数据。那么就进入for循环了,如果第一个客户端发的是1,第二个客户端发的是2,那么服务端就应该给第一个客户端回复一个1,当接收到客户端句柄的时候,第一个for循环的else下面的if判断就把数据放入output里面了,这时,第二个客户端发的数据来了,那么client_data就变成2了,然后就到下一个for循环,这时,wList是有2条数据的,那么第一个w就是1,但是此时就要给客户端发送数据,这是发送的数据client_data结果可是2,那么就出现问题了。
那么此时,我们就可以用队列来解决。
只要客户端连接上来,那么就往message里面添加一条数据,类型为队列。message[conn] = Queue(),那么
message={
‘c1’:队列,
‘c2’:队列,
}
每一次请求过来,就生成一个队列。C1的数据放到c1的队列里面。
如果有数据就应该在指定队列中插入数据。然后在从指定的队列取出数据,然后发给客户端。
下面贴出代码
客户端的就不贴出了,代码没有变动。
当然,此代码当然不是很严谨,会有bug,但是主要学习的是多客户端发送数据,queue的队列使用。
要想解决本代码报错,删除最后一行del message[j]即可。这章老师讲解的有问题,因为在写的for循环里面,最后一行是不需要的,因为,取出一个值,那个值就就没了,删除的肯定是后面存储的值,所以会报错。这里一定要注意。
10.10 SocketServer源码剖析一
线程和进程区别在哪?
就是在代码中将ThreadingTCPServer改WorkingTCPServer那么就由线程变成进程了。
一个类的区别,区别就在一个方法上面。
线程是一个人来了,服务端分配一个资源单独和这个人交互,
进程是这个人可以调用很多资源,给很多人使用。相当于复制一件房子,给房子里面的人授课。
这里BaseServer是执行父类里面的构造方法,super是执行经典类里面的构造方法。
既然去执行父类的构造方法,那么首先去查找一下父类的构造方法,后面的__init__等会在执行,找到父类的构造方法。
传入的这两个值正是下图的端口和类名。
Self.RequestHandlerClass == 自己写自定义的类(MyServer)。
到这里只进行一个初始化,然后在执行自己的
本章需要自己多练习,本章比较饶,需要多看几遍
10.11 SocketServer源码剖析二
本章重点,就是在执行ThreadingTCPServer类的时候,已经按照顺序继承了其它类,按照顺序一个一个找过去。
10.12 SocketServer源码剖析三
本章讲解了进程和线程执行的区别,笔记已经没有太大重要,需要自己去多看源码,多了解源码的执行方式和顺序。
Python不能创建进程。
以下是简单的创建进程的方式,详细请能看懂源码之后在百度怎么创建进程。
10.13 twisted和事件驱动介绍
Twisted是一个事件驱动的网络框架,其中包含了诸多功能,例如:网络协议,线程、数据库管理、网络操作、电子邮件等。
这个框架予许程序员往里面注册自己的类。
简而言之,事件驱动分为二个部分:第一,注册事件;第二,触发事件。
自定义事件驱动框架,命名为:“弑君者”:
相当与把事件注册进去了,不需要关注怎么执行,只要关注框架规则,把需要执行的东西注册到框架里面就可以了,,简单来说就是简化代码的书写,其余的交给框架去解决。
执行index.py查看结果。
这里可以看出,自己创建的类继承了event_driver.BaseHandler的类。
然后run里面,将类执行,然后首先执行子类的execute的方法。
如上述代码,事件驱动只不过是框架规定了执行顺序,程序员在使用框架时,可以向原执行顺序中注册“事件”,从而在框架执行时可以出发已注册的“事件”。
10.14 twisted框架功能介绍和安装
编译加安装
这里的安装很简单,进入到twisted解压后的目录。
Python setup.py build
Python setup.py install
10.15 twisted框架实现socket server 源码剖析
10.16 多线程和多进程的区别和对比
一个应用程序点击开始,就包含一个进程,进程默认包含一个线程,默认有的这个线程就是主线程,应用程序计算需要利用cpu。
假设应用程序在创建一个进程,这两个进程都是可以被cpu调度,如果只有一个cpu,那么进程只有一个在执行。线程之间是可以共享内存的,但是进程之间是无法共享内存的。
进程创建就是独立,相当于重新创建一个教室,里面的东西也要全部重新创建。
在这个里面有一个线程锁,就是如果在一个进程里面有2个线程,在出口有一个线程锁,只有通过这个锁,才能被cpu调用,在python里面默认只能通过一个线程。多线程的程序,是不能同时应用多核的cpu运算的。如果是多进程,就可以直接用多个cpu了。
什么时候用多线程和多进程。
这老师讲的很混乱,自己百度
(和前面答案冲突了,老师没讲清楚,自己百度搜索答案)
10.17 线程的使用和常用方法
Threading用于提供线程相关的操作,线程是应用程序中工作的最小单元。
本章代码:
threading_sample_1.py
查看以上代码,执行了主线程后,然后其余的子线程各自执行了。
上述代码创建了10个“前台”线程,然后控制器就交给了CPU,CPU根据指定算法进行调度,分片执行指令。
当然还有更多方法。
- start线程准备就绪,等待CPU调度
- setName为线程设置名称
- getName获取线程名称
- setDaemon 设置为后台线程或前台线程(默认)
如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止
如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止 - join 逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义
- run 线程被cpu调度后自动执行线程对象的run方法
只需要关注setDaemon即可,这个可能用的比较多,其余的无太大意义。
10.18 线程锁
由于线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,当多个线程同时修改同一条数据时可能会出现脏数据,所以,出现了线程锁 - 同一时刻允许一个线程执行操作。
本章代码:threading_sample_2.py
未上锁的代码及执行结果
假设第一个线程进来了,第二个线程进行执行的时候,那么gl_num =2 了,输出到最后的时候,那么全局变量一直增加,那么就一直输出最大值,那么就产生脏数据了,那么就要加一把锁,如果一个线程进来要执行这个方法,那么我就锁上,等我执行完了才解锁。
执行完成后解锁。
那么第一个线程进来,锁上了,第二个线程进来之后,发生锁上了,就不执行了,就在锁的哪个地方等着了,是不是跟上篇的join感觉一样,join是所有线程操作都给锁上了,而锁只锁该锁的地方。
为什么要上锁呢?
比如mysql的时候,一件商品有100件,200个人同时买,那么就出现商品变为负数了。
10.19 信号量
本章是补充章节,课上并未讲解。
互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。
本章代码:threading_sample_3.py
可以看出输出结果,每次只输出了3个。
10.20 线程Event的使用
event这里的翻译不能叫事件了。暂未想出更合理的叫法。
python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法 set、wait、clear。
事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。
- clear:将“Flag”设置为False
- set:将“Flag”设置为True
- wait :方法会阻塞
这里简单来说就是用主线程控制子线程何时执行,简单说就是碰到红灯停,绿灯行的概念。
本章代码:threading_sample_4.py
到wait()的时候阻塞了,当我set()之后,后面的代码才执行。
还有一点补充,这里不举例代码。
使得线程等待,只有满足某条件时,才释放n个线程
import threading def run(n): con.acquire() con.wait() print("run the thread: %s" %n) con.release() if __name__ == '__main__': con = threading.Condition() for i in range(10): t = threading.Thread(target=run, args=(i,)) t.start() while True: inp = input('>>>') if inp == 'q': break con.acquire() con.notify(int(inp)) con.release()
10.21 多进程使用和介绍
由于进程之间的数据需要各自持有一份,所以创建进程需要的非常大的开销。
而创建进程和创建线程就一个区别。
本章代码: process_sample_1.py
在windows下,无法使用Porcess方法。
顺便补充模块的调用方式,上面的方式明显调用的时候方便很多。
10.22 进程变量共享和进程锁
因为进程内存是独享的,如果想要进程共享数据的话呢?就拿屋子比喻,如果想要2个屋子数据共享,那么就要有穿墙的功能。
进程间默认无法数据共享。
进程各自持有一份数据,默认无法共享数据
代码1:process_sample_2.py
可以看到,列表里面根本没有值,因为数据没有共享。
现在数据共享
方法一:array
使用一个特殊的数组就可以将数据共享了。这里的i表示数字类型,当然还有很多其它类型。
那怎么共享的呢?因为这是一个特殊的类型,只要用array,那么就共享了。
底下的代码其实就是搞笑的。
那么当进程的数据可以共享之后,是不是又会造成脏数据,那么进程也会有一个锁。
和线程一样,只不过,类在不同的模块里面而已。
这里很少使用,但是需要了解。
加锁
10.23 进程池的使用
进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。
进程池中有两个方法:
-
- apply
- apply_async
这里的意思就是前面的执行完了,然后执行callback告诉我前面的执行完了。
Bar方法就是加了一个回调的区别。
这个深入只能看源码了。
10.24 协成的原理和使用
线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作则是程序员。
协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(保存状态,下次继续)。协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序。
协程的适用场景:当程序中存在大量不需要CPU的操作时(IO),适用于协程;
由程序员控制执行的进度,
这就是相当于创建了协程。
执行的方式和yield一样。
以上是主动的协程,那用在哪里?这是单线程,把里面分段切开了,是不是感觉没什么意思。
但是加入来了很多i/o操作,
简单来说就是把线程分成很多协程,线程是有上下文的切换的,切换的时候很慢。而协程是非常快的。
遇到IO操作自动切换:
from gevent import monkey; monkey.patch_all() import gevent import urllib2 def f(url): print('GET: %s' % url) resp = urllib2.urlopen(url) data = resp.read() print('%d bytes received from %s.' % (len(data), url)) gevent.joinall([ gevent.spawn(f, 'https://www.python.org/'), gevent.spawn(f, 'https://www.yahoo.com/'), gevent.spawn(f, 'https://github.com/'), ])
10.25 今日作业
写一个线程池
11.L011
11.1 线程池要点分析
回顾线程和进程。
和创建线程的思想。
比如关闭线程有什么方式等。
11.2 简单版本线程池(一)
建立简单版本的线程池,需要注意三个地方。
1.考虑建立线程数量。
2.线程池的状态。
如with open(file) as obj:
f.write()
3.关闭线程
本章代码 threadpool_sample.py
本章线程池是自己定义创建的
查看执行结果
代码解释
首先自定义个ThreadPool的类,初始化对象参数里面设置一个默认的初始值max_num=20,然后在初始化的时候创建Threading.Thread对象,并put至queue里面,然后在下面定义了函数get_thread从队列里面取出线程以及add_thread来增加线程。
在20行定义了不使用初始值,来保证线程池里面只有10个线程。
22-26定义了一个函数,里面传入2个值,这两个值在哪里传入?在28和32行执行的时候传入
25到26行为什么要等待2秒,是为了在28要创建线程的时候,这里创建20个,但是我线程池里面只有10个的时候,那就我每执行一次,过2秒就会像线程池创建一个新的线程,就不会造成线程使用完毕了。这里thread只是一个类函数的返回值,叫什么名字都可以。
11.3 简单版本线程池(二)
Threading.Thread()每次都创建一个对象,不太好
本章还是沿用上一章代码,直接更改。
首先实现还有2种方式。
1.在28至32行时,thread是一个对象,获取的是get_thread的返回值,如果在queue里面开始放置的值不是线程,而是普通参数,会怎么样,那么就会造成取出的和新建是自己插入的值,那么在开始循环调用的时候,就不使用ret(thread)这个参数了,那么就要使用threading.Thread来创建线程来处理,那么就只使用到了Queue的等待功能。
2.还有一种说法,这里用说法只是为了区别不是方法。
我在put到队列里面的时候,put的不是类,而是threading.Thread()对象,那么也可以,但是这样每次都创建一个对象,那么内存分配就不太好,而不加()就是类,在内存里面只保存了一份。
如果保存的是对象,而且要使用到thread这里值,那么代码方式就不能这么写,因为会报错,TypeError: 'Thread' object is not callable,报错解释是对象和函数重名了,这个要更深入了解,只有研究源代码。
这种方法访问?为什么?因为在Thread类里面,target都是私有字段。所以这种方法并不推荐。
11.4 复杂线程池预备知识之上下文管理(一)
本章代码:sxw.py
第一个知识点
往队列里面一直添加的对象内存地址其实都是一样的。
第二个知识点:
使用with来管理上下文,
既然上述的方式通过with首先执行before,然后执行天青色等烟雨,那么就可以在yield前后有所作为了,这是ii = I ,j = 1,那么 ii.append(j)把1加入进入,那么就有一条数据,执行完成后在删除1,那么就是0条数据了,这样就可以通过这个方式统计进程的数量了。查看代码及执行结果。
结果就是 0(开始没有)→ 1(append一条数据) → 0(数据删除)
11.5 复杂线程池预备知识之上下文管理(二)
上文所讲,With中间的函数如果一直在执行,那么统计append就为1,那么with是不是相当于就是线程或者进程。
本章还是沿用上一章代码:
现在创建一个线程,然后一直监听ii里面的个数。
查看执行结果。
从执行结果可以看出,num函数的for循环直接进行,然后with执行了show,然后ii的长度增加,由于with程序持续了5秒,那么就有5秒ii的里面是有数据的,5秒过后,num的函数还在运行,但是with不在运行,执行end之后,删除了ii里面的数据,长度再次变为0,这一章不是很难,但是对本人有点绕,需要认证理解此代码执行流程。
现在还有一个增强版本的,把with包装成为函数,然后在通过线程来启动with。
查看执行结果,那么同时会启动10个task,也就是10个with
当然,这里也可以使用random来决定with运行的数量,但是这里就不贴出代码了,在git都有相应代码。
11.6 复杂线程池设计思路
本章讲解上节代码,还是要自己回顾。
11.7 复杂线程池代码剖析
源码剖析,一定要看
搜索线程池-武配齐
11.8 paramiko执行简单命令
开发堡垒机之前,先来学习Python的paramiko模块,该模块机遇SSH用于连接远程服务器并执行相关操作。
本章代码paramiko_sample.py
SSHClient
用于连接远程服务器并执行基本命令
基于用户名密码连接:
SSH封装transport
这个原理
由于代码大部分一致,这里就不贴出基于密钥的连接了。
11.9 面向对象封装多个远程操作(思路)
在上一章,对于第二种方式transport可能理解不太清楚,这里,把transport的顺序调换一下,就理解的更清楚了。
可以看出,ssh.connect被transport代替了。也可以说ssh.connect是基于transport来做的。
SFTP CLIENT,这里就不演示,基于transport的方式来封装,登录,上传,和下载的。
到这里就有疑问了,SSH有两种方式连接,为什么到SFTP就只有一种方式了。
这里就有以下代码,如之前学习过更改HA的配置,那么实现就有如下步骤:
- 本地生成文件uuid.ha.
- 上传文件至服务器 uuid.ha
- ha.cfg -> ha.cfg.bak
- uuid.ha -> ha.cfg
- reload
如果使用代码实现,那么就最好创建一个类
Class HA(object): def __init__(self,host,port): self.hostname = host self.port = port def cmd(self): 执行连接 def cmd1(self): 连接: def ftp(): 上传文件 def run(self): cmd() cmd1() ftp() obj = HA() obj.run()
这样是不是,在调用的时候,直接调用run方法就可以完成那5部操作了?当然,下面还有重点,如 ftp操作是用ftpclient,命令操作那么就要使用sshclient。
那么这里观察第一种ssh的方式,直接创建ssh,那么是不是只能做ssh的操作?
如果使用transport,那么就写一个transport就行了。简单来说,ssh就好像是一体的,一次性把操作给做完了,但是只能执行命令,但是transport,把ssh分离了,里面的操作什么的自己定义。
11.10 面向对象封装多个远程操作(实现一)
本章首先实现上一张思想的功能。
本章代码:paramiko_ha_1.py
以上代码实现了创建文件,并且上传至服务器,而且改名。
11.11 面向对象封装多个远程操作(实现二)
如果用上面的方式,就有2次连接,那么一个操作就要连接一次,那么时间都浪费在连接上了。那么是否可以把sftp和执行命令相同的命令合并一下,只需要连接一次?
以下是优化代码:paramiko_ha_2.py
这里把连接直接写成了一个函数,直接在upload和rename里面调用就行了,就没有必要创建多个连接,这就是ssh和transport使用的区别。
11.12 paramiko实现持续连接服务器之肆意妄为一(linux)
实现,相当于一个远程工具而以,远程上一台主机,然后执行命令。
本章代码:
Paramiko_baolei_1.py
由于本章使用到了select模块,所以本章在linux下执行。
上述代码最主要的两个if判断,首先判断打开的chan里面有没有数据,如果有把接收到的数据接收到屏幕上,第二个if判断,判断用户终端有没有数据输入,如果有,那么就发送给远端服务器。整个流程就是stdin来监听用户终端有没有命令输入,如果有,就发送给远端服务器,远端服务器返回数据后,chan监听到了就打印在屏幕终端。
简单小总结:stdin接收用户输入发送给远端服务器。
Chan接收服务器返回的数据输入到用户终端。
执行结果
11.13 paramiko实现持续连接服务器之肆意妄为二(linux)
在上一章,实现了远程登录,但是在执行结果可以看出,是没有tab补全这个功能的。
因为是当我回车之后终端才接收到我的数据,如果是tab,没有回车,终端程序获取不到tab的指令,如果想要实现tab不全功能,那么就要把我终端每输入一个字符,那么就发送到服务器,这样就实现了tab补全,当然回车也算一条指令。
本章代码:paramiko_baolei_2.py
11.14 paramiko实现持续连接服务器之记录操作日志
本章节就是记录用户登录堡垒机的操作日志,
方式一:
在远程的时候,是否还有tab补全命令这个功能,tab补全可以当成空格来看,但是还是会记录在里面。本篇沿用上一章代码,因为在代码多了3行文件写入。
查看执行结果。
查看history文件,由于是linux,编码等问题,未能自动换行以及显示tab等字符。可是一个^M等于一个回车,可以看到hos后面的tab已经记录了。
方式二:
不记录tab的输入,是什么样的?代码也简单,只需要稍做更改即可。及一个if判断,如果是空格,那么就不做记录。
在ho的时候tab,然后host出来之后hostn再一次tab。
注意,堡垒机篇章没有两全其美的办法,上面两种方式任选一种。
11.15 paramiko实现持续连接服务器之肆意妄为三
在以上两种方式实现的全部都是在linux上的远程,windows当然也有,windows因为没有tty,所以实现方式使用socket来接收实现的。
本章代码:paramiko_baolei_3.py
截止到本章,堡垒机的基础知识已经讲解完毕,这里整合了linux和windows的登录方式,但是在paramiko模块的目录下,有一个damo.py文件里,已经有了windows和linux的代码,以上几章讲解就是基于daem.py文件来讲解的。
11.16 paramiko实现堡垒机功能
本章结合自定义脚本,结合paramiko下的脚本demo.py, interactive.py来实现登录一台堡垒机来登录自己的服务器。
自定义代码集齐简单,不需要做过讨论,需要主要的是思想,在堡垒机上远程其它服务器的时候,如果退出了该程序,是否需要退出堡垒机?
设置如下:
11.17 Python操作MySQL API
Python 操作 Mysql 模块的安装
linux: yum install MySQL-python window: http://files.cnblogs.com/files/wupeiqi/py-mysql-win.zip
首先以下代码只是简单的插入功能,但是涵盖了创建连接,操作数据库,关闭连接等代码
还可以增加多条数据,把要增加的数据保存到一个列表里。
使用executemany实现。
修改数据和删除数据大致相同。
现在讲解查询数据的几个参数使用。
Python查询Mysql使用 fetchone() 方法获取单条数据, 使用fetchall() 方法获取多条数据。
- fetchone(): 该方法获取下一个查询结果集。结果集是一个对象
- fetchmany(num):获取自定义的行数
- fetchall():接收全部的返回结果行.
- rowcount: 这是一个只读属性,并返回执行execute()方法后影响的行数。
这里有一个指针,如果使用fetchone,只会取出一条数据,然后指定就停留在第二行那里,当然也会有指定重置的方法。
还有一个参数fetchall参数,取出所有的数据,但是默认取出的时候数列表包含元祖的方式,如果想要转换成列表加字典的方式,那么以下方式:
#cur = conn.cursor(cursorclass = MySQLdb.cursors.DictCursor)
11.18 今日作业
12. L012
12.1 RabbitMQ简单消息队列
如果A的发送能力强于B的接收能力,如果A一直发送,B来不及接收,那么就会造成阻塞。
那么我们就创建一个队列,生产者把消息往队列里面放置,而消费者只去队列里面取数据。
现在先不使用Rabbit,使用Quene来创建一个简单队列查看一下基本的消息队列。
本小结代码:Queue_sample.py
上述简单创建一个队列,一个队列最多存储10个值,然后定义一个生产者函数和一个消费者函数,然后开启5个线程一直执行生产者函数,消费者只开启一个线程从里面取出值。
现在讲解RabbitMQ,首先安装。本章节在linux上讲解。
RabbitMQ安装 安装配置epel源 安装erlang $ yum -y install erlang 安装RabbitMQ $ yum -y install rabbitmq-server 启动rabbitMQ服务 service rabbitmq-server start 安装API pip install pika 源码 https://pypi.python.org/pypi/pika
对于RabbitMQ来说,生产和消费不再针对内存里的一个Queue对象,而是某台服务器上的RabbitMQ Server实现的消息队列。
本小结代码:producer_sample.py,consumer.py
生产者:
消费者:
执行结果
12.2 RabbitMQ消费者失联策略
生产者只要往队列里面放就行了,而消费者不一样,如果消费者在拿的过程中,网络断了等情况,消息拿出来了,还没处理完成,这个执行就不能继续了,这条消息就丢失了,这里no_ack =True 取走了,断电了,丢失了就丢失了,如果设置成False,消费者处理完了要告诉一下生产者,如果没有应答,就回重新把这个消息放到队列里面,防止消息丢失,只能在一定程度上防止丢失,就是消费者死了防丢失,如果消息队列死了,那就只有其它办法了。
现在在代码上新增消费者拿到数据返回应答,模拟消费者挂掉之后是否还有数据。
本章沿用上章代码,只是增加一个消息处理之后的应答代码。
以下是博客解释
acknowledgment 消息不丢失
no-ack = False,如果消费者遇到情况(its channel is closed, connection is closed, or TCP connection is lost)挂掉了,那么,RabbitMQ会重新将该任务添加到队列中。
由于结果过于麻烦,就不截图作证,仔细看本章代码即可。
12.3 RabbitMQ服务器异常策略
以上是消费者死了,如果服务器宕机了呢?那就是服务器端持久化,将消息保存到硬盘。
但是持久化,比如,原来创建了一个hello的非持久化的,现在参数改变,但是无法将已经创建的hello改变成为持久化的。
本小节代码还是沿用上述章节,只是做了小改变。生产者消费者都需要改变。
durable 消息不丢失
消费者
总结,生产者多两行代码,消费者多一行,由于很难演示服务端宕机,这里注意以下即可。
发布订阅
以上图片解析很简单,就是我发布者发布一条消息给一个固定的东西,就是exchange,通过exchange发送给订阅者。
发布订阅和简单的消息队列区别在于,发布订阅会将消息发送给所有的订阅者,而消息队列中的数据被消费一次便消失。所以,RabbitMQ实现发布和订阅时,会为每一个订阅者创建一个队列,而发布者发布消息时,会将消息放置在所有相关队列中。
本小结代码。
发布者
订阅者
结果演示
发布者开启一个窗口,发布信息,开启二个订阅者,二个订阅者都可以收到消息。
这里补充一点,exchange 类型如果是fanout,跟我绑定的消息类型,我收到消息,我都会发出去。之前exchange是空,那么只会把消息发送给一个指定的队列,这是是fatout的作用。
12.4 RabbitMQ实现发布订阅和关键字匹配
关键字发送匹配,简单来说根据关键字来把消息发送到相应的队列。
博客解释
exchange type = direct
之前事例,发送消息时明确指定某个队列并向其中发送消息,RabbitMQ还支持根据关键字发送,即:队列绑定关键字,发送者将数据根据关键字发送到消息exchange,exchange根据 关键字 判定应该将数据发送至指定队列。
本章代码:rabbit_fabu_pipei_1.py
rabbit_dingyue_pipei_1.py,rabbit_dingyue_pipei_2.py
发布者:
订阅者1:
订阅者2:
现在执行测试,首先发布者的关键字是wuxinzhe,但是订阅者两者都有lizexiong,但是订阅者1是wuxinzhe,订阅者2是liuwen,如果发送消息,那么只有订阅者1可以收到消息。
订阅者二是收不到消息的
现在将发布者关键字改为lizexiong,那么2个订阅都有该关键字,都是可以收到消息
12.5 RabbitMQ关键字模糊匹配
上节讲解的是详细匹配,但在rabbit里面还有是模糊匹配的。
exchange type = topic
在topic类型下,可以让队列绑定几个模糊的关键字,之后发送者将数据发送到exchange,exchange将传入”路由值“和 ”关键字“进行匹配,匹配成功,则将数据发送到指定队列。
- # 表示可以匹配 0 个 或 多个 单词
- * 表示只能匹配 一个 单词
本节还是沿用上一章代码。只是exchange类型发生了一点改变。
代码所做的改变非常小,但是这里还是贴出改变部分。
发布者:
订阅者1:
订阅者2:
订阅者1,模糊标识为#,匹配0个或者多个,而订阅者2模糊标识为*,匹配1个,所以只有订阅者1可以收到消息。
12.6 今日作业
12.7 初识memcached(补)
Memcache 搭建和启动步骤略过,请参考单独的文档。
这里安装Python API
python操作Memcached使用Python-memcached模块
下载安装:https://pypi.python.org/pypi/python-memcached
yum -y install python-memcached
第一次操作memcache
12.8 memcached天生爱集群
python-memcached模块原生支持集群操作,其原理是在内存维护一个主机列表,且集群中主机的权重值和主机在列表中重复出现的次数成正比。
看到以上,权重为2的主机,在列表里面出现了2次。
那么用户存储进入memcahced怎么选择的呢?
如果用户根据如果要在内存中创建一个键值对(如:k1 = "v1"),那么要执行一下步骤:
- 根据算法将 k1 转换成一个数字
- 将数字和主机列表长度求余数,得到一个值 N( 0 <= N < 列表长度 )
- 在主机列表中根据 第2步得到的值为索引获取主机,例如:host_list[N]
- 连接 将第3步中获取的主机,将 k1 = "v1" 放置在该服务器的内存中
举个例子来说
在源码里面有一个函数,比如我要存储K1,那么就回把K1转换成一个数字。
之后,这个数字和主机列表除了之后得余数(余数肯定在主机列表个数的范围内)。
那么就会存储在列表下标为2的主机里面。
集群设置,主机列表是列表包含元祖,如果一个主机的权重为2,那么就回在列表里面出现几次,就是这么简单。
如果存完值后,主机列表发生变化,是否取值错误,因为已经存储数据的主机已经位置甚至已经不在甚至已经down机了,存储的数据不存在,所以会取值错误。
后面有讲解源码,可以查看。
12.9 memcached常用方法
在memcacahe有很多方法,如,增加值,删除值,替换值,这里不做过多讲解,
需要注意set和add的区别,一个是覆盖,一个是增加。
最重要的是以下实例,脏数据。
gets 和 cas
如商城商品剩余个数,假设改值保存在memcache中,product_count = 900
A用户刷新页面从memcache中读取到product_count
= 900
B用户刷新页面从memcache中读取到product_count
= 900
如果A、B用户均购买商品
A用户修改商品剩余个数 product_count=899
B用户修改商品剩余个数 product_count=899
如此一来缓存内的数据便不在正确,两个用户购买商品后,商品剩余还是 899
如果使用python的set和get来操作以上过程,那么程序就会如上述所示情况!
如果想要避免此情况的发生,只要使用 gets 和 cas 即可,如:
本节注意看代码标注的地方,这个参数必须要开启,而且二个程序之间的时间差,都有等待,
如果二个程序在同时点击运行,那么mc.cas开始自增的时候,另一个程序去判断发现不一致就回报错。但是本程序运行结果是报错,但是mc.cas_ids一直没有自增,但结果表示正常,可能是一个bug。
只有在下次程序运行才回变成12,正常应该是在最后一次print mc.cas_ids时就自增的。
Ps:本质上每次执行gets时,会从memcache中获取一个自增的数字,通过cas去修改gets的值时,会携带之前获取的自增值和memcache中的自增值进行比较,如果相等,则可以提交,如果不相等,那表示在gets和cas执行之间,又有其他人执行了gets(获取了缓冲的指定值), 如此一来有可能出现非正常数据,则不允许修改。
12.10 Redis
本章讲解安装pyhont 操作redis。
Linux下安装
pip install redis
windows
自己百度找安装包
API使用
redis-py 的API的使用可以分类为:
- 连接方式
- 连接池
- 操作
- String 操作
- Hash 操作
- List 操作
- Set 操作
- Sort Set 操作
- 管道
- 发布订阅
操作模式
redis-py提供两个类Redis和StrictRedis用于实现Redis的命令,StrictRedis用于实现大部分官方的命令,并使用官方的语法和命令,Redis是StrictRedis的子类,用于向后兼容旧版本的redis-py。
连接池
redis-py使用connection pool来管理对一个redis server的所有连接,避免每次建立、释放连接的开销。默认,每个Redis实例都会维护一个自己的连接池。可以直接建立一个连接池,然后作为参数Redis,这样就可以实现多个Redis实例共享一个连接池。
12.11 Redis操作
String操作,redis中的String在在内存中按照一个name对应一个value来存储。如图:
set(name, value, ex=None, px=None, nx=False, xx=False)
在Redis中设置值,默认,不存在则创建,存在则修改
参数:
ex,过期时间(秒)
px,过期时间(毫秒)
nx,如果设置为True,则只有name不存在时,当前set操作才执行
xx,如果设置为True,则只有name存在时,岗前set操作才执行
以下是一些常用命令
由于参数过多,这里只贴出连接。
参数忘记使用 如 help set
只列出几个难以理解的,如
SETBIT key offset value
Key key的值
Offset 2进制的位数
Values 二进制只能为0或1
http://www.cnblogs.com/wupeiqi/articles/5132791.html
12.12 Redis 发布订阅
Demo
订阅者:
这里没有发布者代码,因为发布非常简单,直接在redis里面发布了。
13.L013
13.1 监控系统执行过程
首先确定框架,然后实现简单的单项监控。
首先记录视频的架构模式
首先把要监控的项以及总接口规范。
Load
Cpu
plugins_api
按照思路,以上的暂时不用管,直接写配置文件,将配置文件写死。从简单到复杂。
13.2 监控系统之简单阈值设置
在上一章,仅仅实现了固定的配置模版,而且将监控取到的值给输入到屏幕。
现在把拿到的信息发布到redis,有订阅者收到监控的值(这里代表服务器)。
首先在backend目录下定义redis的模块。
然后在客户端的index里面调用这里模块,然后将获取到的监控值发布到redis即可。
客户端到这里只需要2个简单的操作即可。
服务端比较简单,这里只做一个订阅的功能。
接下来,在一些监控软件里面,监控有阀值系统,当在多少指标的时候就产生告警?
现在来看一下新的配置文件,麻烦的是字典里面包含字典,字典里面包含列表然后在包含字典。
在这里,比较有一个更好用的模块,operator
Index.py
Index本节做的最大改变就是config格式及内容改变,然后取出设置的告警阀值和动态获取到的值做比较。
13.3 监控系统之阈值处理
到现在为止,是不是所有的数据都在客户端,包括计算阀值,包括采集数据,这里只是一个思想,当服务端拿到了redis的数据后,只要代码稍微更改,就可以在服务端比较2个值的数据,都可以。
本章讲解的是思想,如果在config里面值为max,或者为min,avg,那么或者客户端拿到的值为负数的时候,那么还有经过一次计算在比较值,在第二节的strip就类似一个简单的处理。
以下代码是什么?
使用反射,获取__builtin的max等方法来进行比较。上图中的“formula”=max,是不是和operator一样,这样就可以拿到一个列表或者元祖的最大值或者平均值了。而且这个动作是看配置文件可选项的。
以上定义了取最大,最小,或者不设置。
13.4 监控系统之复杂阈值操作
前几个章节操作,只要达到就告警,但是在监控里面,有这么一种方式,哪一个值连续多少时间在什么数值的时候才告警。这个功能可选。
比如这里有需求:
1. 在5秒内,一个值连续3次就告警。
那么就有以下思路。
1.第一次,设置时间当前11:50:30 ,错误第一次
2.第二次, 11:50:31,错误第二次
3.第三次, 11:50:32,错误第三次
那么告警
有告警,当然也有重置告警,条件为
1.告警之后,重置为0.
2.告警间隔时间大于5秒,重置为0.
由于此功能可选,有的只要发现就告警?那么就可以在配置文件里面做动作。
这个配置可选,如果有就进行复杂告警。
代码也只需要在告警下面判断。
13.5 监控系统之扩展知识
一些加入主机组和以后web的思路。
13.6 Web流程中的HTML
本章讲解基本HTML流程,如用户端发送请求后,服务器将一些固定的格式发送给客户端,经过浏览器的处理,成非常炫的界面展现出来。
13.7 Html文档树之head信息(一)
讲解了HTML的基本规范,基本格式。
13.8 Html文档树之head信息(二)
在ie8上加上这句,以ie7的兼容模式去运行。
然后还是head里面的信息。
13.9 内嵌标签和块级标签
块级标签占正行,以下是区别。
13.10 Html常用标签(一)
一些特殊字符的表示,如<h1>.
现在列出一些常用的标签。
段落
链接
去新的页面里打开链接
还有一个锚的作用
13.11 Html常用标签(二)
H系列的标签,
Select标签
13.12 Html常用标签(三)
还有一种2中下拉选择框,一种是全部显示,一种分组显示的。还分单选和多选。
下面是显示几个,也可以全部显示
下面是多选
以下是分组
13.13 Html常用标签(四)
input系列
input是自闭合的,<input/>
checkbox,复选框
Redio,圆心选择
当name相同的时候就只能单选了。
Text及passwd
Button,按钮里面放入字符
以下现在没有区别,在表单在有区别
file文件上传
Textarea 输入框,注意,不是input系列的
以下就如from表单讲解,提交作用。
Submit和button区别就在这里,submit提交网页会变
还有提交在哪里?
Action就是提交的位子,在标签内,默认的值会被当成key,用户输入的当成键值形成字典。
当然,这里如果有选择框,也要加属性,然后提交
那如果是单选框呢?如,男,女,那我就为他设置一个默认值来提交,values就是一个值。
代码总结
13.14 Html常用标签(五)
这里没学from提交方式,所以使用搜狗或者百度来测试
Label,让别的标签带可以点击的功能。
就在与lable for 来自id,那么不止选择框,这个指定的key也可以点击。
加黑点
分组
注意dt和dd的区别
Table
表格合并
竖着合并
给表格加标题
13.15 Html常用标签(六)
Fieldset,输入在一个框里。
13.16 CSS基本使用
本章主要讲解了三种css样式的应用方式。
13.17 CSS基本使用及作业
自己看书。
Display 显示隐藏属性
就是隐藏弹出的框。
由内联标签转换为块级标签
由块级标签转换为内联标签
浮动
14.L014
14.1 css选择器(一)
本章主要讲解选择器的类型。
本章讲解了4种选择器。
- 标签选择器。
- Id选择器
- Class选择器
- 关联选择器
标签选择器
类似于直接声明所有同种标签的样式,如<dir>font-size:11px</div>
Id选择器
Id标签不能重复使用,但是class可以,一般使用class
Class选择器及关联选择器
这里代表谁下面的谁,谁下面的应用什么。
这里的.a相当于第三级子元素了。
14.2 css选择器(二)
组合选择器
以,为分割。逗号的地方显示除逗号前面一个以外,前面的路径都相同。
现在多出一例,找到class样式 input 为 text的标签
自定义属性
14.3 css背景位移和边距
本章讲解padding和margin,主要在登录框的时候使用,那么就不用顶格写了。
14.4 css之position(一)
绝对定位和相对定位,
absolute结合relative使用才有意义。
14.5 css之position(二)
本章讲解对今天的总结。
使用此参数将div居中,也叫水平居中。此方法同时使用body。
还有一个问题,float漂浮的时候,父div没了,那么就用以下参数。
或者在最外成div加一个高度,但是一般不这么做,因为外层div高度一般不确定。
14.6 css之加载页面实例
z-index等参数,不宜记录,需要回来查看视频,特别是left:50%,margin-lefg:-16这个参数的使用。
本章主要讲解界面的覆盖,类似于那种注册协议弹出来之后,界面都无法操作的样式。
本章有个小作业,就是实现类始于这种方式的代码。
在代码中,已经解释过此代码。
14.7 css之布局页面实例
本章模拟的是模拟出一个网站的排版。代码在当天课程目录下。
14.8 js之存在方式
JavaScript是一种属于网络的脚本语言,已经被广泛用于Web应用开发,常用来为网页添加各式各样的动态功能,为用户提供更流畅美观的浏览效果。通常JavaScript脚本是通过嵌入在HTML中来实现自身的功能的。
1、文件形式
<script type=”text/javascript”src="js/oldboy.js"></script>
2、嵌入html
<script type='text/javascript'>alert('page');</script>
3、代码块的位置
<body>标签内的代码底部
因为如果放在head里面,如果执行了执行到了js,如果没有找到这个动作,那么就会很尴尬。
14.9 js之字符串和数组操作
这里函数有两种,一种是基本函数,需要手动调用,还有一种自执行函数,自己调用执行。
基本函数
自执行函数
字符串常用方法和属性
obj.trim()
获得对象的字段的值,然后转成string类型,并且去掉前后空白
obj.charAt(index)
返回指定索引处的字符。
obj.substring(start,end)
用于提取字符串中介于两个指定下标之间的字符
obj.indexOf(char)
方法可返回某个指定的字符串值在字符串中首次出现的位置
obj.length
返回长度,这里不做过多解释,但是有一点要注意,这里没有()直接执行的一个属性,而不是方法。
数组
声明,如:
var array = Array() 或 var array = []
下面还有数组的一些常用方法,就不一一列出了。
注意:字典是一种特殊的数组
14.10 js之字典和循环操作
循环
第一种for循环方式
一般使用第二种
还有一种类始于python字典的数组
14.11 Dom基本操作
文件对象模型(Document Object Model,简称DOM),是W3C组织推荐的处理可扩展标志语言的标准编程接口。
选择器
- document.getElementById('id')
- document.getElementsByName('name')
- document.getElementsByTagName('tagname')
以上去根据ID,根据名称,根据标签去html找匹配的参数。
这里举一个例子,如设置一个div里面有一个参数123,如果document.getElementByEd找到了,那么就把他变成天青色等烟雨,在网页显示是天青色等烟雨,而不是123.
下面还有document.getElementsByName('name'),name有多个,那么获取的就是一个数组,所以要用for来取值,否则会报错。
还有一个基于标签的,这个匹配的就更多了,当然还是使用数组的取值方式。
14.12 Dom之事件注册和执行
现在首先演示一例,通过js来改变样式。
找到id为l1的标签,然后将背景变为红色。
- 获取或者修改样式
obj.className
找到id为l1的标签,然后把red样式给i.
现在做一个动态样例,专业来说就是注册事件,比如,我有2个按钮,我点一下,字体是这个颜色,我点一下颜色就清空。
这里既然有了点击以下设置函数,那么我还可以设置一个按钮,点击一下清空样式。
尽然有了单击,那么就有双击。
只要把onclick改成ondblclick就可以了。
这里只简单的解释两个,如元素获得焦点,意思就是当鼠标在可输入框里的时候就是获得焦点。
当然,也可以绑定多个事件,这个一定要注意,不是单一的。
14.13 Dom之事件注册和执行(易错点)
这里如果是elements,那么就是数组,要使用for循环才能拿出来。
14.14 Dom之页面加载和页面框架加载
这里还有一个特殊事件
一张图片或页面加载完成后,自动触发一个事件,而且,这个方法是写在script里面的。
当整个页面加载完成后自动执行这里面的内容。
而document.ready是整个页面的框架加载完成
区别在于,window.onload这一个url请求图片,当请求返回之后,才执行,而document.ready当html的标签渲染完成就执行,相当于我把请求发出去就完成了,不管有没有回应。
14.15 Dom之操作html标签
前面讲解了className是获取值,但是如果不className赋值,那么也可以将className打印出来。
打开调试窗口。
- 获取或设置属性
setattribute(key,val) getattribute(key)
Id的值肯定为l1
上面的例子不是很清晰,那么来一个更直观的,自定义属性。
自定义名称为name=lizexiong,依旧获取到了。
既然有获取,那么就有设置值。
- 获取或修改样式中的属性
obj.style.属性
比如一个标签里面有2个属性,我点击一下之后只更改一个属性。
比如有字体大小和背景颜色2个属性,那么我更改背景颜色。
这里就不演示结果了。
- 提交表单
document.getElementById(‘form’).submit()
现在让button拥有提交表单的功能, 通过js改变button的默认属性
Button也可以更改属性为跳转,只需一行小代码。
Confirm
弹窗,提示是或者否
这里使用第三个代码js3.html
点击是返回true,点击否返回False。
- setInterval("alert()",2000); clearInterval(obj)
自动刷新
每5秒执行一次
现在增加代码,中止自动刷新
执行一次就被调用中止了。
这个只执行一次,在删除邮箱提交的什么时候使用,只执行一次。
14.16 Dom之搜索框和跑马灯实例
跑马灯
搜索框
两个小实例都没什么难度。
14.17 jQuery之选择器(预览)
现在加载完成后获取div的id,然后在将值改变
14.18 jQuery之样式和属性操作(预览)
自己看吧,毛都没讲。
14.19 今日作业
15.L015
15.1 jQuery选择器
最常用的几种选择器
本章讲解了格式各样的选择器,选择器的作用就是找到符合条件的标签,当然,仅仅只是找。
15.2 左侧菜单(一)
本章节函数并没有什么卵用,主要见下章节。
15.3 左侧菜单(二)
上一章完成了如下的简单功能,使导航栏固定隐藏了,而且还可以判断是哪个标签内容。
本章节优化以及完成各功能。
下面列出完成代码。
可以看出用jQuery只使用了2行代码就完成了我们所需要的功能,其实这2部完成的东西太多了。
15.4 jQuery筛选(一)
筛选,简单来说就是在选择器找到东西后,使用筛选更加精确的查找。
本章节主讲map。
查看结果
15.5 jQuery筛选(二)
下面是查找分类里面的东西。
本章主要注意
Next下一个,
NextAll下面的所有,
NextUntil指导找到哪一个就不找了。
下面就是往上找了。
本章总结其实就那么几个。归类一下。
15.6 实例:编辑当前行
本章节实现一个简单的弹出框,且弹出框有相对应的数据。暂只实现这么多功能。
首先,学习本章之前,首先要学会for循环。
For循环的两种方式。
由于代码太长,这里直接贴出代码
<!DOCTYPE html> <html> <head> <title></title> <style> .hide { display:none; } .model { background:#ddd; position:fixed; left:50%; top:50%; width:400px; height:300px; margin-left:-200px; margin-top:-150px; } </style> </head> <body> <table border='1'> <tr> <td>1</td> <td>2</td> <td>3</td> <td onclick='GetPrev(this);'>编辑</td> </tr> </table> <table border='1'> <tr> <td>11</td> <td>22</td> <td>33</td> <td onclick='GetPrev(this)'>编辑</td> </tr> </table> <table border='1'> <tr> <td>111</td> <td>222</td> <td>333</td> <td onclick='GetPrev(this)'>编辑</td> </tr> </table> <div id='dialog' class='model hide'> <form> <p>hostname:<input type='text' id='hostname'/></p> <p>   IP:<input type='text' id='ip'/></p> <p>  port:<input type='text' id='port'/></p> <input type='submit' value='提交'/> <input type='submit' value='取消'/> </form> </div> <script type='text/javascript' src='jquery-3.2.1.js'></script> <script type='text/javascript'> function GetPrev(arg){ var list = []; var pre = $(arg).prevAll() $.each(pre,function(i){ var item = pre[i] //console.log(item); //这里打印出了标签加内容,现在我只要内容 var item = $(item).text(); //到这里注意,重新给item加了一个$符号,如果不加是无法使用js的。 //console.log(item); list.push(item); }) var list= list.reverse(); //接下来,在弹出的框设置值 $('#hostname').val(list[0]); //val如果后面不接参数就是获取value的值,否则就是设置值 $('#ip').val(list[1]); $('#port').val(list[2]); $('#dialog').removeClass('hide') } </script> </body> </html>
本章主要分为哪以下几个部分:
1.Table表格,里面有内容,最后一个是编辑。
2.一个div标签,当弹出的窗口用。
3.需要实现点击tab里面的编辑,弹出div标签,并将table表格里面的内容放到div的窗口里。
本章主要的就是实现上述要求,没什么难度。
15.7 jQuery属性以及Form表单验证
本章讲解以下分类,属性。
这里主要讲解toggleClass的作用,简单来说就是,我的一个属性,我点一下加上去,然后点一下然后取消。
看结果,点击toggle来显示div块,取消div块。
Text与html
Text是获取标签里面的内容,只是文本;而html是里面所有内容,包括标签。
Text
HTML
看到这里,html可以将标签输出,那么就可以直接使用html更改标签。
val
val主要是针对以下三类来说,因为只有这基类才有value这个值。
-
- Input
- Select
- textarea
Val的使用方法
Select测试。
现在开始修改表单
默认submit会将数据默认提交到一个url,现在我在submit在绑定一个事件
Submit这样就执行了两个事件,因为submit本身就有一个事件,现在还有一个就是我创建的。
这里我们自己定义的事件控制力度很强,如果我们自己定义的事件返回值为False,那么第二个事件就不在执行了,也就是说不会提交表单了。
既然这样,我们就可以使用这种方式来判断用户是否为空了,如果为空,那么我就返回False,那么就无法提交了。这里又要使用一种全新的for循环方式。
查看结果
这里是不是还有取消按钮没有设置,在取消上也绑定一个事件。
本章新增了如果用户输入为空就无法提交表单的功能,这是本章最主要的添加,使用了新的for循环知识,以及submit有默认的事件。
本章还有一个小点要注意。
$(“:text”) 等于 $(‘input[type=”text”]’)
后面那种复杂的写法要注意单引号和双引号不能出现2次。
15.8 实例:checkbox 全选、反选和取消
这章节讲解属性部分的属性部分。
Attr是针对所有的标签属性设置的,而prop为checked而生的。相当于给checked等值专门定义了一个方便的方法。
本章就反选的时候的for循环需要小小的注意,并没有什么难度。
15.9 jQuery高度
本章讲解CSS块。
首先CSS使用有以下几个方式,包括字典的方式可以传入多个样式,这里就不多讲解。
返回顶部
至于高度演示,这里先保留,因为视频未演示出来。
15.10 实例:滚动章节
本章还是延续讲解CSS块。
这里讲解两个小知识点。
- Offset 整个页面
- Position 父标签
使用offset查看当前块距离html的距离。
以上代码明显只有div,为什么距离top是8?因为默认body也有距离,默认就是8.
现在来一个章节滚动的实例
此实例比较饶,需要清晰知道以下几个参数。
- 滚动条的高度。
- 当前页面的高度
- 整个html页面的高度。
- 每个标签离顶部的距离
知道以上几点,然后看代码注释就不会太难。
15.11 jQuery Ajax请求
Ajax请求简单来说就是页面不刷新就可以提交请求。
Ajax分为本地和跨域请求。
- 本地:本地起一个程序,发送请求然后在返回数据。
- 跨域:访问和别人协商好的网站,发送请求,拿回数据。
本地的需要写一个d程序,这里还没学习,所以直接演示跨域。
本地和跨域还是有区别的。
在跨域请求中有三个字段。
dataType:’jsonp’
jsonp:’callback’
jsonpCallback:’list’ //需要注意的就是这里
上面的list是和其它平台已协商好的,通过list()把内容给封装进去。
跨域请求
查看平台返回的数据类型。
这里,开始一步一步完善在网页上要展示的内容
1.首先,只是获取数据,在console输出
这里的数据首先是对象Object{data:array[7]}.
那么要取出数据就是data.data
2.数据格式详解
首先弄清楚数据格式。
上述获取到的类型注意是个对象data,然后data里面放了数据。
Console.log(data.data) 的数据格式如下,里面有一个数组。
既然是数组,那么就需要循环了。
接下来,for循环data.data[i]
又得到一个对象。
那么在取值就是data.data.[i].week
这里注意,每次取出的都是一个object,data.data是由一个对象取出的,data.data[i]的值还是一个对象。
本章小规律,出现了object和数据,如果数据取值,那么就是循环,如果object取值,那么就是.
3.以下,首先实现简单的,将获取到日期显示到网页上。
接下来把节目给列出来。
首先查看v.list又是一个数组,那么就要用到for循环
查看每个数组下标内的数据。
那么可以看到有time,name,link等字段。
那么就可以继续把这些信息像网页上显示。
上述中link字段我们也获取到了,所以,这里也可以直接设置为链接的形式。
也是优化本章的最后一部。
15.12 今日作业
16.L016
16.1 前端剩余内容前瞻
16.2 上周作业剖析(一)
本章实现。
首先,如果用户进入编辑模式,那么怎么判断字段是否可以编辑?怎么知道用户是否进入编辑模式?
1. 规范了文件分类,将js放在一个单独的文件夹下。
2. 实现点击进入编辑模式,然后选择全选,可以进入编辑模式。
3. 通过editing字段判断是否进入编辑模式。
4. 通过edit=True判断td标签是否可编辑。
5. 通过children()来得到每个tr,每个td的内容。
本章主要是实现CheckAll以及EditMode的部分小功能。
本章代码 index_1.html edit_row_1.html
16.3 上周作业剖析(二)
本章主要讲解取消功能以及反选功能。
首先讲解取消功能思路。
CheckCancel
1.取消选中的checkbox,如果已经进入编辑模式,让选中行退出编辑状态。
如果是选中状态,取消选中。
如果是编辑状态,退出编辑状态,并把<input>内的值设置为文本框固定的值。
CheckReverse
1.是否进入编辑模式
遍历所有tr,找到checkbox标签。
如果是选中状态,变为不选中。
找到td标签,查看是否可编辑字段,将input值变为固定值。
如果不是选中状态,变为选中状态。
找到td标签,查看是否可编辑字段。
如果td标签现在是input标签,那么将input变为固定字段
否则将固定变为input字段。
如果不是编辑模式
只需要将选中的状态改变即可。
本章内容较饶,但是知识点就讲解了取消和反选。
本章代码index_2.html edit_row_2.html
16.4 上周作业剖析(三)
本章主要讲解 EditMode函数,实现首先选择之后在进入编辑模式和退出编辑模式。
本章思路。
1.判断是否进入编辑模式。
如果已经进入编辑模式。
那么删除编辑模式样式,这里可以说退出编辑模式。
如果判断checked是选中状态,那么将input标签转为固定字段。
否则没有选中
那么判断没有选择的标签是否为input。
如果为input标签那么就将input内容转换为固定字段。
否则什么都不做。
如果没有进入编辑模式
那么进入编辑模式。
判断checked是否选中,如果选择,将固定字段转为input标签。
本章代码 edit_row_3.js
16.5 上周作业剖析(四)
本章节主讲两个内容。
首先讲解优化代码。
优化代码章节很简单,就是将相同的代码写成函数,然后在调用的时候直接调用,省去了代码书写以及修改的便捷。
这里由于和视频讲解的代码不完全相同(已经做了优化),所以会复杂一点,这里还是简写为2个函数,但是RowIntoEditMode本人做了优化,所以为RowIntoEditModeEx版本。
本小节代码
edit_row_4_1.js
本章重点讲解,如果在判断的时候出现select标签,那么怎么编辑select的内容
首先,可以声明一个字典来存储值。(注意这里的单引号和双引号)以及字典名称的关键字。
本章要讲解思路。
首先td.attr判断该标签是否可编辑之后然后在判断是否为select标签,如果是select标签,那么就开始做select的处理,否则,做input等的处理。
首先以下是简单的判断为select后把td改成select标签,但是改标签内容是死的。
上图可见,直接套了判断是否为select类型,是的话转换为select类型,但是这种格式固定死了,怎么办呢?
那么首先搞两个全局变量,前文已经提到过全局变量,相当于存储值。
那么怎么使用?看alert的实例。
这是alert的三种方式,主要注意下面2个。
那么我全局变量的值是否可以这样取出来。
那么我就可以在html标签里面再次定义一个值。
那么就可以通过for方法获取全局key。
JS for循环里面,第一个都是索引,第二个就是value。
现在可以获取全局变量value的值,那么现在动态构造select的值。
可以看到如果循环 index代表字典的键,value代表字典的值。
以下为实现效果。
本章实现思路。
1.判断是否为edit可以编辑字段。
判断类型是否为select。
通过window获取字典的值,然后开始循环,然后字符串拼接。
如果不是进入else处理,一般为input等标签
本章的代码更新不多,只更新了RowIntoEditModeEx函数。但是index和js都需要更新。
edit_row_4_2.js index_4_2.html
16.6 上周作业剖析(五)
在上一章,实现了下拉框的遍历显示,但是还有一个问题,我的下拉框本来是有默认值的,但是在可编辑状态,变成了第一个值,怎么使选中的默认值保持不变呢?
本章思路。
只需要在遍历字典的时候与html标签页的默认值做对比,如果字典取出来的id等于html的值,那么就加上selected默认选中类型就可以了。
Index主页文件也需要更改。
本章也比较简单,只是简单的几行代码。
本章代码:
edit_row_5.js index_5.html
16.7 jQuery文档处理
本章主要讲解以下操作。
append:在标签后面追加内容(这个前面已经讲解过)。(子元素后面增加)
Prepend:在标签前面追加内容。
Befoce:在同级标签的前面添加内容。
appendTo:把一个标签添加进另一个标签。
Clone:完全clone一个标签的内容。
append
查看执行结果
Prepend
查看执行结果
appendTO
查看执行结果
Before
查看执行结果
现在讲解Clone,本章最重要的章节。
Copy一个标签的所有内容然后在做操作,如,append到一个标签后等操作。主要看你拿到这个clone的标签后想怎么操作。
现在做一个clone选择框的操作,然后append到后面。
但是到这里实现了可以增加复选框,本人也通过其它方式实现过,但是这里如果这里的+要变成-呢?那么是否可以定义找到自己然后在删除当前自己的行就行了?
本小节需要注意的是形式参数传入进入的时候如果是ths,那么函数就不能是ths,并需换一个,而且函数里面传入两个参数,后面的参数是可以省略的,只用把this本身传入进去就可以了。
16.8 jQuery事件
本章的事件为上周作业做铺垫,什么是事件?鼠标点击一下是一个事件,点击两下也是一个事件,onclick是 js dom里面的方式,前面我们也一直使用这种方法,但是在jQuery里面也有更为方便的事件使用,它可以批量操作一大堆相同的标签,如<select></select>的全选等功能。
本章就是讲解了不同的事件触发方式而已,这里是加载完成后,直接就注册了事件,本章的实际用途在下一章就会讲解。
16.9 上周作业剖析(六)
截止到这里,代码有一个大的改版。
上周作业截止到这里,需要一个大的改版,那就是在进入编辑模式本人自作多情使用了多重if判断,但是在这里checkbox可以自行绑定事件,实现比前文更加简洁的代码,更易于维护的代码。
而之前所有是否为input状态的代码,全部交由新的函数来维护定义。
所以本章取消了RowIntoEditModeEx函数,。所有checkbox是否选中等操作,我们重新定义一个函数。
本章代码主要部分是怎么给多个checkbox标签定义事件。
这是本章的主要代码,这个代码加上可以说完善了整个功能。
本章还需要注意的是什么?
默认checkbox会有一个事件(checked),我们也定义了一个事件click,如果这里加上一个console.log(),那么如果先执行我们的事件,那么checked是没有选上的,那么返回就是false,如果先执行checked原始事件,选择上了,我们再去调用checkbox的属性,那么我们得到的就是True,我们的事件后执行。
其实,视频把这个部分将复杂了,简单就是说,我们点击chekcbox这个按钮就会勾选上,这是一个事件,我们加了一个click又是一个事件,那么我点击一下checkbox,那么就会执行2个事件,这里得到的结论就是先执行checked的原始事件,然后我们得到值,就是刚开始checkbox没有选中,没有checked事件,当点击一下,checked 属性加上去,首先执行了这个事件,然后click得到checked的值,就是这个意思。
16.10 上周作业剖析(七)
作业截止到本章已经完成了,这里在来一个提高逼格的东西,就是识别用户使用输入什么键,然后做一些什么操作。比如,用户按住ctrl键,然后select标签下面的标签都改变等操作。
那么实现思路和要求就是:
1.select绑定change事件。
2.默认只是修改自己。
3.判断是否按住ctrl键,是的话修改所有。
本章麻烦的的就是找到值,改变值,如果未选中就不一起改变以及新学到的知识,js识别用户按下的键是什么。
本章代码:index.html edit_row.js
16.11 jQuery扩展方法
本章就是讲解扩展自定义函数,只举两个简单的例子。
上面lizexiong是我自己定义的函数,但是没有什么常用的地方。如果真有需求,一般使用自执行函数,自执行函数在前面讲解过,章节为14.9.
自己定义,执行自己。
这章看看这种自执行的使用方式。
16.12 jQuery扩展方法实现Form表单验证
简单讲解登录和注册界面怎么写。没有代码保存。
16.13 jQuery开源插件
本章简介了一些插件,但是没讲怎么用。
16.14 Bootstrap使用和介绍
16.14.1 Bootstrap环境搭建
下面是Bootstrap的可视化工具,这里不怎么使用。
BootStrap有2个文件bootstrap.css 和 bootstrap.js,但是bootstrap.js使用签必须导入jquery,所以在这里一共要导入三个文件,当然,还有用国内cdn网络的方式来使用bootstrap。
基本的HTML模板
小伙伴们,上一小节的视频课程已经对Bootstrap提供的模板做了详细的介绍,在这一小节中我们把这个模板代码粘贴过来,以供你们学习查看,你们可以根据实际情况在此基础上进行扩展,只需要确保文件引用顺序一致即可。
如右侧代码编辑器中就是最基本的HTML模板。
我们来简单解释一下其中几条的重要代码:
bootstrap模板为使IE6、7、8版本(IE9以下版本)浏览器兼容html5新增的标签,引入下面代码文件即可。
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
同理为使IE6、7、8版本浏览器兼容css3样式,引入下面代码:
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
这里写一个本地导入的模版
16.14.2 全局样式
16.14.2.1 BootStrap的全局样式
16.14.2.2 Bootstrap中的排版 - 标题
查看默认标题与bootstrap标题的区别。
16.14.2.3 Bootstrap中的排版 - 文本
查看效果
16.14.2.4 Bootstrap中的排版 - 表格
16.14.2.5 Bootsteap中的表单(1)
查看网页结果
16.14.2.6 Bootsteap中的表单(2)
16.14.2.7 Bootsteap中图片及辅助类
16.14.3 Bootstrap渐进
16.14.3.1 Viewport
16.14.3.2 栅格布局
根据屏幕的大小做出相应的样式调整。比如,在小于900px的时候和大于300的时候显示蓝色,其它时候显示红色。
或者,在大屏(lg),小屏(sm),怎么按格数显示。
计算用12/?就是几分之几的显示。
16.14.3.3 单位
16.14.3.4 图标
图标就是文字,放大不会失真,并且可以单独设置格式等。
16.14.4 Bootstrap组件介绍
16.14.4.1 BootStrap组件是什么
16.14.4.2 Bootstrap组件中的怪异属性
16.14.4.3 字体图标
16.14.4.4 下拉菜单
下拉菜单就要开始使用bootstrap的js了。
16.14.4.5 控件组
本章代码较长,请在文件查看
16.14.4.6 导航
16.14.4.7 分页
16.14.4.8 进度条
16.14.4.9 列表
16.14.4.10 面板
16.14.4.11 Bootstrap中的插件
16.15 前端其他框架
这个只有css。
还有jQuery EasyUI,这个已经过时了,适合后台网页,因为比较丑。
16.16 今日作业介绍
用bootstrap写出如下界面。
16.17 Web框架请求流程前瞻
听不懂,后期在看
17.L017
17.1 上节作业知识点之布局和事件
首先,这里的布局意思就是当我的内容分区后,内容怎么居中显示。
第二个内容就是弹出框点击确定后怎么自动消失,这里没有实例网页,用到的时候在说
17.2 上节作业知识点之a标签其他属性
补充的是A标签放上去之后出现什么样式。
本章只是个小知识点,可以深入做成事件,比如放上去在消除下划线等。
17.3 上节作业知识点之z-index值
边框设置为百分之50就是圆。
17.4 上节作业知识点之伪类和伪元素(一)
有一种样式,把鼠标放上去一种颜色,鼠标移走一种颜色,这个可以使用js实现,但是这里使用css一句话就可以实现。
17.5 上节作业知识点之伪类和伪元素(二)
本章将第二节和第三节联合在一起讲解。
首先,这里给出的思想是定一个clearfix类,里面有这种功能,以后要使用的时候调用就可以了,那么就按照这种思路来写。
首先要实现什么功能?如果遇到div里面套div,那么如果里面的div加上了float属性,那么最外层的div管不住自己的儿子,那么最外层div的样式就会失效,如最外层的背景颜色就会失效。那么加上clear:both(这里是清除全部样式的意思,一般用在清除float),那么就可以再次管理到自己的儿子。
但是,如果一个网页出现多次float?那么当然写成一个样式,以后直接调用更好。
这里看着代码很少,但是要注意精髓,上面clearfix里面的样式一旦少了一个,效果可能就无法完成,仔细查看每个样式的含义。
17.6 实例:返回顶部终极版-----------------------------------------
讲的跟狗屎一样,后期补充。
17.7 上节作业知识点之CSS箭头
17.8 自定义Web框架之Socket和WSGI
Web框架最本质的东西。
分为2类,一类为我们自己写,二类为基于wsgi处理请求。
首先,我们自己写一个,那么就会很简单。
现在wsgi的类型。博客中代码有问题,以这里的为准。
17.9 自定义Web框架之路由系统
自定义现在有2种选择:
一:我们写socket,也写框架
二:不写socket,只写框架。
现在,我们不写socket,只写框架。
根据不同url,返回不同字符串,根据不同的url做不同的响应。
以下可以根据断点调试来获取想要的信息,比如path_info获取用户输入的url信息。
以下根据不同url返回不同的值
但是是不是这种方式太low了?而且如果url多了呢?
以下方式就可以解决根据列表的方式来拿到网页的内容。
下面的方式需要注意的就是列表的取值。
17.10 自定义Web框架之模版渲染
以上,我们拿出来的网页都是静态的,但是,现在,我想定义一种格式,把时间戳为动态的在网页上展示出来。
首先网页文件里面需要有替换的文件字符。
然后程序定义。
以上用了简单的字符串替换,但是方式太low了,所以这里有一个模块,非常厉害。
Jinjia2 格式为 {{}},替换里面的字符串就行。
当然,要先安装jinja2这个模块。
然后在html文件里面写上模版语言,
然后,主程序导入jinja2.
以上是否已经替换成功了,那么在介绍一下模版语言,如for,if等。
if
17.11 MVC和MTV
以上是网页请求流程,从上图可以看出,每个部分就有每个部分的分工,
所以,见以下:
以上就是MVC和MTV框架,只不过文件夹不同了,分工也不同了。
那么现在把代码分类,主讲MVC和MTV框架。
url文件存放的是路由关系,也就是网页的映射。
Models 对数据所有处理的代码。
Views 放置网页文件。
Controllers 放置处理请求的函数的地方。
那什么是MTV呢?
现在
Models 依旧是对数据所有处理的代码。
Templates 放置的是html的文件。
Views 放置的是处理请求的函数的地方。
17.12 Django源码之WSGI入口
本章重点,函数加括号把函数执行了,那么类对象加括号是把__call__方法执行了。
17.13 创建Django程序以及文件介绍
Python的WEB框架有Django、Tornado、Flask 等多种,Django相较与其他WEB框架其优势为:大而全,框架本身集成了ORM、模型绑定、模板引擎、缓存、Session等诸多功能。
首先要安装django
创建django程序
- 终端命令:django-admin startproject sitename
- IDE创建Django程序时,本质上都是自动执行上述命令
其他常用命令:
python manage.py runserver 0.0.0.0
python manage.py startapp appname
python manage.py syncdb
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser
然后在创建其它功能的的app,一个app可以当作一个功能的分类。
这样与我创建的zexiong全部在同级目录下。
查看一下目录结构
这里Django有一个自带的后台管理程序,我们启动看看。
在此之前,我们要创建一个存储用户的库,然后创建一个可以登录的用户。
在Django 1.9及未来的版本种使用migrate代替syscdb.
然后创建用户
然后启动这个后台就可以了。
这样就可以登录进去了
17.14 Django之第一次实现Http请求
命令创建的django项目只能用命令启动,除非是IDE创建的项目才能图形启动。
这里只需要2个简单的更改。
一个是路由系统,用户请求来了匹配什么
二是,匹配到了由谁来处理。
然后本人通过命令的方式启动
17.15 Django之模版渲染
首先,我们暂时实现简单的自定义的网页返回,
路由系统还是保持不变
Views模块去找home.html
首先,先访问试试
这里在settings里面指定路径,在我们指定的template去寻找html文件
当然,我们这里使用的django版本是1.11以上,在1.7的时候,需找路径需要这么配置,这里需要注意就行了。
现在访问就正常了。
之前学习的时候使用了jinja2的模版,这里当然也可以使用。
这里呢,补充解释代码。
return render(request,'home.html',dic)
这里的render等于以下代码
import datetime from django import template import DjangoDemo.settings now = datetime.datetime.now() fp = open(settings.BASE_DIR+'/templates/Home/Index.html') t = template.Template(fp.read()) fp.close() html = t.render(template.Context({'current_date': now})) return HttpResponse(html
17.16 Django之模版语言和自定simple_tag
首先,在django里面也可以使用jinja2的模版,上面也使用到了简单的方式,现在使用一下for循环,这里我们也可以看到,这里的传入的值,直接传入一个字典,列表也放在字典里面了。
以上代码中出现了一个forloop.first,这个叫做模版语言,模版也有自己的语言,前文中也使用过。
- {{ item }}
- {% for item in item_list %} <a>{{ item }}</a> {% endfor %}
forloop.counter
forloop.first
forloop.last - {% if ordered_warranty %} {% else %} {% endif %}
- 母板:{% block title %}{% endblock %}
子板:{% extends "base.html" %}
{% block title %}{% endblock %} - 帮助方法:
{{ item.event_start|date:"Y-m-d H:i:s"}}
{{ bio|truncatewords:"30" }}
{{ my_list|first|upper }}
{{ name|lower }}
以上也看到了,比如帮助方法,{{ name|lower }}可以将大写字母转为小写,但是语言提供的方法远远不够我们用,所以这里自定义simple_tag。
1.在app中创建templatetags模块
2.创建任意 .py 文件,如:xx.py
里面自定义了2个函数,一个是算+法,一个是插入标签。
3.在使用自定义simple_tag的html文件中导入之前创建的 xx.py 文件名
4.使用simple_tag
5.在settings中配置当前app,不然django无法找到自定义的simple_tag
然后查看结果
这种自定义可以让人感觉很简洁,自己定一个函数,然后在函数里面写功能,然后传入参数就可以了。
17.17 Django之母板
首先这里明确两个要使用到的东西的概念。
母板:简单来来说是一个架子,我写一个网页直接把母板的框架直接继承过来,只有一些内容是我的,直接通过木板显示出来。
include:导入其它的html文件到当前页面。
首先,在templates下创建一个存储母板的文件夹。
我们要继承母板的自定义界面。
插入的html
在URL路由系统插入需要路由的url
最后,在views处理请求的函数中将要相应的网页返回给客户端
访问查看
17.18 Django之静态文件配置
关于静态文件的配置,可以专门创建一个文件夹来保存对应的文件。如:css,js,
bootstrap等。
查看母板的配置
最后也是最重要的,需要配置文件指定找到我们的static文件配置。
17.19 Django之用户登录实例
首先,我们理清一下思路
当用户请求过来的时候,我们url路由文件帮我们转接请求到views里面去,views根据请求信息让哪个函数处理,然后在去找网页,然后返回给用户。
首先写路由系统,请求来了交给谁去处理。
现在views去处理请求。
这里,用户会有get请求和post请求,如果是get,那么什么都不做,如果是post,那么检验用户输入是否正确。
现在查看login.html网页。
到这里还有一部步,在django里面,settings有个设置现在要注释,这里我们只知道需要注释就行了,后期讲解作用。
现在访问,如果用户名密码输入错误,就回提示,如果正确,就跳转到百度。
17.20 Django之Model基本操作和增删改查实例
本章通过Models操作数据库,这里首先不使用数据库,但是类似数据库操作。本章需要通过django amdin来操作完善html和css。
1.首先,路由系统,通过路由系统来帮助我们找到所请求的url。
2.创建models表。
Models模块编写操作数据库的动作。
Models这里解释以下参数含义。
1、models.AutoField 自增列= int(11)
如果没有的话,默认会生成一个名称为 id 的列,如果要显示的自定义一个自增列,必须将给列设置为主键 primary_key=True。
2、models.CharField 字符串字段
必须 max_length 参数
3、models.BooleanField 布尔类型=tinyint(1)
不能为空,Blank=True
4、models.ComaSeparatedIntegerField 用逗号分割的数字=varchar
继承CharField,所以必须 max_lenght 参数
5、models.DateField 日期类型 date
对于参数,auto_now =True则每次更新都会更新这个时间;auto_now_add 则只是第一次创建添加,之后的更新不再改变。
当然这里只是列出少数方法,多余的可上网上自寻查找。
3.注册APP,settings添加app
这一步在上述章节中早已创建,所以略过。
4.生成相应的表
python manage.py makemigrations
相当于 在该app下建立 migrations目录,并记录下你所有的关于modes.py的改动,比如0001_initial.py, 但是这个改动还没有作用到数据库文件
python manage.py migrate
将该改动作用到数据库文件,比如产生table之类
创建完成后会出现以下文件夹。
5.admin后台注册表
这里如果不在admin后台注册,就无法在django后面看到这个表。
这里可以登录上admin后台网页看看
python manage.py createsuperuser 创建用户
这里创建的管理员账户,否则没有帐号无法登录。
6.请求处理模块views对数据进行操作,然后对网页进行渲染
以下是一些常用用法
models.UserInfo.objects.all()
models.UserInfo.objects.all().values('user') #只取user列
models.UserInfo.objects.all().values_list('id','user') #取出id和user列,并生成一个列表
models.UserInfo.objects.get(id=1)
models.UserInfo.objects.get(user='yangmv')
登录网页查看。
7.用户自定义添加
这里我们首先要写一个表单
然后在views模块里面处理。
8.用户自定义删除,更新
当然,有添加肯定的有删除,更新
那么也需要更改views即可。
由于这里学习知识有限,删除,更新功能只能存在一种。
17.21 作业
18.L018
18.1 今日内容
18.2 Django请求生命周期
就是一个完整的请求流程,上章如果学习的不错·,这个流程应该会非常清晰,下图中多了一个中间件的角色,下面章节会讲解。
18.3 路由系统详解
18.3.1 单一路由对应
这里我们详细的讲解URL路由系统,路由系统里面一般使用正则表达式来匹配的。
首先我们创建一个django项目day18,首先,然后创建一个app01的功能项目。
平时,我们访问的时候都是http://127.0.0.1/index来访问,如果,只有http://127.0.0.1,那么就会进入默认网页,如果没有默认网页,就会提示我们有哪些url。
其实,以上的信息在url里面都是正则匹配的,如果写一条如果没有加上url后缀,就默认跳转到指定主页那么也可以,见以下。
访问查看
注意,这个万能匹配一定要放在最后,为什么?
因为这个万能匹配的本尊是.* 匹配所有,如果放在最前面,那么什么都会匹配到交给index去处理。
18.3.2 基于正则的路由
给函数传参数,之前,我们的def index(request)后面都没有任何参数的,那么,那种场景会使用到?
如,我的URL这里有100个,是分页显示的,1-100为后缀,那么,我们不可能写100个url,那么就使用到了正则。
首先,我们写一个,访问http://127.0.0.1/user_list/$num,输入哪个数字就返回哪个数字。
到这里就有疑问,在url里面,我们根本没有给函数def user_list传入参数,为什么会有参数传入呢?因为这是django自动传入的,甚至可以加入多个参数, /开隔开。
以上,传入的值的顺序按照传入的顺序排列的。我们无法更改,如果想更改的怎么办?那么我们可以传入一个字典,如果传入的是字典,那么,我的值的顺序就可以随意调换了。
查看位置是否调换
18.3.3 根据app对路由规则进行分类
上图可以看出,如果按照请求url来将请求转到对应app下面去处理,如果这里解释不懂,看下面两张图可以看出来。
别忘了在settings里面注册。
首先,我们只访问http://127.0.0.1/app01,看看结果
然后输入全路径访问
小总结
比如,这里有个app01项目 ,里面有login,index等网页,那么只要请求是app01开头的,那么都转到app01的urls下面路由,就是app01的路由是单独存放在自己文件夹下面的文件。
18.4 基于反射实现动态路由设计(一)
本章只需要了解,不需要用到。
以下文件只做参考,现在水平无法看懂。
django中的路由系统和其他语言的框架有所不同,在django中每一个请求的url都要有一条路由映射,这样才能将请求交给对一个的view中的函数去处理。其他大部分的Web框架则是对一类的url请求做一条路由映射,从而是路由系统变得简洁。
通过反射机制,为django开发一套动态的路由系统Demo
这里查看本地文档或者git。
18.5 基于反射实现动态路由设计(二)
不同的web框架有推荐的方式。
Django一般使用前面3种就解决掉需求了,所以很少用反射
18.6 Django中间件(一)
django 中的中间件(middleware),在django中,中间件其实就是一个类,在请求到来和结束后,django会根据自己的规则在合适的时机执行中间件中相应的方法。
在django项目的settings模块中,有一个 MIDDLEWARE_CLASSES 变量,其中每一个元素就是一个中间件,如下图。
以上列表里面存放的类,是可以协成传统的导入方式的。
在django里面,所有的中间件都有这2个函数, 当请求来的时候执行一个,回应请求的时候执行一个。
这里,首先写一个自定义中间件的案例,然后围绕整个案例来分析
当然,最后别忘了注册。
访问查看
在中间件里面,总共有4个方法,我们已经使用了2个,现在使用剩下的
访问查看
上面看到了,request,view是进来的时候处理,response是出去的时候处理,如果在定义一个中间件,那么处理流程是什么样的呢?
当然,也别忘了注册
查看执行结果
出去的时候先走的yyy的中间件。为什么出现上述情况?
1.当请求进来了,到达中间件边缘。
2.Django自动去settings里找到MIDDLEWARE_CLASSES(这是一个元祖)。
3.for 类 in MIDDLEWARE_CLASSES:
然后根据类创建对象
Obj = 类()
Obj.process_request
源码里面是这么执行的。
首先有4个列表,每循环一个中间件的时候把一个方法放到列表里面,然后依次执行列表里面的方法,所以就出现了上上图的情况,整个流程还是相对简单的。
到这里,常用的方法我们使用了3个,还有一个exception没有使用,这个只有在报错的时候才会执行,这里模拟报错测试。
18.7 Django中间件(二)
上图中流程有黄线,蓝线和黑线,并在图中有了标识。
首先在流程中,注意return的重要性,只要碰到return,中间件的其它函数都不在执行,直接交给response来处理,然后返回给用户。并且,response必须要有return值,这个return response的值就是其它函数处理return返回的内容,如 ,
就是返回值。且中间件process_request可不可有返回值,可以,但是如果有返回值,那么就直接执行response函数,中间件其它函数将不在执行。
下面一个一个解析在各函数有return值的情况。
黄线:正常流程process_response接收到 函数index的返回值,然后返回给用户,那么用户接收到的就是index的内容,该过程不在演示。
蓝线:process_request有返回值,那么不在执行其它函数,不管是中间件的函数,还是 def index函数,那么会直接将返回值给response,然后返回给用户。
事例可以看到,返回给用户的居然不是index的内容,而是return的内容。
黑线:按照正常的流程,如果出现报错,并且碰到return,那么就不会在执行任何函数,直接将return值返回给response,然后返回给用户。到这里,我们要明确一个思路,
一.中间件执行的流程,是通过判断是否有proces_request等方法,如果有,放入列表,然后执行列表,如果碰到return,那么正常流程的的其它方法不在执行,直接将return的值返回给response,然后返回给用户,这就是中间件的流程以及思路。
有了以上思路,下面的方法就好解决了,我的exception方法都报错,但是只有一个return,那么结果是什么已经可以想到了,只有执行到有return的地方才会返回到最后一步。
查看访问结果
18.8 Django源码如何执行中间件------------------------------------------
这里有4个接收中间件函数的列表
18.9 Django缓存
缓存,顾名思义就是当多次重复的请求我不去请求数据库,我可以直接请求已经请求保存过的介质,该介质可以是数据库,文件,内存。可以提高访问速度,减少数据库的访问压力。
本章暂只演示缓存的使用,所以本章使用文件的方式来缓存。
首先,我们写一个动态界面
这个时间是一个动态界面
现在开始写缓存配置
然后应用缓存配置
访问查看
15分钟内值不会变,都在缓存里面拿值。
18.10 Session和Cookie(一)
什么是cookie,什么是session?
这里我们不使用百度官方语言解释,cookie保存在用户客户端本地,可以用来在某个WEB站点会话间持久的保持数据。而session保存在服务器, Session其实是利用Cookie进行信息处理的,当用户首先进行了请求后,服务端就在用户浏览器上创建了一个Cookie,当这个Session结束时,其实就是意味着这个Cookie就过期了。
举一个简单的例子,用户发送请求给服务器,服务器通过一个值来判断你是否有这个权限进入某个网页,比如,我怎么判断一个用户是否已经登录?图中已有解释,生成一段字符串然后保存session,然后发送给客户端保存,客户端在来请求的时候,如果验证通过,就在服务器上生成一个字典中的字典来存储用户是否已经登录,是否有哪些权限访问哪些网页的值。
所有有一些网站,比如,京东,淘宝等,浏览器禁用了cookie之后就无法登录了,就是因为无法接收服务器发来的cookie,登录的时候发送不了服务器所需要的值导致无法登录。
下面来一个案例演示模拟cookie和session的登录跳转。
首先是路由系统
然后是网页请求处理。
简单的登录网页
这里还有一步一定要做,否则会报错,本章暂没讲该操作作用,下面章节会补充。
18.11 Session和Cookie(二)
第一节简单演示了session和cookie的基本功能和基本原理,但是django的session放在哪里呢,默认django的session放在数据库里面,asp.net默认放在内存里面,django也可以配置放在内存,放在数据库好处是这里我们可以很简单看到这是什么值。
首先,我们重新请求一下,查看session id。
然后查看数据库
简单来说,session_key就是key,session_data就是values,这个values里面又是一个字典,存储的就是is_login :true, 服务器根据客户端发来的session_key来判断你是谁,然后通过sessin_key来取session_data的值,session_data的值因为是自定义的,所以可以存储一些用户名,控制登录权限等。
现在,写一个案例,通过用户名给判断登录的网页是什么,顺便打印出,session_data的值是什么。
request.session 就是 数据表字段session_data。
本小节沿用上面代码,所以只列出了views.py文件。
访问查看,这里只看调试界面。
本章重点核心就是session的存放以及格式是字典套字典,主要学会使用
怎么取字典值和赋值就可以。
18.12 Session和Cookie(三)
本章就实现两个功能.
1. 登录后用户名显示在右上角。
2. 用户可以注销。
首先实现登录后用户名显示在右上角。
此功能非常简单,只要加入网页渲染就可以了。
一个简单的html网页
访问查看
第二章,也是本章重点,注销登录。
注销登录,就是清除掉session,清除掉values的值之后,那么不就是注销登录吗?
首先路由系统
然后函数处理
然后看看html网页
这里没有必要查看登录界面,直接看调试窗口,在logout后,IS_LOGIN这个值就被删除了。
Cookie默认2周过期,现在手动设置,那么设置的就是客户端cookie的消失时间,如果客户端的cookie消失了,那么就要重新登录了,这里虽然是在服务端设置,但是并不是服务端的session消失,而是客户端的cookie消失。
5秒之后是浏览器上的cookie消失,而不是服务器数据库的文件,因为2端只要有一端消失,那么就要重新登录,这个超时时间是在写入cookie的时候设置给浏览器的。
消失的是浏览器的session_ID,而数据库的一直都在,虽然是在服务器上做的设置。
小总结:
Session:用来保存用户会话信息,依赖于cookies。
超时时间:写入cookies时,设置一个超时时间。
还可以设置关闭浏览器,cookie自动过期。
18.13 Django Form基础
From章节可能因为版本问题,无法提交空字符串,但是可以提交空格,和空字符串效果一样。(视频里面直接提交为空)
Django form有2大功能。
生成html标签,生成如(input)这样的标签
验证用户输入,验证用户输入是否为空,是否合法。
之前我们写form表单,需要判断用户是否为空,用户提交的信息是否合法?用的最简单的Request.POST.get(‘name’)这种方式,如果输入的值有100个输入框呢?所以,django的form表单自带了方便的功能,就是上面的2大功能。
本章节内容过多,单独创建一个form的django项目。
首先还是路由系统
然后网页处理函数views
本节主要是使用获取用户提交的数据的方法的使用,以及提交不合法将报错信息展示在前端。
Html
当然最后别忘了关闭settings的配置
现在访问网页查看
现在来一次正常的提交,正常的提交会在调试界面打出来
18.14 Django Form定制化
From章节可能因为版本问题,无法提交空字符串,但是可以提交空格,和空字符串效果一样。(视频里面直接提交为空)
上一章讲解,提交空格会报错,但是都是默认的值,这里我们可以手动设置空的提示信息,以及手机号在输入框设置提示信息。
访问查看
输入空格信息,然后查看源码
当然,还可以写正则匹配输入框内的信息是否合法,如,是否为一个手机号的格式。
一般,输入框内的验证机制差不多都是这种类是手机号的类型,知道这一种,其它的方式就差不多了解了。
还有种情况,在页面上可能存在下拉框,怎么把标签给插入进去?
访问查看
这里补充一点,如果这种提交验证机制,最好写两套,前端一套,后台一套,因为如果前端禁用js之后,那么前端就没有任何作用了。
总结
且,在html网页里面我们自己写input标签,报错信息也是会提示的,因为我是通过网页渲染后网页才给用户的,并不是静态直接访问,这里这点要注意。
18.15 Django From漂亮的显示错误信息
在error后面可以有三个参数 as_data() ,as_ul(),as_json(),默认是ul样式,但是如果是as.data()那么就是原生字符串,因为默认的是as_ul()无法给信息加样式,所以只能转成原生的,然后在加上样式。
这里我们使用as.data()来将自定义错误信息显示出来。
查看提示是否为原生字符串
这里需要load网页来帮忙
Html网页
访问查看是否已经没有ul的样式了。
18.16 Ajax发送简单数据类型
通过前端写数据提交到后台。
这里我们先不从网页拿数据,暂时只拿自定义的静态数据做演示。
路由系统
函数处理
网页js
Js设置
访问查看
18.17 Ajax发送复杂数据类型(数组、字典)
首先查看是否能发送一个数组类型。
测试发送一个集合到后台看看能否发送。
可以拿到数据,但是值经过了他们自己的处理,变得有点变态,类似于取下标然后从字典取值。
如果不想要这些自动的处理,我们现在需要2个设置,对这个数据进行处理,如果想要发送到后台,并且后台能处理这些数据,需要将数据json序列化一次,默认是不予许发送字典和数组到后台的,而且使用下图中的方式发送之后,就是json解析后的字典的格式。
查看返回的数据(注意,这里html服务需要重载)
现在数据已经到了后台,如果想返回给前端,返回到前端的首先是一个字符串,是无法通过arg.status这样的方式获取数据的,然后前端只有转换为字典。
然后前端还需要将接收到的字符串转为字典对象。
访问查看,正常情况。
现在人为模拟错误,查看网页报错信息。
18.18 本节作业框架实例(一)
本章作业小有失败,但是起码清楚了知道了逻辑,头脑里面知道改去怎么判断。
主要注意
本章的作业要求,以及笔记截图就保留,为了后期复习时查看。
首先写login,注意login的路径
然后别忘了配置
然后,login.html里面的登录要用form表单的方式来生成。
创建一个form
以下的只是代码参考,并不是源码啊
以下的才是代码
可以在以下界面定义错误信息
然后在处理函数这里导入定义,前端通过model取出username和password了
然后到登录界面,如果登录成功,然后,我们这里就不要用跳转了,用跳转加seeion来实现用户登录
然后接下来的index的素材没有,自己补充,当然,index的路由系统也别忘了写,
这个index里面有很多模块,在模块里面加上a标签,可以实现跳转,比如lists什么的页面功能。
我没有以下界面的素材,大概理解就好了
然后资产这个界面,要在数据库model里面把数据取出来,展示在资产的网页上,这里资产的页面使用table写,前面我们也学了js,配合着用
从后台拿数据,写一个for循环就可以了,动态的生成tr,tr
添加的时候要跳转到另一个界面通过form表单来添加。
类似下面的页面上添加
这里使用到了form_tag,所以,下面要注册
刷新功能,在用户点击保存的时候提交数据后,刷新
19.L019
19.1 今日内容
19.2 Django基础回顾
这里回忆了django的路由系统,路由系统分类,这里有一个以前没用过的,就是views分类,把不同的功能写到不同的文件里面,这里的views.py直接删除,创建一个模块views,在views里面在创建模块做区分。
这里views.py直接删除,创建一个模块views。
首先是路由系统导入。
存放网页文件结构也一样,所以在函数views.account里面也要定义这样的路径。
JS也可以改变一种方式,本章未演示成功,下一章节就是讲解这个功能。
实验需要接下章…….
19.3 Django静态文件引用优化
上一章js导入没改变成功,是因为在1.7.9里面有自动处理,现在的版本可能需要手动添加。所以本章补充的是导入js的方式。
下面图片想知道上一章演示为什么没成功就是因为这个设置,如果想深究这个设置,要么看源码,要么看视频。
现在版本为1.10.1,所以第一种方式演示还是失败,但是还是把步骤列出来。
第二种方法
类似与我们自己写一种simple_tag
访问查看
第二种方式演示成功
19.4 Form基本功能应用之保存用户输入内容
本章规范form表单的生成。
在views导入也要注意,我们这里有两个account,所以这里可以取一个别名,
而且,还有一种场景,比如用户密码输入的时候,密码输入错误,那么用户就需要重新输入,但是,用户名肯定不需要重新输入,那么就把用户输入的值也直接渲染在网页上面。
前端展示
过于封装用户提交的数据,代码还可以更简洁。
19.5 Form基本功能应用之验证以及错误信息
本章详细讲解Error的错误信息的二种方式。
1.error原生方式,默认为ul,利用与from表单提交。
2.error.json方式,转为json,利用与ajax请求。
1.现在编写利用form表单来提交错误信息到前端。
首先views里面,可以看到错误信息,刚开始其实也是一个类,里面有创建的username,passsword等,username下也有不同的报错信息等,比如username[0]就是常规的报错信息,
然后将error整个返回给前端,前端拿到数据根据自己需求来取数据。
首先查看error,error[‘username’]到底都是什么值
可以看到取值方式
Html文件
由于前端没有error.username[0]这种直接取下标的方式,但是我们又要下标的方式取值,那么只有自定义simple_tag。
自定义simple_tag
注册app
Html取到error[‘usernaem’][0]地址这并加一个样式查看
网页查看
2.现在使用error.as_json的方式来返回给前端,利用与ajax
查看打印的error的值
返回到前端,看看前端的ajax怎么写的。
查看网页console窗口
19.6 Form进阶功能应用之生成select标签
本章实验目的,生成select标签,升级操作,将select标签放入文件里面读出来然后在展示在网页上,然后在升级,每刷新一次就可以将文件里面的数据给重新读一遍,而不需要重启服务。
按照流程,首先是url路由系统
函数处理系统
然后form表单
这里还是补充解释为什么要用class,并且super这个类的父类的构造方法,因为在这个函数里面,都是静态字段数据,静态字段保存在内存里面,只执行一次,只有重新重启才会加载,而父类的构造方法正好可以在刷新的时候重新载入,但是这里我们定义了__init__,那么为了让2个__init__方法都执行,那么就super,就主动触发了父类,当数据库里面的数据刷新的时候,就回自动载入最新的数据。
数据库文件信息
网页信息
访问网页查看
19.7 Form进阶功能应用之生成动态select标签
针对动态的数据生成__init__方法,本章讲解此方法的原理,为什么要在里面生成__init__,然后此方法可以优化,优化原理是,反正都会执行一次__init__,那么不在__init__外面的代码就可以取消了,因为反正__init__都会执行一次。
优化后代码。
19.8 Model创建表之基础字段
本章讲解就是一些基础字段的用法,接下来,一步一步来演示一些基础字段的基础用法。
访问网页
生成相应的表
python manage.py makemigrations
python manage.py migrate
访问http://127.0.0.1:8888/index/,然后在调试界面查看结果
修改models
可以看到字段内容信息了。
现在看看创建时间和更新时间,怎么看创建时间和更新时间?
After获取的是多行数据,是对象的集合,那么就可以用以下方式来获取。
如果现在要新增字段,字段里面可能有内容,那有了创建时间和更新时间,那么如果新增字段后,以前如果有数据,这个字段是没有数据的,创建时间和更新时间是监控不到的,那么怎么表示呢?
那么就有2种值,null=True值为空,default=’’给字段设置一个默认值,来解决。
数据表结构发生变化,执行
python manage.py makemigrations
python manage.py migrate
只有设置了null=True和defautl=’’,那么才不会提示更新时间是否变化这样类似的操作。
现在使用数据库图形化工具来看一下表字段。
19.9 Model创建表之基础字段(二)
截止到这里,首先来注册一下admin,更利于后面章节的演示。
登录到后台,提交空信息,查看一下报错。
到这里,还有一个blank=True属性,还有一个图片,blank在admin界面创建数据的时候,可以输入为空,而图片呢?就是一个简单的上传。
登录admin查看
19.10 Model创建表之字段参数
本章主要讲解数据库字段的一些参数,比如default,是否可以为重复值等,本章需要可以自行百度。
例:下面2者一般都是同时出现的。
19.11 插曲之上传文件
本章实验目的,搞清楚上传的原理,本章会简单的查看源码。
首先,路由系统
这里,先编写主页文件。
函数处理系统
现在上传一个文件看调试界面
上传成功
现在看一下源码
然后我们根据父类一个一个个找
是不是就找到这个方法了,每次读64k,然后用yield记录上次传送的值,就是这样。
19.12 Model创建表之一对多
本章内容为一张表对多张表,简单来说其实只要熟悉数据库本章就很容易写出来,
举个简单的例子,有一个主机表,主机表有一个属性,区分的是主机的类型,那如果全部使用文字表示,那么非常占用资源,那么我直接关联到另一张表就可以了,这种操作,在数据库里面叫做外键。
HostType没有创建id?但是会自动创建,然后自动关联的也是这个id,而HostColor我们自己指定了nid来当作外键,而且,HostColor外键是有多个值的。
现在创建一些值来测试。
创建主表的时候直接选择关联就可以了。
19.13 Model创建表之多对多
本章讲解数据库表多对多,什么是多对多,这里首先举一个例子,比如一个管理系统,有用户,有用户组,有时候一个用户可以属于多个组,那么一对多就无法解决了,所以,多对多会有3张表,第三张表存储用户id和组id的对应关系。
19.14 Model创建表之一对一
一对一,在数据库里面没有这个概念,只有通过人为的去设置,一对一就是在一对多的基础上来做的,就是上一个约束,不让这条数据第二次被调用,这就是一对一。
举个简单但是不太准确的例子,有一个用户表,还有一个登录表,只有出现在登录表里面的数据才能登录,在登录表关联用户表的时候,用户ID选择一次就不能再次选择了,就是这样实现一对一。
这次创建点数据来测试
User2里面的ID只能使用一次
19.15 Model操作表之基础操作
# 增 # # models.Tb1.objects.create(c1='xx', c2='oo') 增加一条数据,可以接受字典类型数据 **kwargs # obj = models.Tb1(c1='xx', c2='oo') # obj.save() # 查 # # models.Tb1.objects.get(id=123) # 获取单条数据,不存在则报错(不建议) # models.Tb1.objects.all() # 获取全部 # models.Tb1.objects.filter(name='seven') # 获取指定条件的数据 # 删 # # models.Tb1.objects.filter(name='seven').delete() # 删除指定条件的数据 # 改 # models.Tb1.objects.filter(name='seven').update(gender='0') # 将指定条件的数据更新,均支持 **kwargs # obj = models.Tb1.objects.get(id=1) # obj.c1 = '111' # obj.save() # 修改单条数据
本章节需要特别注意的地方是,在增,删,改,查中我们是可以传送字典进入的,那样,我们就不需要一个一个的输入值了。
就比如在from的时候 clear()得到用户输入的值,那是一个字典,直接把字典的值填在这里就不需要一个一个输入了,这里演示直接将字典传入进去。
查看数据库是否创建。
这里还有一个需要注意的地方,在查询出数据的时候,使用的方式不同,得到的数据类型不同,那么操作就不同。
主要是区别values和values_list的区别。
Values获取的是字典。
Values_list获取的则是一个元祖。
所以,两种方式,得到两种不同类型的值,那么就可以做更方便的案例。
例如,之前,在index里面的内容是通过文件来获取的,那么这里直接通过数据库来获取。
19.16 Model操作表之进阶操作
本章的进阶操作将的就是数据库更加细化的操作,这里内容太多,需要用的时候在来测试。
# 获取个数 # # models.Tb1.objects.filter(name='seven').count() # 大于,小于 # # models.Tb1.objects.filter(id__gt=1) # 获取id大于1的值 # models.Tb1.objects.filter(id__gte=1) # 获取id大于等于1的值 # models.Tb1.objects.filter(id__lt=10) # 获取id小于10的值 # models.Tb1.objects.filter(id__lte=10) # 获取id小于10的值 # models.Tb1.objects.filter(id__lt=10, id__gt=1) # 获取id大于1 且 小于10的值 # in # # models.Tb1.objects.filter(id__in=[11, 22, 33]) # 获取id等于11、22、33的数据 # models.Tb1.objects.exclude(id__in=[11, 22, 33]) # not in # isnull # Entry.objects.filter(pub_date__isnull=True) # contains # # models.Tb1.objects.filter(name__contains="ven") # models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感 # models.Tb1.objects.exclude(name__icontains="ven") # range # # models.Tb1.objects.filter(id__range=[1, 2]) # 范围bettwen and # 其他类似 # # startswith,istartswith, endswith, iendswith, # order by # # models.Tb1.objects.filter(name='seven').order_by('id') # asc # models.Tb1.objects.filter(name='seven').order_by('-id') # desc # group by # # from django.db.models import Count, Min, Max, Sum # models.Tb1.objects.filter(c1=1).values('id').annotate(c=Count('num')) # SELECT "app01_tb1"."id", COUNT("app01_tb1"."num") AS "c" FROM "app01_tb1" WHERE "app01_tb1"."c1" = 1 GROUP BY "app01_tb1"."id" # limit 、offset # # models.Tb1.objects.all()[10:20] # regex正则匹配,iregex 不区分大小写 # # Entry.objects.get(title__regex=r'^(An?|The) +') # Entry.objects.get(title__iregex=r'^(an?|the) +') # date # # Entry.objects.filter(pub_date__date=datetime.date(2005, 1, 1)) # Entry.objects.filter(pub_date__date__gt=datetime.date(2005, 1, 1)) # year # # Entry.objects.filter(pub_date__year=2005) # Entry.objects.filter(pub_date__year__gte=2005) # month # # Entry.objects.filter(pub_date__month=12) # Entry.objects.filter(pub_date__month__gte=6) # day # # Entry.objects.filter(pub_date__day=3) # Entry.objects.filter(pub_date__day__gte=3) # week_day # # Entry.objects.filter(pub_date__week_day=2) # Entry.objects.filter(pub_date__week_day__gte=2) # hour # # Event.objects.filter(timestamp__hour=23) # Event.objects.filter(time__hour=5) # Event.objects.filter(timestamp__hour__gte=12) # minute # # Event.objects.filter(timestamp__minute=29) # Event.objects.filter(time__minute=46) # Event.objects.filter(timestamp__minute__gte=29) # second # # Event.objects.filter(timestamp__second=31) # Event.objects.filter(time__second=2) # Event.objects.filter(timestamp__second__gte=31)
19.17 Model操作表之一对多操作实例(一)
19.17.1 框架编写之动态数据库读出展示
本章实验目的,使用form表单生成用户和用户组,用户组select数据从数据库里面获取。
首先编写路由系统
Form表单生成
函数处理
主页文件
Models以及生成数据库表
创建组
访问create_user主页查看
19.17.2 数据增加之用户自定义添加的三种方式
第一种,通过对象的方式
这种方式最为麻烦,但最推荐
这种方式首先通过组的id获取组的对象,也就是获取id等于2,那么获取2对应的组名,然后根据对象自动添加到数据库。(这里会比较饶,但是后期用多了才会理解)
查看数据库
第二种方式,通过数据库,简单而又麻烦
这种方式,要在外键表的字段加一个_id,这是django自己定义的,想通过数据库方式直接添加,就要加上_id.
看数据库内容,已经插入进去了,而且和是不是外键字段自动加上了_id.
第三种方式,通过**kwargs来直接传入,就不需要手动来输入了,但是在form的时候需要把usergroup改为数据库字段usergroup_id.
这里的方式很奇妙,为什么form读出的时候usergroup可以读出到,usergroup_id也可以读取到?
因为在form这里相当于一个中转站,我的变量名在这里就是接收,接收然后在赋值,然后拿着这个变量给前端或者给后台数据库。
查看数据库
19.17.3 网页展示之数据查询
本章在强调,usergroup可是一个对象,这里的外键对应的是一个对象,通过对象来找到实际参数。
首先获取全部数据在网页上渲染
然后html渲染的时候直接通过找对象的方式来找。
19.17.4 数据查询之get请求
19.17.5 Get请求之-多表操作
本章单独为一小章专门讲解,在model对象里面的时候 拿数据的时候是使用. ,查询数据的时候使用__,简单来说就是在models.filter里面想跨表拿字段的时候,就使用__。
Ps:个人感觉这里是由于语法限制,因为使用.会报错。
19.17.6 Get请求之-三表操作
本章是在上一章的延伸,如果Usergroup还关联了一个外键,此外键判断是否能够登录,那么取数据的时候呢?
当然还是__操作,只不过麻烦了一点点。
这个是额外补充小章节,目的就是为了输入该组是否为vip,如果为vip,那么就列出组里所有用户。
直接列出过程。
创建第三张表,关联第二张表
查看三张表数据
Vip表
Usergroup表
用户表
查看取值函数
查看html网页
访问查看
19.18 今日作业
见job
20.L020
20.1 本周BBS论坛开发需求
20.2 设计表结构
本节视频50分钟,主要讲解表结构,后期会章节会补充内,还讲解了左连接,右连接,内连接查询,本章视频没有必要在看浪费时间。
20.3 外键关联与多对多关系的实现
上图中id作为关联,但是我一个主机如果想属于多个组怎么办。
那么我来第三张表,就实现了多对多,也叫双向的一对多。
Django里面会自动创建第三张表。
本章也简单补充了onetoone的含义,因为在数据库中没有这种onetoone的限制,就是外键只予许引用一条,这条对应了,就不能在选择了,之前章节也讲解过,所以django在代码上给我们做了限制。
为什么要补充讲解onetoone,因为我们这里用户认证引用了原生的djang给我们的user模块,里面包含了基础字段,如密码,邮箱等。这里继承原生的user,我们后期会在做讲解。
20.4 设计表结构2
首先,来看建了哪些表,表里面有哪些内容。
帖子表
评论表
点赞表
帖子板块
用户和用户组
上面有几个地方需要注意,这里是详细解释。
这里讲解一些评论的子评论的关系。
如果 1级评论,是没有父亲的,那么儿子是2,如果评论是3,那么父亲是1,如果评论是4,那么自己父亲是,父亲的父亲是1,那么关联关系就出来了。
那么就是自己关联自己,这个概念往下看。
还有一个需要注意,就是
上面继承了原生User,原生的User里面有些默认字段,如密码,邮箱等,这里为什么要时刻用OneToOne,因为,我去取用户名和密码的时候,一个人只能用自己的帐号密码,自己用了,别人就不能选择,这是代码上做的限制,叫做一对一。
现在注册admin,来尝试发表一篇文章,看看外键的关系。
创建了一篇文章,看返回值。
额外笔记篇章
同步数据库的时候还记得父评论那里会报一个错吗,就是自己关联自己那里。
那么就给关联自己的表起个别名,不查自己的原表名,查这个自己表的别名
20.5 设计基本页面架构
本章主要是把基本页面来固定,由于网页是在bootstrap网站直接copy的,那么本章就是更替基本的js文件路径等。
这里有一点要补充
STATISFILES_DIRS这里并不是在路径里面需要出现,标注了这个参数,那么这里的static也可以为static,在路径的时候直接<script src="/static/js/jquery-3.2.1js"></script>就可以,只是会在static下的文件夹去需找这个文件,这里要注意。
首先我们把基本页面给写出来。
首先把bootstrip的样式文件以及js文件都改写成为本地。
20.6 实现页面的自动切换
本章实现内容,实现各板块页面的自动切换特效。
本章需要掌握知识点。
1. 使用block 木板的方式将views获取到的数据库内的信息全部展示在前端网页。
2. url实现id访问,比如http://127.0.0.1/category/1.
3. 在admin界面展示所需要的数据库字段信息,如数据库字段有,id,name,那么展示在admin的界面。
4. 给访问的url起别名,即使要访问的url变化,前端引用的url名也不需要改变。
5. jquery里面使用click会刷新界面,那么跳转也会失效,所以使用直接引用request.path的方式。
准备工作
在admin界面创建几个帖子,创建几个板块。
1.使用block 木板的方式将views获取到的数据库内的信息全部展示在前端网页。
这里补充一个不重要的点,这里把index里面模版的一些东西删掉了,这里可以忽略,后期可以记起来这句话意思最好,没什么影响。
在前端页面查看。
2.url实现id访问,比如http://127.0.0.1/category/1.
现在欧美专区里面切换,那么我url根据数据板块id来实现把每个id内容全部展示在上面。
Views 网页渲染界面
3.在admin界面展示所需要的数据库字段信息,如数据库字段有,id,name,那么展示在admin的界面。
现在显示帖子的一些内容,当然,多对多表的内容显示不了
4.给访问的url起别名,即使要访问的url变化,前端引用的url名也不需要改变。
为什么会有别名,如果用户访问的url需要更改,前端调用了这个url名称,url更改,那么前端的都需要更改,如果有了别名,url不管怎么变,前端只认识别名。
然后注意这里的前端怎么获取url以及id的。
根据这里的值,来到url里面来匹配id,然后交给views处理。
5.jquery里面使用click会刷新界面,那么跳转也会失效,所以使用直接引用request.path的方式。
当,这里失效的代码注释了,只需要关注以下即可。
使用的使用就是如果渠道了url的值,然后跟url匹配,如果匹配,就是当前页面。
补充
调试方法
这里可以直接更改代码。
20.7 设计论坛贴子列表展示设计(本章自定义404)
本章目的,将数据库的内容像帖子一样展示在前端页面,本章没有步骤,一步一步往下来。
1.创建一个自定义样式,把布局改一下,改成一个帖子发布的样式。
然后导入css,并将主页body样式改变一左一右。
将主页body的内容一左一右浮动起来,这里可以使用float,但是这里直接使用bootstrap来实现。
现在查看一下效果
2.每篇文章有自己单独的div,把每篇文章的图片,标题,简介全部取出来,展示在网页上,这里首先使用float的方式展示
首先改写css样式。
首先,如下div为什么要写那么多?因为在left板块中,每一篇文章要占一个div,一个div下有图片,有简介,一个图片一个div,一个简介一个div,这个布局也比较清晰。
效果
3.图片展示
首先,我们的uploads是存在的,为什么不显示?
Uploads是存在的,为什么不展示?
前端无法直接访问uploads,那么就要让前端访问uploads
直接在statics下面做一个软链接就可以了,但是软连接只有linux下可以使用,因为windows软连接在链接后面会有一个lnk的后缀名,程序无法识别。
那么方法其实很多,这里可以自己改变上传的目录到static下面去,或者在index里面写死。
这里直接在数据库部署上传的时候直接上传值static/uploads里面就行了。
那么以前上传的帖子路径也要改一下,在admin后台更改,这里就不贴出改的方法了。
现在图片太大,把图片改成固定大小
那么网页还有问题,图片和文字重合了,现在要调开。
这里有两点要该,一个是右浮动的格数,如果还是9那么就会被挤下去,要改成8,因为一行栅格只有12格,还有给简介部分加一个左边距。不然还是会和图片重叠起来。
每一篇帖子上面还有一条间隔分割线,然后为了让每篇帖子有点距离,css把下边距调一下。
现在在看一下效果。
4.作者,点赞等数据展示。
把作者,点赞,等数据拿出来,显示在标题下面,并且横着显示。
首先,点赞数怎么拿出来?
点赞表这里外键关联了帖子列表,一个文章可以有多个点赞,那么就需要通过帖子列表来反向查找了,通过点赞关注了帖子表,那么帖子表反向查询点赞表的点赞信息,用户等信息,这就是反向查询。
反向查询会自动出现方法,如,我的点赞表是Thumbup,那么就会自动出现Thumbup_set的方法。
当然,这些展示也使用一个div来括起来。
5.文章简介展示
这里还要创建一个总结的字段,存总结,个人觉得可以取文章的一点点内容来实现,这样可能更有挑战一点,但是首先把简单的思路完成,要在数据库更新一个存储文章总结的字段。
数据库更新,由于dos不支持中文,所以中文无法识别,默认字段为2.
查看效果展示
6. 点击文章标题,进入文章详情。
这里可以使用a标签来解决。
url多了,现在当然要写url路由系统
函数views处理,这里有2种情况,一种是有网页,一种是没有网页报错,首先写没有网页的结果以及404网页。
然后编写404报错界面
首先访问查看,注意,现在只能使用url的方式访问,因为现在url已经在index变成article了。
现在如果文章存在,那么就会显示内容
网页
查看访问效果
20.8 实现用户认证
本章只是简单实现了使用django注销,以及用户名展示。
上图还有一出,显示数据库userprofile表里面的用户名,使用django自带的方式。
url路由系统
函数处理
Django自带的用户注销
本章代码就这么一点,所以本章注销就不演示。
20.9 实现CSRF跨站处理
本章主要讲解CSRF跨站的原理以及实现方法,然后简单写了一个登录页面,具体实现登录下章节讲解。
根本本人理解CSRF原理是比如我打开一个银行网站,登录之后,然后又不小心打开了一个第三方网站,然后第三方网站模拟银行网站从我浏览器发出一个正常的转账请求,这就是攻击,CSRF在每次多一个验证码,用户每次form表单提交的时候都会有这个验证码进行比对,如果没有这个验证码,那么请求就丢弃。
本章首先来个小提示。
1. 在views函数处理的时候,只有使用render才会检测CSRF请求。
2. 尽量在每个form都使用CSRF。
3.CSRF带的验证码只有POST请求的时候才会有。
先写一个登录界面
url系统
Views函数处理查看request.post的请求内容是什么,是不是有验证码。
提交之后看后台,有一个csrf验证码的字段。
这就是CSRF跨站保护。
以下来一个网上的文档解释CSRF。
CSRF 背景与介绍
CSRF(Cross Site Request Forgery, 跨站域请求伪造)是一种网络的攻击方式,它在 2007 年曾被列为互联网 20 大安全隐患之一。其他安全隐患,比如 SQL 脚本注入,跨站域脚本攻击等在近年来已经逐渐为众人熟知,很多网站也都针对他们进行了防御。然而,对于大多数人来说,CSRF 却依然是一个陌生的概念。即便是大名鼎鼎的 Gmail, 在 2007 年底也存在着 CSRF 漏洞,从而被黑客攻击而使 Gmail 的用户造成巨大的损失
CSRF 攻击实例
CSRF 攻击可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击站点,从而在并未授权的情况下执行在权限保护之下的操作。比如说,受害者 Bob 在银行有一笔存款,通过对银行的网站发送请求 http://bank.example/withdraw?account=bob&amount=1000000&for=bob2 可以使 Bob 把 1000000 的存款转到 bob2 的账号下。通常情况下,该请求发送到网站后,服务器会先验证该请求是否来自一个合法的 session,并且该 session 的用户 Bob 已经成功登陆。黑客 Mallory 自己在该银行也有账户,他知道上文中的 URL 可以把钱进行转帐操作。Mallory 可以自己发送一个请求给银行:http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory。但是这个请求来自 Mallory 而非 Bob,他不能通过安全认证,因此该请求不会起作用。这时,Mallory 想到使用 CSRF 的攻击方式,他先自己做一个网站,在网站中放入如下代码: src=”http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory ”,并且通过广告等诱使 Bob 来访问他的网站。当 Bob 访问该网站时,上述 url 就会从 Bob 的浏览器发向银行,而这个请求会附带 Bob 浏览器中的 cookie 一起发向银行服务器。大多数情况下,该请求会失败,因为他要求 Bob 的认证信息。但是,如果 Bob 当时恰巧刚访问他的银行后不久,他的浏览器与银行网站之间的 session 尚未过期,浏览器的 cookie 之中含有 Bob 的认证信息。这时,悲剧发生了,这个 url 请求就会得到响应,钱将从 Bob 的账号转移到 Mallory 的账号,而 Bob 当时毫不知情。等以后 Bob 发现账户钱少了,即使他去银行查询日志,他也只能发现确实有一个来自于他本人的合法请求转移了资金,没有任何被攻击的痕迹。而 Mallory 则可以拿到钱后逍遥法外。
CSRF 攻击的对象
在讨论如何抵御 CSRF 之前,先要明确 CSRF 攻击的对象,也就是要保护的对象。从以上的例子可知,CSRF 攻击是黑客借助受害者的 cookie 骗取服务器的信任,但是黑客并不能拿到 cookie,也看不到 cookie 的内容。另外,对于服务器返回的结果,由于浏览器同源策略的限制,黑客也无法进行解析。因此,黑客无法从返回的结果中得到任何东西,他所能做的就是给服务器发送请求,以执行请求中所描述的命令,在服务器端直接改变数据的值,而非窃取服务器中的数据。所以,我们要保护的对象是那些可以直接产生数据改变的服务,而对于读取数据的服务,则不需要进行 CSRF 的保护。比如银行系统中转账的请求会直接改变账户的金额,会遭到 CSRF 攻击,需要保护。而查询余额是对金额的读取操作,不会改变数据,CSRF 攻击无法解析服务器返回的结果,无需保护。
防御策略:在请求地址中添加 token 并验证
CSRF 攻击之所以能够成功,是因为黑客可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于 cookie 中,因此黑客可以在不知道这些验证信息的情况下直接利用用户自己的 cookie 来通过安全验证。要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于 cookie 之中。可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。
token 可以在用户登陆后产生并放于 session 之中,然后在每次请求时把 token 从 session 中拿出,与请求中的 token 进行比对,但这种方法的难点在于如何把 token 以参数的形式加入请求。对于 GET 请求,token 将附在请求地址之后,这样 URL 就变成 http://url?csrftoken=tokenvalue。 而对于 POST 请求来说,要在 form 的最后加上 <input type=”hidden” name=”csrftoken” value=”tokenvalue”/>,这样就把 token 以参数的形式加入请求了。但是,在一个网站中,可以接受请求的地方非常多,要对于每一个请求都加上 token 是很麻烦的,并且很容易漏掉,通常使用的方法就是在每次页面加载时,使用 javascript 遍历整个 dom 树,对于 dom 中所有的 a 和 form 标签后加入 token。这样可以解决大部分的请求,但是对于在页面加载之后动态生成的 html 代码,这种方法就没有作用,还需要程序员在编码时手动添加 token。
20.10 实现用户登录
本章使用django的认证方式,那么就不用自己写判断了。
然后在login把err_msg放上去
本章也比较简单,不演示结果
20.11 发贴页面处理
本章把简单的发帖界面的样式先写出来,复杂的在后面章节。
首先写出发帖按钮以及发帖界面。
url路由系统
函数处理
因为前端板块选择使用select标签,所以这里要把板块获取到。
前端网页
本章并没有讲解完毕,所以没有总结。
20.12 通过富文本编辑器发贴
本章通过第三方插件来实现富文本编辑器,使用方式和js差不多,都是引用,但是这里需要注意的是不要在整体页面加载ckeditor,在只需要使用的界面加载。
使用ckeditor
为了保证不用每此加载ckeditor,那么只在发帖界面引用。
然后在发帖界面引用
查看效果
20.13 后台通过forms对表单进行验证
本章实现通过forms实现对用户输入的验证,这里暂时只能将报错信息显示在后台,还未将信息到前端,然后实现发帖成功后跳转到成功界面,并且将帖子列表做成超链接让用户点击,同时实现最新的贴在前面的功能。
这里用户输入是否合法最好在前端检测,因为这样会减轻对后台服务器的压力。
下图的数据form的数据字段最好和数据库字段一模一样,因为等会会使用**kargs来匹配。
Views函数处理,记得导入form表单。
这里,我需要发帖成功后打印出帖子名称,点击超链接到这个帖子里面来,但是这里我又不想写新的界面,所以可以使用if判断。
这里还有2个问题,一个是发帖的时候格式会把html的标签保存进入,我们要取消这个标签。还有一个就是最新的帖子在最前面。
发帖测试
20.14 实现图片文件上传
这里要注意,存入数据库的是文件路径,所以如果有多个用户,图片按照用户ID的文件夹来存储,还有文件大于2.5M就直接放入临时文件,不放入内存。
那么我们就要写一个上传文件的方法。
本章很简单,但是比较绕,只要搞清楚两个问题即可。
下面定义上传文件方法,将文件存储在文件夹里面,并返回存储路径。
将文件传进去,并获得函数返回的存储路径将路径写入到数据库。
效果
20.15 多级评论树的设计实现
本章讲的为理论,本章思路会非常饶。
首先本章的评论是上面的情况,我们要转换为字典的层级关系才能实现。
首先写一个测试文件,将列表转换为字典。
查看结果
20.16 多级评论树的设计实现2
本章由于前端无法使用递归方法,所以这里只有自定义templatetag的方式来实现,就是把上章的代码在写一遍。
首先把评论在后台先添加。
首先创建templatetag
然后把上一章的内容写成函数。
查看结果
20.17 多级评论树的设计实现3
本章主要实现把字符串在自定义里面拼接然后返回给前端。
1.本章有一点要注意,就是最后一步return 的时候 一定要导入mark模块,否则无法格式化。(这个地方由于没有标出,所以黄色提示)
2.注意margin-left的每一级缩进。
然后到查找的循环函数里。
查看css样式
查看网页结果
21.L021
21.1 聊天室几种实现方式
首先简单聊一下有这么几个角色, UserA WebServer UserB,怎么发消息,取消息。
短轮询: 由于如果一直保持一个连接,那么服务器压力会很大,所以,A每次请求服务之后,如果有消息就拿回来,没有消息也是白跑一趟,这种就是短轮询。
长轮询: 服务器无法主动连接客户端,那么我就把用户阻塞,比如,c1给c2发消息,web服务器让c2阻塞几分钟,如果有消息就唤醒这个阻塞,几分钟没有消息那么这个阻塞就超时,这样客户端发请求的机会就少了很多,这个就叫长轮询。
WebSocket: 和socket服务器和客户端一样,一直保持一个连接,有消息就发送过去,但是这种方式需要服务器和客户端都支持,占时只有谷歌支持。
本节聊天室使用长轮询来做。
21.2 设计聊天室表结构
本章讲解为表结构做设计,比如,我有qq好友,qq群,每个好友有自己的好友和自己的组,以及现在web聊天是一个全新的功能,那么就要重新创建一个app项目。
补充:数据库双向的一对多,那么就是多对多。
首先创建app
创建web_chat的表结构
别忘了在settings里面注册。
然后数据库生成表
然后在admin里面注册。
现在创建几个用户添加好友查看一下。
首先这几个用户,比如lizexiong用户是没有和任何人成为好友的,但是后面用户添加我为好友之后我便自动有了这几个好友。
现在创建组
21.3 设计聊天基本的聊天页面
本章讲解,主要设计web-chat项目的url,template项目分离,为了让设计更好的分层,比如,web-chat分给一个团队来做,那么就不需要动整体项目的框架等。
Web_chat的项目分离到自己的项目下
首先简单看一下页面
21.4 设计聊天基本的聊天页面2
本章主要实现把聊天界面的框架样式部署好,比如,有几个div,每个div是哪些内容等。
首先查看dashboard界面。
查看此页面的css样式
最后实现texearea界面回车,那么就把消息打印出来,并清空消息。
这里在index界面已经扩展了其它界面如果想用script,我们预留的扩展,{% block bottom-js%}
查看调试界面
21.5 设计聊天基本的聊天页面3
本章实现把发送框的信息加入到聊天框。
当然,这里还有一个问题,就是聊天信息会跑到网页外,问题下一章解决。
21.6 设计聊天基本的聊天页面4
本章解决聊天框信息跑到窗口外以及聊天信息要在最下面显示的问题。
解决聊天信息跑到外面的方式很简单,只需要更改以下css样式就可以了。
上面只解决了消息溢出的问题,但是下拉框还在最上面,下面还需要js帮忙。
21.7 获取当前用户的联系人列表
本章主要实现和需要注意的地方。
1.dashboard 界面一些标签页,类似与导航栏的那种。
2.利用ajax的简写get方法获取联系人信息,但是前端后端数据交互需要通过json来转换。
写一个导航的标签页,把我们的联系人列表,聊天框等代码直接放入这个div。
前端的联系人列表。
下面是一个大的部分,是为了在数据库查询出我的好友,并把好友放在前端联系人列表里面。
首先使用ajax的get方法(这种书写方式比较方便),来获取后台的值。
然后网页加载加载完成执行
刚才定义了一个url,所以这里还要写url
然后函数处理,函数处理做了这些事情,把数据从数据库拿出来生成字典,然后json转换,然后给前端处理。
查看前端效果
本章笔记加小补充:
1. python manage.py shell
作用是如果需要调试django,直接进入python,import 当前项目是会报错的,因为django环境变量没有设置,如果直接使用上述命令,那么就简单直接帮我们调试好了环境变量。
2.上述中我们使用的qqgroup_set这种方法,因为userprofile没有关联qqgroup,那么就无法确定,如果不知道qqgroup_set方法,那么就要使用以下的for循环来找到用户,很low,不建议用。
这里是查出群的用户,qqgroup_set反向更方便的查出来了。
21.8 把消息发送到服务器端
本章主要实现以下:
1. 点击联系人,实现聊天框出现与谁在聊天。
2. 实现把消息发送到后台服务器,后台服务器接收到一个字典包含了如消息来自谁,发给谁,消息等内容
3 用两种方式实现ajax.post的csrf错误。
现在前端要写一个js把联系人的信息放在聊天框的上面。
首先,这个js不能卸载document里面,因为如果卸载里面,如果我联系人都没加载完成,你就把信息放在聊天框上面了,不合理,所以要给联系人绑定一个onclick,那么就现在标签里面。
现在实现把聊天框的信息发送到后台。
然后url系统
然后views函数处理,查看后台是否能收到消息
这里访问汇报CSRF错误。
后台错误
解决CSRF错误
解决CSRF错误,但是这里是ajax,不是form表单,所以每次发送的时候都把token带上就可以,我们可以把token隐藏在网页上面,然后发送的时候取出来一起发送出去。
上图的{% csrf_token%}就是生成以下的标签。
然后定一个取token的函数,然后把token加入到字典。
现在发送测试,完全ok了。
前端调试
后端调试
截止到这里,本章内容也快讲解完毕,但是,上面处理csrf错误,每次发送都要把token拿出来发送过去,有没有一劳永逸的办法,那就是把token放在http请求头里面,但是代码有点多,这是官方给出的办法。
那把官方的代码拷贝过来,然后注释之前我们自己写的函数,查看结果。
注释:
官方代码:
结果调试
21.9 服务器收到消息后放入消息队列
本章实现服务器收到发来的消息后把消息放在队列里面,这样想,如果我不在线,那么只有发现我上线了才把消息推给我,这样,就要给每个用户生成一个单独的queue,因为你的消息是不可能共享给别人的,自己必须有私有的。
首先要导入队列还有消息发送的时间,然后生成一个全局QUQUE来存储消息和时间等一些信息。
然后就是怎么判断这个消息是谁的,加上发送消息的时间戳,将生成的队列加上个人信息,
然后返回给前端,字典的数目是多少。
效果
发送消息后,消息数目这个条目累加。
21.10 从服务器端获取新消息
本章实现把后台收到的消息实现在用户上线就拿到。
首先要定义一个get请求去拿数据。
注意,这里的url 2合1,因为post或者get都在这个函数里面判断。
函数判断
然后把收消息放在加载完成联系人之后执行。
查看效果,首先我用lizexiong给wuxinzhe发送几条消息
Wuxinzhe登录查看
剩下的就是将收到的消息展示在前端页面了。
21.11 从服务器端收到新消息后展示到前端
本章实现把收到的消息展示在前端聊天框。
首先收取消息函数肯定需要轮询,不然不刷新无法收取消息,现在每3秒收取一次消息。
然后收取消息函数得到callback返回值之后继续处理。
然后是字符串拼接函数,将接收到的消息展示出来。
查看效果
截止现在,收发消息已经ok了,但是我们接收消息使用的是轮询的方式,这样会有3秒的延迟,我们下一章解决这个问题。
21.12 实现long polling的方式接收消息
本章实现实时发送消息,之前实现了每3秒执行取新消息的函数来取消息,这样会照成延迟,而且会照成服务器压力,本章实现实时消息发送,而且没有消息就阻塞,有消息了在拿出来,如果阻塞超时之后在执行这个函数。
以下的方式需要注释,因为他会没次开启一个线程来执行新的函数,这样仔细想想,我这个函数可能阻塞了,下面又开一个线程,所以取消。
最好的方式就是自己执行完成之后调用自己,那么页面刷新一次之后,这个取消息的函数在结束的时候又执行自己,那么当前阻塞的什么的都可以按照顺序接力下去。
而且这个递归请求自己的函数一定要放在ajax函数体内,不能放在大函数内,如果放在大函数内,就是无限的执行自己,导致服务宕机。
主要消息处理,队列阻塞,新增代码用红线标识
到这里就可以实时一对一发送消息了,但是本章还有问题,那就是无法在与另外一个人来聊天,会显示当前的聊天界面,问题下面章节会解决。
21.13 实现同时与不同人聊天
本章实现上节未完成bug,上节bug无法切换到另一个人的聊天,本章实现思路,在切换另一个聊天窗口的时候,把当前联系人的id,聊天类型全部保存下来,然后切换到另一个聊天窗口,另一个聊天窗口去判断之前有没有聊天记录存储,如果有,就拿出来展示在页面上。
保存聊天记录字典
然后首先写保存当前聊天记录的函数
注意:
本章总结一下,要首先把当前和谁聊天的记录保存下来,然后在到新的联系人页面上,如果在字典里面之前存储了聊天记录,那么就拿出来。
在执行OpenDialogBox(ele)函数的时候,会执行DumpSession();函数,
OpenDialogBox(ele)函数点击的时候获取的是比如lizexiong的id为1,那么这里就回获取id为1,wuxinzhe id为2,那么就id为2,这里是动态的,我每次点击,id都会变为当前联系人的,我怎么保存上一个窗口的聊天记录呢?
所以在DumpSession()里面获取id的时候使用的var current_contact_id = $(".chat-header span").attr('contact_id') 这种方式,当点击了OpenDialogBox的时候,这个时候还没有把最新的contact_id更新到页面上,所以也就是取的是上一次的id。21.14 实现实时的一对一同时与不同好友聊天
本章比较饶,整合起来10多步,但是在代码有详细标识。
1. 实现聊天与被聊天人不在打开的同一窗口,会显示未读消息条数。
2. 点击未读消息,显示聊天记录并清空未读消息数。
1.代码优化。
2.如果打开的聊天框不等于当前窗口,那么我需要。
1.在字典里把保存的老的消息拿到。
2.在把新接收的消息拿到。
3.把老的消息和新的消息想起来。
4.把不是当前窗口的消息保存到字典。为就是不处于和别人聊天的窗口。
5.然后在html页面更新未读消息条数。
6.点击未读消息窗口,有历史消息和最新消息,并清除未读消息条目。
查看函数
第9步,增加未读消息并配置一个样式,0就是初始值
第10步,把冲突的名称改掉
最后,点击未读消息窗口,有历史消息和最新消息,并清除未读消息条目。
只有在有消息来的时候才显示
21.15 实现群组聊天与一对一聊天的代码的完美复用
本章实现让联系人列表和群组列表的复用,以及一些简单装饰器的使用,比如,没有登录必须跳到登录界面。
首先我们实现django自带登录装饰器使用。
主要是导入login_required模块的功能,然后上装饰器,现在访问一下,它会把我们导入到一个accounts/login的界面。我们没有写。
如果想自定义这个url,在settings里面更改就可以了
现在访问聊天专区然后查看url
现在实现群组框复用联系人框。
首先直接把index首页的a标签直接改变一下就可以。
然后要把html的界面埋上几个标记,后期好判断。
为什么要埋藏标记,记得我们views后台字典存储的联系人列表和group列表字段吗?这里一定要一样。
然后从复选框的埋的标记里面动态来生成联系人或者群组的html标签。
然后生成的标签也需要变量来实现
现在还有问题,就是群组界面会出现联系人的信息,并且,没刷新一次就会把群组信息多在网页上追加一个,这个问题下一章解决。
21.16 实现群组聊天与一对一聊天的代码的完美复用2
本章实现将群组聊天记录实现,本章分为2步。
1. 将后台获取到的数据发送到前端。
2. 前端将获取到的数据展示出来。
记住,只有GET才需要后台给我的返回值,POST也就是法消息的时候,只要确定是否把数据放到队列就可以了,不需要返回值。
然后将消息显示在网页上,其实只要加一个固定的大判断就可以,判断是否是单人聊天还是组聊天,单人聊天之前章节就写了,现在只要写组聊天的就可以。
如果不是单人聊天类型,那么就肯定是组聊天了,然后剩下的就是和单人聊天一样的代码了。
结果本人已经演示过了,所以这里不在截图
21.17 Bug修复
本章主要讲解bug修复。1.刚打开网页时,没有点击聊天人,header标签没有任何信息,无法收取任何消息,可以加个默认参数上去解决。
2.网页如果不刷新,没有任何消息,收消息的函数没有数据就回进入无限循
3.点击联系人,群组切换后,未读消息提示消失。
22.L022
22.1 自定义分页之分片取数据
本章讲解简单通过get传来的参数来实现简单分页,只要是get直接url传的方式以及后台获取处理。
首先数据表创建。
接下来是URL。
然后是函数处理,我们先把要测试的数据准备好,准备100条数据。
然后在views里注释掉这些生成测试数据的代码,然后把这些用户信息展示在网页上。
然后通过url的get请求来发送的页数给后台get请求获取来实现简单分页。
查看URL的链接。
22.2 自定义分页之类封装分片取数据
上一章实现了基本分页功能,但是如果有很多地方都需要分页,那么就把分页这个功能封装成为一个类。
22.3 自定义分页之创建页面页码和XSS
本章主要讲解将翻页连接的标签动态显示在html上以及xss攻击原理。
Xss攻击原理
比如django里会把一些返回到前端html自动渲染为字符串,这是为什么?
1.在一些评论界面,我如果评论 <script></script>如果不转换为字符串,那么我的评论脚本就可以执行。
2.甚至我可以写一个script将用户的cookie盗取。
现在实现将翻页的超链接显示在网页上。
查看结果
22.4 自定义分页之封装生成页码方法
本章实现让页面动态起来,让当前页面一直在中间显示,且本章并把代码优化,写进类的函数里面。
查看效果
22.5 自定义分页之优化分页功能
上章实现了动态分页的功能,但是并不完善,总页数的最大值没有限度,甚至最小页数出现了负数,本章解决掉。本章文字理论。
那么本章只要动态更改start和end的值就可以了
效果演示
22.6 自定义分页之上一页和下一页
本章实现上一页和下一页,还有首页和尾页实现,代码比较简单,后期可以自己编写。
22.7 自定义分页总结
本章就是对分页的几点小总结,这里就布列出来,另外,class page(object):
最好写成一个文件,写成一个插件,来直接调用最好。
22.8 Web框架功能分析
讲解了web框架的基本流程以及session,cookie等,这里笔记前面已经记录。
22.9 Bottle框架
22.9.1 Bottle
Bottle是一个快速、简洁、轻量级的基于WSIG的微型Web框架,此框架只由一个 .py 文件,除了Python的标准库外,其不依赖任何其他模块。
如果借助其它的wsgi,那就需要依赖wsgi和其它模版引擎。
Windows 安装
pip install bottle
Bottle框架大致可以分为以下部分:
- 路由系统,将不同请求交由指定函数处理
- 模板系统,将模板中的特殊语法渲染成字符串,值得一说的是Bottle的模板引擎可以任意指定:Bottle内置模板、mako、jinja2、cheetah
- 公共组件,用于提供处理请求相关的信息,如:表单数据、cookies、请求头等
- 服务,Bottle默认支持多种基于WSGI的服务,如:
server_names = { 'cgi': CGIServer, 'flup': FlupFCGIServer, 'wsgiref': WSGIRefServer, 'waitress': WaitressServer, 'cherrypy': CherryPyServer, 'paste': PasteServer, 'fapws3': FapwsServer, 'tornado': TornadoServer, 'gae': AppEngineServer, 'twisted': TwistedServer, 'diesel': DieselServer, 'meinheld': MeinheldServer, 'gunicorn': GunicornServer, 'eventlet': EventletServer, 'gevent': GeventServer, 'geventSocketIO':GeventSocketIOServer, 'rocket': RocketServer, 'bjoern' : BjoernServer, 'auto': AutoServer, }
框架的基本使用
#!/usr/bin/env python # -*- coding:utf-8 -*- from bottle import template, Bottle root = Bottle() @root.route('/hello/') def index(): return "Hello World" # return template('<b>Hello {{name}}</b>!', name="Alex") root.run(host='localhost', port=8080)
以上路由系统什么就不解释,这个可以很简单的看出关联关系,主要是
@root.route('/hello/')的url里面 / 的使用,里面有了 / 访问的时候一定要加上。
现在我们看看源码使用什么运行起来的
接下来可以很明确了,是wsgi,我们如果要更改在启动的时候都可以更改.
22.9.2 路由系统
路由系统是的url对应指定函数,当用户请求某个url时,就由指定函数处理当前请求,对于Bottle的路由系统可以分为一下几类:
- 静态路由(静态不多说,直接匹配字符串的类型)
- 动态路由(可以匹配数字,以及正则表达式等)
- 请求方法路由(限制get或者post方法的方式)
- 二级路由(类始于django,把自己小项目的url归到自己项目下管理)
1.静态路由
@root.route('/hello/') def index(): return template('<b>Hello {{name}}</b>!', name="Alex")
2.动态路由
@root.route('/wiki/<pagename>') def callback(pagename): ... @root.route('/object/<id:int>') def callback(id): ... @root.route('/show/<name:re:[a-z]+>') def callback(name): ... @root.route('/static/<path:path>') def callback(path): return static_file(path, root='static')
3.请求方法路由
@root.route('/hello/', method='POST') def index(): ... @root.get('/hello/') def index(): ... @root.post('/hello/') def index(): ... @root.put('/hello/') def index(): ... @root.delete('/hello/') def index(): ...
4.二级路由
这里举两个例子来查看
web01
web02
index
接下来查看访问方式
22.9.3 模板系统
模板系统用于将Html和自定的值两者进行渲染,从而得到字符串,然后将该字符串返回给客户端。我们知道在Bottle中可以使用 内置模板系统、mako、jinja2、cheetah等,以内置模板系统为例:
以下案例只是一个简单的内置模版案例。
1.语法
- 单值
- 单行Python代码
- Python代码快
- Python、Html混合
<h1>1、单值</h1> {{name}} <h1>2、单行Python代码</h1> % s1 = "hello" <h1>3、Python代码块</h1> <% # A block of python code name = name.title().strip() if name == "Alex": name="seven" %> <h1>4、Python、Html混合</h1> % if True: <span>{{name}}</span> % end <ul> % for item in name: <li>{{item}}</li> % end </ul>
include(sub_template, **variables)
% include('header.tpl', title='Page Title')
Page Content
% include('footer.tpl')
defined(name)
# 检查当前变量是否已经被定义,已定义True,未定义Falseget(name, default=None)
# 获取某个变量的值,不存在时可设置默认值
setdefault(name, default)
# 如果变量不存在时,为变量设置默认值
22.9.4 公共组件
由于Web框架就是用来【接收用户请求】-> 【处理用户请求】-> 【响应相关内容】,对于具体如何处理用户请求,开发人员根据用户请求来进行处理,而对于接收用户请求和相应相关的内容均交给框架本身来处理,其处理完成之后将产出交给开发人员和用户。
【接收用户请求】
当框架接收到用户请求之后,将请求信息封装在Bottle的request中,以供开发人员使用【响应相关内容】
当开发人员的代码处理完用户请求之后,会将其执行内容相应给用户,相应的内容会封装在Bottle的response中,然后再由框架将内容返回给用户
所以,公共组件本质其实就是为开发人员提供接口,使其能够获取用户信息并配置响应内容。
1.request
Bottle中的request其实是一个LocalReqeust对象,其中封装了用户请求的相关信息:
request.headers
请求头信息
request.query
get请求信息
request.forms
post请求信息
request.files
上传文件信息
request.params
get和post请求信息
request.GET
get请求信息
request.POST
post和上传信息
request.cookies
cookie信息
request.environ
环境相关相关
2.response
Bottle中的request其实是一个LocalResponse对象,其中框架即将返回给用户的相关信息:
response
response.status_line
状态行
response.status_code
状态码
response.headers
响应头
response.charset
编码
response.set_cookie
在浏览器上设置cookie
response.delete_cookie
在浏览器上删除cookie
实例:
from bottle import route, request @route('/login') def login(): return ''' <form action="/login" method="post"> Username: <input name="username" type="text" /> Password: <input name="password" type="password" /> <input value="Login" type="submit" /> </form> ''' @route('/login', method='POST') def do_login(): username = request.forms.get('username') password = request.forms.get('password') if check_login(username, password): return "<p>Your login information was correct.</p>" else: return "<p>Login failed.</p>"
22.9.5 服务
对于Bottle框架其本身未实现类似于Tornado自己基于socket实现Web服务,所以必须依赖WSGI,默认Bottle已经实现并且支持的WSGI有:
server_names = { 'cgi': CGIServer, 'flup': FlupFCGIServer, 'wsgiref': WSGIRefServer, 'waitress': WaitressServer, 'cherrypy': CherryPyServer, 'paste': PasteServer, 'fapws3': FapwsServer, 'tornado': TornadoServer, 'gae': AppEngineServer, 'twisted': TwistedServer, 'diesel': DieselServer, 'meinheld': MeinheldServer, 'gunicorn': GunicornServer, 'eventlet': EventletServer, 'gevent': GeventServer, 'geventSocketIO':GeventSocketIOServer, 'rocket': RocketServer, 'bjoern' : BjoernServer, 'auto': AutoServer, }
使用时,只需在主app执行run方法时指定参数即可:
#!/usr/bin/env python # -*- coding:utf-8 -*- from bottle import Bottle root = Bottle() @root.route('/hello/') def index(): return "Hello World" # 默认server ='wsgiref' root.run(host='localhost', port=8080, server='wsgiref')
默认server="wsgiref",即:使用Python内置模块wsgiref,如果想要使用其他时,则需要首先安装相关类库,然后才能使用。如:
# 如果使用Tornado的服务,则需要首先安装tornado才能使用 class TornadoServer(ServerAdapter): """ The super hyped asynchronous server by facebook. Untested. """ def run(self, handler): # pragma: no cover # 导入Tornado相关模块 import tornado.wsgi, tornado.httpserver, tornado.ioloop container = tornado.wsgi.WSGIContainer(handler) server = tornado.httpserver.HTTPServer(container) server.listen(port=self.port,address=self.host) tornado.ioloop.IOLoop.instance().start()
PS:以上WSGI中提供了19种,如果想要使期支持其他服务,则需要扩展Bottle源码来自定义一个ServerAdapter
更多参见:http://www.bottlepy.org/docs/dev/index.html
22.10 Flask框架
本章就是简单介绍Flask框架的一些内容,让人能够了解Flask框架的内容。
Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求进行预处理,然后触发Flask框架,开发人员基于Flask框架提供的功能对请求进行相应的处理,并返回给用户,如果要返回给用户复杂的内容时,需要借助jinja2模板来实现对模板的处理(模版引擎都没有),即:将模板和数据进行渲染,将渲染后的字符串返回给用户浏览器。
“微”(micro) 并不表示你需要把整个 Web 应用塞进单个 Python 文件(虽然确实可以 ),也不意味着 Flask 在功能上有所欠缺。微框架中的“微”意味着 Flask 旨在保持核心简单而易于扩展。Flask 不会替你做出太多决策——比如使用何种数据库。而那些 Flask 所选择的——比如使用何种模板引擎——则很容易替换。除此之外的一切都由可由你掌握。如此,Flask 可以与您珠联璧合。
默认情况下,Flask 不包含数据库抽象层、表单验证,或是其它任何已有多种库可以胜任的功能。然而,Flask 支持用扩展来给应用添加这些功能,如同是 Flask 本身实现的一样。众多的扩展提供了数据库集成、表单验证、上传处理、各种各样的开放认证技术等功能。Flask 也许是“微小”的,但它已准备好在需求繁杂的生产环境中投入使用。
#!/usr/bin/env python # -*- coding:utf-8 -*- from werkzeug.wrappers import Request, Response @Request.application def hello(request): return Response('Hello World!') if __name__ == '__main__': from werkzeug.serving import run_simple run_simple('localhost', 4000, hello)
上述代码中运行起端口的时候直接执行了hello函数。
一.第一次
from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Hello World!" if __name__ == "__main__": app.run()
代码风格差不多和 bottle差不多。
二.路由系统
路由系统没有直接支持正则的。
- @app.route('/user/<username>')
- @app.route('/post/<int:post_id>')
- @app.route('/post/<float:post_id>')
- @app.route('/post/<path:path>')
- @app.route('/login', methods=['GET', 'POST'])
DEFAULT_CONVERTERS = { 'default': UnicodeConverter, 'string': UnicodeConverter, 'any': AnyConverter, 'path': PathConverter, 'int': IntegerConverter, 'float': FloatConverter, 'uuid': UUIDConverter, }
注:对于Flask默认不支持直接写正则表达式的路由,不过可以通过自定义来实现,见:https://segmentfault.com/q/1010000000125259
三、模板
1、模板的使用
Flask使用的是Jinja2模板,所以其语法和Django无差别
2、自定义模板方法
Flask中自定义模板方法的方式和Bottle相似,创建一个函数并通过参数的形式传入render_template,如:
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <h1>自定义函数</h1> {{ww()|safe}} </body> </html> #!/usr/bin/env python # -*- coding:utf-8 -*- from flask import Flask,render_template app = Flask(__name__) def wupeiqi(): return '<h1>Wupeiqi</h1>' @app.route('/login', methods=['GET', 'POST']) def login(): return render_template('login.html', ww=wupeiqi) app.run()
四.公共组件
1、请求
对于Http请求,Flask会讲请求信息封装在request中(werkzeug.wrappers.BaseRequest),提供的如下常用方法和字段以供使用:
request.method
request.args
request.form
request.values
request.files
request.cookies
request.headers
request.path
request.full_path
request.script_root
request.url
request.base_url
request.url_root
request.host_url
request.host
表单处理Demo
@app.route('/login', methods=['POST', 'GET']) def login(): error = None if request.method == 'POST': if valid_login(request.form['username'], request.form['password']): return log_the_user_in(request.form['username']) else: error = 'Invalid username/password' # the code below is executed if the request method # was GET or the credentials were invalid return render_template('login.html', error=error)
上传文件Demo
from flask import request from werkzeug import secure_filename @app.route('/upload', methods=['GET', 'POST']) def upload_file(): if request.method == 'POST': f = request.files['the_file'] f.save('/var/www/uploads/' + secure_filename(f.filename)) ...
Cookie操作
from flask import request @app.route('/setcookie/') def index(): username = request.cookies.get('username') # use cookies.get(key) instead of cookies[key] to not get a # KeyError if the cookie is missing. from flask import make_response @app.route('/getcookie') def index(): resp = make_response(render_template(...)) resp.set_cookie('username', 'the username') return resp
2、响应
当用户请求被开发人员的逻辑处理完成之后,会将结果发送给用户浏览器,那么就需要对请求做出相应的响应。
a.字符串
@app.route('/index/', methods=['GET', 'POST']) def index(): return "index"
b.模板引擎
from flask import Flask,render_template,request app = Flask(__name__) @app.route('/index/', methods=['GET', 'POST']) def index(): return render_template("index.html") app.run()
c.重定向
#!/usr/bin/env python # -*- coding:utf-8 -*- from flask import Flask, redirect, url_for app = Flask(__name__) @app.route('/index/', methods=['GET', 'POST']) def index(): # return redirect('/login/') return redirect(url_for('login')) @app.route('/login/', methods=['GET', 'POST']) def login(): return "LOGIN" app.run()
d.错误页面
指定URL,简单错误
from flask import Flask, abort, render_template app = Flask(__name__) @app.route('/e1/', methods=['GET', 'POST']) def index(): abort(404, 'Nothing') app.run() from flask import Flask, abort, render_template app = Flask(__name__) @app.route('/index/', methods=['GET', 'POST']) def index(): return "OK" @app.errorhandler(404) def page_not_found(error): return render_template('page_not_found.html'), 404 app.run()
e.设置相应信息
使用make_response可以对相应的内容进行操作
from flask import Flask, abort, render_template,make_response app = Flask(__name__) @app.route('/index/', methods=['GET', 'POST']) def index(): response = make_response(render_template('index.html')) # response是flask.wrappers.Response类型 # response.delete_cookie # response.set_cookie # response.headers['X-Something'] = 'A value' return response app.run()
3、Session
除请求对象之外,还有一个 session 对象。它允许你在不同请求间存储特定用户的信息。它是在 Cookies 的基础上实现的,并且对 Cookies 进行密钥签名要使用会话,你需要设置一个密钥。
- 设置:session['username'] = 'xxx'
- 删除:session.pop('username', None)
from flask import Flask, session, redirect, url_for, escape, request app = Flask(__name__) @app.route('/') def index(): if 'username' in session: return 'Logged in as %s' % escape(session['username']) return 'You are not logged in' @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': session['username'] = request.form['username'] return redirect(url_for('index')) return ''' <form action="" method="post"> <p><input type=text name=username> <p><input type=submit value=Login> </form> ''' @app.route('/logout') def logout(): # remove the username from the session if it's there session.pop('username', None) return redirect(url_for('index')) # set the secret key. keep this really secret: app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
4、message
message是一个基于Session实现的用于保存数据的集合,其特点是:使用一次就删除
index.html <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> {% with messages = get_flashed_messages() %} {% if messages %} <ul class=flashes> {% for message in messages %} <li>{{ message }}</li> {% endfor %} </ul> {% endif %} {% endwith %} </body> </html>
from flask import Flask, flash, redirect, render_template, request app = Flask(__name__) app.secret_key = 'some_secret' @app.route('/') def index1(): return render_template('index.html') @app.route('/set') def index2(): v = request.args.get('p') flash(v) return 'ok' if __name__ == "__main__": app.run()
五.中间件
from flask import Flask, flash, redirect, render_template, request app = Flask(__name__) app.secret_key = 'some_secret' @app.route('/') def index1(): return render_template('index.html') @app.route('/set') def index2(): v = request.args.get('p') flash(v) return 'ok' class MiddleWare: def __init__(self,wsgi_app): self.wsgi_app = wsgi_app def __call__(self, *args, **kwargs): return self.wsgi_app(*args, **kwargs) if __name__ == "__main__": app.wsgi_app = MiddleWare(app.wsgi_app) app.run(port=9999)
Flask还有众多其他功能,更多参见:
http://docs.jinkan.org/docs/flask/
http://flask.pocoo.org/
22.11 Tornado框架初识
Tornado 是 FriendFeed 使用的可扩展的非阻塞式 web 服务器及其相关工具的开源版本。这个 Web 框架看起来有些像web.py 或者 Google 的 webapp,不过为了能有效利用非阻塞式服务器环境,这个 Web 框架还包含了一些相关的有用工具 和优化。
Tornado 和现在的主流 Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快。得利于其 非阻塞的方式和对 epoll 的运用,Tornado 每秒可以处理数以千计的连接,这意味着对于实时 Web 服务来说,Tornado 是一个理想的 Web 框架。我们开发这个 Web 服务器的主要目的就是为了处理 FriendFeed 的实时功能 ——在 FriendFeed 的应用里每一个活动用户都会保持着一个服务器连接。(关于如何扩容 服务器,以处理数以千计的客户端的连接的问题,请参阅 C10K problem。)
Tornado支持异步,就是请求发送过来,我去处理,然后断开连接,执行的结果怎么样都先给你返回。
22.12 自定义Form表单验证
本章讲解自定义form表单验证,由于讲解并不是很清晰,所以本章没有注释,按照常规的web框架流程去看,就可以看懂下面的代码。
自定义模型版定
模型绑定有两个主要功能:
- 自动生成html表单
- 用户输入验证
Index <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <link href="{{static_url("commons.css")}}" rel="stylesheet" /> </head> <body> <h1>hello</h1> <form action="/index" method="post"> <p>hostname: <input type="text" name="host" /> </p> <p>ip: <input type="text" name="ip" /> </p> <p>port: <input type="text" name="port" /> </p> <p>phone: <input type="text" name="phone" /> </p> <input type="submit" /> </form> </body> </html>
#!/usr/bin/env python # -*- coding:utf-8 -*- import tornado.ioloop import tornado.web from hashlib import sha1 import os, time import re class MainForm(object): def __init__(self): self.host = "(.*)" self.ip = "^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$" self.port = '(\d+)' self.phone = '^1[3|4|5|8][0-9]\d{8}$' def check_valid(self, request): #获取类__init__的所有值 form_dict = self.__dict__ for key, regular in form_dict.items(): post_value = request.get_argument(key) # 让提交的数据 和 定义的正则表达式进行匹配 ret = re.match(regular, post_value) print key,ret,post_value class MainHandler(tornado.web.RequestHandler): #根据是get获取post请求来执行函数返回给用户 def get(self): self.render('index.html') def post(self, *args, **kwargs): obj = MainForm() result = obj.check_valid(self) self.write('ok') settings = { 'template_path': 'template', 'static_path': 'static', 'static_url_prefix': '/static/', 'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh', 'login_url': '/login' } application = tornado.web.Application([ (r"/index", MainHandler), ], **settings) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
23.L023
23.1 今日内容
23.2 ORM一对多之创建数据和正向查找
本章内容比较简单,在做BBS项目的时候其实已经做过一遍了,但是这里还是简单写一下本章内容。
准备测试数据。
里面有用户lizexiong,wuxinzhe,有用户类型CEO,ADMIN,USER
首先正想查找出所有用户以及用户组,由于外键关联会自动生成一个id,所以,这里显示的是USER_TYPE名称,而不是ID。
结果
到这里,什么是正想查找,什么是反向查找呢?
ForeignKey在哪里开始关联的谁,在源头查找那就是正向,如果是在被关联的表里查找,那就是反向查找。
这里注意到双下划线的使用。
这里来一个小例子,在userlsit表里查找出CEO组所有的用户。
23.3 ORM一对多之反向查找
本章主要是反向查找的一些小实验。
条件,获取某个人是什么类型,当前类型下面有多少人。
本章主要注意get和filter的用法
- 输入参数
get 的参数只能是model中定义的那些字段,只支持严格匹配
filter 的参数可以是字段,也可以是扩展的where查询关键字,如in,like等 - 返回值
get 返回值是一个定义的model对象
filter 返回值是一个新的QuerySet对象,然后可以对QuerySet在进行查询返回新的QuerySet对象,支持链式操
QuerySet一个集合对象,可使用迭代或者遍历,切片等,但是不等于list类型(使用一定要注意) - 异常
get 只有一条记录返回的时候才正常,也就说明get的查询字段必须是主键或者唯一约束的字段。当返回多条记录或者是没有找到记录的时候都会抛出异常
filter 有没有匹配的记录都可以
Get方式反向查找
Filter方式查找
使用get通过user_type表查出这个组下面有哪些用户。
使用filter通过user_type表查出这个组下面有哪些用户。
23.4 ORM一对多之女神的点赞实例(一)
本章开始数据表结构与上一段无关。
本章其实也是数据库实例的关联表操作,本章要求,打印出一篇文章被点赞的总数。
准备测试数据。
在admin上注册,添加文章,点赞数的测试过程这里就不列出了。
查看点赞信息
现在打印出一篇文章以及被点赞的总数。
23.5 ORM一对多之女神的点赞实例(二)
本章是接上一章,本章要求把一个人赞过的文章全部列出来。
注意思路,因为要找出文章,条件是一个人赞过的所有文章,因为找出文章,所以最后的结果是文章名,所以要从文章表开始查。
然后并查出这些文章有那些人点过赞。
二、一篇文章被哪些人点过赞,条件一已经实现了,但是是从,从Article开始查询的,这里从userinfo表开始查,其它表作为条件。
23.6 ORM一对多内容梳理
本章就是总结,需要多用,不然很容易搞混。
23.7 ORM多对多之正向添加数据
本章内容比较简单,多对多的原理早在前文已经学习过,本章主要是多对多的把主机添加到管理员中。
测试数据
这里我们知道,如果多对多,会自动生成第三张表,将两张表的id关联起来。
现在将id小于3的主机全部添加到lizeixong用户管理。
查看结果
23.8 ORM多对多之反向添加数据
本章通过把主机id等于3的全部给用户id大于1的管理,因为是在host_admin关联的host,所以要用反向。
查看数据库结果
用户id大于1的就一个2,所以给了用户id为2的管理。
23.9 ORM多对多之自定义第三张关系表
前文我们知道,如果是多对多,那么会自动多出一张2张id的对应表,这张表其实可以自定义,甚至可以加字段,这样就会方便很多,自定义的好处会在这两张详细列出来。
测试数据有4台主机和3个用户。
自定义第三张表创建数据有二种方式,一种是传对象进去,一种直接传值进去。
查看结果
总结:
这种自定义第三张表的方式比django为我们自己创建的方式,更简单,更方便,
这种方式就不需要查询,一次插入就可以完成我们想要的结果,而且,还有一点,前端selelct的value一般为数值,那么就更好插入了。
具体的对比会在下一章详细讲解。
23.10 ORM多对多之两种方式对比
本章详细讲解django为我们自动创建第三张表 和我们自定义第三张表的好处和区别。
首先,比如我们要装系统,每个管理员装系统装的哪台机器什么状态如果要记录下来怎么办?那么我们可以使用自定义第三章表来实现,可以自己加字段,那么就很方便了。
首先,我们查看第一种django为我们自动创建表的正向查和反向查。
第一种方式的正向查找,找出id为1的管理员管理多少台主机。
反向查找id为1的主机有几个管理员正在管理。
现在第二种自定义第三张表查找。
首先找出所有的主机和管理员的对应关系。
查看结果
现在查找出lizexiong管理的所有主机。
总结:
可以看到,自定义第三张表可以很简单的查询出所有对应关系,当然,django也可以查询,只不过推荐使用自定义第三张表。
23.11 ORM之select_related
Select_related是django的优化方法,为什么呢?如果查询的时候加入了select_related方法,那么sql的查询语句就回把需要关联的表提交加载进入内存,第二次来的时候就直接调用就可以了,如果不加select_related,那么第二次多表查询的时候又要去请求数据库,所以说是优化数据库的查询方法。
可以看到,上面的查询语句是没有加select_related方法的,只查询了一张表,第二次还要多表查询的时候又要请求数据库,下面的查询语句加了,所以就下次请求直接在内容里面调用即可。
23.12 ORM连表操作梳理
本章总结了一下查询的方式
23.13 ORM之F
Django有一个F,这个F代表什么,代表当前行的一个参数,这里可能不理解,看下面的例子。
有这么一个需求,我需要把用户表里面的所有年龄全部加1,如果是sql语句这么实现
Update 表名 set age = age + 1
就可以解决,但是如果是django呢?那么F就要用到了
Models.表名.objects.all().update(age=F(‘age’)+1)
那么这条语句就等于上面的那条sql语句了,为什么是F,因为在django里面,age=age+1是不可以的,因为age这个变量根本就没有定义,()里面的是条件,age不存在,F(‘age’)代表查找出来的当前行的age,所以这就是F的用处。
当然,如果要使用F,需要导入模块
form django.db.models import F
23.14 ORM之Q
Q的含义其实也很简单,比如一条语句:
models.表名.objects.filter(),这里传条件的时候可以是filter(name=’lizexiong’,age=’26’),这个name和age是并且的关系,只有满足才能查出来,但是,如果这里有一个需求,这里有很多主机,只要主机满足了 上架状态,或者序列号是什么的时候才查询出来,那么这里就是or的条件了,那么filter无法完成,那么就需要Q的帮助。
下面就是Q的语法,每个Q之间是或的关系,然后将几个Q拼接起来,最后汇总,那么就可以将并且和或者一起放入查询条件了,并且,在Q里面是支持__跨表查询的。
23.15 今日作业(一)
写一个多条件查询的网页
23.16 Tornado源码分析----------------
没听懂。。。后期源码时回来补充。
23.17 Tornado源码内容梳理
23.18 Tornado之模版操作介绍
首先模版自定义功能,这里只简单演示以下母板继承,如果需要使用更复杂的功能,视频没有讲出,但是把最基本的方式在这里展示出来。
继承母板示例
layout.html
Index.html
Python文件
查看结果
在模板中默认提供了一些函数、字段、类以供模板使用:
- escape: tornado.escape.xhtml_escape 的別名
- xhtml_escape: tornado.escape.xhtml_escape 的別名
- url_escape: tornado.escape.url_escape 的別名
- json_encode: tornado.escape.json_encode 的別名
- squeeze: tornado.escape.squeeze 的別名
- linkify: tornado.escape.linkify 的別名
- datetime: Python 的 datetime 模组
- handler: 当前的 RequestHandler 对象
- request: handler.request 的別名
- current_user: handler.current_user 的別名
- locale: handler.locale 的別名
- _: handler.locale.translate 的別名
- static_url: for handler.static_url 的別名
- xsrf_form_html: handler.xsrf_form_html 的別名
Tornado默认提供的这些功能其实本质上就是 UIMethod 和 UIModule,我们也可以自定义从而实现类似于Django的simple_tag的功能:
自定义的UIMethod是一个函数,自定义的UIModule是一个类。
函数就是simple_tag功能,至于UIModule自定义模块,视频没有讲解。
1.首先我们自定义UImethod和UiModule
UImethod.py
UIModule.py
2.把自定义的函数和模块导入注册到main函数里面。
3.使用
结果
23.19 Tornado之静态文件和cookie
本章将的静态文件和django一个函数,在settings里一模一样。
下面就是torando静态文件设置
settings = { 'template_path': 'template', 'static_path': 'static', 'static_url_prefix': '/static/', }
23.20 Web扩展之自定义session
本章实现torando的session的功能,复习本章之前需要了解cookie和session以及类使用方法的原理。
本章由于代码较多,已经将代码重要的地方注释,这里还是贴出代码,建议去查看源码。
特别提示,torando里面url的 /login /login/ 是完全的两回事
Session原理图
login.html
Main.py
总结:
本章笔记思路。
24.L024
24.1 CMDB架构需求实现
浅谈ITIL
TIL即IT基础架构库(Information Technology Infrastructure Library, ITIL,信息技术基础架构库)由英国政府部门CCTA(Central Computing and Telecommunications Agency)在20世纪80年代末制订,现由英国商务部OGC(Office of Government Commerce)负责管理,主要适用于IT服务管理(ITSM)。ITIL为企业的IT服务管理实践提供了一个客观、严谨、可量化的标准和规范。
1、事件管理(Incident Management)
事故管理负责记录、归类和安排专家处理事故并监督整个处理过程直至事故得到解决和终止。事故管理的目的是在尽可能最小地影响客户和用户业务的情况下使IT系统恢复到服务级别协议所定义的服务级别。
2、问题管理(Problem Management)
问题管理是指通过调查和分析IT基础架构的薄弱环节、查明事故产生的潜在原因,并制定解决事故的方案和防止事故再次发生的措施,将由于问题和事故对业务产生的负面影响减小到最低的服务管理流程。与事故管理强调事故恢复的速度不同,问题管理强调的是找出事故产生的根源,从而制定恰当的解决方案或防止其再次发生的预防措施。
3、配置管理(Configuration Management)
配置管理是识别和确认系统的配置项,记录和报告配置项状态和变更请求,检验配置项的正确性和完整性等活动构成的过程,其目的是提供IT基础架构的逻辑模型,支持其它服务管理流程特别是变更管理和发布管理的运作。
4、变更管理(Change Management)
变更管理是指为在最短的中断时间内完成基础架构或服务的任一方面的变更而对其进行控制的服务管理流程。变更管理的目标是确保在变更实施过程中使用标准的方法和步骤,尽快地实施变更,以将由变更所导致的业务中断对业务的影响减小到最低。
5、发布管理(Release Management)
发布管理是指对经过测试后导入实际应用的新增或修改后的配置项进行分发和宣传的管理流程。发布管理以前又称为软件控制与分发
事件管理的目标是在不影响业务的情况下,尽可能快速的恢复服务,从而保证最佳的效率和服务的可持续性。事件管理流程的建立包括事件分类,确定事件的优先级和建立事件的升级机制。
问题管理是调查基础设施和所有可用信息,包括事件数据库,来确定引起事件发生的真正潜在原因,一起提供的服务中可能存在的故障。
配置管理的目标是:定义和控制服务与基础设施的部件,并保持准确的配置信息。
变更管理的目标是:以受控的方式,确保所有变更得到评估、批准、实施和评审。
发布管理的目标是:在实际运行环境的发布中,交付、分发并跟踪一个或多个变更。
服务台:服务台是IT部门和IT服务用户之间的单一联系点。它通过提供一个集中和专职的服务联系点促进了组织业务流程与服务管理基础架构集成。服务台的主要目标是协调客户(用户)和IT部门之间的联系,为IT服务运作提供支持,从而提高客户的满意度。
CMDB介绍
CMDB --Configuration Management Database 配置管理数据库, CMDB存储与管理企业IT架构中设备的各种配置信息,它与所有服务支持和服务交付流程都紧密相联,支持这些流程的运转、发挥配置信息的价值,同时依赖于相关流程保证数据的准确性。
在实际的项目中,CMDB常常被认为是构建其它ITIL流程的基础而优先考虑,ITIL项目的成败与是否成功建立CMDB有非常大的关系。
70%~80%的IT相关问题与环境的变更有着直接的关系。实施变更管理的难点和重点并不是工具,而是流程。即通过一个自动化的、可重复的流程管理变更,使得当变更发生的时候,有一个标准化的流程去执行,能够预测到这个变更对整个系统管理产生的影响,并对这些影响进行评估和控制。而变更管理流程自动化的实现关键就是CMDB。
CMDB工具中至少包含这几种关键的功能:整合、调和、同步、映射和可视化。
- 整合是指能够充分利用来自其他数据源的信息,对CMDB中包含的记录源属性进行存取,将多个数据源合并至一个视图中,生成连同来自CMDB和其他数据源信息在内的报告;
- 调和能力是指通过对来自每个数据源的匹配字段进行对比,保证CMDB中的记录在多个数据源中没有重复现象,维持CMDB中每个配置项目数据源的完整性;自动调整流程使得初始实施、数据库管理员的手动运作和现场维护支持工作降至最低;
- 同步指确保CMDB中的信息能够反映联合数据源的更新情况,在联合数据源更新频率的基础上确定CMDB更新日程,按照经过批准的变更来更新 CMDB,找出未被批准的变更;
- 应用映射与可视化,说明应用间的关系并反应应用和其他组件之间的依存关系,了解变更造成的影响并帮助诊断问题。
CMDB 资产管理部分实现
需求
- •存储所有IT资产信息
- •数据可手动添加
- •硬件信息可自动收集
- •硬件信息可自动变更
- •可对其它系统灵活开放API
- •API接口安全认证
配置项分析
每个配置项需存储的属性信息分析
立业之本:定义表结构
- 各种硬件都能存
- 资产变更有纪录
- 资产ID永不变
- 资产要有状态机
重中之重:接口设计好
- 可对内外灵活开放接口
- 接口定义要标准化
- 一定要提供排错依据
- 数据返回要标准
- 要能增删改查
- 所有异常要抓住
- 接口安全要注意
24.2 自定义用户认证
本章实现使用django自定义用户表,在BBS中,我们使用的django自带的系统后台账户,我们继承来写的,那时候django一张表,我们自己写一张表,现在我们自定义用户认证表,但是还是借助django来写。
因为之前django默认用户字段是用户名,邮箱等一些字段,我们这里可以改成我们自定义需要的,比如,用户名 是否为管理员,是否激活等。
这里完成之后,我自定义的表就出来了,但是我先要告诉django,我不用它提供的,我有自定义的表。(还有在app里面注册,这里就不贴图了)
现在创建迁移文件和数据库表。
现在创建管理员用户查看字段是否为我们自定义的。
24.3 自定义用户认证admin配置
上章讲解了自定义用户表,结构,字段等,但是admin管理界面还是使用的默认的,我们也可以自定义admin管理界面,但是个人认为,本章只需要了解即可.
首先,我们看看默认的admin界面,password直接显示出来了。
现在我们自定义admin界面。
自定义admin完成了,现在告诉django admin管理也用我们自己的。
查看我们自定义的admin界面
24.4 CMDB表结构设计
本章数据表过多,这里推荐直接查看代码models.
24.5 CMDB表结构设计2
上一章实现了表结构创建,但是由于关联过多,现在要实现以下效果。
资产表创建的时候可以把server或者cpu等一些信息创建了。
接下来就看怎么实现上面图中的方式。
第二步,查看AssetAdmin怎么定义的,表里面关联哪些表
第三步,关联的表需要显示哪些内容,权限怎么样。
24.6 设计支持windows 和linux的客户端
接下来的所有章节视频都是一笔带过,所以直接跳转到API讲解。
24.7 Django rest framework 框架使用与配置
首先安装
pip install djangorestframework pip install markdown # Markdown support for the browsable API. pip install django-filter # Filtering support
注册
INSTALLED_APPS = ( ... 'rest_framework', )
然后在写一个全局的url
urlpatterns = [ ... url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')) ]
创建serializers.py文件,这个序列化文件定义前端展示什么东西。
因为本章是测试,现在创建一个测试的views。
然后在把rest的url在单独写出来。
然后告诉django我们rest的接口在哪里。
然后查看
24.8 如何实现世界上最安全且简单的API权限认证
首先,api接口我不能让人知道我的接口就随便改数据,首先肯定要经过验证才能更改。
首先看上图, 如果我们要去服务端认证,我们要拿着自己的token过去,但是token给黑客劫持,黑客就能拿着我的token去认证,所以不安全,所以这里,我将时间戳和token加密成hash值,不发送token,发送给服务器,让服务器拿着时间戳和用户名等数据来加密,得到的hash值如果匹配,那么就是谁,到这里,黑客如果劫持了 时间戳和hash值给服务器,服务器还是会认证通过,所以,我们这里最主要在服务器用户请求在5分钟来只予许请求一次,5分钟的第二次就拒绝掉,如果5分钟之后黑客拿着我的数据给服务器认证,那么我5分钟前的认证已经失效了,只有在重新生成服务器才会认证,当然,如果认证数据量比较大怎么办,服务器本地可以缓存一个表,专门放认证通过的数据,这个缓存表没5分钟清空一次,那么就不会变太大,这就是世界上最安全的认证方式。
25.L025
25.1 上节内容回顾
不负责任的老师,需要回来看视频吧。
25.2 上节内容回顾2
本章回顾了一部分,但是还演示了比如不是默认的user表的接口展示,多表怎么在接口展示出来,如果关联了其它表,其它的表都需要写url和views(否则会报错),但是如果不想写这么多表,可以通过限制展示字段来控制是否需要把其它表也同时写出来。
现在找到有外键的Asset表来测试。
这里如果展示字段里面关联的表没有写出来就会报错,这里使用Manufactory的外键字段做测试
接下来是views配置
然后最后是url配置
登录api web查看
点击"Assets": "http://127.0.0.1:8888/api/Assets/",
当然,还可以层级的把表关系显示出来,但是不推荐,因为,这样会查询太多的表,降低速度。
查看效果
25.3 API安全谁代码
本章还是复习,复习内容有:
1.从开始收集和汇报开始讲解,讲解setting里面字典组成url,然后加上用户名,加上token然后加密的方式,然后判断有没有资产id,根据结果返回url。
2.get 方式 ,字符串拼接的时候是?号或者&号。?就是以get的方式把请求发送过去。
3.md5值 截断几个字符发送过去。
4.客户端发送数据给我服务端的时候,服务器用了装饰器,然后在里面判断是否超时,md5是否正确等。
25.4 新资产汇报
本章是新功能讲解,包括:
1.客户端把数据发送过来,服务器会进行强制字段检测,比如,sn,资产id等数据。
2.讲解客户端怎么通过服务器端强制检查的,讲的稀烂。
3.models.get_or_create的使用,如果没有就创建这条数据,并返回flase或者true
25.5 新资产批准入库
本章19分钟开始
1.asset 的myauth里面有一个报错。
2.在服务端settings里面配置token。
3.自定义批准入库。
4.request.post.getlist 这里是后台获取,但是在前端checkbox获取多选就不能用get,只能用getlist.(32分钟)
5.ContentType.objects.get_for_model(queryset.model) 35分钟讲解。
6.contentType 简单实现 多级评论,50分钟。
本章讲解的是狗屎。
26.L026
26.1 今日内容介绍
26.2 CMDB实现方式介绍
使用ruby来汇报信息,以后强大了在尝试。
26.3 分布式Session框架的实现
本章实现使用troando的session实现。其实troando的sesson前文早已讲解过,但是这里重新写一遍,本章内容如下。
1.使用troando 的扩展initialize实现session。
2.实现session通过一致性哈希,存储在不同服务器。
本章代码不在贴出,但是会一句一句讲解代码含义,但是这里不在指定代码在多少行,按照顺序往下面讲解
1.application
application = tornado.web.Application([ (r"/index",MainHandler), (r"/login",LoginHandler), ],**settings)
首先,这里有2个url,/index,/login,每个url后面跟了一个函数,代码如下(r"/index",MainHandler), 意思就是index的url来了请求交给MainHandler函数处理。 这里的主要是url路由系统,然后把settings加载进入我这个系统,
2.url后面的函数
class LoginHandler(BaseHandler): def get(self): self.render('login.html') def post(self): username = self.get_argument('username') password = self.get_argument('pwd') if username == 'lizexiong' and password == '123': self.my_session['is_login'] = True self.redirect('/index') print 'login success..' else: self.render('login.html') class MainHandler(BaseHandler): def get(self): ret = self.my_session['is_login'] print ret,'----' if ret: self.write("ok") del self.my_session['is_login'] print self.my_session['is_login'] else: self.render('login.html')
这里的类都继承了BaseHandler,我们的扩展和操作都是在这个类里面实现的
3.
class BaseHandler(tornado.web.RequestHandler): def initialize(self): # my_session['k1'] 自动 访问 __getitem__ 方法 #4.就是这里定义session处理,在每个class执行之前 self.my_session = Session(self)
BaseHandler 函数继承了tornado.web.RequestHandlertornado.web.RequestHandler本来是给我们自定义的MainHandler这个继承的,现在为了扩展,我们继承了2层所有关于session的操作都是在BaseHandler里面执行的,意思就是说,我们执行MainHandler类的时候,就会先执行 BaseHandler这个类。
这个类里面执行了self.my_session = Session(self),这个self是什么?
加入 obj = MainHandler() 那么obj就是self。
4.Session的操作。
create_session_id = lambda: sha1('%s%s' % (os.urandom(16), time.time())).hexdigest() session_container = {} class Session(object): session_id = "__sessionId__" def __init__(self,request): session_value = request.get_cookie(Session.session_id) print session_value if not session_value: self._id = create_session_id() else: self._id = session_value request.set_cookie(Session.session_id,self._id) def __getitem__(self, key): ret = None try: return session_container[self._id][key] except Exception, e: pass return ret def __setitem__(self, key, value): if session_container.has_key(self._id): session_container[self._id][key] = value else: session_container[self._id] = {key:value} def __delitem__(self,key): if session_container.has_key(self._id): del session_container[self._id][key]
首先,到Session类,肯定要初始化类,那么就执行__init__里面的方法
def __init__(self,request): session_value = request.get_cookie(Session.session_id) print session_value if not session_value: self._id = create_session_id() else: self._id = session_value request.set_cookie(Session.session_id,self._id)
如果request.get_cookie(Session.session_id) 这个获取的肯定是一个随机长度的字符串。 如果没有这个字符串,那么 就使用create_session_id() 随机生成一个,如果有
就把session_value赋值给self._id,相当于self._id不管怎么样都是等于一个随机的字符串。最后,由request.set_cookie(Session.session_id,self._id) tornado自定义的方法把值写成一个字典格式,类始于 a ={‘Session.session_id’:{‘self._id’:{}}}
如果第二步登录函数里面登录成功了,那么就会执行
self.my_session['is_login'] = True
现在有了key和value,那么根据tornado的特性,就会自动执行
def __setitem__(self, key, value): if session_container.has_key(self._id): session_container[self._id][key] = value else: session_container[self._id] = {key:value}
的方法, 如果有self._id=随机字符串,那么就生成一个字典,如果没有,还是生成一个字典。
到这里其实难点就已经结束了,其实,本章最容易搞混的概念如下:
self.my_session 这个字典的值
request.set_cookie(Session.session_id,self._id) 这里会生成字典的值。
Self.my_session这个值主要是比如传入self.my_session['is_login'] = True
那么就会先初始化类,拿到一个随机字符串,也就是 self._id,然后在设置一个值,
如 session_container ={‘self._id’:{‘is_login’:’true’}}
而request.set_cookie(Session.session_id,self._id)是tornado自己的方法,会把一个固定字符的表示传入进入生成一个字典,但是字典的key最前面就是Session.session_id,所以说,这里是两个字典,刚开始学习很容易把这两个字典当成一个,这值是设置cookie的值,也可以说是返回给客户端要接收的cookie,跟我们后面的判断没太大关系的。
关于本章主要内容分布式Session什么意思,就是怕session数据过大,或者使之有灾备,将session通过算法将session分布存储到不同服务器的存储活redis中,由于中间算法比较麻烦,这里只讲解原理,但是还是把所有代码贴出来。
源代码
一致性哈希
#!/usr/bin/env python #coding:utf-8 import sys import math from bisect import bisect if sys.version_info >= (2, 5): import hashlib md5_constructor = hashlib.md5 else: import md5 md5_constructor = md5.new class HashRing(object): """一致性哈希""" def __init__(self,nodes): '''初始化 nodes : 初始化的节点,其中包含节点已经节点对应的权重 默认每一个节点有32个虚拟节点 对于权重,通过多创建虚拟节点来实现 如:nodes = [ {'host':'127.0.0.1:8000','weight':1}, {'host':'127.0.0.1:8001','weight':2}, {'host':'127.0.0.1:8002','weight':1}, ] ''' self.ring = dict() self._sorted_keys = [] self.total_weight = 0 self.__generate_circle(nodes) def __generate_circle(self,nodes): for node_info in nodes: self.total_weight += node_info.get('weight',1) for node_info in nodes: weight = node_info.get('weight',1) node = node_info.get('host',None) virtual_node_count = math.floor((32*len(nodes)*weight) / self.total_weight) for i in xrange(0,int(virtual_node_count)): key = self.gen_key_thirty_two( '%s-%s' % (node, i) ) if self._sorted_keys.__contains__(key): raise Exception('该节点已经存在.') self.ring[key] = node self._sorted_keys.append(key) def add_node(self,node): ''' 新建节点 node : 要添加的节点,格式为:{'host':'127.0.0.1:8002','weight':1},其中第一个元素表示节点,第二个元素表示该节点的权重。 ''' node = node.get('host',None) if not node: raise Exception('节点的地址不能为空.') weight = node.get('weight',1) self.total_weight += weight nodes_count = len(self._sorted_keys) + 1 virtual_node_count = math.floor((32 * nodes_count * weight) / self.total_weight) for i in xrange(0,int(virtual_node_count)): key = self.gen_key_thirty_two( '%s-%s' % (node, i) ) if self._sorted_keys.__contains__(key): raise Exception('该节点已经存在.') self.ring[key] = node self._sorted_keys.append(key) def remove_node(self,node): ''' 移除节点 node : 要移除的节点 '127.0.0.1:8000' ''' for key,value in self.ring.items(): if value == node: del self.ring[key] self._sorted_keys.remove(key) def get_node(self,string_key): '''获取 string_key 所在的节点''' pos = self.get_node_pos(string_key) if pos is None: return None return self.ring[ self._sorted_keys[pos]].split(':') def get_node_pos(self,string_key): '''获取 string_key 所在的节点的索引''' if not self.ring: return None key = self.gen_key_thirty_two(string_key) nodes = self._sorted_keys pos = bisect(nodes, key) return pos def gen_key_thirty_two(self, key): m = md5_constructor() m.update(key) return long(m.hexdigest(), 16) def gen_key_sixteen(self,key): b_key = self.__hash_digest(key) return self.__hash_val(b_key, lambda x: x) def __hash_val(self, b_key, entry_fn): return (( b_key[entry_fn(3)] << 24)|(b_key[entry_fn(2)] << 16)|(b_key[entry_fn(1)] << 8)| b_key[entry_fn(0)] ) def __hash_digest(self, key): m = md5_constructor() m.update(key) return map(ord, m.digest()) """ nodes = [ {'host':'127.0.0.1:8000','weight':1}, {'host':'127.0.0.1:8001','weight':2}, {'host':'127.0.0.1:8002','weight':1}, ] ring = HashRing(nodes) result = ring.get_node('98708798709870987098709879087') print result """
Session
from hashlib import sha1 import os, time create_session_id = lambda: sha1('%s%s' % (os.urandom(16), time.time())).hexdigest() class Session(object): session_id = "__sessionId__" def __init__(self, request): session_value = request.get_cookie(Session.session_id) if not session_value: self._id = create_session_id() else: self._id = session_value request.set_cookie(Session.session_id, self._id) def __getitem__(self, key): # 根据 self._id ,在一致性哈西中找到其对应的服务器IP # 找到相对应的redis服务器,如: r = redis.StrictRedis(host='localhost', port=6379, db=0) # 使用python redis api 链接 # 获取数据,即: # return self._redis.hget(self._id, name) def __setitem__(self, key, value): # 根据 self._id ,在一致性哈西中找到其对应的服务器IP # 使用python redis api 链接 # 设置session # self._redis.hset(self._id, name, value) def __delitem__(self, key): # 根据 self._id 找到相对应的redis服务器 # 使用python redis api 链接 # 删除,即: return self._redis.hdel(self._id, name)
分布式session执行代码就是如下:
ring = HashRing(nodes)
result = ring.get_node('98708798709870987098709879087')
print result
把nodes的参数传入进去,然后ring把cookie存储到计算好不同权重的节点上面去。
26.4 自定义Form表单验证(一)
本章讲解tornado自定义form表单的验证以及form表单数据的合法性。
首先我们复习一下怎么取出类的所有静态字段和对象的多有静态字段。
获取类的所有静态字段
获取对象的所有静态字段。
其实form就是按照这个实现的。
本章重要代码就在这一块里面,如果想看所有代码请上git。
26.5 自定义Form表单验证(二)
上一章实现了自定义验证form表单的值是否正确,但是代码是可以优化的,我们写成一个函数,以后更多验证来了也可以调用。
本章主要是两个self的传入和使用,如果有点饶,后期推荐回来看视频也可以。
26.6 自定义Form表单验证(三)
上一章实现了使用自定义form表单值是否正确,但是只有一张Mainform的表单,如果这是又来一张loginform表单,里面验证的is_valid()内容都是一样的,那么代码就重复了,所以现在我们把is_valid()写成一个基类,让form表单来继承那么就ok了。
如果以后出现class LoginForm,那么验证条件是一样的,那么直接
Obj = LoginForm() obj.is_valid就好了,提高代码的重用性。
26.7 自定义Form表单验证(四)
上一章实现了代码is_valid()验证的重用性,可以使多张form表单都用一种验证规则,但是一些字段的正则匹配那么也能在这样提高重用了,而且可以提供是否支持验证这些字段。
首先为了怕后期代码改变,这里首先截图出所有代码。
#!/usr/bin/env python # -*- coding:utf-8 -*- import tornado.ioloop import tornado.web from hashlib import sha1 import os, time import re class IPField(object): REGULAR = "^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$" def __init__(self,error_msg_dict=None, required=True): error_msg = {} if error_msg_dict: error_msg.update(error_msg_dict) #把这两个字段封装到IpField类里面 self.required = required self.error_msg = error_msg def check_valid(self,request,k): flag = True import re if self.required: if not request.get_argument(k): flag =False print self.error_msg['required'] else: if re.match(self.REGULAR,request.get_argument(k)): pass else: flag =False print self.error_msg['valid'] else: pass return flag class BaseForm(object): #2.__这里的self就是把自己MainForm传入进入,就是self,但是还有一个self(MainHandler)怎么办,那么他就是request #就是这里的self=MainForm request=MainHandler def is_valid(self,request): flag = True print self.__dict__,'BaseForm' for v in self.__dict__.values(): print v,'BaseForm------' for k,v in self.__dict__.items(): #这是v就是一个对象了 ret = v.check_valid(request,k) print ret,'ret---' if not ret: flag = False return flag class MainForm(BaseForm): def __init__(self): # self.host = "(.*)" # self.ip = "^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$" # self.port = '(\d+)' # self.phone = '^1[3|4|5|8][0-9]\d{8}$' #对象里面封装了正则表达式和错误信息 self.ip = IPField(error_msg_dict={'required':"IP不能为空",'valid':'IP格式错误'},required=True) print self.__dict__,'MainForm' class MainHandler(tornado.web.RequestHandler): def get(self): self.render('index.html') def post(self, *args, **kwargs): flag = True print IPField.__dict__,'IPFIELD' obj = MainForm() #1__这里要注意,我要传入把当前对象传入进去,那么就是MainHandler就是self,自己传入进去,然后 obj.is_valid(self) if obj.is_valid(self): self.write('ok') else: self.write('error') settings = { 'template_path': 'template', 'static_path': 'static', 'static_url_prefix': '/static/', 'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh', 'login_url': '/login' } application = tornado.web.Application([ (r"/index", MainHandler), ], **settings) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
接下来一行一行解释代码。
1.首先是执行我们url后面的函数
class MainHandler(tornado.web.RequestHandler): def get(self): self.render('index.html') def post(self, *args, **kwargs): flag = True print IPField.__dict__,'IPFIELD' obj = MainForm() #1__这里要注意,我要传入把当前对象传入进去,那么就是MainHandler就是self,自己传入进去,然后 obj.is_valid(self) if obj.is_valid(self): self.write('ok') else: self.write('error')
首先如果是post请求,那么就会执行函数post的内容, 在这里,首先打印出了IPfield的全部对象,
“print IPField.__dict__,'IPFIELD'”,
结果为“{'check_valid': <function check_valid at 0x0000000002EEF128>, '__module__': '__main__', 'REGULAR': '^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}$', '__dict__': <attribute '__dict__' of 'IPField' objects>, '__weakref__': <attribute '__weakref__' of 'IPField' objects>, '__doc__': None, '__init__': <function __init__ at 0x0000000002EEF0B8>} IPFIELD”,可以看到有一个check_vaild函数的内容等。
然后obj = MainForm() 初始化一个对象,然后执行obj.is_valid(self) 把 obj也就是自己传入了进去。
2.查看MainForm()函数,看看做了哪些操作
class MainForm(BaseForm): def __init__(self): # self.host = "(.*)" # self.ip = "^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$" # self.port = '(\d+)' # self.phone = '^1[3|4|5|8][0-9]\d{8}$' #对象里面封装了正则表达式和错误信息 self.ip = IPField(error_msg_dict={'required':"IP不能为空",'valid':'IP格式错误'},required=True) print self.__dict__,'MainForm'
这里只是封装了一个 self.IP的对象,然后打印出所有对象,
这里有一句self.ip = IPField(error_msg_dict={'required':"IP不能为空",'valid':'IP,那么这时 MainForm BaseForm 都有这么一个对象
{'ip': <__main__.IPField object at 0x0000000002F6BB00>} MainForm
并且MainForm继承了 BaseForm,那么这里没有is_valid()函数,那么就去父类中找。
3.IPField 和 BaseForm函数
class IPField(object): REGULAR = "^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$" def __init__(self,error_msg_dict=None, required=True): error_msg = {} if error_msg_dict: error_msg.update(error_msg_dict) #把这两个字段封装到IpField类里面 self.required = required self.error_msg = error_msg def check_valid(self,request,k): flag = True import re if self.required: if not request.get_argument(k): flag =False print self.error_msg['required'] else: if re.match(self.REGULAR,request.get_argument(k)): pass else: flag =False print self.error_msg['valid'] else: pass return flag class BaseForm(object): #2.__这里的self就是把自己MainForm传入进入,就是self,但是还有一个self(MainHandler)怎么办,那么他就是request #就是这里的self=MainForm request=MainHandler def is_valid(self,request): flag = True print self.__dict__,'BaseForm' for v in self.__dict__.values(): print v,'BaseForm------' for k,v in self.__dict__.items(): #这是v就是一个对象了 ret = v.check_valid(request,k) print ret,'ret---' if not ret: flag = False return flag
首先MainForm 封装了一个IPField函数,那么封装了什么呢?
封装了是否需要验证,以及报错的错误信息,然后 有一个check_valid函数,用来判断用户输入是否为空,是否合法。
现在要结合2个函数来查看,首先BaseForm函数is_valid(),
def is_valid(self,request): 传入了2个参数,一个是自己,一个是request,self=BaseForm,request=MainForm的对象,里面包含表单信息。
然后for k,v in self.__dict__.items(): 循环自己的所有对象,那么自己只有IPField这个对象,但是经过循环,里面的函数以及静态字段都可以使用了。
v.check_valid(request,k),每循环一次,就执行一次check_valid()函数,其实这里就这么一个对象,相当于循环一次取值了而已。
v.check_valid 传入了request,k 两个值, request就是MianForm用户提交的表单对象。
K就是key就是ip(必须和前端name保持一致,否则无法找到这个对象),然后接收到
v.check_valid返回的值,然后进行判断。
最后,v.check_valid里面做了什么呢?
最后里面其实很简单,传入了self,request,k ,然后判断用户输入的是否为空,是否合法。
本章比较饶,主要练习class的用法。
26.8 自定义Form表单验证(五)
本章纯讲解理论,如果后期有需要可以回来查看代码和视频。
#!/usr/bin/env python # -*- coding:utf-8 -*- import tornado.ioloop import tornado.web import re class Field(object): def __init__(self, error_msg_dict, required): self.id_valid = False self.value = None self.error = None self.name = None self.error_msg = error_msg_dict self.required = required def match(self, name, value): self.name = name if not self.required: self.id_valid = True self.value = value else: if not value: if self.error_msg.get('required', None): self.error = self.error_msg['required'] else: self.error = "%s is required" % name else: ret = re.match(self.REGULAR, value) if ret: self.id_valid = True self.value = ret.group() else: if self.error_msg.get('valid', None): self.error = self.error_msg['valid'] else: self.error = "%s is invalid" % name class IPField(Field): REGULAR = "^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$" def __init__(self, error_msg_dict=None, required=True): error_msg = {} # {'required': 'IP不能为空', 'valid': 'IP格式错误'} if error_msg_dict: error_msg.update(error_msg_dict) super(IPField, self).__init__(error_msg_dict=error_msg, required=required) class IntegerField(Field): REGULAR = "^\d+$" def __init__(self, error_msg_dict=None, required=True): error_msg = {'required': '数字不能为空', 'valid': '数字格式错误'} if error_msg_dict: error_msg.update(error_msg_dict) super(IntegerField, self).__init__(error_msg_dict=error_msg, required=required) class CheckBoxField(Field): def __init__(self, error_msg_dict=None, required=True): error_msg = {} # {'required': 'IP不能为空', 'valid': 'IP格式错误'} if error_msg_dict: error_msg.update(error_msg_dict) super(CheckBoxField, self).__init__(error_msg_dict=error_msg, required=required) def match(self, name, value): self.name = name if not self.required: self.id_valid = True self.value = value else: if not value: if self.error_msg.get('required', None): self.error = self.error_msg['required'] else: self.error = "%s is required" % name else: if isinstance(name, list): self.id_valid = True self.value = value else: if self.error_msg.get('valid', None): self.error = self.error_msg['valid'] else: self.error = "%s is invalid" % name class FileField(Field): REGULAR = "^(\w+\.pdf)|(\w+\.mp3)|(\w+\.py)$" def __init__(self, error_msg_dict=None, required=True): error_msg = {} # {'required': '数字不能为空', 'valid': '数字格式错误'} if error_msg_dict: error_msg.update(error_msg_dict) super(FileField, self).__init__(error_msg_dict=error_msg, required=required) def match(self, name, value): self.name = name self.value = [] if not self.required: self.id_valid = True self.value = value else: if not value: if self.error_msg.get('required', None): self.error = self.error_msg['required'] else: self.error = "%s is required" % name else: m = re.compile(self.REGULAR) if isinstance(value, list): for file_name in value: r = m.match(file_name) if r: self.value.append(r.group()) self.id_valid = True else: self.id_valid = False if self.error_msg.get('valid', None): self.error = self.error_msg['valid'] else: self.error = "%s is invalid" % name break else: if self.error_msg.get('valid', None): self.error = self.error_msg['valid'] else: self.error = "%s is invalid" % name def save(self, request, upload_path=""): file_metas = request.files[self.name] for meta in file_metas: file_name = meta['filename'] with open(file_name,'wb') as up: up.write(meta['body']) class Form(object): def __init__(self): self.value_dict = {} self.error_dict = {} self.valid_status = True def validate(self, request, depth=10, pre_key=""): self.initialize() self.__valid(self, request, depth, pre_key) def initialize(self): pass def __valid(self, form_obj, request, depth, pre_key): """ 验证用户表单请求的数据 :param form_obj: Form对象(Form派生类的对象) :param request: Http请求上下文(用于从请求中获取用户提交的值) :param depth: 对Form内容的深度的支持 :param pre_key: Html中name属性值的前缀(多层Form时,内部递归时设置,无需理会) :return: 是否验证通过,True:验证成功;False:验证失败 """ depth -= 1 if depth < 0: return None form_field_dict = form_obj.__dict__ for key, field_obj in form_field_dict.items(): print key,field_obj if isinstance(field_obj, Form) or isinstance(field_obj, Field): if isinstance(field_obj, Form): # 获取以key开头的所有的值,以参数的形式传至 self.__valid(field_obj, request, depth, key) continue if pre_key: key = "%s.%s" % (pre_key, key) if isinstance(field_obj, CheckBoxField): post_value = request.get_arguments(key, None) elif isinstance(field_obj, FileField): post_value = [] file_list = request.request.files.get(key, None) for file_item in file_list: post_value.append(file_item['filename']) else: post_value = request.get_argument(key, None) print post_value # 让提交的数据 和 定义的正则表达式进行匹配 field_obj.match(key, post_value) if field_obj.id_valid: self.value_dict[key] = field_obj.value else: self.error_dict[key] = field_obj.error self.valid_status = False class ListForm(object): def __init__(self, form_type): self.form_type = form_type self.valid_status = True self.value_dict = {} self.error_dict = {} def validate(self, request): name_list = request.request.arguments.keys() + request.request.files.keys() index = 0 flag = False while True: pre_key = "[%d]" % index for name in name_list: if name.startswith(pre_key): flag = True break if flag: form_obj = self.form_type() form_obj.validate(request, depth=10, pre_key="[%d]" % index) if form_obj.valid_status: self.value_dict[index] = form_obj.value_dict else: self.error_dict[index] = form_obj.error_dict self.valid_status = False else: break index += 1 flag = False class MainForm(Form): def __init__(self): # self.ip = IPField(required=True) # self.port = IntegerField(required=True) # self.new_ip = IPField(required=True) # self.second = SecondForm() self.fff = FileField(required=True) super(MainForm, self).__init__() # # class SecondForm(Form): # # def __init__(self): # self.ip = IPField(required=True) # self.new_ip = IPField(required=True) # # super(SecondForm, self).__init__() class MainHandler(tornado.web.RequestHandler): def get(self): self.render('index.html') def post(self, *args, **kwargs): # for i in dir(self.request): # print i # print self.request.arguments # print self.request.files # print self.request.query # name_list = self.request.arguments.keys() + self.request.files.keys() # print name_list # list_form = ListForm(MainForm) # list_form.validate(self) # # print list_form.valid_status # print list_form.value_dict # print list_form.error_dict # obj = MainForm() # obj.validate(self) # # print "验证结果:", obj.valid_status # print "符合验证结果:", obj.value_dict # print "错误信息:" # for key, item in obj.error_dict.items(): # print key,item # print self.get_arguments('favor'),type(self.get_arguments('favor')) # print self.get_argument('favor'),type(self.get_argument('favor')) # print type(self.get_argument('fff')),self.get_argument('fff') # print self.request.files # obj = MainForm() # obj.validate(self) # print obj.valid_status # print obj.value_dict # print obj.error_dict # print self.request,type(self.request) # obj.fff.save(self.request) # from tornado.httputil import HTTPServerRequest # name_list = self.request.arguments.keys() + self.request.files.keys() # print name_list # print self.request.files,type(self.request.files) # print len(self.request.files.get('fff')) # obj = MainForm() # obj.validate(self) # print obj.valid_status # print obj.value_dict # print obj.error_dict # obj.fff.save(self.request) self.write('ok') settings = { 'template_path': 'template', 'static_path': 'static', 'static_url_prefix': '/static/', 'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh', 'login_url': '/login' } application = tornado.web.Application([ (r"/index", MainHandler), ], **settings) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
26.9 自定义Form表单验证(六)
本章把上一章内容梳理了一遍。
主要还讲解了模型绑定,如果form表单django不能提交多行数据,但是可以生成列表然后自己去写,就是一些思想,本章讲解的很混乱。
26.10 Python发送Http请求的模块
本章主要实现liburl2和requests发送http请求的功能,但是只是简单演示了一下。
Urllib2 需要自己写方法。
Requests 什么都已经封装好了,可以直接拿过来用。
这里简单写一点。
Python标准库中提供了:urllib、urllib2、httplib等模块以供Http请求,但是,它的 API 太渣了。它是为另一个时代、另一个互联网所创建的。它需要巨量的工作,甚至包括各种方法覆盖,来完成最简单的任务。
首先是liburl2的方式,我们要自己写方法。
import urllib2 import json import cookielib def urllib2_request(url, method="GET", cookie="", headers={}, data=None): """ :param url: 要请求的url :param cookie: 请求方式,GET、POST、DELETE、PUT.. :param cookie: 要传入的cookie,cookie= 'k1=v1;k1=v2' :param headers: 发送数据时携带的请求头,headers = {'ContentType':'application/json; charset=UTF-8'} :param data: 要发送的数据GET方式需要传入参数,data={'d1': 'v1'} :return: 返回元祖,响应的字符串内容 和 cookiejar对象 对于cookiejar对象,可以使用for循环访问: for item in cookiejar: print item.name,item.value """ if data: data = json.dumps(data) cookie_jar = cookielib.CookieJar() handler = urllib2.HTTPCookieProcessor(cookie_jar) opener = urllib2.build_opener(handler) opener.addheaders.append(['Cookie', 'k1=v1;k1=v2']) request = urllib2.Request(url=url, data=data, headers=headers) request.get_method = lambda: method response = opener.open(request) origin = response.read() return origin, cookie_jar # GET result = urllib2_request('http://127.0.0.1:8001/index/', method="GET") # POST result = urllib2_request('http://127.0.0.1:8001/index/', method="POST", data= {'k1': 'v1'}) # PUT result = urllib2_request('http://127.0.0.1:8001/index/', method="PUT", data= {'k1': 'v1'})
Requests
GET请求
# 1、无参数实例 import requests ret = requests.get('https://github.com/timeline.json') print ret.url print ret.text # 2、有参数实例 import requests payload = {'key1': 'value1', 'key2': 'value2'} ret = requests.get("http://httpbin.org/get", params=payload) print ret.url print ret.text
POST请求
# 1、基本POST实例 import requests payload = {'key1': 'value1', 'key2': 'value2'} ret = requests.post("http://httpbin.org/post", data=payload) print ret.text # 2、发送请求头和数据实例 import requests import json url = 'https://api.github.com/some/endpoint' payload = {'some': 'data'} headers = {'content-type': 'application/json'} ret = requests.post(url, data=json.dumps(payload), headers=headers) print ret.text print ret.cookies
其他请求
requests.get(url, params=None, **kwargs) requests.post(url, data=None, json=None, **kwargs) requests.put(url, data=None, **kwargs) requests.head(url, **kwargs) requests.delete(url, **kwargs) requests.patch(url, data=None, **kwargs) requests.options(url, **kwargs) # 以上方法均是在此方法的基础上构建 requests.request(method, url, **kwargs)
26.11 实例:requests实现《破解》微信公众平台
本章没有微信公共号,若需要,回来的时候看视频。
26.12 实例:requests实现《破解》Web微信
26.13 Scrapy框架介绍
Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 其可以应用在数据挖掘,信息处理或存储历史数据等一系列的程序中。
其最初是为了页面抓取 (更确切来说, 网络抓取 )所设计的, 也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services ) 或者通用的网络爬虫。Scrapy用途广泛,可以用于数据挖掘、监测和自动化测试。
Scrapy 使用了 Twisted异步网络库来处理网络通讯。整体架构大致如下
Scrapy主要包括了以下组件:
- 引擎(Scrapy)
用来处理整个系统的数据流处理, 触发事务(框架核心) - 调度器(Scheduler)
用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址 - 下载器(Downloader)
用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的) - 爬虫(Spiders)
爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面 - 项目管道(Pipeline)
负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。 - 下载器中间件(Downloader Middlewares)
位于Scrapy引擎和下载器之间的框架,主要是处理Scrapy引擎与下载器之间的请求及响应。 - 爬虫中间件(Spider Middlewares)
介于Scrapy引擎和爬虫之间的框架,主要工作是处理蜘蛛的响应输入和请求输出。 - 调度中间件(Scheduler Middewares)
介于Scrapy引擎和调度之间的中间件,从Scrapy引擎发送到调度的请求和响应。
Scrapy运行流程大概如下:
1.引擎从调度器中取出一个链接(URL)用于接下来的抓取
2.引擎把URL封装成一个请求(Request)传给下载器
3.下载器把资源下载下来,并封装成应答包(Response)
4.爬虫解析Response
5.解析出实体(Item),则交给实体管道进行进一步的处理
6.解析出的是链接(URL),则把URL交给调度器等待抓取
一、安装
pip install Scrapy
注:windows平台需要依赖pywin32,请根据自己系统32/64位选择下载安装,https://sourceforge.net/projects/pywin32/
二、基本使用
1、创建项目
运行命令:
scrapy startproject your_project_name
自动创建目录:
project_name/ scrapy.cfg project_name/ __init__.py items.py pipelines.py settings.py spiders/ __init__.py
文件说明:
- scrapy.cfg 项目的配置信息,主要为Scrapy命令行工具提供一个基础的配置信息。(真正爬虫相关的配置信息在settings.py文件中)
- items.py 设置数据存储模板,用于结构化数据,如:Django的Model
- pipelines 数据处理行为,如:一般结构化的数据持久化
- settings.py 配置文件,如:递归的层数、并发数,延迟下载等
- spiders 爬虫目录,如:创建文件,编写爬虫规则
注意:一般创建爬虫文件时,以网站域名命名
26.14 实例:爬取校花照片
本章实例编写 从校花网爬去校花的图片并下载到本地,本章代码有详细解释,当然,如果想实现本章内容,需要对html 源码了解清晰。
这里来补充一下,看下源码结构。
进入project_name目录,运行命令
scrapy crawl xiaohuar
–nolog
以上代码将符合规则的页面中的图片保存在指定目录,并且在HTML源码中找到所有的其他 a 标签的href属性,从而“递归”的执行下去,直到所有的页面都被访问过为止。以上代码之所以可以进行“递归”的访问相关URL,关键在于parse方法使用了 yield Request对象。
注:可以修改settings.py 中的配置文件,以此来指定“递归”的层数,如:DEPTH_LIMIT = 1
26.15 Scrapy框架流程梳理-----------------
本章保留,后期复习,由于没有网站测试,后期补充。
27.L027
27.1 算法概要
本节内容
1.算法定义
2.时间复杂度
3.空间复杂度
4.常用算法实例
算法定义
算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。如果一个算法有缺陷,或不适合于某个问题,执行这个算法将不会解决这个问题。不同的算法可能用不同的时间、空间或效率来完成同样的任务。一个算法的优劣可以用空间复杂度与时间复杂度来衡量。
一个算法应该具有以下七个重要的特征:
①有穷性(Finiteness):算法的有穷性是指算法必须能在执行有限个步骤之后终止;
②确切性(Definiteness):算法的每一步骤必须有确切的定义;
③输入项(Input):一个算法有0个或多个输入,以刻画运算对象的初始情况,所谓0个输入是指算法本身定出了初始条件;
④输出项(Output):一个算法有一个或多个输出,以反映对输入数据加工后的结果。没 有输出的算法是毫无意义的;
⑤可行性(Effectiveness):算法中执行的任何计算步骤都是可以被分解为基本的可执行的操作步,即每个计算步都可以在有限时间内完成(也称之为有效性);
⑥高效性(High efficiency):执行速度快,占用资源少;
⑦健壮性(Robustness):对数据响应正确。
27.2 时间复杂度
前三章纯废话,主要讲解哪几种算法用的时间少,那种情况使用那种算法。
计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间,时间复杂度常用大O符号(大O符号(Big O notation)是用于描述函数渐进行为的数学符号。更确切地说,它是用另一个(通常更简单的)函数来描述一个函数数量级的渐近上界。在数学中,它一般用来刻画被截断的无穷级数尤其是渐近级数的剩余项;在计算机科学中,它在分析算法复杂性的方面非常有用。)表述,使用这种方式时,时间复杂度可被称为是渐近的,它考察当输入值大小趋近无穷时的情况。
大O,简而言之可以认为它的含义是“order of”(大约是)。
无穷大渐近
大O符号在分析算法效率的时候非常有用。举个例子,解决一个规模为 n 的问题所花费的时间(或者所需步骤的数目)可以被求得:T(n) = 4n^2 -
2n + 2。
当 n 增大时,n^2; 项将开始占主导地位,而其他各项可以被忽略——举例说明:当 n = 500,4n^2;
项是 2n 项的1000倍大,因此在大多数场合下,省略后者对表达式的值的影响将是可以忽略不计的。
数学表示扫盲贴 http://www.cnblogs.com/alex3714/articles/5910253.html
一、计算方法
1.一个算法执行所耗费的时间,从理论上是不能算出来的,必须上机运行测试才能知道。但我们不可能也没有必要对每个算法都上机测试,只需知道哪个算法花费的时间多,哪个算法花费的时间少就可以了。并且一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。
一个算法中的语句执行次数称为语句频度或时间频度。记为T(n)。
2.一般情况下,算法的基本操作重复执行的次数是模块n的某一个函数f(n),因此,算法的时间复杂度记做:T(n)=O(f(n))。随着模块n的增大,算法执行的时间的增长率和f(n)的增长率成正比,所以f(n)越小,算法的时间复杂度越低,算法的效率越高。
在计算时间复杂度的时候,先找出算法的基本操作,然后根据相应的各语句确定它的执行次数,再找出T(n)的同数量级(它的同数量级有以下:1,Log2n ,n ,nLog2n ,n的平方,n的三次方,2的n次方,n!),找出后,f(n)=该数量级,若T(n)/f(n)求极限可得到一常数c,则时间复杂度T(n)=O(f(n))。
3.常见的时间复杂度
按数量级递增排列,常见的时间复杂度有:
常数阶O(1), 对数阶O(log2n), 线性阶O(n), 线性对数阶O(nlog2n), 平方阶O(n^2), 立方阶O(n^3),..., k次方阶O(n^k), 指数阶O(2^n) 。
其中,
1.O(n),O(n^2), 立方阶O(n^3),..., k次方阶O(n^k) 为多项式阶时间复杂度,分别称为一阶时间复杂度,二阶时间复杂度。。。。
2.O(2^n),指数阶时间复杂度,该种不实用
3.对数阶O(log2n), 线性对数阶O(nlog2n),除了常数阶以外,该种效率最高
例:算法
for(i=1;i<=n;++i) { for(j=1;j<=n;++j) { c[ i ][ j ]=0; //该步骤属于基本操作 执行次数:n^2 for(k=1;k<=n;++k) c[ i ][ j ]+=a[ i ][ k ]*b[ k ][ j ]; //该步骤属于基本操作 执行次数:n^3 } }
则有 T(n)= n^2+n^3,根据上面括号里的同数量级,我们可以确定 n^3为T(n)的同数量级
则有f(n)= n^3,然后根据T(n)/f(n)求极限可得到常数c
则该算法的 时间复杂度:T(n)=O(n^3)
四、
定义:如果一个问题的规模是n,解这一问题的某一算法所需要的时间为T(n),它是n的某一函数 T(n)称为这一算法的“时间复杂性”。 当输入量n逐渐加大时,时间复杂性的极限情形称为算法的“渐近时间复杂性”。 我们常用大O表示法表示时间复杂性,注意它是某一个算法的时间复杂性。大O表示只是说有上界,由定义如果f(n)=O(n),那显然成立f(n)=O(n^2),它给你一个上界,但并不是上确界,但人们在表示的时候一般都习惯表示前者。 此外,一个问题本身也有它的复杂性,如果某个算法的复杂性到达了这个问题复杂性的下界,那就称这样的算法是最佳算法。 “大O记法”:在这种描述中使用的基本参数是 n,即问题实例的规模,把复杂性或运行时间表达为n的函数。这里的“O”表示量级 (order),比如说“二分检索是 O(logn)的”,也就是说它需要“通过logn量级的步骤去检索一个规模为n的数组”记法 O ( f(n) )表示当 n增大时,运行时间至多将以正比于 f(n)的速度增长。 这种渐进估计对算法的理论分析和大致比较是非常有价值的,但在实践中细节也可能造成差异。例如,一个低附加代价的O(n2)算法在n较小的情况下可能比一个高附加代价的 O(nlogn)算法运行得更快。当然,随着n足够大以后,具有较慢上升函数的算法必然工作得更快。 O(1) Temp=i;i=j;j=temp; 以上三条单个语句的频度均为1,该程序段的执行时间是一个与问题规模n无关的常数。算法的时间复杂度为常数阶,记作T(n)=O(1)。如果算法的执行时间不随着问题规模n的增加而增长,即使算法中有上千条语句,其执行时间也不过是一个较大的常数。此类算法的时间复杂度是O(1)。 O(n^2) 2.1. 交换i和j的内容 sum=0; (一次) for(i=1;i<=n;i++) (n次 ) for(j=1;j<=n;j++) (n^2次 ) sum++; (n^2次 ) 解:T(n)=2n^2+n+1 =O(n^2) 2.2. for (i=1;i<n;i++) { y=y+1; ① for (j=0;j<=(2*n);j++) x++; ② } 解: 语句1的频度是n-1 语句2的频度是(n-1)*(2n+1)=2n^2-n-1 f(n)=2n^2-n-1+(n-1)=2n^2-2 该程序的时间复杂度T(n)=O(n^2). O(n) 2.3. a=0; b=1; ① for (i=1;i<=n;i++) ② { s=a+b; ③ b=a; ④ a=s; ⑤ } 解:语句1的频度:2, 语句2的频度: n, 语句3的频度: n-1, 语句4的频度:n-1, 语句5的频度:n-1, T(n)=2+n+3(n-1)=4n-1=O(n). O(log2n ) 2.4. i=1; ① while (i<=n) i=i*2; ② 解: 语句1的频度是1, 设语句2的频度是f(n), 则:2^f(n)<=n;f(n)<=log2n 取最大值f(n)= log2n, T(n)=O(log2n ) O(n^3) 2.5. for(i=0;i<n;i++) { for(j=0;j<i;j++) { for(k=0;k<j;k++) x=x+2; } } 解:当i=m, j=k的时候,内层循环的次数为k当i=m时, j 可以取 0,1,...,m-1 , 所以这里最内循环共进行了0+1+...+m-1=(m-1)m/2次所以,i从0取到n, 则循环共进行了: 0+(1-1)*1/2+...+(n-1)n/2=n(n+1)(n-1)/6所以时间复杂度为O(n^3). 我们还应该区分算法的最坏情况的行为和期望行为。如快速排序的最 坏情况运行时间是 O(n^2),但期望时间是 O(nlogn)。通过每次都仔细 地选择基准值,我们有可能把平方情况 (即O(n^2)情况)的概率减小到几乎等于 0。在实际中,精心实现的快速排序一般都能以 (O(nlogn)时间运行。 下面是一些常用的记法: 访问数组中的元素是常数时间操作,或说O(1)操作。一个算法如 果能在每个步骤去掉一半数据元素,如二分检索,通常它就取 O(logn)时间。用strcmp比较两个具有n个字符的串需要O(n)时间。常规的矩阵乘算法是O(n^3),因为算出每个元素都需要将n对 元素相乘并加到一起,所有元素的个数是n^2。 指数时间算法通常来源于需要求出所有可能结果。例如,n个元 素的集合共有2n个子集,所以要求出所有子集的算法将是O(2n)的。指数算法一般说来是太复杂了,除非n的值非常小,因为,在 这个问题中增加一个元素就导致运行时间加倍。不幸的是,确实有许多问题 (如著名的“巡回售货员问题” ),到目前为止找到的算法都是指数的。如果我们真的遇到这种情况,通常应该用寻找近似最佳结果的算法替代之。
27.3 冒泡排序
冒泡排序原理就是2个相邻的值一直比较,每一个大循环结束,就可以选出最大的或者最小的,然后依次类推,有多少个数就循环多少个大循环。
冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。
它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端,故名。
data_set = [ 9,1,22,31,45,3,6,2,11 ] loop_count = 0 for j in range(len(data_set)): for i in range(len(data_set) - j- 1): # -1 是因为每次比对的都 是i 与i +1,不减1的话,最后一次对比会超出list 获取范围,-j是因为,每一次大loop就代表排序好了一个最大值,放在了列表最后面,下次loop就不用再运算已经排序好了的值 了 if data_set[i] > data_set[i+1]: #switch tmp = data_set[i] data_set[i] = data_set[i+1] data_set[i+1] = tmp loop_count +=1 print(data_set) print(data_set) print("loop times", loop_count)
27.4 选择排序
选择排序的原理就是大循环拿一个数来和后面所有的数比较,如果大或者小就更换,第一个大循环完成之后就不在比较它,从下一个数再次开始比较。
27.5 选择排序优化版
选择排序优化版,记住最大或者最小值的下标,但是值只在外层循环结束后更改
27.6 插入排序
本章很饶,但是代码在里面全部都有注释。
27.7 快速排序
快速排序非常的绕,后期时间长在回来查看,需要配合视频以及这里的笔记才能回忆,在代码里只有简单的注释,这里会有详细的排序过程。
例如这里有一个数组。
array = [86, 29, 86, 47, 96, 75, 3, 21, 16, 58]
我们从下标0 开始比较,生成一个变量 k= array[start]我们要跟这个数开始比较, 那么下标i=0,j=9,从右边开始比较,那么
[58, 29, 86, 47, 96, 75, 3, 21, 16, 86] i=0,j=9, 9<0,那么调换位置
J比较过一次了,那么就要从i开始比较,i的下标从1 到3都没有大于86的,到i=4,那么
结果就是
[58, 29, 86, 47, 86, 75, 3, 21, 16, 96] i=4,j=9 然后在从 j开始比较,就这样当i=j的时候就不在比较了,那么也列表肯定也是左边的逗比 86小,右边都比86大,然后在从中间开始分开,然后再次循环上述动作,直到比较完成为止。
本章长时间不看肯定会忘掉,视频在快速排序30分钟左右开始讲解。
本章排序要注意。
1. 不管是从i 移动比较还是从 j 移动比较,都是与 k=86比较,不管86换到哪个地方,这一轮比较里面都是和它比较并调换位置。
2. 从j开始比较,如果比k小的调换位置之后然后在从i开始比较,如果j没有找到比k小的,那么一直自身-1 找下去,直到满足条件或者跳出循环。
3.第一轮比较完成,我们要递归循环分成2组一直循环,就比如代码里面的
quick_sort(array,start,left_flag-1)
quick_sort(array,left_flag+1,end)
这里分组再次递归,并不是所有的时候是将这个列表等分,所以传入函数的值为left_flag -1 或者left_flag+1,因为有时候left_flag第一次替换的时候就满足i=j这个条件了,那时候left的下标肯定是最大的,因为数组越界问题,所以要减一或者加一,也可以理解,在n次递归的时候,最后一个值已经确定大小了,自己已经排序完毕,不需要在排序了。
下面是视频比较的笔记,这里就不删除了。
设要排序的数组是A[0]……A[N-1],首先任意选取一个数据(通常选用数组的第一个数)作为关键数据,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序。值得注意的是,快速排序不是一种稳定的排序算法,也就是说,多个相同的值的相对位置也许会在算法结束时产生变动
注:在待排序的文件中,若存在多个关键字相同的记录,经过排序后这些具有相同关键字的记录之间的相对次序保持不变,该排序方法是稳定的;若具有相同关键字的记录之间的相对次序发生改变,则称这种排序方法是不稳定的。
要注意的是,排序算法的稳定性是针对所有输入实例而言的。即在所有可能的输入实例中,只要有一个实例使得算法不满足稳定性要求,则该排序算法就是不稳定的。
排序演示
示例
假设用户输入了如下数组:
下标 |
0 |
1 |
2 |
3 |
4 |
5 |
数据 |
6 |
2 |
7 |
3 |
8 |
9 |
创建变量i=0(指向第一个数据), j=5(指向最后一个数据), k=6(赋值为第一个数据的值)。
我们要把所有比k小的数移动到k的左面,所以我们可以开始寻找比6小的数,从j开始,从右往左找,不断递减变量j的值,我们找到第一个下标3的数据比6小,于是把数据3移到下标0的位置,把下标0的数据6移到下标3,完成第一次比较:
下标 |
0 |
1 |
2 |
3 |
4 |
5 |
数据 |
3 |
2 |
7 |
6 |
8 |
9 |
i=0 j=3 k=6
接着,开始第二次比较,这次要变成找比k大的了,而且要从前往后找了。递加变量i,发现下标2的数据是第一个比k大的,于是用下标2的数据7和j指向的下标3的数据的6做交换,数据状态变成下表:
下标 |
0 |
1 |
2 |
3 |
4 |
5 |
数据 |
3 |
2 |
6 |
7 |
8 |
9 |
i=2 j=3 k=6
称上面两次比较为一个循环。
接着,再递减变量j,不断重复进行上面的循环比较。
在本例中,我们进行一次循环,就发现i和j“碰头”了:他们都指向了下标2。于是,第一遍比较结束。得到结果如下,凡是k(=6)左边的数都比它小,凡是k右边的数都比它大:
下标 |
0 |
1 |
2 |
3 |
4 |
5 |
数据 |
3 |
2 |
6 |
7 |
8 |
9 |
如果i和j没有碰头的话,就递加i找大的,还没有,就再递减j找小的,如此反复,不断循环。注意判断和寻找是同时进行的。
然后,对k两边的数据,再分组分别进行上述的过程,直到不能再分组为止。
注意:第一遍快速排序不会直接得到最终结果,只会把比k大和比k小的数分到k的两边。为了得到最后结果,需要再次对下标2两边的数组分别执行此步骤,然后再分解数组,直到数组不能再分解为止(只有一个数据),才能得到正确结果。
#_*_coding:utf-8_*_ __author__ = 'Alex Li' def quick_sort(array,left,right): ''' :param array: :param left: 列表的第一个索引 :param right: 列表最后一个元素的索引 :return: ''' if left >=right: return low = left high = right key = array[low] #第一个值 while low < high:#只要左右未遇见 while low < high and array[high] > key: #找到列表右边比key大的值 为止 high -= 1 #此时直接 把key(array[low]) 跟 比它大的array[high]进行交换 array[low] = array[high] array[high] = key while low < high and array[low] <= key : #找到key左边比key大的值,这里为何是<=而不是<呢?你要思考。。。 low += 1 #array[low] = #找到了左边比k大的值 ,把array[high](此时应该刚存成了key) 跟这个比key大的array[low]进行调换 array[high] = array[low] array[low] = key quick_sort(array,left,low-1) #最后用同样的方式对分出来的左边的小组进行同上的做法 quick_sort(array,low+1, right)#用同样的方式对分出来的右边的小组进行同上的做法 if __name__ == '__main__': array = [96,14,10,9,6,99,16,5,1,3,2,4,1,13,26,18,2,45,34,23,1,7,3,22,19,2] #array = [8,4,1, 14, 6, 2, 3, 9,5, 13, 7,1, 8,10, 12] print("before sort:", array) quick_sort(array,0,len(array)-1) print("-------final -------") print(array)
27.8 二叉树
27.8.1 树的特征和定义
树是一种重要的非线性数据结构,直观地看,它是数据元素(在树中称为结点)按分支关系组织起来的结构,很象自然界中的树那样。树结构在客观世界中广泛存在,如人类社会的族谱和各种社会组织机构都可用树形象表示。树在计算机领域中也得到广泛应用,如在编译源程序时,可用树表示源程序的语法结构。又如在数据库系统中,树型结构也是信息的重要组织形式之一。一切具有层次关系的问题都可用树来描述。
树(Tree)是元素的集合。我们先以比较直观的方式介绍树。下面的数据结构是一个树:
树有多个节点(node),用以储存元素。某些节点之间存在一定的关系,用连线表示,连线称为边(edge)。边的上端节点称为父节点,下端称为子节点。树像是一个不断分叉的树根。
每个节点可以有多个子节点(children),而该节点是相应子节点的父节点(parent)。比如说,3,5是6的子节点,6是3,5的父节点;1,8,7是3的子节点, 3是1,8,7的父节点。树有一个没有父节点的节点,称为根节点(root),如图中的6。没有子节点的节点称为叶节点(leaf),比如图中的1,8,9,5节点。从图中还可以看到,上面的树总共有4个层次,6位于第一层,9位于第四层。树中节点的最大层次被称为深度。也就是说,该树的深度(depth)为4。
如果我们从节点3开始向下看,而忽略其它部分。那么我们看到的是一个以节点3为根节点的树:
三角形代表一棵树
再进一步,如果我们定义孤立的一个节点也是一棵树的话,原来的树就可以表示为根节点和子树(subtree)的关系:
上述观察实际上给了我们一种严格的定义树的方法:
1. 树是元素的集合。
2. 该集合可以为空。这时树中没有元素,我们称树为空树 (empty tree)。
3. 如果该集合不为空,那么该集合有一个根节点,以及0个或者多个子树。根节点与它的子树的根节点用一个边(edge)相连。
上面的第三点是以递归的方式来定义树,也就是在定义树的过程中使用了树自身(子树)。由于树的递归特征,许多树相关的操作也可以方便的使用递归实现。我们将在后面看到。
27.8.2 树的实现
树的示意图已经给出了树的一种内存实现方式: 每个节点储存元素和多个指向子节点的指针。然而,子节点数目是不确定的。一个父节点可能有大量的子节点,而另一个父节点可能只有一个子节点,而树的增删节点操作会让子节点的数目发生进一步的变化。这种不确定性就可能带来大量的内存相关操作,并且容易造成内存的浪费。
一种经典的实现方式如下:
27.8.3 树的内存实现
拥有同一父节点的两个节点互为兄弟节点(sibling)。上图的实现方式中,每个节点包含有一个指针指向第一个子节点,并有另一个指针指向它的下一个兄弟节点。这样,我们就可以用统一的、确定的结构来表示每个节点。
计算机的文件系统是树的结构,比如Linux文件管理背景知识中所介绍的。在UNIX的文件系统中,每个文件(文件夹同样是一种文件),都可以看做是一个节点。非文件夹的文件被储存在叶节点。文件夹中有指向父节点和子节点的指针(在UNIX中,文件夹还包含一个指向自身的指针,这与我们上面见到的树有所区别)。在git中,也有类似的树状结构,用以表达整个文件系统的版本变化 (参考版本管理三国志)。
27.8.4 二叉树:
二叉树是由n(n≥0)个结点组成的有限集合、每个结点最多有两个子树的有序树。它或者是空集,或者是由一个根和称为左、右子树的两个不相交的二叉树组成。
特点:
(1)二叉树是有序树,即使只有一个子树,也必须区分左、右子树;
(2)二叉树的每个结点的度不能大于2,只能取0、1、2三者之一;
(3)二叉树中所有结点的形态有5种:空结点、无左右子树的结点、只有左子树的结点、只有右子树的结点和具有左右子树的结点。
二叉树(binary)是一种特殊的树。二叉树的每个节点最多只能有2个子节点:
二叉树
由于二叉树的子节点数目确定,所以可以直接采用上图方式在内存中实现。每个节点有一个左子节点(left children)和右子节点(right children)。左子节点是左子树的根节点,右子节点是右子树的根节点。
如果我们给二叉树加一个额外的条件,就可以得到一种被称作二叉搜索树(binary search tree)的特殊二叉树。二叉搜索树要求:每个节点都不比它左子树的任意元素小,而且不比它的右子树的任意元素大。
(如果我们假设树中没有重复的元素,那么上述要求可以写成:每个节点比它左子树的任意节点大,而且比它右子树的任意节点小)
二叉搜索树,注意树中元素的大小
二叉搜索树可以方便的实现搜索算法。在搜索元素x的时候,我们可以将x和根节点比较:
1. 如果x等于根节点,那么找到x,停止搜索 (终止条件)
2. 如果x小于根节点,那么搜索左子树
3. 如果x大于根节点,那么搜索右子树
二叉搜索树所需要进行的操作次数最多与树的深度相等。n个节点的二叉搜索树的深度最多为n,最少为log(n)。
27.8.5 二叉树的遍历
遍历即将树的所有结点访问且仅访问一次。按照根节点位置的不同分为前序遍历,中序遍历,后序遍历。
前序遍历:根节点->左子树->右子树
中序遍历:左子树->根节点->右子树
后序遍历:左子树->右子树->根节点
例如:求下面树的三种遍历
前序遍历:abdefgc
中序遍历:debgfac
后序遍历:edgfbca
27.8.6 二叉树的类型
(1)完全二叉树——若设二叉树的高度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树。
(2)满二叉树——除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树。
(3)平衡二叉树——平衡二叉树又被称为AVL树(区别于AVL算法),它是一棵二叉排序树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树
如何判断一棵树是完全二叉树?按照定义,
教材上的说法:一个深度为k,节点个数为 2^k - 1 的二叉树为满二叉树。这个概念很好理解,
就是一棵树,深度为k,并且没有空位。
首先对满二叉树按照广度优先遍历(从左到右)的顺序进行编号。
一颗深度为k二叉树,有n个节点,然后,也对这棵树进行编号,如果所有的编号都和满二叉树对应,那么这棵树是完全二叉树。
- 如何判断平衡二叉树?
(b)左边的图 左子数的高度为3,右子树的高度为1,相差超过1
(b)右边的图 -2的左子树高度为0 右子树的高度为2,相差超过1
27.8.7 二叉树遍历实现
class TreeNode(object): def __init__(self,data=0,left=0,right=0): self.data = data self.left = left self.right = right class BTree(object): def __init__(self,root=0): self.root = root def preOrder(self,treenode): if treenode is 0: return print(treenode.data) self.preOrder(treenode.left) self.preOrder(treenode.right) def inOrder(self,treenode): if treenode is 0: return self.inOrder(treenode.left) print(treenode.data) self.inOrder(treenode.right) def postOrder(self,treenode): if treenode is 0: return self.postOrder(treenode.left) self.postOrder(treenode.right) print(treenode.data) if __name__ == '__main__': n1 = TreeNode(data=1) n2 = TreeNode(2,n1,0) n3 = TreeNode(3) n4 = TreeNode(4) n5 = TreeNode(5,n3,n4) n6 = TreeNode(6,n2,n5) n7 = TreeNode(7,n6,0) n8 = TreeNode(8) root = TreeNode('root',n7,n8) bt = BTree(root) print("preOrder".center(50,'-')) print(bt.preOrder(bt.root)) print("inOrder".center(50,'-')) print (bt.inOrder(bt.root)) print("postOrder".center(50,'-')) print (bt.postOrder(bt.root))
28.L028
28.1 监控讨论
主要围绕zabbix讨论,前4节可略过。
监控系统需求讨论
1.可监控常用系统服务、应用、网络设备等
2.一台主机上可监控多个不同服务、不同服务的监控间隔可不同
3.同一个服务在不同主机上的监控间隔、报警阈值可不同
4.可以批量的给一批主机添加、删除、修改要监控的服务
5.告警级别:
-
- 不同的服务 因为业务重要程度不同,如果出了问题可以设置不同的报警级别
- 可以指定特定的服务或告警级别的事件通知给特定的用户
- 告警的升级设定
6.历史数据 的存储和优化
-
- 实现用最少的空间占用量存储最多的有效数据
- 如何做到1s中之内取出一台主机上所有服务的5年的监控数据?
7. 数据可视化,如何做出简洁美观的用户界面?
8.如何实现单机支持5000+机器监控需求?
9.采取何种通信方式?主动、被动?
10.如何实现监控服务器的水平扩展?
采用什么架构?
•Mysql
•主动通信? Snmp,wget…
•被动通信?Agent ---how to communicate with the monitor server
•Socket server –> Sockect client
•能否用现成的c/s架构? Rabbit mq, redis 订阅发布, http ?
采用HTTP好处
1.接口设计简单
2.容易水平扩展做分布式
3.Socket稳定成熟,省去较多的通信维护精力
Http特性:
1.短连接
2.无状态
3.安全认证
4.被动通信
28.2 架构设计
28.3 设计表结构
本章讲解设计表接口以及在后台admin的展示,本章如果后期需要回忆,最好的方式就是直接看代码 model和admin两个文件,(本章在这里贴出代码没有任何意义)
28.4 监控客户端的开发
28.5 监控客端获取监控配置信息
28.6 监控客户端开始监控并向服务器端汇报数据
28.7 服务器端接收监控数据并保存
28.8 历史数据如何优化保存
28.9 历史数据优化存储代码实现
29.L029
29.1 上节内容回顾及监控阈值判断思路