guobaoyuan
一 Python简介
1.1 Python介绍
python的创始人为吉多•范罗苏姆(Guido van Rossum)
胶水语言
1.2 Python应用场景
目前Python主要应用领域:
云计算: 云计算最火的语言, 典型应用OpenStack
WEB开发: 众多优秀的WEB框架,众多大型网站均为Python开发,Youtube, Dropbox, 豆瓣。。。, 典型WEB框架有Django
科学运算、人工智能: 典型库NumPy, SciPy, Matplotlib, Enthought librarys,pandas
系统运维: 运维人员必备语言
金融:量化交易,金融分析,在金融工程领域,Python不但在用,且用的最多,而且重要性逐年提高。原因:作为动态语言的Python,语言结构清晰简单,且库丰富,成熟稳定,科学计算和统计分析都很牛逼,生产效率远远高于c,c++,java,尤其擅长策略回测
图形GUI: PyQT, WxPython,TkInter

1.3 Python是一门怎样的语言
编程语言主要是几个角度进行分类,编译型和解释型、静态语言和动态语言、强类型定义语言和弱类型定义语言

1.3.1 编译和解释的区别是什么
编译器是把源程序的每一条语句都编译成机器语言,并保存成二进制文件,这样运行时计算机可以直接以机器语言来运行此程序,速度很快; 
而解释器则是只在执行程序时,才一条一条的解释成机器语言给计算机来执行,所以运行速度是不如编译后的程序运行的快的. 这是因为计算机不能直接认识并执行我们写的语句,它只能认识机器语言(是二进制的形式)
 

1.3.2 编译型vs解释型
(1) 编译型
    优点:编译器一般会有预编译的过程对代码进行优化。因为编译只做一次,运行时不需要编译,所以编译型语言的程序执行效率高。可以脱离语言环境独立运行。
    缺点:编译之后如果需要修改就需要整个模块重新编译。编译的时候根据对应的运行环境生成机器码,不同的操作系统之间移植就会有问题,需要根据运行的操作系统环境编译不同的可执行文件。
(2) 解释型
    优点:有良好的平台兼容性,在任何环境中都可以运行,前提是安装了解释器(虚拟机)。灵活,修改代码的时候直接修改就可以,可以快速部署,不用停机维护。
    缺点:每次运行的时候都要解释一遍,性能上不如编译型语言。


1.4 Python的优缺点
1.4.1 Python优点
   1 Python的定位是“优雅”、“明确”、“简单”,所以Python程序看上去总是简单易懂,初学者学Python,不但入门容易,而且将来深入下去,可以编写那些非常非常复杂的程序。
   2 开发效率非常高,Python有非常强大的第三方库,基本上你想通过计算机实现任何功能,Python官方库里都有相应的模块进行支持,直接下载调用后,在基础库的基础上再进行开发,大大降低开发周期,避免重复造轮子。
   3 高级语言————当你用Python语言编写程序的时候,你无需考虑诸如如何管理你的程序使用的内存一类的底层细节
   4 可移植性————由于它的开源本质,Python已经被移植在许多平台上(经过改动使它能够工 作在不同平台上)。如果你小心地避免使用依赖于
   5 系统的特性,那么你的所有Python程序无需修改就几乎可以在市场上所有的系统平台上运行
   6 可扩展性————如果你需要你的一段关键代码运行得更快或者希望某些算法不公开,你可以把你的部分程序用C或C++编写,然后在你的Python程序中使用它们。
   7 可嵌入性————你可以把Python嵌入你的C/C++程序,从而向你的程序用户提供脚本功能。

1.4.2 Python缺点
    1 速度慢,Python 的运行速度相比C语言确实慢很多,跟JAVA相比也要慢一些,因此这也是很多所谓的大牛不屑于使用Python的主要原因,但其实这里所指的运行速度慢在大多数情况下用户是无法直接感知到的,必须借助测试工具才能体现出来,比如你用C运一个程序花了0.01s,用Python是0.1s,这样C语言直接比Python快了10倍,算是非常夸张了,但是你是无法直接通过肉眼感知的,因为一个正常人所能感知的时间最小单位是0.15-0.4s左右,哈哈。其实在大多数情况下Python已经完全可以满足你对程序速度的要求,除非你要写对速度要求极高的搜索引擎等,这种情况下,当然还是建议你用C去实现的。
    2 代码不能加密,因为PYTHON是解释性语言,它的源码都是以名文形式存放的,不过我不认为这算是一个缺点,如果你的项目要求源代码必须是加密的,那你一开始就不应该用Python来去实现。
    3 线程不能利用多CPU问题,这是Python被人诟病最多的一个缺点,GIL即全局解释器锁(Global Interpreter Lock),是计算机程序设计语言解释器用于同步线程的工具,使得任何时刻仅有一个线程在执行,Python的线程是操作系统的原生线程。在Linux上为pthread,在Windows上为Win thread,完全由操作系统调度线程的执行。一个python解释器进程内有一条主线程,以及多条用户程序的执行线程。即使在多核CPU平台上,由于GIL的存在,所以禁止多线程的并行执行。关于这个问题的折衷解决方法,我们在以后线程和进程章节里再进行详细探讨。

1.5 Python解释器

     当我们编写Python代码时,我们得到的是一个包含Python代码的以.py为扩展名的文本文件。要运行代码,就需要Python解释器去执行.py文件。由于整个Python语言从规范到解释器都是开源的,所以理论上,只要水平够高,任何人都可以编写Python解释器来执行Python代码(当然难度很大)。事实上,确实存在多种Python解释器。

1.5.1 CPython
     当我们从Python官方网站下载并安装好Python 2.7后,我们就直接获得了一个官方版本的解释器:CPython。这个解释器是用C语言开发的,所以叫CPython。在命令行下运行python就是启动CPython解释器。
     CPython是使用最广的Python解释器。教程的所有代码也都在CPython下执行。

1.5.2 IPython
     IPython是基于CPython之上的一个交互式解释器,也就是说,IPython只是在交互方式上有所增强,但是执行Python代码的功能和CPython是完全一样的。好比很多国产浏览器虽然外观不同,但内核其实都是调用了IE。
     CPython用>>>作为提示符,而IPython用In [序号]:作为提示符。

1.5.3 PyPy

     PyPy是另一个Python解释器,它的目标是执行速度。PyPy采用JIT技术,对Python代码进行动态编译(注意不是解释),所以可以显著提高Python代码的执行速度。
     绝大部分Python代码都可以在PyPy下运行,但是PyPy和CPython有一些是不同的,这就导致相同的Python代码在两种解释器下执行可能会有不同的结果。如果你的代码要放到PyPy下执行,就需要了解PyPy和CPython的不同点。

1.5.4 Jython
    Jython是运行在Java平台上的Python解释器,可以直接把Python代码编译成Java字节码执行。

1.5.5 IronPython
    IronPython和Jython类似,只不过IronPython是运行在微软.Net平台上的Python解释器,可以直接把Python代码编译成.Net的字节码。

1.5.6 小结
     Python的解释器很多,但使用最广泛的还是CPython。如果要和Java或.Net平台交互,最好的办法不是用Jython或IronPython,而是通过网络调用来交互,确保各程序之间的独立性。

二 Python基础

2.1 变量
2.1.1 变量是什么 
    变量是个容器
 做什么用
存储数据到内存
 怎么用

2.1.2 变量定义的规则
变量名只能是 字母、数字或下划线的任意组合
变量名的第一个字符不能是数字
以下关键字不能声明为变量名
[\'and\', \'as\', \'assert\', \'break\', \'class\', \'continue\', \'def\', \'del\', \'elif\', \'else\', \'except\', \'exec\', \'finally\', \'for\', \'from\', \'global\', \'if\', \'import\', \'in\', \'is\', \'lambda\', \'not\', \'or\', \'pass\', \'print\', \'raise\', \'return\', \'try\', \'while\', \'with\', \'yield\']
2.1.3 变量书写方法
 驼峰体 : TrafficCost
 下划线 : traffic_cost     python官方推荐,多用
 大写的一般是常量: AGE_OF_OLDBOY 

2.1.4 变量在内存中的赋值实质
 
 
name= ALEX LI
name2 =alex li

name重新复制时会重新开辟内存空间,而不会在原来值的内存空间直接赋值

#python有自动的内存回收机制,
#定时在内存中扫描,查看那些内存是没有引用关系的,清理这些内存


2.2 交互程序举例
2.2.1 用户输入

name=input("please input your name:")
age=input("please input your age:")
print(name,age)

2.2.2 Getpass隐藏密码

import getpass
username=input("input your username:")
password=getpass.getpass("input your password:")
print("username:",username)
print("password:",password)

2.2.3 If语句判断用户和密码

import getpass
username=input("input your username:")
password=getpass.getpass("input your password:")
if username == "wenyanjie" and password =="123":
    print("Welcome !")
else:
    print("wrong username or password")

2.2.4 猜数字程序
Int函数,type函数,if多条件语句
age=25
age_guess=int(input("please guess age:"))
print(type(age_guess))
if age_guess > age:
    print("age_guess is big,try to smaller")
elif age_guess < age :
    print("age_guess is small,try to bigger")
else:
    print("you\'re right!")


2.2.5 作业1 猜年龄,可以让用户最多猜三次
age=25
for i in range(3):
    print(("第 %d 次:") % (i+1))
    age_guess = int(input("Please input your guess age:"))
    if age_guess > age :
        print("age_guess is big,try to smaller!")
    elif age_guess < age:
        print("age_guess is small,try to bigger!")
    else:
        print("you\'re right!....")
        break
if i == 2:
    print("3 times is gone ")


2.2.6 作业2猜年龄,每隔3次,问他一下,还想不想继续玩,y,n
age=24
i=1
while True:
    if i!=1 and i % 3 == 1 :
        mess=input(" 3 times is gone, Are you continue(y/n)?")
        if mess == "y":
            print("继续游戏")
        elif mess == "n":
            print("谢谢你的参与")
            break
        else:
            print("输入错误,退出")
            break
    print(("第 %d 次:") % (i))
    i=i+1
    age_guess = int(input("Please input your guess age:"))
    if age_guess > age :
        print("age_guess is big,try to smaller!")
    elif age_guess < age:
        print("age_guess is small,try to bigger!")
    else:
        print("you\'re right!....")
        break


2.2.7 作业3 编写登陆接口
输入用户名密码
认证成功后显示欢迎信息
输错三次后锁定

i=0
user_list=[]
pass_list=[]
count_list=[]
while True:
    lock = 0
    username = input("please input username:")
    password = input("please input password:")
    f = open("username_lock.txt", \'r\',encoding= \'utf-8\')
    for line in f.readlines():
        if line == username:
            lock = 1
    f.close()
    print(lock)
    if lock != 1 and username in user_list:
        count_list[user_list.index(username)] = count_list[user_list.index(username)] + 1
        print(count_list)

    if lock != 1 and username not in user_list:
        user_list.append(username)
        print(user_list)
        pass_list.append("123456")
        print(pass_list)
        count_list.append(1)
        print(count_list)

    print(lock)
    if lock == 1:
        print("this username is locking")
    elif username in user_list and password == pass_list[user_list.index(username)]:
        print("Login success!")
        break
    else:
        print("username or password is wrong!")
        if count_list[user_list.index(username)] >= 3:
            print("3 times is gone,this username is locking")
            f = open("username_lock.txt", \'a\',encoding= \'utf-8\')
            f.write(\'\n\'+username)
            f.close()
    if i % 3 == 2:
        mess = input(" 3 times is gone, Are you continue(y/n)?")
        if mess == "y":
            print("继续登录")
        elif mess == "n":
            print("拜拜")
            break
        else:
            print("输入错误,退出")
            break
    i=i+1




2.3 缩进
1 每行程序必须顶头
2 同一级代码顶头位置必须一致
3 官方建议缩进4个空格

2.4 注释
单行注视:# 被注释内容
多行注释:""" 被注释内容 """  实质是:将被注释内容变成字符串

在PyCharm中ctrl+/ 是快速加多行注释
在python中单引号和双引号没有任何区别,只是用于单行
三个单引号或双引号
2.5 字符编码
Chr( ) 填写数字,对应的是ascll码上的内容
Ord(\'’)填写字符串,对应的是ascll码上的位置
二进制=bit =8bits
8bits=1Byte=1字节
1024Bytes=1KB
1024KB=1MB=100万字节 =100万字符=1兆
1024MB=1GB
1024GB=1TB
1024TB=1PB

1个二进制位是计算机里的最小表示单元
1个字节是计算机里最小的存储单位

ASCII 256,每一个字符占8位
GB2312  1980 

unicode万国码
  Utf-32 占 4字节
  Utf-16 占 2字节
  Utf-8 = 8bits 可变长编码 英文占一个字节,所有的中文占3个字节,欧洲文字占2个字节

 



2.6 字符编码的转化

Python2上例子:
 

Python3上例子:

 


 

Python2 需要执行编码,和编码转化
Python3 优化,内存中都是unicode编码,针对各种不同的输出编码都可自动转化
 
 
 
 



2.6 print



2.7 冒号

在python中,冒号(:)用来标识语句块的开始,快中的每一个语句都是缩进的(缩进量相同)。当回退到和已经闭合的块一样的缩进量时,就表示当前块已经结束。

2.8 id方法(输出数据的内存地址)
输出数据的唯一地址
>>> name=[1,2,[\'wen\',\'yan\',\'jie\']]
>>> id(name)
139674108695616
>>> id(name[0])
39640168
>>> id(name[2])
139674108697920
>>> id(name[2][0])
139674306668704

2.9 dir与help (获取该变量的所有方法)

name=(\'wen\',\'yan\')       
help(name)
dir(name)   可以获取当前对象或者模块的方法与属性


2.10 查看方法是否需要参数的方法

在pycharm指定方法名时,会有显示使用信息。 Self就是没有参数,有x等变量的就是需要传参数的。
 

 


2.11 exit函数
Exit退出整个程序

一 查看数据类型

data1 = 123 #数字不能加双引号,加双引号后默认会当做字符类型 
data2 = "hello" 
data3 = ["tom","joy"] 
data4 = ("tom","joy") 
data5 = { "name":"tom" "age":18 } 
print(type(data1)) 
print(type(data2)) 
print(type(data3)) 
print(type(data4)) 
print(type(data5))
print(type(True))
打印结果为:
[root@bogon data_type]# ./type.py 
<type \'int\'> 
<type \'str\'> 
<type \'list\'> 
<type \'tuple\'> 
<type \'dict\'>
<type \'bool\'>

还有betys类型
二 数字int
2.1 数字运算
n1 = 123 
n2 = 456 
n3 = 789 
print(n1+n2+n3) #就相当于数学上的n1+n2+n3 
print(n1.__add__(n2).__add__(n3)) #实际上上一步就是调用的这个方法


2.2 bit_length()
Bit_length:获取数字可表示的二进制最短位数
n4 = 4 
print(n4.bit_length()) #4转换为二进制后为00000100
打印结果为:
3


2.3 八进制

oct  将数字转化为八进制的函数 
 

2.4 十六进制
计算机内存交互用的是16进制 
hex() 将数字转化为十六进制

16进制详解在博客
 


三  字符串str
(通用序列操作)
3.1 索引
序列的所有元素都是有编号的,从0开始递增。这些元素可以通过编号分别访问。
>>>greeting=’Hello’
>>>greeting[0]
‘H’
使用负数索引,Python会从右边(即最后1个元素)开始计数
>>>greeting[-1]
‘o’

循环显示字符串内的每个元素
s20="hello tom,you is DSB"
start=0
while start < len(s20):
    temp=s20[start]
    print(temp)
start+=1

s="alex"
for item in s:
    print(item)


3.2 切片

分片操作可以访问一定范围内的元素。分片通过冒号(:)隔开的两个索引来实现
>>>tag=’<a href=”http://www.python.org”>Python web site</a>’
>>>tag[9:30]
‘http://www.python.org’’
>>>tag[32:-4]
‘Python web site’
只要分片中最左边的索引比它右边的晚出现在序列中,结果就是一个空的序列
>>>tag[32:4]

分片空置结尾索引,可以得到直到结尾的序列元素;分片空置开始索引,可以得到从开头到结尾索引的序列
>>>tag[-4:]
</a>

步长:在普通的分片中,步长是1。分片操作就是按照这个步长逐个遍历序列的元素,然后返回开始和结束点之间的所有元素。
>>>tag[9:30:1]
‘http://www.python.org’’
>>>tag[9:30:2]
‘ht:/w.yhnog’’

3.3 +序列相加
使用加运算进行序列连接操作
>>> "Hello, "+"world!"
\'Hello, world!\'
>>> x="Hello, "
>>> y="world!"
>>> x+y
\'Hello, world!\'

同一类型的数据类型可以进行+操作,例:整数+整数,字符+字符
字符不能与整形进行计算,比较。不过可以转换操作数的类型。
3.4 *乘法
用数字x乘以一个序列会生成新的序列
>>> x="Hello, "
>>> x * 3
\'Hello, Hello, Hello, \'


3.5 in成员资格
为了检查一个值是否在序列中,可以使用in运算符
>>> permissions=\'rw\'
>>> \'w\' in permissions
True
>>> \'x\' in permissions
False

3.6 len长度,max最大值和min最小值
 内建函数len,min,max
len()函数返回序列中所包含元素的数量
min()函数返回序列中的最小的元素
max()函数返回序列中的最大的元素
>>> x="Hello"
>>> max(x)
\'o\'
>>> min(x)
\'H\'
>>> len(x)
5


字符串格式化输出
name=input("name:")
age=input("age:")
job=input("job:")
salary=input("salary:")
info=\'\'\'
---------info of %s-------
NAME: %s
AGE : %d
JOB : %s
SALARY : %fRMB
------------end-----------
\'\'\' % (name,name,int(age),job,int(salary))
print(info)

>>> msg="my name is %s,i am %s years old!"
>>> msg
\'my name is %s,i am %s years old!\'
>>> msg % (\'wen\',25)
\'my name is wen,i am 25 years old!\'




(字符串特殊操作)
3.7 capitalize(首字母大写)
Str capitalize 
>>> a1="hello"
>>> ret1=a1.capitalize()
>>> print ret1
Hello

3.8 * center(字符串居中)
字符串居中,center括号内,第一个字段为长度,第二个字段单引号内指定两边的填充符
>>> a2="hello"
>>> ret2=a2.center(20,\'*\')     #20表示一个20个字符的字符串;*表示两边不够,用*填充
>>> print ret2
*******hello********


3.9 * count(子序列计数)
count子序列的个数,统计count后括号内字符串出现的次数,后面不加参数表示查询全部。
变量内每个字符串称为子序列,可以指定长度查询,0,表示从第一个开始查询,4表示长度,从第一个开始到第四个,空格也算
>>> a3="this is world"
>>> ret3=a3.count("is",0,4)
>>> print(ret3)
1


3.10 * endswith(是否是指定字符串结尾)
查看是否为指定字符结尾,是返回True,不是返回False,同样可以指定子序列
>>> temp1="hello"
>>> print(temp1.endswith("e"))  #默认是查看最后一位
False
>>> print(temp1.endswith("e",0,2))  #查看第2段是否是e
True
>>> print(temp1.endswith("lo"))
True

3.11 expandtabs(将tab换成空格)
Expandtabs将tab换成空格,一个tab转换为8个空格,也可以指定转换为多少空格
>>> content="hello\t999"     # \t表示tab键
>>> print(content)
hello	999
>>> print(content.expandtabs())    #默认把tab键转换为8个空格
hello   999
>>> print(content.expandtabs(20))   #把tab键转换为20个空格
hello               999


3.12 * find(寻找子序列的位置)
Find 寻找子序列的位置,子序列是自定义的,可以自定义一个或多个字符串,空格也算
会返回第1个值的索引,找不到会返回-1
>>> s="tom hello"
>>> print(s.find("l"))
6
>>> print(s.find("ll"))
6


3.13 format(字符串的格式化)
#{0}为占位符
>>> s1="hello {0},age {1}"     #{0}第一个占位符、{1}第二个占位符
>>> print(s1)
hello {0},age {1}
>>> new1=s1.format(\'tom\',19)    #tom赋值给第一个占位符,19赋值给第二个占位符
>>> print(new1)
hello tom,age 19

>>> s1="hello {name},age {age}" 
>>> print(s1)
hello {0},age {1}
>>> new1=s1.format(name=\'tom\',age=19) 
>>> print(new1)
hello tom,age 19


 
 


3.14 * index(寻找子序列)
Index寻找子序列,没找到会报错,类似于find,使用find就可以
 


3.15 isalnum(判断字符是否是字母和数字)
Isalnunm判断字符是否是字母和数字,是返回True,不是返回False
>>> s2="tom123hello"
>>> print(s2.isalnum())
True
>>> s2=" \;\'"
>>> print(s2.isalnum())
False



3.16 isalpha(判断字符是否全部是字母)
Isalpha判断字符是否全部是字母
>>> s3="hello"
>>> print(s3.isalpha())
True
>>> s3="hello123"
>>> print(s3.isalpha())
False


3.17 isdigit(检测是否都是数字)
isdigit检测是否都是数字
>>> s4="1234"
>>> print(s4.isdigit())
True

3.18 islower(检查是否全部是小写)
islower检查是否全部是小写
>>> s5="hello"
>>> print(s5.islower())
True

3.19 isspace(判断是否是空格)
isspace判断是否是空格
>>> s6="   "
>>> print(s6.isspace())
True

3.20 istitle(判断是否是标题)
Istitle判断是否是标题,每个单词的首字母为大写
>>> s7="The School"
>>> print(s7.istitle())
True

3.21 isupper(判断是否全部是大写)
isupper判断是否全部是大写
>>> s8="HELLO"
>>> print(s8.isupper())
True


3.22 * join(连接序列中的元素)
Join可以用指定连接符将字符串连接起来,可以不加,不加时连接符为空。
注意:需要连接的序列的元素必须是字符串,不能是数字
>>> li=["tom","eric"]
>>> yz=("joy","hello")
>>> s9="_".join(li)
>>> print(s9)
tom_eric
>>> s10="@".join(yz)
>>> print(s10)
joy@hello

 
3.23 ljust(内容左对齐)
Ljust内容左对齐,右侧填充,也可以指定宽度
>>> s11="hello"
>>> print(s11.ljust(20,"*"))
hello***************


3.24 * lower(把字符串变小写)
lower把字符串变小写
>>> s12="HELLO"
>>> print(s12.lower())
hello


3.25 lstrip(移除左边的空格)
lstrip移除左边的空格  还可以指定移除指定内容
>>> s3="   hello"
>>> print(s3.lstrip())
Hello

 
3.26 rstrip(移除右边的空格)
rstrip移除右边的空格
>>> s4="hello    "
>>> print(s4.rstrip())
hello

3.27 * strip(左右两边的空格全部去掉)
strip左右两边的空格(\n,\t)全部去掉
>>> s15="  hello   \t\n"
>>> print(s15.strip())
hello

可以加参数:
s= \'**hello,world!**\'
s=s.strip("*")
print(s)
输出结果为:
hello,world!

3.28 partition(指定分割符)
partition指定的分隔符,分割符会保留
>>> s16="hello wen python"
>>> fen=s16.partition("wen")
>>> print(fen)
(\'hello \', \'wen\', \' python\')

3.29 * replace(替换指定字符)
replace替换指定字符,可以指定范围
Replace方法返回某字符串的所有匹配项均被替换之后得到的字符串。类似于文本编辑器的”查找并替换”
>>> s17="hello wen python"
>>> temp17=s17.replace("wen","wenyanjie")
>>> print(temp17)
hello wenyanjie python
>>> temp17=s17.replace("wen","wenyanjie",2)  #替换几次

3.30 rfind(从右到左寻找)
 



3.31 rjust(右对齐)



3.32 * split(将字符串分割成列表)
split, 指定分隔符分割字符串,将分隔符去除,显示其他部分成列表。默认分割符是空格,且会在结果列表中没有分割符出现。
>>> s18="hellohello"
>>> print(s18.split())
[\'hellohello\']
>>> print(s18.split("e"))
[\'h\', \'lloh\', \'llo\']

3.33 rsplit(从右到左分割)
Rsplit 从右到左分割


3.34 * startswith(是否是指定字符串开头)
startswith判断是否是某个字符或字符串开头,相对于endswith



3.35 swapcase(大小写互换)
 


3.36 title(字符串变换成标题)
title变换成标题,即把字符串变为首字母大写



3.37 * upper(全变大写)

返回小写字母转为大写字母的字符串
name="aleX"
print(name.upper())
输出结果:
ALEX


3.38 translate (字符翻译)

 
 


四  列表list
(通用序列操作)
3.1 索引
序列的所有元素都是有编号的,从0开始递增。这些元素可以通过编号分别访问。
>>> lis=[\'H\', \'e\', \'l\', \'l\', \'o\']
>>> lis[0]
\'H\'
>>>name_list=["eirc","alex","tony"]
>>>name_list[0]
eirc
使用负数索引,Python会从右边(即最后1个元素)开始计数
>>> lis[-1]
\'o\'

循环显示列表内的每个元素
lis=[\'H\', \'e\', \'l\', \'l\', \'o\']
start=0
while start<len(lis):
    temp=lis[start]
    print(temp)
start+=1

s=[\'H\', \'e\', \'l\', \'l\', \'o\']
for item in s:
    print(item)
for item in range(0,len(s)):
    print(s[item])


赋值

 

3.2 分片
分片操作可以访问一定范围内的元素。分片通过冒号(:)隔开的两个索引来实现
name_list=["eirc","alex","tony"]
>>> name_list[0:2]
[\'eirc\', \'alex\']
只要分片中最左边的索引比它右边的晚出现在序列中,结果就是一个空的序列
>>> name_list[3:2]
[]
分片空置结尾索引,可以得到直到结尾的序列元素;分片空置开始索引,可以得到从开头到结尾索引的序列
>>> name_list[1:]
[\'alex\', \'tony\']

步长:在普通的分片中,步长是1。分片操作就是按照这个步长逐个遍历序列的元素,然后返回开始和结束点之间的所有元素。
>>> name_list[0::1]
[\'eirc\', \'alex\', \'tony\']
>>> name_list[0::2]
[\'eirc\', \'tony\']

3.3 +序列相加
使用加运算进行序列连接操作
>>> name_list=["eirc","alex","tony"]
>>> name2_list=["wen","yan","jie"]
>>> name_list+name2_list
[\'eirc\', \'alex\', \'tony\', \'wen\', \'yan\', \'jie\']

3.4 *乘法
用数字x乘以一个序列会生成新的序列
>>> name_list=["eirc","alex","tony"]
>>> name_list*3
[\'eirc\', \'alex\', \'tony\', \'eirc\', \'alex\', \'tony\', \'eirc\', \'alex\', \'tony\']


3.5 in成员资格
为了检查一个值是否在序列中,可以使用in运算符
>>> subject=["$$$","Get","rich","now!!$$$"]
>>> "$$$" in subject 
True
>>> "wen" in subject 
False


(列表特殊操作)
3.6 append(在列表末尾追加元素)
append追加,在列表末尾插入指定元素
>>> name_list=["erix","egon","tony","tom"]
>>> name_list.append("server")
>>> print(name_list)
[\'erix\', \'egon\', \'tony\', \'tom\', \'server\']


3.7 count(统计元素在列表中出现的次数)
count统计元素在列表中出现的次数
>>> name_list
[\'erix\', \'egon\', \'tony\', \'tom\', \'server\', \'server\']
>>> print(name_list.count("server"))
2


3.8 extend(把整个列表插入到另一个列表内)
extend扩展插入,把整个列表插入到另一个列表内
>>> name_list=["erix","egon","tony","tom"]
>>> age_list=[\'14\',\'15\',\'16\',\'17\']
>>> name_list.extend(age_list)
>>> print(name_list)
[\'erix\', \'egon\', \'tony\', \'tom\', \'14\', \'15\', \'16\', \'17\']

3.9 index(获取元素在列表中的索引)
Index方法用于从列表中找出某个值第一个匹配项的索引位置。当index没有找到元素索引时,会触发异常
name_list=["eric","egon","tony","tom"]
print(name_list.index(\'egon\'))
print(name_list.index(\'egon2\'))
运行结果:
1
Traceback (most recent call last):
  File "C:/python_fullstack_wen/day13/test.py", line 17, in <module>
    print(name_list.index(\'egon2\'))
ValueError: \'egon2\' is not in list


3.10 inside插入(将元素插入到列表中)
insert方法用于将对象插入到列表中,在指定的位置上

name_list=["eric","egon","tony","tom"]
name_list.insert(3,"123")
print(name_list)
输出结果为:
[\'eric\', \'egon\', \'tony\', \'123\', \'tom\']


3.11 pop(删除列表中的一个元素,返回其值)
Pop删除列表中的一个元素(默认是最后一个元素),并且返回该元素的值。
Pop的参数是索引号,不是值内容
>>> name_list=["1","2","3","4"]
>>> print(name_list.pop())
4
>>> print(name_list)
[\'1\', \'2\', \'3\']
>>> print(name_list.pop(0))
1
>>> print(name_list)
[\'2\', \'3\']


3.12 remove(移除列表中的元素)
Remove移除列表中的元素,只移除从左边找到的第一个元素
name_list=["1","2","3","3","4"]
name_list.remove("3")
print(name_list)
输出结果:
[\'1\', \'2\', \'3\', \'4\']


3.13 reverse(反转修改列表)
Reverse,反转,将列表从右往左显示并这样修改列表本身
name_list=["1","2","3","4"]
name_list.reverse()
print(name_list)
输出结果:
[\'4\', \'3\', \'2\', \'1\']

3.14 sort(对列表进行原位置排序,根据元素首字母)
Sort方法用于在原位置对列表进行排序。在原位置上修改意味着改变了原来的列表,返回空值。
name_list=["erix","egon","tony","tom"]
name_list.sort()
print(name_list)
输出结果:
[\'egon\', \'erix\', \'tom\', \'tony\']

如果要保持原位置的列表不变,还需要进行排序。使用sorted函数
>>> x=[4,6,2,1,7,9]
>>> y=sorted(x)
>>> print(y)
[1, 2, 4, 6, 7, 9]
>>> print(x)
[4, 6, 2, 1, 7, 9]

3.15 del(删除指定索引位置的元素)
Del 删除指定索引位置的元素
name_list=["erix","egon","tony","tom"]
del name_list[1]
print(name_list)
输出结果:
[\'erix\', \'tony\', \'tom\']


3.16 copy(列表的浅复制和深复制)
copy方法返回一个具有相同值的新列表(这个方法实现的是浅复制,只复制父对象,不会复制对象的内部的子对象)
deepcopy方法深复制,拷贝对象及其子对象

import copy
a=[1,2,3,4,[\'a\',\'b\']]  #原始对象
b=a  #赋值,传对象的引用
c=copy.copy(a)  #对象拷贝,浅拷贝
d=copy.deepcopy(a) #对象拷贝,深拷贝

a.append(5)  #修改对象a
a[4].append(\'c\') #修改对象a中的[\'a\',\'b\']数组对象

print(\'a=\',a) 
print(\'b=\',b)
print(\'c=\',c)
print(\'d=\',d)
输出结果为:
a= [1, 2, 3, 4, [\'a\', \'b\', \'c\'], 5]     #原始对象变化后
b= [1, 2, 3, 4, [\'a\', \'b\', \'c\'], 5]     #赋值,会随着原始对象变化
c= [1, 2, 3, 4, [\'a\', \'b\', \'c\']]       #浅拷贝,对父对象的子对象没有复制,会随原始数据变化
d= [1, 2, 3, 4, [\'a\', \'b\']]          #深拷贝


3.17 list函数

将序列转化成列表类型



四 元组tuple
元组和列表类似,用小括号括起,用发基本相同,只是,元组是无法修改的,而列表是可以的

 

 

 



五 字典 dict
   字典是一种通过名字来引用值的数据结构。这种数据结构称为映射。字典是Python中唯一的内建的映射类型。字典中的值并没有特殊的顺序,但是都存储在一个特定的键(Key)下。键可以是数字、字符甚至是元组。

5.1 创建字典  
   字典由多个键及其对应的值构成的键--值对组成(也把键--值对称为项)。每个键和他的值之间用冒号(:),项之间用逗号(,)隔开,而整个字典是由一对大括号起来。空字典(不包括任何项)由两个大括号组成{} 。 字典中的键是唯一的(其他类型的映射也是如此),而值不唯一
>>> phone={\'Alice\':\'2341\',\'Beth\':\'5678\',\'Cecil\':\'2568\'}
>>> print(phone)
{\'Beth\': \'5678\', \'Alice\': \'2341\', \'Cecil\': \'2568\'}
 

5.2 dict(通过序列创建字典)
dict函数通过其他映射(比如其他字典)或者(键,值)对的序列建立字典
>>> items=[(\'name\',\'Wen\'),(\'age\',42)]
>>> print(items)
[(\'name\', \'Wen\'), (\'age\', 42)]
>>> d=dict(items)
>>> print(d)
{\'age\': 42, \'name\': \'Wen\'}
Dict函数通过关键字参数创建字典:
>>> d=dict(name=\'Wen\',age=42)
>>> print(d)
{\'age\': 42, \'name\': \'Wen\'}


5.3 len(字典键--值对的数量)
len(d)返回d字典中的项(键--值对)的数量
>>> print(d)
{\'age\': 42, \'name\': \'Wen\'}
>>> len(d)
2

5.4 索引
d[k] 返回关联到键k上的值。Key即为索引,字典没有切片。
>>> print(d)
{\'age\': 42, \'name\': \'Wen\'}
>>> d[\'age\']
42
>>> d[\'name\']
\'Wen\'


loop循环输出字典

  
上面这个效率低,不要用
 
返回元组


 
 
用这个


for i in dic.items():
    print(i)
for i,j in dic.items():
    print(i,j)
for i in dic.keys():
    print(i,dic[i])
for i in dic:
    print(i,dic[i])

5.5 赋值
d[k]=v 将v关联到键k上
>>> print(d)
{\'age\': 42, \'name\': \'Wen\'}
>>> d[\'age\']
42
>>> d[\'age\']=52
>>> print(d)
{\'age\': 52, \'name\': \'Wen\'}

5.6 del(删除字典中键--值对)

>>> print(d)
{\'age\': 52, \'name\': \'Wen\'}
>>> d[\'learn\']="python"
>>> print(d)
{\'age\': 52, \'name\': \'Wen\', \'learn\': \'python\'}
>>> del d[\'age\']
>>> print(d)
{\'name\': \'Wen\', \'learn\': \'python\'}


5.7 in(成员资格)
k in d检查d中是否含有键为k的项。在字典中找的是键,不是值,成员资格在列表中找的是值
>>> print(d)
{\'name\': \'Wen\', \'learn\': \'python\'}
>>> \'learn\' in d
True
>>> \'python\' in d
False

5.8 字典的格式化字符串

  P58


(字典方法)
5.9 clear(清空字典所有内容)
clear方法清除字典中的所有的项,是原地操作且无返回值。
>>> print(d)
{\'age\': 25, \'name\': \'Wen\', \'learn\': \'python\'}
>>> d.clear()
>>> print(d)
{}

5.10 copy(字典的浅复制与深复制)
copy方法返回一个具有相同键-值对的新字典(这个方法实现的是浅复制,只复制父对象,不会复制对象的内部的子对象)
deepcopy方法深复制,拷贝对象及其子对象

>>> import copy
>>> a={\'name\':\'wen\',\'learn\':[\'python\',\'jinrong\']}
>>> b=a
>>> ac=a.copy()
>>> ad=copy.deepcopy(a)
>>> a[\'age\']=25                              #修改对象a
>>> a[\'learn\'][1]=\'jiaoyi\'                       #修改对象a中的[\'python\',\'jinrong\']数组对象
>>> a
{\'age\': 25, \'name\': \'wen\', \'learn\': [\'python\', \'jiaoyi\']}  #原始对象变化后
>>> b
{\'age\': 25, \'name\': \'wen\', \'learn\': [\'python\', \'jiaoyi\']}  #赋值,会随着原始对象变化
>>> ac
{\'name\': \'wen\', \'learn\': [\'python\', \'jiaoyi\']}          #浅拷贝,对父对象的子对象没有复制,会随原始数据变化
>>> ad
{\'name\': \'wen\', \'learn\': [\'python\', \'jinrong\']}         #深拷贝

 
赋值原理中一定要知道:在程序上看起来赋值是直接修改原来的值,其实是开辟了新的内存存放新的值,旧的值还是在那里等待内存回收的。

5.11 get(根据key获取值)
一般访问字典中不存在的项时会报错,使用get访问一个不存在的键时,没有任何异常,而是得到了None值,还可以自定义”默认值”,替换None:
>>> name={}
>>> name[\'name\']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: \'name\'
>>> name.get(\'name\')
>>> name.get(\'name\',"N")
\'N\'
>>> name["name"]="wen"
>>> name.get(\'name\')
\'wen\'

5.12 has_key(检查是否含有指定的key)
has_key,检查字典中的指定key是否存在,存在返回True,不存在返回False,这个方法在Python2中有效,在Python3中已经取消,并用in代替了
>>> print(name)
{\'name\': \'wen\'}
>>> name.has_key(\'name\')
True
>>> name.has_key(\'age\')
False


5.13 items和iteritems(将字典所有的项以列表方式返回)
items和iteritems将字典所有的项以列表方式返回,列表中的每一项都表示为(键,值)对的形式。但是并没有遵循特定的次序。
>>> print(name)
{\'age\': 25, \'name\': \'wen\', \'learn\': \'python\'}
>>> name.items()
[(\'age\', 25), (\'name\', \'wen\'), (\'learn\', \'python\')]
iteritems大致与items相同,但是会返回一个迭代器而不是列表
>>> name.iteritems()
<dictionary-itemiterator object at 0x7f0e236dd0a8>


5.14 keys和iterkeys(将字典中键以列表形式返回)
Keys将字典中的键以列表形式返回,而iterkeys则返回针对键的迭代器
>>> print(name)
{\'age\': 25, \'name\': \'wen\', \'learn\': \'python\'}
>>> name.keys()
[\'age\', \'name\', \'learn\']
>>> name.iterkeys()
<dictionary-keyiterator object at 0x7f0e236dd0a8>

5.15 values和itervalues(将字典中的值以列表形式返回)

>>> print(name)
{\'age\': 25, \'name\': \'wen\', \'learn\': \'python\'}
>>> name.values()
[25, \'wen\', \'python\']
>>> name.itervalues()
<dictionary-valueiterator object at 0x7f0e236dd0a8>


5.16 pop(移除字典中的键-值对)
pop方法用来获得对应于给定键的值,然后将这个键-值对从字典中移除
>>> print(name)
{\'age\': 25, \'name\': \'wen\', \'learn\': \'python\'}
>>> name.pop(\'age\')
25
>>> print(name)
{\'name\': \'wen\', \'learn\': \'python\'}

Pop也可以指定默认返回值default,防止键不存在时的错误
 

5.17 popitem(弹出字典中随机项)
popitem弹出随机的项,因为字典没有最后一个元素或有关顺序的概念。若想一个接一个地移除并处理项,popitem非常有用,不用首先获取键的列表。
>>> name={\'age\':25,\'name\':\'wen\',\'learn\':\'python\'}
>>> name
{\'age\': 25, \'name\': \'wen\', \'learn\': \'python\'}
>>> name.popitem()
(\'age\', 25)
>>> name
{\'name\': \'wen\', \'learn\': \'python\'}
 
5.18 setdefault(根据key获得值,可以不存在,添加此键)
setdefault与get类似,能够获得与给定键相关联的值,若键不存在,setdefault返回默认值并且相应的更新字典。
>>> name
{\'name\': \'wen\', \'learn\': \'python\'}
>>> name.setdefault(\'name\')
\'wen\'
>>> name.setdefault(\'age\')
>>> name
{\'age\': None, \'name\': \'wen\', \'learn\': \'python\'}

5.19 update(用字典更新另一个字典)
Update方法可以利用一个字典项更新另外一个字典
>>> name
{\'age\': 20, \'name\': \'wen\', \'learn\': \'python\'}
>>> name2
{\'age\': 25, \'sex\': \'man\'}
>>> name.update(name2)
>>> name
{\'age\': 25, \'sex\': \'man\', \'name\': \'wen\', \'learn\': \'python\'}

5.20 enumerate函数

li=[11,22,33]
new_dict=dict(enumerate(li,10))
print(new_dict)


for i,j in enumerate(li,100):
print(i,j)
100 Tony
101 alex
102 eric
103 rain
104 seven


http://blog.csdn.net/churximi/article/details/51648388


5.21 fromkeys(用给定的键建立新字典)
Formkeys方法使用给定的键建立新的字典,每个键都对应一个默认的值None,但是可以自己提供默认值
info={}
info=info.fromkeys([1,2,3])
print(info)
{1: None, 2: None, 3: None}

info={}
info=info.fromkeys([1,2,3],\'Test\')
print(info)
{1: \'Test\', 2: \'Test\', 3: \'Test\'}


总结
定义
name={
    \'name\':\'wen\',
    \'age\':25,
    \'learn\':\'python\'
}
修改字典
Name[‘name’]=’wenyanjie’
删除字典
Pop
Popitem
Del name[‘age’]
查看字典
Print(name)
get
In
Keys
values
loop循环
for i in name:
    print(i,name[i])
for i in name.items():
    print(i)
for k,v in name.items():  #不用,效率低
    print(k,v)


六 集合 set
 
集合也是无序的 
寻找集合元素需要 print(\'alex\' in linux) 

 
 

 


 

set的增删改查 

 

clear

 不存在的就增加,存在就不增加

 
 

 

 




 



六 条件语句

6.1 布尔型 bool
  布尔值True和False属于布尔类型
标准值False和None、所有类型的数字0(包括浮点型、长整型和其他类型)、空序列(比如空字符串、元组和列表)以及空的字典都为假。其他的一切都被解释为真,包括特殊值True。
  Bool函数可以用来转换其他值。
>>> bool(\'I think python\')
True
>>> bool(42)
True
>>> bool(\'\')
False
>>> bool(0)
False
注意:尽管[]和’’都是假值,但是并不是他们就是相等的。


6.2 条件语句:if,else,elif

# age=25
#
# age_guess=int(input("please guess age:"))
# print(type(age_guess))
# if age_guess > age:
#     print("age_guess is big,try to smaller")
# elif age_guess < age :
#     print("age_guess is small,try to bigger")
# else:
#     print("you\'re right!")



6.3 运算符
比较运算符
用在条件中的最基本的运算符就是比较运算符,他们用来比较其他对象。
表达式	描述符
x==y	x等于y
x<y	x小于y
x>y	x大于y
x>=y	x大于等于y
x<=y	x小于等于y
x!=y	x不等于y

身份运算(同一运算符)
x is y	x和y是一个对象
x is not y	x和y是不同的对象

判断数据的数据类型
>>> type(1) is int
True
>>> type(\'1\') is int
False
>>> type(\'1\') is str
True
成员运算
x in y	x是y容器(例如序列)的成员
x not in y 	x不是y容器的成员


赋值运算

运算符	描述	实例
=	简单的赋值运算符	c=a+b将a+b的运算结果赋值为c
+=	加法赋值运算符	c+=a等效于c=c+a
-=	减法赋值运算符	c-=a 等效于 c=c-a
*=	乘法赋值运算符	c*=a 等效于c=c*a
/=	除法赋值运算符	c/=a 等效于c=c/a
%=	取模赋值运算符	c%=a等效于c=c%a
**=	幂赋值运算符	c**=a等效于c=c**a
//=	取整除赋值运算符	c//=a等效于c=c//a



逻辑运算
运算符	描述	实例
And	布尔”与”如果x为False,x and y返回False,否则他返回y的计算值	(a and b)返回true
Or	布尔”或”如果x是True,它返回True,否则它返回y的计算值	(a or b)返回true
not	布尔”非”如果x为True,返回False。如果x为False,它返回True	Not(a and b)返回false

短路逻辑(惰性求值)
     布尔运算符有个特性:只有在需要求值时才进行求值。举例来说,表达式x and y 需要两个变量都为真时才为真,所以如果x为假,表达式就会立刻返回false,而不管y的值。实际上,如果x为假,表达式会返回x的值,否则它就返回y的值。这种行为被称为短路逻辑或惰性求值。




位运算

 
 




6.4 三元运算

 

 


七  循环语句
For I in range(20):
	Print(i)

While True:
	Print(love)


八  程序练习
9.1 购物车程序
例:
python shopping.py
input your salary:5000
你可以买下面的东西:
1. iphone 5800
2. coffee   30  
3. book    50
4. condom 90
>>:1
买不起,还差800
>>:2
放入购物车,扣钱,同时打印余额。。。4970
>>:3
.....
>>:4
......
>>:q
您买了下面的东西
coffee 30
book  100
您还有4870
bye

方法1:
salary=int(input("input your salary:"))
goods=["apples","peache","banana","lemen","orange","pear"]
prices=[5,8,10,6,4,9]
goods_buy=[]
while True:
    print("num\t\tgoods\t\tprices")
    for i in range(len(goods)):
        print(i + 1, ".\t\t", goods[i], "\t\t", prices[i])
    print("q .\t\tquit")
    num1 = input(">>:")
    if num1 == \'q\':
        if len(goods_buy) != 0:
            print("您购买的商品是:")
        for j in range(len(goods)):
            count1 = goods_buy.count(goods[j])
            if count1 > 0:
                print(goods[j], prices[j] * count1)
        print("您还有 %d" % (salary))
        print("bye bye")
        break
    else:
        num=int(num1)
    if (num - 1) in range(len(goods)) and salary < prices[num - 1]:
        print("买不起,还差 %d " % (prices[num - 1] - salary))
    elif (num - 1) in range(len(goods)) and salary >= prices[num - 1]:
        salary = salary - prices[num - 1]
        goods_buy.append(goods[num - 1])
        print("放入购物车,扣钱,余额为 %d" % (salary))
    else:
        print("请输入商品列表中的标号")
方法2:
product_list = [[\'Iphone7\',5800],
                [\'Coffee\',30],
                [\'疙瘩汤\',10],
                [\'Python Book\',99],
                [\'Bike\',199],
                [\'ViVo X9\',2499],
                ]
shopping_cart = []
salary = int(input("input your salary:"))
while True:
    index=0
    for product in product_list:
        print(index,product)
        index+=1
    choice=input(">>:").strip()
    if choice.isdigit(): #判断是否为数字
        choice = int(choice)
        if choice >= 0 and choice < len(product_list):#商品存在
            product=product_list[choice]  #取到商品
            if product[1] <= salary: #判断能否买的起
                #买得起
                shopping_cart.append(product) #加入购物车
                salary-=product[1] #扣钱
                print("Added product "+product[0]+" into shopping cart,your current balance " + str(salary))
            else:
                print("买不起,穷逼!产品价格是:"+str(product[1])+" 你还差"+str(product[1]-salary)+" 钱")
        else:
            print("商品不存在!")
    elif choice == \'q\':
        print("--------已购买商品列表--------")
        for i in shopping_cart:
            print(i)
        print("您的余额为:",salary)
        print("--------end-------")
        break
    else:
        print("无此选项!")




九 文件
文件
1 流程
  1、 打开文件
  2、 操作文件
  3、 关闭文件

open
test  文件以gbk保存  windows默认gbk解码  open文件默认是gbk解码
test2  文件以utf8保存  windows默认gbk解码  open文件需要执行utf8解码
f=open("test")
f=open("test2",encoding="utf8")

read
data=f.read()    #读取文件全部内容
data=f.read(5)   #从光标位置读5个字符 (py3按照字符取,py2按照字节取)
read读取到什么值,取决于当前光标的位置

readline
data=f.readline()  #读取1行内容以及\n,即字符串形式
print(data)        #print开头自带\n

readlines
data=f.readlines() #返回列表结果,且列表每个元素都有\n
print(date)
此时data是一个列表

#练习
在第4行输出时加入“岳飞”:
count=0
for line in f.readline():
    if count==3:
        line="".join([line.strip(),"岳飞"])
    print(line.strip())
    count+=1

count=0
for line in f: #优化内存
    if count==3:
        line="".join([line.strip(),"岳飞"])
    print(line.strip())
    count+=1

此上的f.readline()和f区别:
f.readline()将文件全部的内容都会读取到内存
f是将文件一行一行的读取到内存
f是文件句柄,是可迭代对象,按行序列



写
w 覆盖写(在一个句柄内第1次全部覆盖写,其他是追加写)
f=open("test2",mode="w",encoding="utf8")
write模式不可以read
read模式不可以write
f.write("hello")
#将f中的内容全部清空,写入hello
#如果不存在f指定的文件,会新建   
f.write("hello\nworld")
将f.write写入的int变量等类型内容转化为str格式

a追加写
f=open("test3",mode="a",encoding="utf8")
f.write("hello3")


flush操作
写操作会先把写的内容写入内存的缓存区
f.write("hello test4") #文件创建,但是内容还是没有写入去,还在内存缓存中
import time
time.sleep(100)
f.close()
flush方法会立刻将内存缓存的内容放入磁盘
f.write("hello test5")
f.flush()
import time
time.sleep(100)
f.close()
应用:进度条
import sys  #sys模块
sys.stdout.write("hello")
#sys.stdout标准输出文件,就是屏幕本身就是文件
for i in range(100):
    sys.stdout.write(\'#\')
    sys.stdout.flush()
    import time
    time.sleep(0.5)
作业:打印百分比
import sys
for i in range(101):
    s="\r%d%% %s"%(i,"#"*i)
    # \r是将光标定位到开头  %%将%输出
    sys.stdout.write(s)
    sys.stdout.flush()
    import time
    time.sleep(0.5)

x模式
f=open("test3",mode="x",encoding="utf8")
不要覆盖,提醒我就行


可读可写模式 r+ w+ a+
r+  #光标默认在开始位置,
    #可读,追加写
f=open("test5",mode="r+",encoding="utf8")
print(f.read())
f.write("where is egon?") #追加写

w+  #先覆盖所有内容,再写内容
    #根据光标进行读
f=open("test5",mode="w+",encoding="utf8")
print(f.read())
f.write("where is egon?")
print(f.read())


f.seek(0) #将光标移到开始位置,按照字节移动
f.seek(0,0)
f.seek(-3,2) #将光标移到结尾位置,按照字节向左移动
seek的三种模式0(从头开始计算光标位置),1(当前位置开始),2(末尾位置开始)


f.tell()  #将光标位置打印出来
f=open("test5",mode="w+",encoding="utf8")
print(f.read())
f.write("hello温艳杰")
f.seek(5)  #将光标移到第5个字节处
print(f.read())


a+模式 #默认光标在开始位置,根据光标读
       #根据无论光标在哪,都是追加内容
f=open("test5","a+")
f.seek(0)
print(f.read())
f.write("yuan")    #写在结尾
f.seek(0)
f.write("yuan")    #没有办法对文件进行修改工作,
                   #内存,磁盘已经占有了,存储好了
                   #写在结尾

#a+ 光标默认在最后位置,不管光标位置,一定是追加写
    读取内容:seek调整

seek的应用:
断点续传:ftp作业中有


b模式
图片,媒体文件等是2进制文件
rb,wb,ab

f=open("test5","rb")  #只是取内存的byte字节数据,所以不需要解码
print(f.read())       #取到的是字节数据

f=open("test6","wb")
f.write("hello温艳杰")  # str 换成 bytes,因此不能这么写
f.write("hello温艳杰".encode("utf-8"))
#在写的时候可以指定编码类型

f=open("test6","ab") #读字节,写字节,追加写


with
with open("test6") as f:
    f.read()
等同于
f=open("test6")
f.read()
f.close()





十 函数
  
10.1 使用函数解决的问题
        1 代码冗余
        2 可维护性差
        3 可读性差
        4 组织结构不清晰(一个功能定义一个函数)

10.2 两种函数
10.2.1 内置函数
sum,max,len等
1 函数                      描述
 2 int(x [,base ])         将x转换为一个整数
 3 long(x [,base ])        将x转换为一个长整数
 4 float(x )               将x转换到一个浮点数
 5 complex(real [,imag ])  创建一个复数
 6 str(x )                 将对象 x 转换为字符串
 7 repr(x )                将对象 x 转换为表达式字符串
 8 eval(str )              用来计算在字符串中的有效Python表达式,并返回一个对象
 9 tuple(s )               将序列 s 转换为一个元组
10 list(s )                将序列 s 转换为一个列表
11 chr(x )                 将一个整数转换为一个字符
12 unichr(x )              将一个整数转换为Unicode字符
13 ord(x )                 将一个字符转换为它的整数值
14 hex(x )                 将一个整数转换为一个十六进制字符串
15 oct(x )                 将一个整数转换为一个八进制字符串

10.2.2 自定义函数
            例子:
                def print_line():
                    print("#"*12)
                def print_msg():
                    print("alex  SB  SB")
                print_line()
                print_msg()
                print_line()

10.3 定义函数
10.3.1 为什么做函数定义 
  函数使用原则:要先定义,后使用
  可以这样理解:函数名相当于变量名,函数的代码相当于变量的值,定义函数相当给函数名与函数代码绑定,
引用函数名,就是引用函数代码
   
   
10.3.2 函数定义格式
            \'\'\'
            def 函数名(arg1,arg2,arg3):
                "描述信息"
                函数体
                return 1
            \'\'\'
查看函数注释:
def foo():
    \'foo function\'
    print("from the foo")
print(foo.__doc__)
输出结果:
foo function

10.3.3  定义无参函数
def foo():
    print("from the foo")

10.3.4 定义有参函数
   def bar(x,y):
            res=x+y
            return res

10.3.5 定义空函数
  空函数可以先搭建程序清晰的体系,一步一步完成程序 
    def auth():
        pass

10.3.6 有参与无参的区别及其应用场景
这个功能需要外部传值,就是有参函数,否则,就是无参函数


10.4 调用函数
10.4.1 调用无参、有参和空函数  
   函数加小括号,才是调用

   调用无参函数:(定义无参,调用也无参)
        foo()
    调用有参函数:(定义有参,调用也必须有参)
        res=bar(1,2)
    调用空函数:
        auth()
10.4.2 语句和表达式形式调用函数
    语句形式调用:foo()
表达式形式调用: (此时函数有返回值)
res=bar(1,2)*10
    bar(bar(1,2),3)  #函数调用作为另一个函数的参数

10.5 函数返回值return
(1)函数返回值可以是任意类型数据
(2)无参函数通常没有返回值,有参函数通常有返回值
(3)函数没有return返回值但会返回None
没有return---> None 
def foo():
    print("from the foo")
res=foo()
print(res)
输出结果:
from the foo
None

(4)返回多个值会作为元组类型返回
        return 1---->1
        return 1,2,3 -----> (1,2,3)
def my_max(x,y):
    res=x if x>y else y
    return res
res1=my_max(1,2)
print(res1)
输出结果:
2

def bar(x,y):
    return 1,2,3,4,5,[1,2],{"a":2},{1,2,3}
res1=bar(1,2)
print(res1)
输出结果:
(1, 2, 3, 4, 5, [1, 2], {\'a\': 2}, {1, 2, 3})


 (5)接收返回值:
        单个返回值:res=bar(1,2)
        多个返回值:res=bar(1,2)  res是元组类型
        多个返回值:a,b,c=bar(1,2)  a,b,c分别接收返回值

def bar(x,y):
    return 1,2,3
a,b,c=bar(1,2)
print(a)
print(b)
print(c)
输出结果:
1
2
3

(6)函数只会执行一个return,就结束函数并返回值,无论写多少return



小知识:解压变量
 

10.6 有参函数的参数
python是强类型语言,参数的类型不需要指定类型,但是会有不合法的数据类型,python不能限制,只能注释提醒
例:x,y没有指定数据类型,所以什么类型都行。使用灵活,但是乱用会报错。可以使用注释信息提醒
def my_max(x,y):
    res=x if x>y else y
    return res
print(my_max(1,2))
print(my_max(\'a\',\'b\'))
print(my_max(\'a\',1))
输出结果:
2
b
TypeError: unorderable types: str() > int() #报错

10.6.0 函数写注释方法
方法1:
def my_max(x,y):
    """x->int,y->int,res->int,cal max value"""
    res=x if x>y else y
    return res

方法2:
def my_min(x:int,y:int)->int:
    print(x if x<y else y)
my_min(1,2)
print(my_min.__annotations__)
输出结果:
1
{\'y\': <class \'int\'>, \'x\': <class \'int\'>, \'return\': <class \'int\'>}


__annotations__用显示出函数的注释信息



10.6.1 形参和实参的概念

形参与实参的定义
            def foo(x,y):  # 在函数定义阶段,括号内定义的参数-->形式参数(本质就是变量名)
                print(x)
                print(y)
            foo(1,2)    ##在函数调用阶段,括号内定义的参数-->实际参数(本质就是变量值,实参必须有一个确定的值)
            # 形参和实参的绑定关系在定义时没有生效,在调用时生效,形参被赋值实参的值,函数结束时解除绑定
        实参要保证是不可变的类型:字符,数字,元组,如果是可变的类型,形参也可以修改实参。因此需要注意:不要在函数内动用全局变量,函数的功能就与函数有关,不要动全局变量。
 
实参的值为不可变类型时: 形参不能改变实参的值
def bar(x):
    print(x)
    x=3
x=1
bar(x)
print(x)
输出结果:
1
1

实参的值为可变类型时: 形参可以改变实参的值
def bar(x):
    x.append(4)
x=[1,2,3]
bar(x)
print(x)
输出结果为:
[1, 2, 3, 4]

10.6.2 实参的角度分析:书写三种方式
(1)位置实参
按照位置传值,按照位置写就是位置实参
def foo(x,y):
    print("x=",x,",y=",y)
foo(1,2)
foo(2,1)
输出结果为:
x= 1 ,y= 2
x= 2 ,y= 1

(2)关键字实参
按照关键字传值:关键字实参 
什么等于什么 就是关键字传值  
def foo(x,y):
    print("x=",x,",y=",y)
foo(x=1,y=2)
foo(y=2,x=1)
输出结果为:
x= 1 ,y= 2
x= 1 ,y= 2

(3)位置实参和关键字实参混着用
1 位置参数必须在关键字参数之前。 按位置传值必须在按关键字传值的前面

def foo(x,y):
    print("x=",x,",y=",y)
foo(1,y=2)
输出结果:
x= 1 ,y= 2

def foo(x,y):
    print("x=",x,",y=",y)
foo(y=2,1)    
输出结果:
SyntaxError: positional argument follows keyword argument  #报错

2 对于一个形参只能赋值一次,不能重复赋值

def foo(x,y):
    print("x=",x,",y=",y)
foo(1,x=1,y=2)
输出结果:
TypeError: foo() got multiple values for argument \'x\'  #报错

          
    
10.6.3 形参的角度分析
(1)位置形参
必须传值的参数,传值时多或少都不行
def foo(x,y):
    print("x=",x,",y=",y)
foo(1,2,3)
输出结果:
TypeError: foo() takes 2 positional arguments but 3 were given  #报错

            
(2)默认形参
1,以不传

1 常用的,变化比较小的值设为参数的默认值
def foo(x,y=1):
    print("x=",x,"y=",y)
foo(1)
输出结果为:
x= 1 y= 1

Python自带许多有默认形参的函数
 


2 形参设置默认参数时,必须放到形参中位置参数的后面
 

可以这样理解:函数名相当于变量名,函数的代码相当于变量的值,定义函数相当给函数名与函数代码绑定,
引用函数名,就是引用函数代码
在函数定义阶段,函数绑定到一堆代码,python只是会检查函数的语法是否错误;
在函数调用阶段,python才会输出执行函数代码的错误

3 形参默认参数在函数定义的时候就可以被赋值了

x=\'male\'
def register(sex=x):
    print(sex)
x=None
register()
输出结果为:
male

4 默认参数是可变类型时

x=[]
def register(name,name_list=x):
    name_list.append(name)
register("wen")
register("yan")
register("jie")
print(x)
输出结果:
[\'wen\', \'yan\', \'jie\']


(3)形参:*args  
1 功能:将实参那边的位置参数传值多余的值都接收,成元组类型
2 也是属于位置参数,但要放在其他位置参数的后面            
  形参参数排序:1 其他位置参数  2 *args  3 默认参数
  一般*args和默认参数不要一起用
3 *args可以看做是无穷的位置参数,根据实参的数量而变化
4 * 的功能就相当于将形参名打散成多个,接收实参过来的值
  *也可以用在实参里,将值打散分成多个传给形参
  对以后装饰器有用


1 功能:将实参那边的位置参数传值多余的值都接收,成元组类型

def foo(x,*args):
    print(x)
    print(args)
foo(1,2,3,4,5,6,7,8,\'a\',\'b\')
输出结果:
1
(2, 3, 4, 5, 6, 7, 8, \'a\', \'b\')

2 形参参数排序:1 其他位置参数  2 *args  3 默认参数 。 一般*args和默认参数不要一起用

def foo(x,y=1,*args):
    print("x=",x)
    print("y=",y)
    print("args=",args)
foo(1,2,3,4,5)
输出结果:
x= 1
y= 2
args= (3, 4, 5)

def foo(x,*args,y=1):
    print("x=",x)
    print("y=",y)
    print("args=",args)
foo(1,2,3,4,5)
输出结果:
x= 1
y= 1
args= (2, 3, 4, 5)


3 *的概念
   * 的功能就相当于将形参名打散成多个,接收实参过来的值
    *也可以用在实参里,将值打散分成多个传给形参
例:将args分成多个形参接收实参过来的值
*args=1,2,3
args=(1,2,3)
*(1,2,3)=1,2,3
从形参角度:
def foo(*args):
    print(args)
foo(1,2,3)
输出结果:
(1, 2, 3)


从实参角度:将实参的元组打散分别传给形参
def bar(x,y,z):
    print(x)
    print(y)
    print(z)
bar(*(1,2,3))
输出结果:
1
2
3

*功能的再一例子:
def my_sum(nums):   
    res=0
    for i in nums:
        res+=i
    return res
print(my_sum((1,2,3,4,5)))

def my_sum(*nums):   #比上面例子多一个*号
    res=0
    for i in nums:
        res+=i
    return res
print(my_sum(1,2,3,4,5))  #比上面例子少一对括号


(4)形参: **kwargs  
接收关键字参数
1  功能:将实参那边的多余的关键字传值变成字典类型,赋值给kwargs
2  混着用的位置:在形参角度看,**kwargs位于*args之后
3  **的概念
* 的功能就相当于将形参名打散成多个对(默认参数,即x=1),接收实参过来的值
* 也可以用在实参里,将字典打散分成多个关键字参数(即x=1)传给形参
4  *args和**kwargs联用

1 将实参那边的关键字传值多余的变成字典类型,赋值给kwargs

def foo(x,**kwargs):
    print(x)
    print(kwargs)
foo(1,y=2,a=3,b=4)
输出结果:
1
{\'a\': 3, \'y\': 2, \'b\': 4}

使用**kwargs报错的例子:
def foo(x,**kwargs):
    print(x)
    print(kwargs)
foo(1,2,3,4)
输出结果:
TypeError: foo() takes 1 positional argument but 4 were given  #报错

2  **的概念
* 的功能就相当于将形参名打散成多个对(默认参数,即x=1),接收实参过来的值
* 也可以用在实参里,将字典打散分成多个关键字参数(即x=1)传给形参

从形参的角度:
def foo(**kwargs):
    print(kwargs)
foo(x=1,y=2,z=3)
输出结果:
{\'z\': 3, \'x\': 1, \'y\': 2}

从实参角度:
def foo(x,y,z=1):
    print(x)
    print(y)
    print(z)
foo(**{\'x\':1,\'y\':2,\'z\':3})   #相当于 foo(x=1,y=2,z=3)
输出结果为:
1
2
3


3 在形参角度,**kwargs位于*args之后

def foo(x,*args,**kwargs):
    print(x)
    print(args)
    print(kwargs)
foo(1,y=1,z=2)
输出结果:
1
()
{\'y\': 1, \'z\': 2}

再例:
def foo(x,*args,**kwargs):
    print(x)
    print(args)
    print(kwargs)
foo(1,2,3,4,5,6,a=7,y=1,z=2)
输出结果:
1
(2, 3, 4, 5, 6)
{\'y\': 1, \'a\': 7, \'z\': 2}

4 *args和**kwargs联用的好处:灵活
调用auth函数参数输入灵活,实参是关键字参数和位置参数都可以,实参输入位置参数的时候注意位置
def auth(name,password,sex=\'female\'):
    print(name)
    print(password)
    print(sex)
def foo(*args,**kwargs):
    print("from foo")
    auth(*args,**kwargs)
foo(\'yuan\',\'123\')
foo(\'wen\',\'123\',sex=\'male\')
foo(name=\'wen\',password=\'123\',sex=\'male\')

5 *args和**kwargs联用实现执行函数的计时问题
计时功能:
import time
def auth(name,password,sex=\'male\'):
    time.sleep(1)
    print(name,password,sex)
def timmer(*args,**kwargs):
    start_time=time.time()
    auth(*args,**kwargs)
    stop_time=time.time()
    print(\'run time is %s\' %(stop_time-start_time))
timmer(name=1,password=123,sex=\'female\')
输出结果:
1 123 female
run time is 1.0004379749298096



10.7 名称空间与作用域
1 名称空间分为:
   1 内置名称空间   内置在解释器中的名称
   2 全局名称空间   顶头写的名称
   3 局部名称空间
2 找一个名称的查找顺序:先在局部名称空间找,再到全局名称空间找,再到内置名称空间
3 Globals()  查看全局名称空间的内容
  Locals()   查看局部名称空间的内容
4 全局作用域包含内置名称空间和全局名称空间
  局部作用域包含局部名称空间

 
例
1 查看内建名称空间的内容:
 

2 作用域

x=1
def foo():
    x=100
    print(x)
foo()
print(x)
输出结果:
100
1


3 一定要注意函数要先定义,后使用

def foo():
    print("from foo")
    bar()
def bar():
    print("from bar")
foo()
输出结果:
from foo
from bar

def foo():
    print("from foo")
    bar()
foo()
def bar():
print("from bar")
输出结果:
NameError: name \'bar\' is not defined  #报错

4 Globals()  查看全局名称空间的内容
  Locals()   查看局部名称空间的内容

x=1
def func():
    print("from func")
    x=2
    print(globals())
    print(locals())
func()
print(globals())
print(locals())
输出结果:
from func
{\'x\': 1, \'__cached__\': None...
{\'x\': 2}
{\'x\': 1, \'__cached__\': None...
{\'x\': 1, \'__cached__\': None...




10.8 函数嵌套与静态嵌套域

嵌套调用
嵌套调用作用:将一个大的功能细化成各种小的函数功能并调用
def my_max(x,y):
    res=x if x >y else y
    return res
print(my_max(10,100))
def my_max4(a,b,c,d):
    res1=my_max(a,b)
    res2=my_max(res1,c)
    res3=my_max(res2,d)
    return res3
print(my_max4(1,20,3,4))
输出结果:
100
20


在函数内定义的函数 在外面不能用到
def f1():
    def f2():
        def f3():
            pass
    print("---->f1")
    f2()
f2()
输出结果:
NameError: name \'f2\' is not defined  #报错


#嵌套定义    
x找值的过程:先在局部名称空间找,再到上一级的局部名称空间找,再到全局名称空间找,再到内置名称空间
x=0
def f1():
    x=1
    print("---f1---",x)
    def f2():
        x=2
        print("---f2---",x)
        def f3():
            x=3
            print("---f3---",x)
        f3()
    f2()
f1()
输出结果:
---f1--- 0
---f2--- 0
---f3--- 0

In [21]: x=0
    ...: def f1():
    ...:     x=1
    ...:     print("---f1---",x)
    ...:     def f2():
    ...:         x=2
    ...:         print("---f2---",x)
    ...:         def f3():
    ...:             x=3
    ...:             print("---f3---",x)
    ...:         f3()
    ...:     f2()
    ...: f1()
    ...:
---f1--- 1
---f2--- 2
---f3--- 3
10.9 函数对象

函数被称为第一类对象,函数可以被当做数据传递

(1)函数可以被赋值

直接输出函数名的值: 就是函数在内存中的地址
def foo():
    print("foo")
print(foo)
输出结果:
<function foo at 0x000000DD32CAAD90>

函数可以被赋值:将函数名代码的值赋给变量  
def foo():
    print("foo")
f=foo
print(f)
f()
输出结果:
<function foo at 0x0000003AEBEBAD90>
foo

(2)函数可以作为参数传递
函数可以作为参数传递

def foo():
    print("foo")
def bar(func):
    print(func)
    func()
print(bar(foo))
输出结果为:
<function foo at 0x000000A351B4AD90>
foo
None


(3)函数可以作为返回值
函数可以作为函数的返回值
def foo():
    print("foo")
def bar(func):
    print(func)
    func()
    return func
f=bar(foo)
print(f)
f()
输出结果:
<function foo at 0x000000BFD82CAD90>
foo
<function foo at 0x000000BFD82CAD90>
foo

(4)函数可以作为容器类型的元素
函数作为字典的键的值:

def add():
    print("=======>function add")
def search():
    print("=======>function search")
def delete():
    print("=======>function delete")
def change():
    print("=======>function change")
def tell_msg():
    msg=\'\'\'
    search:查询
    add:添加
    delete:删除
    change:修改
    create:新建
    \'\'\'
    print(msg)
def create():
    print(\'=======>function create\')

cmd_dic={
    \'search\':search,
    \'add\':add,
    \'delete\':delete,
    \'change\':change,
    \'create\':create
}
while True:
    tell_msg()
    choice=input("please input your choice:")
    cmd_dic[choice]()

 


10.10 函数闭包
(1)函数闭定义
闭包:首先必须是内部定义的函数,该函数包含对外部作用域而不是全局作用域名字的引用
定义:内部函数的代码包含对外部函数的代码的引用,但一定不是对全局作用域的引用

闭包的基本形式是:
  在函数F1中,定义F2,F2只能引用F1定义的变量,之后F1函数返回F2的函数名字
  这样就保证了可以将F1的执行结果赋予给一个变量,该变量可以在之后的任何时刻随时可以运行

使用闭包的好处:自带状态即变量,可以不用传参就用,方便。


闭包(closure)是函数式编程的重要的语法结构。不同的语言实现闭包的方式不同。Python以函数对象为基础,为闭包这一语法结构提供支持的 (我们在特殊方法与多范式中,已经多次看到Python使用对象来实现一些特殊的语法)。Python一切皆对象,函数这一语法结构也是一个对象。在函数对象中,我们像使用一个普通对象一样使用函数对象,比如更改函数对象的名字,或者将函数对象作为参数进行传递。

(2)简单闭包举例

x=1000
def f1():
    x=1
    def f2():
        print(x)
    return f2
f=f1()
print(f)
x=123
f()
输出结果:
<function f1.<locals>.f2 at 0x000000C02BD1AF28>
1

(3)闭包的__closure__变量
闭包都有__closure__属性
__closure__对象会返回闭包应用外围作用域的变量信息。f.__closure__保存外围作用域的变量内存地址,f.__closure__[0].cell_contents存放的是外围作用域的变量的值。
对于那些不是闭包的函数对象来说,__closure__ 属性值为 None。

x=1
def f1():
    x=1000
    y=2
    def f2():
        y
        print(x)
    return f2
f=f1()  #f ---> 内部的函数f2
f()
print(f.__closure__)
print(f.__closure__[0])
print(f.__closure__[0].cell_contents)
print(f.__closure__[1])
print(f.__closure__[1].cell_contents)
输出结果:
1000
(<cell at 0x000000429E165D68: int object at 0x000000429E0A9EB0>, <cell at 0x000000429E165D98: int object at 0x0000000059C0EF50>)
<cell at 0x000000429E165D68: int object at 0x000000429E0A9EB0>
1000
<cell at 0x000000429E165D98: int object at 0x0000000059C0EF50>
2

举例:__closure__ 属性值为 None

x=1
def f1():
    def f2():
        print(x)
    return f2
f=f1()  #f ---> 内部的函数f2
f()
print(f.__closure__)
输出结果为:
1
None


(4)闭包应用
Windows中cmd中执行pip install requests 安装requests库件
 
 

爬baidu网站的程序
from  urllib.request import urlopen
def get(url):
    return urlopen(url).read()
print(get(\'http://www.baidu.com\'))
print(get(\'http://www/python.org\'))

将上面”爬百度”的程序修改成闭包模式:

from  urllib.request import urlopen
def f1(url):
    def f2():
        print(urlopen(url).read())
    return f2
baidu=f1(\'http://www.baidu.com\')
python=f1(\'http://www.python.org\')
baidu()
python()


10.11 装饰器
10.11.1 什么是装饰器
装饰器本质上是一个python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能。
装饰器的返回值是也是一个函数对象。

装饰器经常用于有切面需求的场景,比如:插入日志,性能测试,事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。


为什么要用装饰器及开放封闭原则

函数的源代码和调用方式一般不修改,但是还需要扩展功能的话就需要在需要扩展的函数的开始使用装饰器。举例:带眼镜
装饰器是任意可调用的对象,本质就是函数

装饰器在python中使用如此方便归因于python的函数能像普通的对象一样能作为参数传递给其他函数,可以被复制给其他变量,可以作为返回值,可以被定义在另一个函数内。

装饰器为可调用对象。

10.11.2 简单的装饰器

import time
def timmer(func):
    def wrapper(*args,**kwargs):
        start_time=time.time()
        res=func(*args,**kwargs)
        stop_time=time.time()
        print("run time is %s "%(stop_time-start_time))
    return wrapper
@timmer            #等同于 index=timmer(index) , 此后index等同于 wrapper   
def index():
    time.sleep(1)
    print("welcom ! wen")
index()
输出结果:
welcom ! wen
run time is 1.0001096725463867 

函数timmer就是装饰器,它把执行真正业务方法的func包裹在函数里面,看起来像index被timmer装饰了。
在这个例子中,函数进入和退出时,被称为一个横切面(Aspet),这种编程方式被称为面向切面的编程(Aspet-Oriented Programming)
@符号是装饰器的语法糖,在定义函数的时候,避免再一次赋值操作。


10.11.3 装饰器的语法
@timmer   
timmer就是一个装饰器
@timmer等同于 被装饰函数名=timmer(被装饰函数名)   被装饰器函数就是紧接@timmer下面的函数


10.11.4 无参装饰器
   如果多个函数拥有不同的参数形式,怎么共用同样的装饰器?
   在Python中,函数可以支持(*args, **kwargs)可变参数,所以装饰器可以通过可变参数形式来实现内嵌函数的签名。

无参装饰器,被装饰函数带参,无返回值


import time
def timmer(func):
    def wrapper(*args,**kwargs):
        print(func)
    return wrapper
@timmer
def index():
    time.sleep(1)
    print("welcom ! wen")
index()
输出结果为:
<function index at 0x000000D132F5AF28>

多个函数共用一个装饰器:
import time
def timmer(func):
    def wrapper(*args,**kwargs):
        start_time=time.time()
        func(*args,**kwargs)
        stop_time=time.time()
        print("run time is: %s" %(stop_time-start_time))
    return wrapper
@timmer
def home(name):
    time.sleep(1)
    print(" %s  home"%name)
@timmer
def auth(name,password):
    time.sleep(1)
print(name,password)
@timmer
def tell():
    time.sleep(1)
    print("------------------")
home("wenyanjie")
auth("wen","1234")
tell()
输出结果为:
 wenyanjie  home
run time is: 1.0002663135528564
wen 1234
run time is: 1.0000183582305908
------------------
run time is: 1.0009756088256836



无参装饰器,被装饰函数带参,且有返回值

import time
def timmer(func):
    def wrapper(*args,**kwargs):
        start_time=time.time()
        res=func(*args,**kwargs)
        stop_time=time.time()
        print("run time is: %s" %(stop_time-start_time))
        return res
    return wrapper

@timmer
def home(name):
    time.sleep(1)
    print(" %s  home"%name)
@timmer
def auth(name,password):
    time.sleep(1)
    print(name,password)
@timmer
def tell():
    time.sleep(1)
    print("------------------")
@timmer
def my_max(x,y):
    return x if x > y else y

home("wenyanjie")
auth("wen","1234")
tell()
res=my_max(1,2)
print("----->",res)
输出结果:
 wenyanjie  home
run time is: 1.0004072189331055
wen 1234
run time is: 1.0009665489196777
------------------
run time is: 1.0001206398010254
run time is: 0.0
-----> 2


10.11.5 有参装饰器
  装饰器还有更大的灵活性,例如带参数的装饰器:在上面的装饰器调用中,比如@timmer,该装饰器唯一的参数是执行业务的函数。装饰器的语法允许我们在调用时,提供其他参数。

def auth2(auth_type):
    def auth(func):
        def wrapper(*args,**kwargs):
            if auth_type == "file":
                name=input("username:")
                password=input("password")
                if name=="wen" and password =="123":
                    print("login successful!")
                    res=func(*args,**kwargs)
                    return res
                else:
                    print("auth error")
            elif auth_type == "sql":
                print("sql no learn.....")
        return wrapper
    return auth

@auth2(auth_type="file")
def login(name):
    print("this is %s index page"%name)
    return 1
flag=login("wenyanjie")
print(flag)
输出结果:
username:wen
password123
login successful!
this is wenyanjie index page
1

上面例子中的auth2是允许带参数的装饰器。它实际上是对原有装饰器的一个函数封装,并返回一个装饰器。我们可以将他理解为一个含有参数的闭包。当我们使用@auth2(auth_type=”file”)调用的时候,python能够发现这一层的封装,并把参数传递到装饰器的环境中。


上面有参装饰器的执行步骤分解:
 


10.11.6 多个装饰器
装饰器是可以叠加的,那么这就涉及装饰器调用顺序。对于python中的“@”语法糖,装饰器的调用顺序与使用@语法糖的顺序相反。多个装饰器的执行流程最上面的装饰器先执行,然后是下边的装饰器

多个无参装饰器

@ccc
@bbb
@aaa
def func():
    pass
#相当与
func=aaa(func)
func=bbb(func)
func=ccc(func)
#相当与
func=ccc(bbb(aaa(func)))


有参装饰器多个

@ccc(\'c\')
@bbb(\'b\')
@aaa(\'c\')
def func():
    pass
#相当与
func=aaa(\'a\')(func)
func=bbb(\'b\')(func)
func=ccc(\'c\')(func)
#相当与
func=ccc(\'c\')(bbb(\'b\')(aaa(\'a\')(func)))


案例:
import time
current_login={\'name\':None,\'login\':False}
def timmer(func):
    def wrapper(*args,**kwargs):
        print(\'one\')
        start_time=time.time()
        res=func(*args,**kwargs)
        stop_time=time.time()
        print("run time is %s"%(stop_time-start_time))
        return func(*args,**kwargs)
    return wrapper
def auth2(auth_type):
    def auth(func):
        def wrapper(*args,**kwargs):
            if current_login[\'name\'] and current_login[\'login\']:
                res=func(*args,**kwargs)
                return res
            if auth_type == "file":
                name=input("username:")
                password=input("password:")
                if name=="wen" and password =="123":
                    print("login successful!")
                    current_login[\'name\']=name
                    current_login[\'login\']=True
                    res=func(*args,**kwargs)
                    return res
                else:
                    print("auth error")
            elif auth_type == "sql":
                print("sql no learn.....")
        return wrapper
    return auth
@timmer
@auth2(auth_type="file")
def login(name):
    print("this is %s index page"%name)
login("wenyanjie")
输出结果:
one
username:wen
password:123
login successful!
this is wenyanjie index page
run time is 4.648637056350708
this is wenyanjie index page

Process finished with exit code 0



10.11.7  eval方法:往文件中放有结构的数据
 
Eval 可以将数据转换成相似的数据类型

10.11.7 被装饰函数将所有注释等自己的定义都传给装饰器


import time
from functools import wraps
def timmer(func):
    @wraps(func)
    def wrapper(*args,**kwargs):
        "wrapper function"
        print(func)
        start_time=time.time()
        res=func(*args,**kwargs)
        stop_time=time.time()
        print("run time is %s "%(stop_time-start_time))
        return res
    return wrapper
@timmer
def my_max(x,y):
    "my_max function"
    a=x if x>y else y
    return a
print(help(my_max))
输出结果:
my_max(x, y)
    my_max function




10.12 迭代器

10.12.1 迭代器定义
   迭代的意思是重复做一些事很多次,就像在循环中做的那样。
   只要该对象可以实现__iter__方法,就可以进行迭代。
   迭代对象调用__iter__方法会返回一个迭代器,所谓的迭代器就是具有next方法的对象。(在调用next方法时不需要任何参数)。在调用next方法时,迭代器会返回它的下一个值。如果next方法被调用,但迭代器没有值可以返回,就会引发一个StopITeration异常。
   一个实现了__iter__方法的对象是可迭代的,一个实现了next方法的对象是迭代器。
   迭代器也有__iter__方法。


10.12.2 迭代器特性
优点:
1 迭代器提供了一种不依赖索引的取值方式,这样就可以遍历那些没有索引的可迭代对象了(字典,集合,文件)
2 迭代器与列表比较,迭代器是惰性计算的,更节省内存
缺点:
1 无法获取迭代器的长度,使用不如列表索引取值灵活 
2 一次性的,只能往后取值,不能倒着取值

迭代规则的关键是?为什么不是使用列表?
1 如果有一个函数,可以一个接一个地计算值,那么在使用时可能是计算一个值时获取一个值,而不是像列表一样获取所有的值。如果有很多值,列表会占用太多的内存。
2 使用迭代器更通用,更简单,更优雅

10.12.3 迭代器代码

(1)索引方式循环
l=[\'a\',\'b\',\'c\',\'d\']
i=0
while i<len(l):
    print(l[i])
    i+=1
for i in range(len(l)):
    print(l[i])

(2)只要被内置了__iter__方法就是可迭代对象
可迭代的:只要对象本身有__iter__方法,那他就是可迭代的

l=[\'a\',\'b\',\'c\',\'d\']
l.__iter__()  #相当于iter(l)
iter(l)
i=iter(l)

i  就是迭代器,i 就是列表l的迭代器

(3)迭代器的__next__方法
迭代器使用__next__方法,可以不依赖下标取值,超出字典长度会有结束报错。
调用__next__方法必须是迭代器,也只有迭代器才有__next__方法。

l=[\'a\',\'b\',\'c\',\'d\']
i=iter(l)
print(i.__next__())
print(i.__next__())
print(i.__next__())
print(i.__next__())
print(i.__next__())
输出结果:
a
b
c
d
StopIteration  #报错


(4)while循环与迭代器
d={\'a\':1,\'b\':2,\'c\':3,\'d\':4} 
i=iter(d)
while True:
    try:
        print(next(i))
    except StopIteration:
        break
输出结果:
b
c
a
d

 

(5)for循环与迭代器
在这里for就是使用可迭代对象的__iter__方法返回此可迭代对象的迭代器,并且执行此迭代器的next方法,并且出现StopITeration报错就结束
For循环与列表
d={\'a\':1,\'b\':2,\'c\':3,\'d\':4}
for i in d:
    print(i)

For循环与文件
with open(\'a.txt\',\'r\') as f:
    for line in f:
        print(line.strip())


(6)迭代器也有__iter__方法
f 的迭代器也有__iter__方法,并且指向f这个迭代器本身
 
  

 
(7)迭代器是一次性的
只能往后取值,不能倒着取值

可迭代对象不是一次性的。

d={\'a\':1,\'b\':2,\'c\':3,\'d\':4}
i=iter(d)
print(next(i))
print(next(i))
print(next(i))
print(next(i))
for x in i:       #迭代器i已经将上面的值取完,for循环在这里没有值可取
print(x)
输出结果:
d
a
b
c

(8)isinstance判断对象是否是可迭代对象和迭代器对象

Isinstance使用例子:
print(type(\'ssssss\') is str)
print(isinstance("ssssss",str))
输出结果:
True
True

用isinstance判断是不是可迭代对象与迭代器对象
#查看可迭代对象和迭代器对象
from collections import Iterable,Iterator
str1=\'hello\'
list1=[1,2,3]
tuple1=(1,2,3)
dict1={"a":1,"b":2,"c":3}
set1={1,2,3,4}
f1=open(\'a.txt\')
#都是可迭代的
str1.__iter__()
list1.__iter__()
tuple1.__iter__()
dict1.__iter__()
set1.__iter__()
f1.__iter__()
print(isinstance(str1,Iterable))     #True
print(isinstance(list1,Iterable))     #True
print(isinstance(tuple1,Iterable))   #True
print(isinstance(dict1,Iterable))    #True
print(isinstance(set1,Iterable))     #True
print(isinstance(f1,Iterable))       #True
#查看是否是迭代器
print(isinstance(str1,Iterator))     #False
print(isinstance(list1,Iterator))     #Flase
print(isinstance(tuple1,Iterator))   #Flase
print(isinstance(dict1,Iterator))    #Flase
print(isinstance(set1,Iterator))    #Flase
print(isinstance(f1,Iterator))      #Flase


10.13 生成器

10.13.1 生成器函数定义

生成器就是一个函数,这个函数内包含有yield这个关键字
返回一个生成器对象。生成器本质就是迭代器
  生成器是一个包含yield关键字的函数。当它被调用时,在函数体中的代码不会被执行,而会返回一个迭代器。每次请求一个值,就会请求生成器中的代码,直到遇到一个yield或者return语句。Yield语句意味着生成一个值。Return语句意味着生成器要停止执行。
生成器是由两部分组成的:生成器的函数和生成器的迭代器。生成器的函数是有def语句定义的,包含yield部分。生成器的迭代器是这个函数返回的部分。生成器函数返回的迭代器可以像其他迭代器那样使用。

10.13.2  yield作用
生成器与return有何区别?
  Return只能返回一次函数就彻底结束了,而yield返回多次值

yield到底干了什么事情?
  1 yield把函数变成生成器-,生成器实质就是迭代器。 相当于把__iter__和__next__方法封装到函数内部。    
  2 用return返回值能返回一次,而yield返回多次
  3 函数在暂停以及继续下一次运行时的状态是由yield保存

   任何包含yield语句的函数称为生成器。Yield不是像return那样返回值,而是每次产生多个值。生成器每次产生一个值(使用yield语句),函数就会被冻结:即函数停在那点等待被重新唤醒。函数被重新唤醒之后就从停止的那点开始。

10.13.3 生成器运行详解

Test函数中yield返回给g一个生成器,实质就是迭代器,通过next方法取值,并且在取值时会执行test函数和取yield的返回值。即next函数会触发生成器函数g的运行,遇到yield停止
from collections import Iterable,Iterator
def test():
    print("first")
    yield 1
g=test()
print(g)
print(next(g))
print(isinstance(g,Iterable))
print(isinstance(g,Iterator))
输出结果:
<generator object test at 0x000000E8CBDE5360>
first
1
True
True


def test():
    print("one")
    yield 1
    print("two")
    yield 2
    print("three") 
    yield 3
g=test()
res=next(g)
print(res)
res=next(g)
print(res)
res=next(g)
print(res)
res=next(g)
print(res)
输出结果为:
one
1
two
2
three
3
StopIteration  #报错


def test():
    print("one")
    yield 1
    print("two")
    yield 2
    print("three")
    yield 3
g=test()
for i in g:
    print(i)
输出结果:
one
1
two
2
three
3



一个yield可以返回多个值,接收的类型是元组
yield可以返会任何类型的值

def test():
    yield 1,2,3,4
g=test()
print(next(g))
输出结果:
(1,2,3,4)



生成器与return有何区别?
  Return只能返回一次函数就彻底结束了,而yield返回多次值

yield到底干了什么事情?
  1 yield把函数变成生成器--->迭代器
  2 用return返回值能返回一次,而yield返回多次
  3 函数在暂停以及继续下一次运行时的状态是由yield保存

例子:

def countdown(n):
    print("start countnum")
    while n>0:
        yield n
        n-=1
    print(\'done\')
g=countdown(5)
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
try:
    print(next(g))      #这一次next函数应用没有yield返回,会报错StopIteration,需要try检测
except StopIteration:
    pass
g=countdown(5)
print(next(g))
for i in g:    #for会将g变成迭代器:iter(g),执行next取值,并捕捉next出现的异常
    print(i)
输出结果为:
start countnum
5
4
3
2
1
done
start countnum
5
4
3
2
1
done


10.13.4 用生成器模拟管道符作用

tailf命令的实现
1 普通函数实现
#/usr/bin/env python
import time
def tail(file_path):
    with open(file_path,\'r\') as f:
        f.seek(0,2)
        while True:
            line=f.readline()
            if not line:
                time.sleep(0.3)
                continue
            else:
                print(line,end="")

tail("/tmp/a.txt")

2 生成器函数实现
#/usr/bin/env python
import time
def tail(file_path):
    with open(file_path,\'r\') as f:
        f.seek(0,2)
        while True:
            line=f.readline()
            if not line:
                time.sleep(0.3)
                continue
            else:
                yield line
g=tail("/tmp/a.txt")
print(g)
for line in g:
print(line)


tail -f |grep “error” 实现

#/usr/bin/env python
import time
#定义阶段:定义两个生成器函数
def tail(file_path):
    with open(file_path,\'r\') as f:
        f.seek(0,2)
        while True:
            line=f.readline()
            if not line:
                time.sleep(0.3)
                continue
            else:
                yield line
def grep(pattern,lines):
    for line in lines:
        if pattern in line:
            yield line
#调用阶段:得到两生成器对象
g=tail("/tmp/a.txt")
gg=grep("error",g)
#next触发执行gg生成器函数
for line in gg:
    print(line)


"""
1 迭代器的应用
  文件名:a.txt  文件内容如下:
      apple 10 3
      tesla 100000 1
      mac 3000 2
      lenovo 30000 3
      chicken 10 3
   实现功能:cat a.txt|grep apple
   要求1:定义迭代器函数cat
   要求2:定义迭代器函数grep
   要求3:模拟管道的功能,将cat的处理结果作为grep的输入

"""
def cat(file_auth):
    with open(file_auth,"r") as f:
        f.seek(0)
        for line in f:
            yield line
def grep(pattern,lines):
    for line in lines:
        if pattern in line:
            yield line
cat_func=cat("/tmp/a.txt")
grep_func=grep("apple",cat_func)
for line in grep_func:
    print(line)


10.14 协程函数

10.14.1 协程函数定义
如果在一个函数内部yield的使用方式是表达式形式的话,如x=yield,那么该函数成为协程函数。
yield是表达式方式使用,则必须用send方法进行传值。
协程函数=生成器+yield表达式+send方法

10.14.2 e.send与next(e)的区别
e.send与next(e)的区别
1 如果函数内yield是表达式形式,那么必须先next(e)
  使用send方法只有在生成器挂起之后才有意义(也就是在yield函数第一次被执行之后),因此要用send方式时需要用next方法初始化生成器。
 用next(e)初始化  相当于 e.send(None) ,可以用装饰器解决初始化问题
2 二者的共同之处是都可以让函数在上次暂停的位置继续运行,不一样的地方在于send在触发下一次代码的执行时,会顺便给yield传一个值。

send可以传多个值,但是必须是元组类型

 


10.14.3 协程函数举例

def eater(name):
    print("%s start to eat food"%name)
    while True:
        food=yield
        print("%s get %s,to start eat"%(name,food))
    print("done")
e=eater("people")
print(e)
next(e)
e.send("apple")
e.send("fruit")
e.send("beef")
e.send("meet")
输出结果:
<generator object eater at 0x000000F944583D00>
people start to eat food
people get apple,to start eat
people get fruit,to start eat
people get beef,to start eat
people get meet,to start eat


def eater(name):
    print("%s start to eat food"%name)
    food_list=[]
    while True:
        food=yield food_list
        print("%s get %s,to start eat"%(name,food))
        food_list.append(food)
    print("done")
e=eater("people")
print(e)
next(e)

print(e.send("apple"))
print(e.send("fruit"))
print(e.send("beef"))
print(e.send("meet"))
输出结果为:
<generator object eater at 0x0000006F92D83D00>
people start to eat food
people get apple,to start eat
[\'apple\']
people get fruit,to start eat
[\'apple\', \'fruit\']
people get beef,to start eat
[\'apple\', \'fruit\', \'beef\']
people get meet,to start eat
[\'apple\', \'fruit\', \'beef\', \'meet\']

"""
2 生成器的应用
把下述函数改成生成器的形式,执行生成器函数的到一个生成器g,
然后g.send(url),打印页面的内容,利用g可以无限send

"""
from  urllib.request import urlopen
def get(url):
    while True:
        def index():
            print(url)
            print(urlopen(url).read())
        url=yield index

g=get("http://www.baidu.com")
next(g)
baidu=g.send("http://www.baidu.com")
baidu()

 



10.14.4 协程函数用装饰器初始化
协程函数使用装饰器完成初始化
def auth(func):
    def wrapper(*args,**kwargs):
        res=func(*args,**kwargs)
        next(res)
        return res
    return wrapper
@auth
def eater(name):
    print("%s start to eat food"%name)
    food_list=[]
    while True:
        food=yield food_list
        print("%s get %s,to start eat"%(name,food))
        food_list.append(food)
    print("done")

e=eater("people")
e.send("123")

 


10.15 面向过程的编程思想及举例

写程序时:
要先想功能,分步实现

10.15.1 os模块中walk输出目录中文件路径
os.walk() 方法用于通过在目录树中游走输出在目录中的文件名,向上或者向下。
 
 
 


 

 



10.15.2 实现面向过程的编程举例

Send可以传多个值,但是必须是元组类型

面向过程的编程思想
像流水线,代码简洁,体系结构


"""
 实现对一个目录下面(包含子目录下面)有一行包含过滤字符串就输出其文件名的绝对路径
  C:\python_fullstack_wen\day24\wen
"""

import time,os
#定义
def init(func):
    "装饰器"
    def wrapper(*args,**kwargs):
        res=func(*args,**kwargs)
        next(res)
        return res
    return wrapper

@init
def search(target):
    "找到目录下所有文件名的绝对路径"
    while True:
        dir_path=yield
        g=os.walk(dir_path)
        for i in g:
            for j in i[-1]:
                file_path="%s\\%s"%(i[0],j)
                target.send(file_path)
@init
def opener(target):
    "打开文件,返回文件句柄"
    while True:
        file_path=yield
        with open(file_path) as f:
            target.send((file_path,f))
@init
def cat(target):
    "查看文件,返回每行内容"
    while True:
        file_path,f=yield
        for line in f:
            target.send((file_path,line))
@init
def grep(pattern,target):
    "过滤这行,如果符合返回文件路径"
    while True:
        file_path,line=yield
        if pattern in line:
            target.send(file_path)
@init
def printer():
    "打印"
    while True:
        file_path=yield
        print(file_path)

#调用
g=search(opener(cat(grep("wenyanjie",printer()))))
g.send("C:\python_fullstack_wen\day24\wen")





 


 

 


 


 

 

 


10.15.3 简单方法实现上面程序

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

__author__ = \'weihuchao\'

import os

def search(dir_name, partten):
    g = os.walk(dir_name)
    for i in g:
        for j in i[-1]:
            file_path = i[0] + "\\" +j
            with open(file_path) as f:
                for line in f:
                    if partten in line:
                        print(file_path)

search("c:\\test", "python")



10.15 列表推导式


10.15.1 列表推导式定义

列表推导式能非常简洁的构造一个新列表:只用一条简洁的表达式即可对得到的元素进行转换变形

10.15.2 列表推导式语法
基本格式如下:
[expr for value in collection ifcondition]
过滤条件可有可无,取决于实际应用,只留下表达式
 

 


列表推导式例子:
l=["egg%s"%i for i in range(10)]
print(l)
类似于这段for代码:
egg_list=[]
for i in range(10):
    egg_list.append("egg%s"%i)
print(egg_list)

列表推导式还可以加更多的if判断for循环
l=[\'egg%s\' %i for i in range(1,101)]
l=[\'egg%s\' %i for i in range(1,101) if i >50 ]
l=[\'egg%s\' %i for i in range(1,101) if i >50 if i<60]
print(l)


10.15.3 列表推导式优点
方便,改变了编程习惯,属于声明式编程

举例:
l=[1,2,3,4]
s="hello"
l1=[(num,i) for num in l for i in s]
print(l1)
输出结果为:
[(1, \'h\'), (1, \'e\'), (1, \'l\'), (1, \'l\'), (1, \'o\'), (2, \'h\'), (2, \'e\'), (2, \'l\'), (2, \'l\'), (2, \'o\'), (3, \'h\'), (3, \'e\'), (3, \'l\'), (3, \'l\'), (3, \'o\'), (4, \'h\'), (4, \'e\'), (4, \'l\'), (4, \'l\'), (4, \'o\')]

这个列表推导式相当于:
l=[1,2,3,4]
s="hello"
l1=[]
for num in l:
    for i in s:
        t=(num,i)
        l1.append(t)
print(l1)

 

10.15.4 列表表达式例子

import os
g=os.walk("C:\python_fullstack_wen\day24\wen")
file_path_list=[]
for i in g:
    for j in i[-1]:
        file_path_list.append("%s\\%s"%(i[0],j))
print(file_path_list)

g=os.walk("C:\python_fullstack_wen\day24\wen")
file_path_list=["%s\\%s"%(i[0],j) for i in g for j in i[-1]]
print(file_path_list)
输出结果:
[\'C:\\python_fullstack_wen\\day24\\wen\\yan1.txt\', \'C:\\python_fullstack_wen\\day24\\wen\\wen1\\yan1.txt\', \'C:\\python_fullstack_wen\\day24\\wen\\wen1\\jie1\\yan1.txt\', \'C:\\python_fullstack_wen\\day24\\wen\\wen1\\yan1\\yan1.txt\', \'C:\\python_fullstack_wen\\day24\\wen\\wen2\\yan2.txt\']
[\'C:\\python_fullstack_wen\\day24\\wen\\yan1.txt\', \'C:\\python_fullstack_wen\\day24\\wen\\wen1\\yan1.txt\', \'C:\\python_fullstack_wen\\day24\\wen\\wen1\\jie1\\yan1.txt\', \'C:\\python_fullstack_wen\\day24\\wen\\wen1\\yan1\\yan1.txt\', \'C:\\python_fullstack_wen\\day24\\wen\\wen2\\yan2.txt\']


可参考: http://www.jb51.net/article/67157.htm


10.16 生成器表达式   
10.16.1 生成器表达式定义    


生成器表达式并不真正的创建数字列表,而是返回一个生成器对象,此对象在每次计算出一个条目后,把这个条目"产生"(yield)出来。生成器表达式使用了"惰性计算"或称作"延时求值"的机制。生成器表达式可以用来处理大数据文件。

序列过长,并且每次只需要获取一个元素时,应该考虑生成器表达式而不是列表解析。
    生成器表达式产生的是一个生成器对象,实质就是迭代器。

10.16.2 生成器表达式语法
语法:
  (expression for iter_val in iterable)
  (expression for iter_val in iterable if cond_expr)


g=("egg%s"%i for i in range(100))
print(g)
print(next(g))
print(next(g))
输出结果为:
<generator object <genexpr> at 0x0000007E9A403D00>
egg0
egg1

可以处理大数据文件:

f=open("a.txt")
l=[]
for line in f:
    line = line.strip()
    l.append(line)
print(l)

f.seek(0)
l1=[line.strip() for line in f]
print(l1)

f.seek(0)
g=(line.strip() for line in f)
print(g)
print(next(g))
输出结果为:
[\'wen\', \'yan\', \'jie\']
[\'wen\', \'yan\', \'jie\']
<generator object <genexpr> at 0x0000000A2B173D00>
wen


10.16.3 List函数可以处理迭代器和可迭代对象

List后面可以跟可迭代对象,和for的实质是一样的。 List函数将可迭代对象使用iter方法,变成迭代器,然后使用迭代器的next方法遍历可迭代器的值,并存储为列表类型,在最后报错的时候结束。

文件a.txt的内容是
    wen
  yan
      jie
编程代码:
f=open(\'a.txt\')
g=(line.strip() for line in f)
l=list(g)
print(l)
输出结果为:
[\'wen\', \'yan\', \'jie\']


10.16.4 sum函数可以处理迭代器和可迭代对象
Sum后面可以跟可迭代对象,和for的实质是一样的。 Sum函数将可迭代对象使用iter方法,变成迭代器,然后使用迭代器的next方法遍历可迭代器的值,并,在最后报错的时候结束。

g=(i for i in range(10))
print(g)
print(sum(g))
print(sum(range(10)))
print(sum([0,1,2,3,4,5,6,7,8,9]))
输出结果:
<generator object <genexpr> at 0x0000008ED3FA3D00>
45
45
45
Sum中也可以跟可迭代的对象,跟for,list的工作实质类型
 


10.16.5 声明式编程

一种编程方式,将需要很多语句的代码写成声明变量的形式

 


10.16.6 生成器表达式举例
在文件a.txt中的内容:
apple 10 3
tesla 1000000 1
mac 3000 2
lenovo 30000 3
chicken 10 3

1 计算购买总共的花费:
以前的做法:
money_l=[]
with open(\'a.txt\') as f:
    for line in f:
        goods=line.split()
        res=float(goods[-1])*float(goods[-2])
        money_l.append(res)
print(money_l)
使用生成器表达式的做法
f=open(\'a.txt\')
g=(float(line.split()[-1])*float(line.split()[-2]) for line in f)
for i in g:
    print(i)
f=open(\'a.txt\')
g=(float(line.split()[-1])*float(line.split()[-2]) for line in f)
print(sum(g))
一句话做法:不要这样做,python代码不是要写少,而是要写好,能看懂,且逻辑好
with open(\'a.txt\') as f:
    print(sum(float(line.split()[-1])*float(line.split()[-2]) for line in f))


2 将a.txt文件中的每行内容转化为字典类型并且存储到列表
以前做法:
res=[]
with open(\'a.txt\') as f:
    for line in f:
        l=line.split()
        d={}
        d["name"]=l[0]
        d["price"]=l[1]
        d["count"]=l[2]
        res.append(d)
print(res)
输出结果:
[{\'price\': \'10\', \'name\': \'apple\', \'count\': \'3\'}, {\'price\': \'1000000\', \'name\': \'tesla\', \'count\': \'1\'}, {\'price\': \'3000\', \'name\': \'mac\', \'count\': \'2\'}, {\'price\': \'30000\', \'name\': \'lenovo\', \'count\': \'3\'}, {\'price\': \'10\', \'name\': \'chicken\', \'count\': \'3\'}]

生成器表达式做法
有报错的:
with open(\'a.txt\') as f:
    res=(line.split() for line in f)
    print(res)
    dic_g=({\'name\':i[0],\'price\':i[1],\'count\':i[2]} for i in res)
    print(dic_g)
print(dic_g)
print(next(dic_g))  #原因在于dic_g生成器迭代需要res生成器迭代,res生成器迭代需要f迭代器迭代,f是打开文件的句柄,一关闭,res生成器和dic_g生成器都不能使用
输出结果为:
<generator object <genexpr> at 0x00000044A0DA3D00>
<generator object <genexpr> at 0x00000044A0DA3E08>
<generator object <genexpr> at 0x00000044A0DA3E08>
ValueError: I/O operation on closed file.         #报错


正确生成器做法:
with open(\'a.txt\') as f:
    res=(line.split() for line in f)
    print(res)
    dic_g=({\'name\':i[0],\'price\':i[1],\'count\':i[2]} for i in res)
    print(dic_g)
    apple_dic=next(dic_g)
    print(apple_dic["count"])
输出结果为:
<generator object <genexpr> at 0x00000081D5243D00>
<generator object <genexpr> at 0x00000081D5243E08>
3


3 将a.txt文件中的每行内容转化为字典类型并且取出单价大于10000的商品存储到列表,
生成器表达式调用生成器表达式

with open(\'a.txt\') as f:
    res=(line.split() for line in f)
    print(res)
    dic_g=({\'name\':i[0],\'price\':i[1],\'count\':i[2]} for i in res if float(i[1]) >10000)
    print(dic_g)
    for i in dic_g:
        print(i)
输出结果为:
<generator object <genexpr> at 0x000000DB4C633D00>
<generator object <genexpr> at 0x000000DB4C633DB0>
{\'price\': \'1000000\', \'count\': \'1\', \'name\': \'tesla\'}
{\'price\': \'30000\', \'count\': \'3\', \'name\': \'lenovo\'}

with open(\'a.txt\') as f:
    res=(line.split() for line in f)
    print(res)
    dic_g=({\'name\':i[0],\'price\':i[1],\'count\':i[2]} for i in res if float(i[1]) >10000)
    print(dic_g)
print(list(dic_g))
输出结果为:
<generator object <genexpr> at 0x00000099A0953D00>
<generator object <genexpr> at 0x00000099A0953DB0>
[{\'price\': \'1000000\', \'name\': \'tesla\', \'count\': \'1\'}, {\'price\': \'30000\', \'name\': \'lenovo\', \'count\': \'3\'}]



今日作业
(1)有两个列表,分别存放来老男孩报名学习linux和python课程的学生名字
linux=[\'钢弹\',\'小壁虎\',\'小虎比\',\'alex\',\'wupeiqi\',\'yuanhao\']
python=[\'dragon\',\'钢弹\',\'zhejiangF4\',\'小虎比\']
问题一:得出既报名linux又报名python的学生列表
linux=[\'钢弹\', \'小壁虎\', \'小虎比\', \'alex\', \'wupeiqi\', \'yuanhao\']
python=[\'dragon\', \'钢弹\', \'zhejiangF4\', \'小虎比\']
li=[i for i in linux for j in python if i==j]
print(li)
li=(i for i in linux for j in python if i==j)
print(list(li))
问题二:得出只报名linux,而没有报名python的学生列表
li=[ i for i in linux if i not in python]
print(li)
li=(i for i in linux if i not in python)
print(list(li))
问题三:得出只报名python,而没有报名linux的学生列表
li=[i for i in python if i not in linux]
print(li)
li=(i for i in python if i not in linux)
print(list(li))

(2)
	shares={
	\'IBM\':36.6,
	\'lenovo\':27.3,
	\'huawei\':40.3,
	\'oldboy\':3.2,
	\'ocean\':20.1
	}
问题一:得出股票价格大于30的股票名字列表
li=( i for i,j in shares.items() if j > 30)
print(list(li))
问题二:求出所有股票的总价格
li=(float(j) for j in shares.values())
print(sum(li))
print(sum(float(j) for j in shares.values()))

(3)
l=[10,2,3,4,5,6,7]
得到一个新列表l1,新列表中每个元素是l中对应每个元素值的平方。过滤出l1中大于40的值,然后求和
l = [10, 2, 3, 4, 5, 6, 7]
l1=[i**2 for i in l]
print(l1)
l2=[i for i in l1 if i >40]
print(sum(l2))



10.17 内置函数

(数学运算类)
10.17.1 abs函数
求绝对值
1、参数可以是整型,也可以是负数
2、若参数是负数,则返回负数的模
print(abs(-1))
print(abs(0))


10.17.2 complex函数
complex([real[, imag]])
创建一个复数
 

10.17.3 divmod函数
divmod(a, b)
分别取商和余数
注意:整型、浮点型都可以
传入两个参数, 返回一个元组, 该元组有两个值, 一个是商, 一个是余数
print(divmod(10,3))
#(3, 1)

功能:用于前端分页

10.17.4 float函数
float([x])
将一个字符串或数转换为浮点数。如果无参数将返回0.0


10.17.5 int函数
int([x])
将一个字符转换为int类型,base表示进制

num=1  #相当于 num=int(1)   其他类型数据也是这样的
print(type(num))
#<class \'int\'>
print(isinstance(num,int))
#True
print(num is 1)    #is 是身份运算,根据is去判断身份
#True
print(type(num) is int)
#True

10.17.6 long函数
long([x[, base]]) 
将一个字符转换为long类型


10.17.7 pow函数
pow(x, y[, z]) 
返回x的y次幂,如果有z,返回x的y次幂对z整除的余数
print(pow(3,2))
print(pow(3,2,2))
输出结果为:
9
1
10.17.8 range函数
range([start], stop[, step]) 
产生一个序列,默认从0开始


10.17.9 round函数
round(x[, n]) 
四舍六入五留偶
print(round(10.4))
#10
print(round(10.5))
#10
print(round(10.6))
#11
 
10.17.10 sum函数
sum(iterable[, start]) 
对集合求和

print(sum((i for i in range(10))))
#45
print(sum(i for i in range(10)))
#45

10.17.11 oct函数
oct(x)
将整数x转换为16进制字符串
print(oct(10))
输出结果为:
0o12

10.17.12 chr函数
chr(i)
返回整数i对应的ASCII字符
print(chr(67))
#C
print(chr(65))
#A


10.17.12 ord函数
与chr()对应, 将字符转化为编码
print(ord(\'A\'))
#65

10.17.13 hex函数

print(hex(10))
#0xa
10.17.13 bin函数
bin(x)
将整数x转换为二进制字符串

print(bin(3))
#0b11
10.17.14 bool函数
bool([x])
将x转换为Boolean类型

print(bool(0))
#Flase
print(bool(None))
#Flase
print(bool(\'\'))
#Flase

(集合类操作)
10.17.15 basestring函数
 basestring()
str和unicode的超类
不能直接调用,可以用作isinstance判断


10.17.16 format函数
format(value [, format_spec])
格式化输出字符串
格式化的参数顺序从0开始,如“I am {0},I like {1}”


10.17.17 unichr函数
unichr(i)
返回给定int类型的unicode


10.17.18 enumerate函数
enumerate(sequence [, start = 0])
返回一个可枚举的对象,该对象的next()方法将返回一个tuple
for i in enumerate([\'a\',\'b\',\'c\',\'d\']):
print(i)
输出结果为:
(0, \'a\')
(1, \'b\')
(2, \'c\')
(3, \'d\')

for i in enumerate({\'x\':1,\'y\':2}):
    print(i)
输出结果为:
(0, \'y\')
(1, \'x\')

10.17.19 iter函数
iter(o[, sentinel])
生成一个对象的迭代器,第二个参数表示分隔符



10.17.20 max函数
max(iterable[, args...][key]) 
返回集合中的最大值

print(max(1,2,3,4,5,6))
#6

max函数中key参数的使用:
输出字典中值最大的键
dic={
    "wen":20,
    "yan":25,
    "jie":30,
    "hashiqi":35
}
print(max(dic))
#yan
print(max(dic.values()))
#35

def get_value(k):
    return dic[k]
print(get_value(\'wen\'))
#20
print(max(dic,key=get_value))
#hashiqi
#使用匿名函数实现函数部分
print(max(dic,key=lambda k:dic[k]))
#hashiqi
#使用zip函数实现
z=zip(dic.values(),dic.keys())
print(max(z))


print(max((1,\'a\'),(2,\'b\')))
#(2, \'b\')
print(max((2,\'a\'),(1,\'b\')))
#(2, \'a\')
print(max((1,\'a\'),(1,\'b\')))
#(1, \'b\')

10.17.21 min函数
min(iterable[, args...][key])
返回集合中的最小值

print(min(1,2,7,4,5))
#1

输出字典中值最小的键
dic={
    "wen":20,
    "yan":25,
    "jie":30,
    "hashiqi":35
}
print(min(dic))
#hashiqi
print(min(dic.values()))
#20

def get_value(k):
    return dic[k]
print(get_value(\'wen\'))
#20
print(min(dic,key=get_value))
#wen

lambda k:dic[k]
f=lambda k:dic[k]
print(f)
#<function <lambda> at 0x000000F44C8FAF28>
print(f(\'wen\'))
#20
#使用匿名函数实现函数部分
print(min(dic,key=lambda k:dic[k]))
#wen

10.17.22 dict函数
dict([arg])
创建数据字典。  被称为工厂函数,能生产字典类型的数据
d={\'a\':1}  
d=dict({\'a\':1})
print(d)
# {\'a\': 1}
d=dict(x=1,y=2,z=3)
print(d)
# {\'z\': 3, \'x\': 1, \'y\': 2}

10.17.23 list函数
list([iterable]) 
将一个集合类转换为另外一个集合类

x=[]
x=list(i for i in range(10))
print(x)
#[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

10.17.24 set函数
set()
set对象实例化
s={1,2,3,4,5}
print(s)
#{1, 2, 3, 4, 5}
s.add(6)
print(s)
#{1, 2, 3, 4, 5, 6}


10.17.25 frozenset函数
frozenset([iterable])
产生一个不可变的set
f=frozenset({1,2,3,4,5})
print(type(f))
# <class \'frozenset\'>

10.17.26 str函数
str([object]) 
转化成string类型

x=\'wenyanjie\'
x=str(\'wenyanjie\')
print(x)
#wenyanjie
print(str(1))
#1
print(type(str(1)))
#<class \'str\'>
print(str({\'a\':1}))
#{\'a\': 1}
print(type(str({\'a\':1})))
#<class \'str\'>



10.17.27 sorted函数
sorted(iterable[, cmp[, key[, reverse]]]) 
对集合排序
sorted返回值是列表,默认是升序。需要加reverse=True参数确定使用降序。
l=[3,4,9,5,6,10]
print(sorted(l))
print(sorted(l,reverse=True))
输出结果为:
[3, 4, 5, 6, 9, 10]
[10, 9, 6, 5, 4, 3]

s=\'hello abc\'
print(sorted(s))
print(sorted(s,reverse=True))
输出结果为:
[\' \', \'a\', \'b\', \'c\', \'e\', \'h\', \'l\', \'l\', \'o\']
[\'o\', \'l\', \'l\', \'h\', \'e\', \'c\', \'b\', \'a\', \' \']

Sorted函数与匿名函数相结合例子
dic={
    "wen":20,
    "yan":25,
    "jie":30,
    "hashiqi":35
}
print(sorted(dic))
print(sorted(dic,key=lambda x:dic[x]))
print(sorted(dic,key=lambda x:dic[x],reverse=True))
输出结果为:
[\'hashiqi\', \'jie\', \'wen\', \'yan\']
[\'wen\', \'yan\', \'jie\', \'hashiqi\']
[\'hashiqi\', \'jie\', \'yan\', \'wen\']

10.17.28 tuple函数
tuple([iterable]) 
生成一个tuple类型




10.17.29 xrange函数
xrange([start], stop[, step]) 
xrange()函数与range()类似,但xrnage()并不创建列表,而是返回一个xrange对象,它的行为与列表相似,但是只在需要时才计算列表值,当列表很大时,这个特性能为我们节省内存




(逻辑判断)
10.17.30 all函数
all(iterable)
1、集合中的元素都为真的时候为真
2、特别的,若为空串返回为True

# all可以传可迭代对象,
print(all(\'\'))
#True
print(all((1,2,3)))
#True
print(all((1,2,3,None)))
#False
print(all((1,\' \',3,None)))
#False
print(all((1,0,3,None)))
#False
#all函数中的生成器表达式不加括号也行,sum也是一样
print(all(i for i in range(1,10)))
#True
print(all((i for i in range(1,10))))
#True


10.17.31 any函数
any(iterable)
1、集合中的元素有一个为真的时候为真
2、特别的,若为空串返回为False

#报错,必须有参数
# print(any())
print(any(""))
#Flase
print(any(["",1]))
#True
10.17.32 cmp函数
cmp(x, y)
如果x < y ,返回负数;x == y, 返回0;x > y,返回正数


(反射)
10.17.33 callable函数
callable(object)
检查对象object是否可调用
1、类是可以被调用的
2、实例是不可以被调用的,除非类中声明了__call__方法

def test():
    pass
print(callable(test))
#True
print(callable(sum))
#True

10.17.34 classmethod函数
classmethod()
1、注解,用来说明这个方式是个类方法
2、类方法即可被类调用,也可以被实例调用
3、类方法类似于Java中的static方法
4、类方法中不需要有self参数

10.17.35 compile函数
compile(source, filename, mode[, flags[, dont_inherit]])
将source编译为代码或者AST对象。代码对象能够通过exec语句来执行或者eval()进行求值。
1、参数source:字符串或者AST(Abstract Syntax Trees)对象。
2、参数 filename:代码文件名称,如果不是从文件读取代码则传递一些可辨认的值。
3、参数model:指定编译代码的种类。可以指定为 ‘exec’,’eval’,’single’。
4、参数flag和dont_inherit:这两个参数暂不介绍




10.17.36 dir函数
dir([object])
1、不带参数时,返回当前范围内的变量、方法和定义的类型列表;
2、带参数时,返回参数的属性、方法列表。
3、如果参数包含方法__dir__(),该方法将被调用。当参数为实例时。
4、如果参数不包含__dir__(),该方法将最大限度地收集参数信息


10.17.37 delattr函数
delattr(object, name)
删除object对象名为name的属性

10.17.38 eval函数
eval(expression [, globals [, locals]])
Eval函数获取传入的字符串, 把他当成命令执行

>>> l = eval("[1,2,3,4]")
>>> l
[1, 2, 3, 4]
10.17.39 execfile函数
execfile(filename [, globals [, locals]])
用法类似exec(),不同的是execfile的参数filename为文件名,而exec的参数为字符串。


10.17.40 filter函数
filter(function, iterable)
构造一个序列,等价于[ item for item in iterable if function(item)]
1、参数function:返回值为True或False的函数,可以为None
2、参数iterable:序列或可迭代对象

fileter()函数用于过滤, 和map()类似取一个元素放入函数中运行, 如果运行结果为True则放入生成器中, 否则不放入, 最后返回这个生成器

name_l=[
    {\'name\':\'egon\',\'age\':18},
    {\'name\':\'wen\',\'age\':25},
    {\'name\':\'yan\',\'age\':35},
    {\'name\':\'jie\',\'age\':45}
]
f=filter(lambda d:d[\'age\']>20,name_l)
print(f)
for i in f:
print(i)
输出结果为:
<filter object at 0x000000062AB80748>
{\'age\': 25, \'name\': \'wen\'}
{\'age\': 35, \'name\': \'yan\'}
{\'age\': 45, \'name\': \'jie\'}




10.17.41 getattr函数
getattr(object, name [, defalut])
获取一个类的属性


10.17.42 global函数
globals()
返回一个描述当前全局符号表的字典


10.17.43 hasattr函数
hasattr(object, name)
判断对象object是否包含名为name的特性



10.17.44 hash函数
hash(object)
如果对象object为哈希表类型,返回对象object的哈希值。
Hash函数用于校验数据的完整性
这要用的hash算法是一样的,那么hash值是一样长度的。
Hash值不能逆推

print(hash(\'wenyanjie\'))
print(hash(\'wenyanjie\'))
输出结果为:
2173869041879882178
2173869041879882178

s=\'wenyanjie\'
print(hash(s))
s=\'wen\'
print(hash(s))
输出结果为:
2173869041879882178
-4035825207384514179


10.17.45 id函数
id(object)
返回对象的唯一标识    获得对象的身份(一般说是内存地址)


Python的优化策略对占内存空间小的值开辟一样的空间,数字是-5-256,相同字符串都是一样的内存地址
a=1
b=2
print(id(a))
print(id(b))
print(a is b)
输出结果为:
1505816368
1505816400
False

x=\'a\'
y=\'a\'
print(id(x))
print(id(y))
print(x is y)
输出结果为:
437092047200
437092047200
True


10.17.46 isinstance函数
isinstance(object, classinfo)
判断object是否是class的实例


10.17.47 issubclass函数
issubclass(class, classinfo)
判断是否是子类



10.17.48 len函数
len(s) 
返回集合长度



10.17.49 local函数
locals() 
返回当前作用域的变量列表



10.17.50 map函数
map(function, iterable, ...) 
遍历每个元素,执行function操作。Map函数的返回值是一个迭代器

map()函数有两个参数, 一个是处理函数, 另一个是可迭代对象, map取出可迭代对象的元素, 进行前面的函数操作, 形成新的迭代器的元素, 最终返回该迭代器。

在列表每个元素后面加_haha:
name_l=[\'wen\',\'yan\',\'jie\',\'hashiqi\']
m=map(lambda x:x+"_haha",name_l)
print(list(m))
输出结果:
[\'wen_haha\', \'yan_haha\', \'jie_haha\', \'hashiqi_haha\']

将列表的每个值都写成此值的2的幂:
l=[1,2,3,4,7,5]
m=map(lambda item:item**2,l)
print(m)
print(list(m))
# for i in m:
#     print(i)
输出结果为:
<map object at 0x0000003DC1FF0588>
[1, 4, 9, 16, 49, 25]
10.17.51 memoryview函数
memoryview(obj) 
返回一个内存镜像类型的对象


10.17.52 next函数
next(iterator[, default]) 
类似于iterator.next()


10.17.53 object函数
object() 
基类


10.17.54 property函数
property([fget[, fset[, fdel[, doc]]]]) 
属性访问的包装类,设置后可以通过c.x=value等来访问setter和getter



10.17.55 reduce函数
reduce(function, iterable[, initializer]) 
合并操作,从第一个开始是前两个参数,然后是前两个的结果与第三个合并进行处理,以此类推

reduce()函数性质同map()函数, 只是传入的函数需要设置两个参数, 具体功能是每次取迭代器中的两个元素,放入函数操作, 下次取出一个值和上一次运行的结果继续在函数中运行, 最后得到一个值并且返回

from functools import reduce
l=list(range(10))
print(l)
print(reduce(lambda x,y:x+y,l))
print(reduce(lambda x,y:x+y,l,10))
输出结果为:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
45
55
<class \'int\'>

 
10.17.56 reload函数
reload(module) 
重新加载模块



10.17.57 setattr函数
setattr(object, name, value)
设置属性值


10.17.58 repr函数
repr(object) 
将一个对象变换为可打印的格式


10.17.59 staticmethod函数
staticmethod
声明静态方法,是个注解


10.17.60 super函数
super(type[, object-or-type]) 
引用父类




10.17.61 type函数
type(object)
返回该object的类型


10.17.62 vars函数
vars([object]) 
返回对象的变量,若无参数与locals()方法类似

print(vars() is locals())
输出结果为:
True


10.17.63 bytearray函数
bytearray([source [, encoding [, errors]]])
返回一个byte数组
1、如果source为整数,则返回一个长度为source的初始化数组;
2、如果source为字符串,则按照指定的encoding将字符串转换为字节序列;
3、如果source为可迭代类型,则元素必须为[0 ,255]中的整数;
4、如果source为与buffer接口一致的对象,则此对象也可以被用于初始化bytearray.







10.17.64 zip函数
zip([iterable, ...]) 
拉链函数, 用于将参数中的可迭代对象的元素组个弄出来匹配成一个一个的元组。
l1=[1,2,3]
s=\'hel\'
print(zip(l1,s))
# <zip object at 0x000000F3E6DD1D08>    zip函数返回的是一个迭代器
for i in zip(l1,s):
    print(i)
#(1, \'h\')
#(2, \'e\')
#(3, \'l\')
Zip函数仅仅返回匹配的值,不匹配的不会返回。
l1=[1,2,3,4]
s=\'hel\'
for i in zip(l1,s):
print(i)
输出结果:
(1, \'h\')
(2, \'e\')
(3, \'l\')


dic={
    "wen":20,
    "yan":25,
    "jie":30,
    "hashiqi":35
}
print(dic.keys(),dic.values())
z=zip(dic.keys(),dic.values())
print(z)
for i in z:
print(i)
z=zip(dic.values(),dic.keys())
print(max(z))
输出结果为:
dict_keys([\'hashiqi\', \'wen\', \'yan\', \'jie\']) dict_values([35, 20, 25, 30])
<zip object at 0x0000004009102EC8>
(\'hashiqi\', 35)
(\'wen\', 20)
(\'yan\', 25)
(\'jie\', 30)
(35, \'hashiqi\')

(IO操作)
10.17.65 file函数
file(filename [, mode [, bufsize]])

file类型的构造函数,作用为打开一个文件,如果文件不存在且mode为写或追加时,文件将被创建。添加‘b’到mode参数中,将对文件以二进制形式操作。添加‘+’到mode参数中,将允许对文件同时进行读写操作
1、参数filename:文件名称。
2、参数mode:\'r\'(读)、\'w\'(写)、\'a\'(追加)。
3、参数bufsize:如果为0表示不进行缓冲,如果为1表示进行行缓冲,如果是一个大于1的数表示缓冲区的大小 。


10.17.66 input函数
input([prompt]) 
获取用户输入


10.17.67 open函数
open(name[, mode[, buffering]]) 
打开文件
与file有什么不同?推荐使用open



10.17.68 print函数
print
打印函数


10.17.69 raw_input函数
raw_input([prompt]) 
设置输入,输入都是作为字符串处理

10.17.70 help函数
help()--帮助信息
print(help(sum))
输出结果为:
Help on built-in function sum in module builtins:

sum(iterable, start=0, /)
    Return the sum of a \'start\' value (default: 0) plus an iterable of numbers
    
    When the iterable is empty, return the start value.
    This function is intended specifically for use with numeric values and may
    reject non-numeric types.

None

10.17.71 __import__函数
__import__()   导入模块

导入time模块
import time
print(time)
time.sleep(3)
输出结果为:
<module \'time\' (built-in)>
或者用
m=__import__("time")
print(m)
m.sleep(2)
输出结果为:
<module \'time\' (built-in)>



10.17.72 dir函数
dir() 
查看某数据的方法  
方法和函数的区别:
  1 调用方法不一样 函数括号 方法点号
  2 方法只对调用它的对象有用
函数没有绑定任何对象
方法可以绑定到具体的对象
 
 


l=\'wenyanjie\'
print(dir(l))
输出结果为:
[\'__add__\', \'__class__\', \'__contains__\', \'__delattr__\', \'__dir__\', \'__doc__\', \'__eq__\', \'__format__\', \'__ge__\', \'__getattribute__\', \'__getitem__\', \'__getnewargs__\', \'__gt__\', \'__hash__\', \'__init__\', \'__iter__\', \'__le__\', \'__len__\', \'__lt__\', \'__mod__\', \'__mul__\', \'__ne__\', \'__new__\', \'__reduce__\', \'__reduce_ex__\', \'__repr__\', \'__rmod__\', \'__rmul__\', \'__setattr__\', \'__sizeof__\', \'__str__\', \'__subclasshook__\', \'capitalize\', \'casefold\', \'center\', \'count\', \'encode\', \'endswith\', \'expandtabs\', \'find\', \'format\', \'format_map\', \'index\', \'isalnum\', \'isalpha\', \'isdecimal\', \'isdigit\', \'isidentifier\', \'islower\', \'isnumeric\', \'isprintable\', \'isspace\', \'istitle\', \'isupper\', \'join\', \'ljust\', \'lower\', \'lstrip\', \'maketrans\', \'partition\', \'replace\', \'rfind\', \'rindex\', \'rjust\', \'rpartition\', \'rsplit\', \'rstrip\', \'split\', \'splitlines\', \'startswith\', \'strip\', \'swapcase\', \'title\', \'translate\', \'upper\', \'zfill\']

函数bytes
将字符串转换成字节格式
print(bytes(\'hello\',encoding="utf8"))
# b\'hello\'

10.17.72 reversed函数
reversed函数的参数必须是一个序列,不能是一个迭代器

print(reversed([1,2,3,4]))
print(list(reversed([1,2,3,4])))
输出结果:
<list_reverseiterator object at 0x00000027FA112F98>
[4, 3, 2, 1]


10.17.73 slice函数
分片
l=[1,2,3,4,5]
print(l[2:4])
s=slice(2,4)
print(l[s])
输出结果为:
[3, 4]
[3, 4]



要让机器或别人帮你做事


10.18 匿名函数

10.18.1 匿名函数语法
匿名函数lambda x: x * x实际上就是:
def f(x):
    return x * x
关键字lambda表示匿名函数,冒号前面的x表示函数参数。

匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。

10.18.2 匿名函数举例

用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:
>>> f = lambda x: x * x
>>> f
<function <lambda> at 0x10453d7d0>
>>> f(5)
25
同样,也可以把匿名函数作为返回值返回,比如:

def build(x, y):
    return lambda: x * x + y * y





10.19 递归函数

10.19.1 递归函数定义

递归就是引用(或者调用)自己的意思

有用的递归函数包含以下几个部分:
1 必须有一个明确的结束条件
2 每次进入更深一层递归时,问题规模相比于上次递归都应有减少
3 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用时通过栈stack这种数据结构实现的,每当进入一个函数调用,栈就会加一层帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出,程序会以一个“超过最大递归深度”的错误信息结束)
  递归中每次函数被调用,针对这个调用函数的新命名空间会被创建,意味着当函数调用“自身”时,实际上运行的是两个不同的函数(或者说是同一个函数具有两个不同的命名空间)

递归的深度:
import sys
print(sys.getrecursionlimit())
输出结果为:
1000

10.19.2 递归函数实现二分法举例

二分法举例:
data=list(range(1000))
def sear(num,data):
    if len(data)>1:
        #二分
        mid_index=int(len(data)/2)
        mid_value=data[mid_index]
        if num > data[mid_index]:
            #num在列表的右边
            data=data[mid_index:]
            sear(num,data)
        elif num < data[mid_index]:
            #num在列表的左边
            data=data[:mid_index]
            sear(num,data)
        else:
            print("find it")
            return
    else:
        if data[0]==num:
            print(\'find it\')
        else:
            print(\'not exists\')
sear(0,data)
sear(999,data)
sear(10000,data)
输出结果为:
find it
find it
not exists










 


方法和函数的区别
  1 调用方法不一样 函数括号 方法点号
  2 方法只对调用它的对象有用
函数没有绑定任何对象
方法可以绑定到具体的对象
 
 




函数式编程

函数式编程概念
编程风格
1不会修改外部值的状态
2 精简,可读性差



十一 面向对象的程序设计
http://www.cnblogs.com/linhaifeng/articles/6182264.html#_label27

 

11.1 什么是面向对象的程序设计

面向过程的程序设计的核心是过程,过程即解决问题的步骤,面向过程的设计就好比精心设计好一条流水线,考虑周全什么时候处理什么东西。
优点是:极大的降低了程序的复杂度
缺点是:一套流水线或者流程就是用来解决一个问题,生产汽水的流水线无法生产汽车,即便是能,也得是大改,改一个组件,牵一发而动全身。
应用场景:一旦完成基本很少改变的场景,著名的例子有Linux内核,git,以及Apache HTTP Server等


面向对象的程序设计的核心是对象。对象(object)基本上可以看做数据(特性)以及由一系列可以存取、操作这些数据的方法所组成的集合。
优点是:解决了程序的扩展性。对某一个对象单独修改,会立刻反应到整体体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
缺点是:可控性差,无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程和结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即使是上帝也无法预测最终结果。
应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方。

面向对象的程序设计并不是全部。对于一个软件质量来说,面向对象的程序设计知识用来解决扩展性。

 


11.2 类和对象
对象(object)基本上可以看做数据(特性)以及由一系列可以存取、操作这些数据的方法所组成的集合。
类,可以看成种类,类型,从一组对象中提取到的相似部分。所有的对象都属于一个类,称为类的实例。
之前学习的数据类型就是类,类就是数据类型
print(int)
print(Garen)
输出结果为:
<class \'int\'>
<class \'__main__.Garen\'>

11.3 类
11.3.1 初始类
1 声明类 (和声明函数很相似)
类的定义格式
class 类名:
    \'类的文档字符串\'
    类体

2 创建一个类:
class Data:
Pass
类体
Python编程中习惯类名使用单数单词并且首字母大写

类是数据与函数的结合,二者称为类的属性
class Garen:        #定义英雄盖伦的类,不同的玩家可以用它实例出自己英雄;
    camp=\'Demacia\'  #所有玩家的英雄(盖伦)的阵营都是Demacia;
    def attack(self,enemy):   #普通攻击技能,enemy是敌人;
        enemy.life_value-=self.aggressivity #根据自己的攻击力,攻击敌人就减掉敌人的生命值。




3 新式类和经典类

大前提:
1.只有在python2中才分新式类和经典类,python3中统一都是新式类
2.新式类和经典类声明的最大不同在于,所有新式类必须继承至少一个父类
3.所有类甭管是否显式声明父类,都有一个默认继承object父类(讲继承时会讲,先记住)
在python2中的区分
经典类:
class 类名:
    pass

经典类:
class 类名(父类):
    pass

在python3中,上述两种定义方式全都是新式类

 


__bases__查看此类的基类(父类)



11.3.2 类的作用1:属性引用
属性引用(类名.属性)
(1)引用类的数据属性(类名.变量名)
print(Garen.camp)   # 引用类的数据属性,该属性与所有对象/实例共享
输出结果为:
Demacia

(2)引用类的函数属性(类名.函数名)
print(Garen.attack)   #引用类的函数属性,该属性也共享,在对象就成绑定方法
Garen.attack(g1)     #使用参数g1对象的原因是self形参
输出结果为:
<function Garen.attack at 0x00000059CE8FAF28>
草丛伦 attack

(3)类的变量属性操作

Garen.name=\'Garen1\'    #增加属性
print(Garen.name)       #查询属性
输出结果为:
Garen1

del Garen.name         #删除属性
print(Garen.name)
输出结果为:
AttributeError: type object \'Garen\' has no attribute \'name\' #报错

Garen.camp="aaaa"      #修改属性 
print(Garen.camp)
输出结果为:
aaaa

(4)类的函数属性的操作
增加类的函数属性
def test():
    print("test")
Garen.test=test
Garen.test()
print(Garen.__dict__)
输出结果为:
test
{\'camp\': \'Demacia\', \'__weakref__\': <attribute \'__weakref__\' of \'Garen\' objects>, \'test\': <function test at 0x0000001A7CB3AF28>, \'__init__\': <function Garen.__init__ at 0x0000001A7CB45048>, \'attack\': <function Garen.attack at 0x0000001A7CB450D0>, \'__doc__\': None, \'__dict__\': <attribute \'__dict__\' of \'Garen\' objects>, \'__module__\': \'__main__\'}

修改类的函数属性
def test2():
    print("test2")
Garen.test=test2
Garen.test()
print(Garen.__dict__)
输出结果为:
test2
{\'attack\': <function Garen.attack at 0x00000007AFF65048>, \'camp\': \'Demacia\', \'__module__\': \'__main__\', \'__doc__\': None, \'__init__\': <function Garen.__init__ at 0x00000007AFF5AF28>, \'__weakref__\': <attribute \'__weakref__\' of \'Garen\' objects>, \'test\': <function test2 at 0x00000007AFF650D0>, \'__dict__\': <attribute \'__dict__\' of \'Garen\' objects>}

删除类的函数属性
print(Garen.test)
del Garen.test
print(Garen.test)
输出结果为:
<function test2 at 0x000000D68CA450D0>
AttributeError: type object \'Garen\' has no attribute \'test\'  #报错



Student类下面的变量   在Student这个类的名称空间中定义的x
 

 

11.3.3 类的作用2:实例化
(1)__init__实例化
类名加括号就是实例化,会自动触发__init__函数的运行,可以用他来为每个实例定制自己的特性
class Garen:
    camp=\'Demacia\'
    def __init__(self,nickname,aggressivity=58,life_value=455):
        self.nickname=nickname  #为自己的盖伦起个别名;
        self.aggressivity=aggressivity #英雄都有自己的攻击力;
        self.life_value=life_value #英雄都有自己的生命值;
    def attack(self,enemy):
        print("attack %s" % enemy.nickname)
实例化:类名+括号
g1=Garen(\'草丛伦\')
#就是在执行Garen.__int__(g1,’草丛伦’),然后执行__init__内的代码g1.nickname=’草丛伦’等

类的实例化:
下面两句是一样的
s1=Student(\'wen\',\'Python4期\',\'Python\',\'123\',\'70\')   #并不是返回值赋值给s1,__init__函数不能有返回值
相当于:
Student.__init__(s1,\'wen\',\'Python4期\',\'Python\',\'123\',\'70\')

(2)self作用
self的作用是在实例化时自动将对象/实例本身传给__init__的第一个参数,self可以是任意名字,但是self是大家公认的。

这种自动传递的机制还体现在g1.attack的调用上,后续会介绍


(3)特殊的类属性
一:我们定义的类的属性到底存到哪里了?有两种方式查看
dir(类名):查出的是一个名字列表
类名.__dict__:查出的是一个字典,key为属性名,value为属性值
(类和对象对属性的操作实质就是对__dict__字典的操作)
s1=Student(\'wen\',\'Python4期\',\'Python\',\'123\',\'70\')
print(Student.__dict__)                       # 查看类的名称空间
print(s1.__dict__)                           #查看对象的名称空间
输出结果为:
{\'__dict__\': <attribute \'__dict__\' of \'Student\' objects>, \'__weakref__\': <attribute \'__weakref__\' of \'Student\' objects>, \'__init__\': <function Student.__init__ at 0x000000403AAA6158>, \'__module__\': \'__main__\', \'__doc__\': None, \'see_result\': <function Student.see_result at 0x000000403AAA61E0>}
{\'password\': \'123\', \'clas\': \'Python4期\', \'course\': \'Python\', \'result\': \'70\', \'name\': \'wen\'}

二:特殊的类属性
类名.__name__# 类的名字(字符串)
print(Student.__name__)
输出结果为:
Student

类名.__doc__# 类的文档字符串
print(Student.__doc__)
输出结果为:
Student\'s class  created by wenyanjie  

类名.__base__# 类的第一个父类(在讲继承时会讲)
print(Student.__base__)
输出结果为:
<class \'object\'>

类名.__bases__# 类所有父类构成的元组(在讲继承时会讲)
print(Student.__bases__)
输出结果为:
(<class \'object\'>,)

类名.__dict__# 类的字典属性
print(Student.__dict__)
输出结果为:
{\'__dict__\': <attribute \'__dict__\' of \'Student\' objects>, \'__init__\': <function Student.__init__ at 0x0000009FDAAD5158>, \'__weakref__\': <attribute \'__weakref__\' of \'Student\' objects>, \'see_result\': <function Student.see_result at 0x0000009FDAAD51E0>, \'__module__\': \'__main__\', \'__doc__\': "Student\'s class  created by wenyanjie  "}

类名.__module__# 类定义所在的模块
print(Student.__module__)
输出结果为:
__main__

类名.__class__# 实例对应的类(仅新式类中)
print(Student.__class__)
输出结果为:
<class \'type\'>

11.4 对象(实例)

对象是关于类而实际存在的一个例子,即实例
#类实例化得到g1这个实例
class Garen:
    camp=\'Demacia\'
    def __init__(self,nickname,aggressivity=58,life_value=455):
        self.nickname=nickname  #为自己的盖伦起个别名;
        self.aggressivity=aggressivity #英雄都有自己的攻击力;
        self.life_value=life_value #英雄都有自己的生命值;
    def attack(self,enemy):
        print("attack %s" % enemy.nickname)

g1=Garen(\'草丛伦\')

11.4.1 对象的属性引用和绑定方法
对象本身可以有类的变量和类的函数作为对象的绑定方法,还可以自己操作对象自己的变量和函数


(1)对象(实例)只有一种作用:属性引用
格式: 实例名.类的变量名
       实例名.绑定方法
       实例名.实例自己的变量名
       实例名.实例自己的函数名
print(g1.nickname)
print(g1.aggressivity)
print(g1.life_value)
输出结果为:
草丛伦
58
455

(2)对象的数据属性操作
查看数据属性信息
print(g1.nickname)
输出结果:
草丛伦

修改数据属性信息
g1.nickname="伦哥"
print(g1.nickname)
输出结果为:
伦哥

添加数据属性
g1.sex="female"
print(g1.sex)
输出结果为:
Female

删除数据属性:
del g1.sex
print(g1.sex)
输出结果为:
AttributeError: \'Garen\' object has no attribute \'sex\' #报错

(3)对象的函数属性操作
添加对象的函数属性
def test():
    print("test")
g1.test=test
g1.test()
输出结果为:
test

修改对象的函数属性操作
def test2():
    print("test2")
g1.test=test2
g1.test()
输出结果为:
test2

删除对象的函数属性操作
del g1.test
g1.test()
输出结果为:
AttributeError: \'Garen\' object has no attribute \'test\'   #报错


(4)查看实例属性
同样是dir和内置__dict__两种方式
特殊实例属性
__class__
__dict__

(5)对象(实例)的绑定方法
对象本身只有数据属性,但是python的class机制会将类的函数绑定到对象上,称为对象的方法,或者叫绑定方法。
print(g1.attack)      #对象的绑定方法
print(Garen.attack)   #对象的绑定方法attack本质就是调用类的函数attack的功能,二者是一种绑定关系
输出结果为:
<bound method Garen.attack of <__main__.Garen object at 0x00000017370815F8>>
<function Garen.attack at 0x0000001737085048>

对象的绑定方法的特别之处在于:obj.func()会把obj传给func的第一个参数
对象的绑定方法操作的是对象自己

11.4.2 对象的交互

仿照Garen类创建一个Riven类:
实例Riven类 
交互:瑞雯攻击草丛伦
class Riven:
    camp=\'Noxus\'
    def __init__(self,nickname,aggressivity=54,life_value=414):
        self.nickname=nickname  #为自己的瑞雯起个别名;
        self.aggressivity=aggressivity #英雄都有自己的攻击力;
        self.life_value=life_value #英雄都有自己的生命值;
    def attack(self,enemy):
        print("attack %s" % (self.nickname,enemy.nickname))
        enemy.life_value -= self.aggressivity

g1=Garen(\'草丛伦\')
r1=Riven(\'瑞雯\')
print(g1.life_value)
r1.attack(g1)
print(g1.life_value)
输出结果为:
455
瑞雯 attack 草丛伦
401

对象调用方法也叫是给对象发个消息

11.5 类名称空间与对象(实例)名称空间
11.5.1 类名称空间

创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性
而类的有两个属性:数据属性和函数属性

其中类的数据属性是共享给所有对象
print(id(g1.camp))     #引用的地址是一样的
print(id(Garen.camp)) 
输出结果为:
364617767096
364617767096


其中类的函数属性是绑定到所有对象。
绑定方法的核心在于‘绑定’,唯一绑定一个确定的对象,不是共享的。
print(id(g1.attack))      #两个引用地址不一样
print(id(Garen.attack))
输出结果为:
1009949719304
1009951072464
分析:g1.attack就是在执行Garen.attack的功能,python的class机制会将Garen的函数属性attack绑定给g1,g1相当于拿到了一个指针,指向Garen类的attack功能。除此之外,g1.attack()会将g1传给attack的第一个参数。

 



11.5.2 对象(实例)名称空间
创建一个对象(实例)就会创建一个对象(实例)的名称空间,存放对象(实例)的名字,称为对象(实例)的属性
在obj.name会先从obj自己的名称空间里找name,找不到则去类中找,类中找不到就找父类。最后找不到就抛出异常。

实质就是在对象名称空间的dict中查找,再到类的名称空间dict中查找,再到父类的名称空间dict中查找。
 



面向对象程序设计: 找出对象,归类(相同的特性和技能,还有每个对象独特的特性和技能,现实中先有对象后有类),
然后面向对象程序编程:先定义类,实例化出对象


类的变量属性是实例共有的

 


类的变量属性一般都是不可变的类型

类的函数属性被对象绑定:

 
到g1的名称空间找attack的名称的变量

对象的绑定方法操作的是对象自己,
 

 

 


11.6  继承与派生

11.6.1 什么是继承

(1)继承定义 
   继承是一种创建新的类的方式,在python中,新建的类可以继承自一个或者多个父类,原始类称为基类或超类,新建的类称为派生类或子类。
   继承解决代码重用的问题。
(2)单继承和多继承
Python中类的继承分为:单继承和多继承

class ParentClass1:  #定义父类
    pass
class ParentClass2:  #定义父类
    pass
class SubClass1(ParentClass1): 
    #单继承,基类是ParentClass1,派生类是SubClass
    pass
class SubClass2(ParentClass1,ParentClass2):
    #python支持多继承,用逗号分隔开多个继承的类
    pass

(3)查看继承
查看继承:
print(SubClass1.__bases__)
print(SubClass2.__bases__)
输出结果为:
(<class \'__main__.ParentClass1\'>,)
(<class \'__main__.ParentClass1\'>, <class \'__main__.ParentClass2\'>)

提示:如果没有指定基类,Python的类会默认继承object类,object是所有Python类的基类,它提供了一些常见方法(如__str__)的实现。
print(ParentClass1.__bases__)
print(ParentClass2.__bases__)
输出结果为:
(<class \'object\'>,)
(<class \'object\'>,)


11.6.1 什么是派生
派生:子类继承了父类的属性,然后衍生出自己新的属性,如果子类衍生出的新的属性与父类的某个属性名字相同,那么再调用子类的这个属性,就以子类自己这里的为准了。

 

11.6.2 继承与抽象(先抽象再继承)

抽象即抽取类似或者比较像的部分。

抽象分为两个层次:
1 将奥巴马和梅西这两对象比较像的部分抽取成类
2 将人,猪,狗这三个类比较像的部分抽取为父类

抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)
 

继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。

抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象得到类
 



11.6.3 继承与重用性
(1)继承实现代码重用
在开发程序的过程中,如果我们定义了一个类,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时,我们不可能从头开始写一个类B,这就用到了类的继承的概念。
通过继承的方式新建类B,让B继承A,B会“遗传”A的所有属性(数据属性和函数属性),实现代码重用
举例:
class Hero:
    def __init__(self,nickname,aggressivity,life_value):
        self.nickname=nickname
        self.aggressivity=aggressivity
        self.life_value=life_value

    def move_forward(self):
        print(\'%s move forward\' %self.nickname)

    def move_backward(self):
        print(\'%s move backward\' %self.nickname)

    def move_left(self):
        print(\'%s move forward\' %self.nickname)

    def move_right(self):
        print(\'%s move forward\' %self.nickname)

    def attack(self,enemy):
        enemy.life_value-=self.aggressivity
class Garen(Hero):
    pass

class Riven(Hero):
    pass

g1=Garen(\'草丛伦\',100,300)
r1=Riven(\'锐雯雯\',57,200)

print(g1.life_value)
r1.attack(g1)
print(g1.life_value)

\'\'\'
运行结果
243
\'\'\'

提示:用已有的类建立一个新的类,这样就重用了已经有的软件中的一部分设置大部分,大大省了编程工作量,这就是常说的软件重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期,对大型软件开发来说,意义重大。

注意:上例中的g1.life_value之类的属性引用,会先从实例中找life_value然后去类中找,然后再去父类中找...直到最顶级的父类。

(2)子类的派生类
当然子类也可以添加自己新的属性或者自己这里重新定义这些属性(不会影响父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。

class Hero:
    def __init__(self,nickname,aggressivity,life_value):
        self.nickname=nickname
        self.aggressivity=aggressivity
        self.life_value=life_value
    def attack(self,enemy):
        print("Hero attack")
        enemy.life_value-=self.aggressivity

class Garen(Hero):
    camp="Demacia"
    def attack(self,enemy):
        print("Garen attack")
    def fire(self):
        print("%s is firing" %self.nickname)

g1=Garen("草丛伦",100,300)

g1.attack(g1)
g1.fire()
输出结果为:
Garen attack
草丛伦 is firing



(3)子类函数属性调用父类函数属性
在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,即: 类名.func() ,此时就与调用普通函数没有区别了,因此即便是self参数也要为其传值,而不是自动传值。
在子类中重用父类的函数:父类名.父类的函数(参数)

class Hero(object):
    def __init__(self,nickname,aggressivety,life_value):
        print("Hear init")
        self.nickname=nickname
        self.aggressivety=aggressivety
        self.life_value=life_value
    def attack(self,enemy):
        print("Hero attack")
        enemy.life_value-=self.aggressivety

class Garen(Hero):
    camp="Dema"
    def __init__(self,nickname,aggressivety,life_value,script):
        Hero.__init__(self,nickname,aggressivety,life_value)
        print("Garen init")
        self.script=script
    def attack(self,enemy):
        Hero.attack(self,enemy)
        print("Garen attack")
g1=Garen(\'garen\',18,200,\'人在塔在\')

print(g1.script)


11.6.4 组合与重用性
(1)组合实现代码重用性
软件重用的重要方式除了继承之外还有另外一种方式,即:组合
组合指的是,在一个类的对象中以另外一个类的对象作为数据属性,称为类的组合

class Teacher(object):
    def __init__(self,name,sex,course):
        self.name=name
        self.sex=sex
        self.course=course
class Student(object):
    def __init__(self,name,sex,course):
        self.name=name
        self.sex=sex
        self.course=course
class Course(object):
    def __init__(self,name,price,peroid):
        self.name=name
        self.price=price
        self.peroid=peroid
python_course=Course("python",15800,"7m")
linux_course=Course("linux",800,"5m")
t1=Teacher(\'egon\',\'male\',python_course)
s1=Student(\'wen\',\'male\',python_course)
print(t1.name,t1.course.name)
print(s1.name,s1.course.peroid)
输出结果为:
egon python
wen 7m

用继承还是用组合的区别方法:
猪是动物     可以用继承解决
老师有课程   可以用组合解决

(2)组合和继承区别
组合与继承都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景不同。

1 继承的方式
通过继承建立了派生类与基类之间的关系,它是一种“是”的关系,比如白马是马。
当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如教授是老师。
>>> class Teacher:
...     def __init__(self,name,gender):
...         self.name=name
...         self.gender=gender
...     def teach(self):
...         print(\'teaching\')
... 
>>> 
>>> class Professor(Teacher):
...     pass
... 
>>> p1=Professor(\'egon\',\'male\')
>>> p1.teach()
teaching

2 组合的方式
用组合的方式建立了类和组合的类之间的关系,它是一种“有”的关系,比如教授有生日,教授教课程。
当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好。

class BirthDate:
    def __init__(self,year,month,day):
        self.year=year
        self.month=month
        self.day=day

class Couse:
    def __init__(self,name,price,period):
        self.name=name
        self.price=price
        self.period=period

class Teacher:
    def __init__(self,name,gender):
        self.name=name
        self.gender=gender
    def teach(self):
        print(\'teaching\')
class Professor(Teacher):
    def __init__(self,name,gender,birth,course):
        Teacher.__init__(self,name,gender)
        self.birth=birth
        self.course=course

p1=Professor(\'egon\',\'male\',
             BirthDate(\'1995\',\'1\',\'27\'),
             Couse(\'python\',\'28000\',\'4 months\'))

print(p1.birth.year,p1.birth.month,p1.birth.day)
print(p1.course.name,p1.course.price,p1.course.period)
\'\'\'
运行结果:
1 27
python 28000 4 months
\'\'\'



11.6.5 接口与归一化设计

(1)什么是接口
继承有两种用途:
一 继承基类的方法,并且做出自己的改变或者拓展(代码重用)
二 声明某个子类兼容与某基类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能。

class Interface:#定义接口Interface类来模仿接口的概念,python中压根就没有interface关键字来定义一个接口。
    def read(self): #定接口函数read
        pass
    def write(self): #定义接口函数write
        pass
class Txt(Interface): #文本,具体实现read和write
    def read(self):
        print(\'文本数据的读取方法\')
    def write(self):
        print(\'文本数据的读取方法\')
class Sata(Interface): #磁盘,具体实现read和write
    def read(self):
        print(\'硬盘数据的读取方法\')
    def write(self):
        print(\'硬盘数据的读取方法\')
class Process(All_file):
    def read(self):
        print(\'进程数据的读取方法\')
    def write(self):
        print(\'进程数据的读取方法\')

在实践中,继承的第一种含义意义并不是很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
继承的第二种用法非常重要,又叫“接口继承”。
接口继承实质是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”------这在程序设计上,叫做归一化
归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合---就好像linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对于底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)


在python中根本就没有一个叫做interface的关键字,上面的代码只是看起来像接口,其实并没有起到接口的作用,子类完全可以不用去实现接口,如果非要去模仿接口的概念,可以借助第三方模块。
http://pypi.python.org/pypi/zope.interface
twisted的twisted\internet\interface.py里使用zope.interface
文档https://zopeinterface.readthedocs.io/en/latest/
设计模式:https://github.com/faif/python-patterns


(2)为什么要用接口
接口提取了一群类共同的函数,可以把接口当做一个函数的集合。
然后让子类去实现接口中的函数

这么做的意义在于归一化,什么叫归一化,就是只要基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。
归一化,让使用者无需关心对象的类型是什么,只需要知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。

(3)举例:必须实现的接口
主动抛出异常:(此方法不要用了,用抽象类好)
 

 


11.6.6 抽象类

(1)抽象类定义
  与Java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化。

(2)抽象类作用
  如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中的抽取相同的内容而来的,内容包括数据属性和函数属性。
  比如我们有香蕉的类,苹果的类,桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么吃一个具体的桃子。。。你永远无法吃到一个叫做水果的东西。
  从设计角度去看,如果类是从现实对象抽象出来的,那么抽象类就是基于类抽象而来的。
  从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同。

(3)python中实现抽象类
抽象类,本质还是类,与普通类额外的特点是:加了装饰器的函数,子类必须实现他们

import abc
class Animal(metaclass=abc.ABCMeta):
    tag="123"
    @abc.abstractmethod
    def run(self):
        pass
    @abc.abstractmethod
    def speak(self):
        pass
class People(Animal):
    def run(self):
        print("people run")
    def speak(self):
        print("people speak")

p1=People()
p1.run()
输出结果为:
people run


(4)抽象类和接口区别
抽象类的本质就是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。

抽象类是一个介于类和接口之间的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计。


11.6.7 继承实现的原理(继承顺序)
(1)继承顺序

1 Python的类可以继承多个类,Java和C#中则只能继承一个类
2 Python的类如果继承了多了个类,那么其寻找方法的方式有两种,分别是:深度优先和广度优先
 
* 当类是经典类时,多继承情况下,会按照深度优先方式查找
* 当类是新式类时,多继承情况下,会按照广度优先方式查找

经典类和新式类在写法上的区别是:
如果当前类或者父类继承了object类,那么该类就是新式类,否则便是经典类
Python3统一都是新式类
Python2中才分新式类与经典类
 

(2)继承顺序举例
继承图:
 
新式类的继承顺序:F,D,B,E,C,A  (广度优先)
经典类的继承顺序:F,D,B,A,E,C  (深度优先)

class A(object):
    def __init__(self):
        print("class A")
class B(A):
    def __init__(self):
        print("class B")
class C(A):
    def __init__(self):
        print("class C")
class D(B):
    def __init__(self):
        print("class D")
class E(C):
    def __init__(self):
        print("class E")
class F(D,E):
    def __init__(self):
        print("class F")
# f1=F()               # 新式类的继承顺序:F,D,B,E,C,A
print(F.__mro__)       # 只有新式才有这个属性可以查看线性列表,经典类没有这个属性
输出结果为:
(<class \'__main__.F\'>, <class \'__main__.D\'>, <class \'__main__.B\'>, <class \'__main__.E\'>, <class \'__main__.C\'>, <class \'__main__.A\'>, <class \'object\'>)


(3)继承原理(python如何实现的继承)

python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如:
print(F.__mro__)  # print(F.mro())
输出结果为:
(<class \'__main__.F\'>, <class \'__main__.D\'>, <class \'__main__.B\'>, <class \'__main__.E\'>, <class \'__main__.C\'>, <class \'__main__.A\'>, <class \'object\'>)

为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到了第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
1 子类会先于父类被检查
2 多个父类会根据他们在列表中的顺序被检查
3 如果对下一个类存在两个合法的选择,选择第一个父类

11.6.8 super()函数
(1)super()函数使用
子类继承了父类的方法,然后想进行修改,注意了是基于原有的基础上修改,那么就需要在子类中调用父类的方法
方法1: 父类名.父类方法()
在11.6.3 继承与重用性 中有使用

方法2:super()函数
在python3中super函数的使用
使用super函数,相当于调用对象的绑定方法,self不需要传值过去

class Hero(object):
    def __init__(self,nickname,aggressivety,life_value):
        print("Hear init")
        self.nickname=nickname
        self.aggressivety=aggressivety
        self.life_value=life_value
    def attack(self,enemy):
        print("Hero attack")
        enemy.life_value-=self.aggressivety
class Garen(Hero):
    camp="Dema"
    def __init__(self,nickname,aggressivety,life_value,script):
        #Hero.__init__(self,nickname,aggressivety,life_value)
        super().__init__(nickname,aggressivety,life_value)
        print("Garen init")
        self.script=script
    def attack(self,enemy):
        #Hero.attack(self,enemy)
        super(Garen, self).attack(enemy)
        print("Garen attack")
g1=Garen(\'garen\',18,200,\'人在塔在\')
print(g1.script)
g1.attack(g1)
输出结果为:
Hear init
Garen init
人在塔在
Hero attack
Garen attack

 

Python2中super的使用
a  super(自己的类,self),父类的函数名字
b  super只能用于新式类
 



(2)super函数的好处
当你使用super()函数时,python会在MRO列表上继续下一个类。只要每个重定义的方法统一使用super()并只调用它一次,那么控制流程最终会遍历完整个MRO列表,每个方法也只会被调用一次。
使用super调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表。

#每个类中都继承了且重写了父类的方法
class A:
    def __init__(self):
        print(\'A的构造方法\')
class B(A):
    def __init__(self):
        print(\'B的构造方法\')
        # A.__init__(self)
        super().__init__()
class C(A):
    def __init__(self):
        print(\'C的构造方法\')
        # A.__init__(self)
        super().__init__()
class D(B,C):
    def __init__(self):
        print(\'D的构造方法\')
        # B.__init__(self)
        # C.__init__(self)
        super().__init__()
    pass
f1=D()
print(D.__mro__) #python2中没有这个属性
输出结果为:
D的构造方法
B的构造方法
C的构造方法
A的构造方法
(<class \'__main__.D\'>, <class \'__main__.B\'>, <class \'__main__.C\'>, <class \'__main__.A\'>, <class \'object\'>)

11.7 多态与多态性
定义角度 多态
调用角度 多态性

11.7.1 多态
多态指的是一类事务有多种形态。
(一个抽象类有多个子类,因此多态的概念依赖于继承)
例:
序列类型有多种形态:字符串,列表,元组
动物有多种形态:人,狗,猪
文件有多种形态:文本文件,可执行文件
 


11.7.2 多态性
多态性:一种调用方式,不同的执行效果。

多态性是指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容的函数。
在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。

多态性是‘一个接口(函数func),多种实现(如f.click())’
11.7.3 静态多态
如任何类型都可以使用运算符+ 进行运算


11.7.4 动态多态
统一接口,不同的实现,这就是多态性。Func函数就是多态性,参数obj就是多态性的具体表现形式。
 


 

>>> def func(animal): #参数animal就是对态性的体现
...     animal.talk()
... 
>>> people1=People() #产生一个人的对象
>>> pig1=Pig() #产生一个猪的对象
>>> dog1=Dog() #产生一个狗的对象
>>> func(people1) 
say hello
>>> func(pig1)
say aoao
>>> func(dog1)
say wangwang

>>> def func(f):
...     f.click()
... 
>>> t1=Text()
>>> e1=ExeFile()
>>> func(t1)
open file
>>> func(e1)
execute file

多态性是‘一个接口(函数func),多种实现(如f.click())’


11.7.5 多态性的好处
Python由于数据类型灵活,本身就支持多态。
(1)增加了程序的灵活性
 以不变应万变,不论对象千变万化吗,使用者都是同一形式去调用,如func(animal)
(2)增加了程序的可扩展性
 通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用  
>>> class Cat(Animal): #属于动物的另外一种形态:猫
...     def talk(self):
...         print(\'say miao\')
... 
>>> def func(animal): #对于使用者来说,自己的代码根本无需改动
...     animal.talk()
... 
>>> cat1=Cat() #实例出一只猫
>>> func(cat1) #甚至连调用方式也无需改变,就能调用猫的talk功能
say miao
\'\'\'
这样我们新增了一个形态Cat,由Cat类产生的实例cat1,使用者可以在完全不需要修改自己代码的情况下。使用和人、狗、猪一样的方式调用cat1的talk方法,即func(cat1)
\'\'\'


11.8 封装
11.8.1 要封装什么
1 数据的封装
2 方法的封装

11.8.2 封装的原因
封装数据的主要原因是: 保护隐密
封装方法的主要原因是: 隔离复杂度

提示:在编程语言里,对外提供的接口(接口可理解为了一个入口),就是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。

11.8.3 封装的两个层面

   封装其实分为两个层面,但无论哪种层面的封装,都要对外界提供访问你内部隐藏内容的接口。
(接口可以理解为入口,有了这个入口,使用者无需且不能够直接访问到内部隐藏的细节,只能走接口,我们可以在接口的实现上附加更多的处理逻辑,从而严格控制使用者的访问)

(1)类和对象本身就有封装
   第一个层面的封装(什么都不用做):创建类和对象会分别创建二者的名称空间,我们只能用类名.或者obj.的方式去访问里面的名字,这本身就是一种封装。 对于这一层面的封装(隐藏),类名.和实例名. 就是访问隐藏属性的接口。
>>> r1.nickname
\'草丛伦\'
>>> Riven.camp
\'Noxus\'
   
(2)私有属性和方法
第二层面的封装:类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用、外部无法访问,或者留下少量接口(函数)供外部访问。

在python中用双下划线的方式实现隐藏属性(设置成私有的)
类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式
class A:
    __N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N
    def __init__(self):
        self.__X=10 #变形为self._A__X
    def __foo(self): #变形为_A__foo
        print(\'from A\')
    def bar(self):
        self.__foo() #只有在类内部才可以通过__foo的形式访问到.
这种自动变形的特点:
1 类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果
2 这种变形其实就是针对外部的变形,在外部是无法通过__x这个名字访问到的。
3 子类无法继承到父类的私有变量属性和函数属性
在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下划线开头的属性在继承给子类时,子类是无法覆盖的。 
不能继承谈何覆盖  
(3)私有属性和方法的注意问题
1 对于这一层面的封装(隐藏),我们需要在类中定义一个函数(接口函数)在它内部访问被隐藏的属性,然后外部就可以使用了。
2 这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:
_类名__属性,然后就可以访问了,如a._A__N
>>> a=A()
>>> a._A__N
>>> a._A__X
>>> A._A__N

3 变形的过程只在类的定义时发生一次,在定义后的赋值操作,不会变形
 

4 在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
#把fa定义成私有的,即__fa
>>> class A:
...     def __fa(self): #在定义时就变形为_A__fa
...         print(\'from A\')
...     def test(self):
...         self.__fa() #只会与自己所在的类为准,即调用_A__fa
                   #在类内部可以直接用__名字来访问到变形的属性
... 
>>> class B(A):
...     def __fa(self):
...         print(\'from B\')
... 
>>> b=B()
>>> b.test()
from A
举例:
 

5 python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果模块名以单下划线开头,那么from module import * 时不能被导入,但是你from module import _private_module依然是可以导入的。
 其实很多时候你去调用一个模块的功能时会遇到单下划线开头的(socker._socket,sys_home,sys._clear_type_cache),这些都是私有的,原则上是供内部调用的,作为外部的你,一意孤行也是可以用的,只不过显得野蛮。  
  Python要想与其他编程语言一样,严格控制属性的访问权限,只能借助内置方法如__getter__。


11.8.4 特性(property静态属性)

(1)什么是特性property
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
property是内置函数。作为装饰器,只要是可调用对象就可。
 

import math
class Circle:
    def __init__(self,radius): #圆的半径radius
        self.radius=radius

    @property   #area=property(are)
    def area(self):
        return math.pi * self.radius**2 #计算面积

    @property   #area=property(perimeter)
    def perimeter(self):
        return 2*math.pi*self.radius #计算周长

c=Circle(10)
print(c.radius)
print(c.area) #可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值
print(c.perimeter) #同上
\'\'\'
输出结果:
314.1592653589793
62.83185307179586
\'\'\'

注意:此时的特性arear和perimeter不能被赋值
c.area=3 #为特性area赋值
\'\'\'
抛出异常:
AttributeError: can\'t set attribute
\'\'\'

Property的优先级比对象赋值高
 
(2)为什么要用property

将一个类的函数定义成特性之后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则

ps:面向对象的封装有三种方式:
【public】
这种其实就是不封装,是对外公开的
【protected】
这种封装方式对外不公开,但对朋友(friend)或者子类公开
【private】
这种封装对谁都不公开

Python并没有在语言上把他们三个内建到自己的class机制中,在C++里一般会将所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现。

举例:测试体脂
 






(3)setter,deleter装饰器(property装饰的属性都有)
被property装饰的属性会优先于对象的属性被使用
而被property装饰的属性,如sex,分成三种:
1 property    #查询
2 sex.setter   #赋值
3 sex.deleter  #删除

class Foo:
    def __init__(self,val):
        self.__NAME=val #将所有的数据属性都隐藏起来

    @property
    def name(self):
        return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置)

    @name.setter
    def name(self,value):
        if not isinstance(value,str):  #在设定值之前进行类型检查
            raise TypeError(\'%s must be str\' %value)
        self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME

    @name.deleter
    def name(self):
        raise TypeError(\'Can not delete\')

f=Foo(\'egon\')
print(f.name)
# f.name=10 #抛出异常\'TypeError: 10 must be str\'
del f.name #抛出异常\'TypeError: Can not delete\'



 

 


只有被property装饰的sex函数就会有setter装饰器
 


 
 

 
 





 
 


 


 



11.8.5 封装与扩展性
封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供了一个良好的合作基础---或者说,只要接口这个基础约定不变,则代码改变不足为虑。

#类的设计者
class Room:
    def __init__(self,name,owner,width,length,high):
        self.name=name
        self.owner=owner
        self.__width=width
        self.__length=length
        self.__high=high
    def tell_area(self): #对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积
        return self.__width * self.__length

#使用者
>>> r1=Room(\'卧室\',\'egon\',20,20,20)
>>> r1.tell_area() #使用者调用接口tell_area
400

#类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码
class Room:
    def __init__(self,name,owner,width,length,high):
        self.name=name
        self.owner=owner
        self.__width=width
        self.__length=length
        self.__high=high
    def tell_area(self): #对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了
        return self.__width * self.__length * self.__high

对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码,就可以用上新功能
>>> r1.tell_area()
8000



10.9 静态方法与类方法

通常情况下,在类中定义的所有函数都是对象的绑定方法(这里的所有函数就是所有的函数,跟self啥的没有关系,self也只是一个再普通不过的参数而已),对象在绑定方法时自动会将自己作为参数传递给方法的第一个参数。除此之外还有两种常见的方法:静态方法和类方法,二者是为类量身定制的,但是实例非要使用,也不会报错。

10.9.1 静态方法(staticmethod实际就是函数)
是一种普通函数,位于类定义的命名空间中,不会对任何实例类型进行操作,
python为我们内置了函数staticmethod来把类中的函数定义成静态方法。
静态方法:1 类的函数
          2 对象使用静态方法需要像普通函数那样引用,而不是绑定方法

class Foo:
    def spam(x,y,z): #类中的一个函数,千万不要懵逼,self和x啥的没有不同都是参数名
        print(x,y,z)
    spam=staticmethod(spam) #把spam函数做成静态方法

基于之前所学的装饰器的知识,@staticmethod等同于spam=staticmethod(spam),于是
class Foo:
    @staticmethod #装饰器
    def spam(x,y,z):
        print(x,y,z)

print(type(Foo.spam)) #类型本质就是函数
Foo.spam(1,2,3) #调用函数应该有几个参数就传几个参数

f1=Foo()
f1.spam(3,3,3) #实例也可以使用,但通常静态方法都是给类用的,实例在使用时丧失了自动传值的机制

\'\'\'
<class \'function\'>
1 2 3
3 3 3
\'\'\'

应用场景:编写类时需要采用很多不同的方式来创建实例,而我们只有一个__init__函数,此时静态方法就派上用场了。

 
 

class Date:
    def __init__(self,year,month,day):
        self.year=year
        self.month=month
        self.day=day
    @staticmethod
    def now(): #用Date.now()的形式去产生实例,该实例用的是当前时间
        t=time.localtime() #获取结构化的时间格式
        return Date(t.tm_year,t.tm_mon,t.tm_mday) #新建实例并且返回
    @staticmethod
    def tomorrow():#用Date.tomorrow()的形式去产生实例,该实例用的是明天的时间
        t=time.localtime(time.time()+86400)
        return Date(t.tm_year,t.tm_mon,t.tm_mday)

a=Date(\'1987\',11,27) #自己定义时间
b=Date.now() #采用当前时间
c=Date.tomorrow() #采用明天的时间

print(a.year,a.month,a.day)
print(b.year,b.month,b.day)
print(c.year,c.month,c.day)

 

 


10.9.2 类方法(classmethod)
类方法是给类用的,类在使用时会将类本身当做参数传给类方法的第一个参数。Python为我们内置了函数classmethod来把类中的函数定义成类方法。
类方法拿到类的地址之后可以属性操作和实例化操作。
 
 
 
类方法,实例也可以用,但是传进去的参数是类


class A:
    x=1
    @classmethod
    def test(cls):
        print(cls,cls.x)
class B(A):
    x=2
B.test()
输出结果为:
<class \'__main__.B\'>   2


import time
class Date:
    def __init__(self,year,month,day):
        self.year=year
        self.month=month
        self.day=day
    @staticmethod
    def now():
        t=time.localtime()
        return Date(t.tm_year,t.tm_mon,t.tm_mday)
class EuroDate(Date):
    def __str__(self):
        return \'year:%s month:%s day:%s\'%(self.year,self.month,self.day)
e=EuroDate.now()
print(e)
输出结果为:
<__main__.Date object at 0x000000D47DC5F898>

因为e就是用Date类产生的,所以根本不会触发EuroDate.__str__ 解决方法就是用classmethod

import time
class Date:
    def __init__(self,year,month,day):
        self.year=year
        self.month=month
        self.day=day
    @classmethod   #改成类方法
    def now(cls):
        t=time.localtime()
        return cls(t.tm_year,t.tm_mon,t.tm_mday)  #哪个类来调用,即用哪个类cls来实例化
class EuroDate(Date):
    def __str__(self):
        return \'year:%s month:%s day:%s\'%(self.year,self.month,self.day)
e=EuroDate.now()
print(e)     #我们的意图是想触发EuroFate.__str__,此时e就是由EuroDate产生的,所以会如我们所愿
输出结果为:
year:2017 month:4 day:21


强调,注意: 静态方法和类方法虽然是给类准备的,但是如果实例去用,也是可以用的,只不过实例去调用的时候容易让人混淆,不知道你要干啥
print(e.now())
或者
x=e.now() #通过实例e去调用类方法也一样可以使用,静态方法也一样
print(x)
输出结果为:
year:2017 month:4 day:21

 

   
10.9.3 时间模块

Python 3.5.1 (v3.5.1:37a07cee5969, Dec  6 2015, 01:54:25) [MSC v.1900 64 bit (AM
D64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import time
>>> time.time()
1492788756.309873
>>> time.time()+86400
1492875175.6528153
>>> time.localtime(time.time()+86400)
time.struct_time(tm_year=2017, tm_mon=4, tm_mday=22, tm_hour=23, tm_min=33, tm_s
ec=27, tm_wday=5, tm_yday=112, tm_isdst=0)
>>> t=time.localtime(time.time()+86400)
>>> t.tm_year
2017
>>> t.tm_mon
4
>>> t.tm_mday
22



10.9.4 总结:在类内部定义的函数3个用途

在类内部定义的函数无非三种用途
一:绑定到对象的方法
	只要是在类内部定义的,并且没有被任何装饰器修饰过的方法,都是绑定到对象的
	
	class Foo:
		def test(self): #绑定到对象的方法
			pass
		def test1(): #也是绑定到对象的方法,只是对象.test1(),会把对象本身自动传给test1,因test1没有参数所以会抛出异常
			pass
	
	绑定到对象,指的是:就给对象去用,
	使用方式:对象.对象的绑定方法(),不用为self传值
	特性:调用时会把对象本身当做第一个参数传给对象的绑定方法
	

	
二:绑定到类的方法:classmethod
	在类内部定义的,并且被装饰器@classmethod修饰过的方法,都是绑定到类的
	
	
	
三:解除绑定的方法:staticmethod
	既不与类绑定,也不与对象绑定,不与任何事物绑定
	绑定的特性:自动传值(绑定到类的就是自动传类,绑定到对象的就自动传对象)
	解除绑定的特性:不管是类还是对象来调用,都没有自动传值这么一说了
	
	所以说staticmethod就是相当于一个普通的工具包
class Foo:
	def test1(self):
		pass
	def test2():
		pass
	@classmethod
	def test3(cls):
		pass
	@classmethod
	def test4():
		pass
	@staticmethod
	def test5():
		pass
		
test1与test2都是绑定到对象方法:调用时就是操作对象本身
	<function Foo.test1 at 0x0000000000D8E488>
	<function Foo.test2 at 0x0000000000D8E510>
test3与test4都是绑定到类的方法:调用时就是操作类本身
	<bound method Foo.test3 of <class \'__main__.Foo\'>>
	<bound method Foo.test4 of <class \'__main__.Foo\'>>
test5是不与任何事物绑定的:就是一个工具包,谁来都可以用,没说专门操作谁这么一说
	<function Foo.test5 at 0x0000000000D8E6A8>

10.9.4 __str__函数的用法
    def __str__(self):
        return str(self.l)
__str__返回一个字符串的类型
 

 


10.10 isintance(object,cls)和issubclass(sub,super)
10.10.1 isintance(obj,cls)检查obj是否是类cls的对象
用isintance(obj,cls)检查obj是否是类cls的对象
class Foo(object):
    pass
obj=Foo()
isinstance(obj,Foo)
print(isinstance(obj,Foo))
输出结果为:
True

10.10.2 issubclass(sub,super)检查sub类是否是super类的派生类
用issubclass(sub,super)检查sub类是否是super类的派生类
class Foo(object):
    pass
class Bar(Foo):
    pass
issubclass(Bar,Foo)
print(issubclass(Bar,Foo))
输出结果为:
True

用isintance(x,类型)判断x的数据类型
x=[]
print(isinstance(x,list))
输出结果为:
True


#_*_coding:utf-8_*_
__author__ = \'Linhaifeng\'
class A:
    pass
class B(A):
    pass
print(issubclass(B,A)) #B是A的子类,返回True
a1=A()
print(isinstance(a1,A)) #a1是A的实例,返回为True


10.11反射
10.11.1 反射定义
 反射的概念是由Smith在1982年首次提出的,主要是程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在lisp和面向对象方面取得了成绩。


10.11.2 python面向对象中的反射
Python面向对象中的反射:通过字符串的形式操作对象和类相关的属性。
Python中的一切事物都是对象(都可以使用反射)

四个可以实现自省的函数
下列方法适用于类和对象(一切皆对象,类本身也是一个对象)

(1)hasattr(object,name)
用hasattr(object,’name’)
判断object中有没有一个name字符串对应的方法或属性,根据字符串

1 对于对象而言
class People:
    country=\'China\'
    def __init__(self,name):
        self.name=name
    def walk(self):
        print("walk ",self.name)
p=People(\'egon\')
print(\'name\' in p.__dict__)   # hasattr函数的实质就是查看此方法或属性是否在对象的__dict__中
print(hasattr(p,\'name\'))
print(hasattr(p,\'name123\'))
输出结果为:
True
True
False


2 对于类而言
class People:
    country=\'China\'
    def __init__(self,name):
        self.name=name
    def walk(self):
        print("walk ",self.name)
p=People(\'egon\')
print(hasattr(People,\'country\'))
print(\'country\' in People.__dict__)
print(hasattr(People,\'__init__\'))
print(hasattr(People,\'country123\'))
输出结果为:
True
True
False

(2)getattr(object,name,default=None)
getattr(object,name,default=None)
有返回值,将此变量名或方法名的值或内存地址作为返回值返回,根据字符串
def getattr(object, name, default=None): # known special case of getattr
    """
    getattr(object, name[, default]) -> value

    Get a named attribute from an object; getattr(x, \'y\') is equivalent to x.y.
    When a default argument is given, it is returned when the attribute doesn\'t
    exist; without it, an exception is raised in that case.
    """
    pass

1 对于对象而言
class People:
    country=\'China\'
    def __init__(self,name):
        self.name=name
    def walk(self):
        print("walk ",self.name)
p=People(\'egon\')
#getattr
res=getattr(p,\'country\')
print(res)
res=getattr(p,\'country123\',None)
print(res)
res=getattr(p,\'walk\')
print(res)
输出结果为:
China
None
<bound method People.walk of <__main__.People object at 0x0000008DB8AD08D0>>


2 对于类而言

class People:
    country=\'China\'
    def __init__(self,name):
        self.name=name
    def walk(self):
        print("walk ",self.name)
p=People(\'egon\')
f1=getattr(People,\'walk\')
print(f1)
f1(p)
f1=getattr(People,\'walk123\',None)  #如果没有要get的那个属性,使用默认值返回
print(f1)
if hasattr(p,"walk"):              #如果没有要get的那个属性,也可以提前判断,继续其他操作
    func=getattr(p,\'walk\')
func()
输出结果为:
<function People.walk at 0x000000916DD55048>
walk  egon
None
walk  egon


如果没有要get的那个属性,也可以提前判断,继续其他操作
 


(3)setattr(x,y,v)
setattr(x,y,v)
赋值类或实例的属性,根据字符串
def setattr(x, y, v): # real signature unknown; restored from __doc__
    """
    Sets the named attribute on the given object to the specified value.

    setattr(x, \'y\', v) is equivalent to ``x.y = v\'\'
    """
    pass

举例:
class People:
    country=\'China\'
    def __init__(self,name):
        self.name=name
    def walk(self):
        print("walk ",self.name)

p=People(\'egon\')
setattr(p,\'age\',\'18\')
print(p.__dict__)
print(p.age)
print(getattr(p,\'age\'))
输出结果为:
{\'age\': \'18\', \'name\': \'egon\'}
18
18


(4)delattr(x,y)
delattr(x,y)
删除类或对象的属性,根据字符串
def delattr(x, y): # real signature unknown; restored from __doc__
    """
    Deletes the named attribute from the given object.

    delattr(x, \'y\') is equivalent to ``del x.y\'\'
    """
    pass

class People:
    country=\'China\'
    def __init__(self,name):
        self.name=name
    def walk(self):
        print("walk ",self.name)
p=People(\'egon\')
setattr(p,\'age\',\'18\')
print(p.__dict__)
delattr(p,"age")
print(p.__dict__)
输出结果为:
{\'name\': \'egon\', \'age\': \'18\'}
{\'name\': \'egon\'}

(5)四个方法的使用举例
class BlackMedium:
    feature=\'Ugly\'
    def __init__(self,name,addr):
        self.name=name
        self.addr=addr

    def sell_house(self):
        print(\'%s 黑中介卖房子啦,傻逼才买呢,但是谁能证明自己不傻逼\' %self.name)
    def rent_house(self):
        print(\'%s 黑中介租房子啦,傻逼才租呢\' %self.name)

b1=BlackMedium(\'万成置地\',\'回龙观天露园\')

#检测是否含有某属性
print(hasattr(b1,\'name\'))
print(hasattr(b1,\'sell_house\'))

#获取属性
n=getattr(b1,\'name\')
print(n)
func=getattr(b1,\'rent_house\')
func()

# getattr(b1,\'aaaaaaaa\') #报错
print(getattr(b1,\'aaaaaaaa\',\'不存在啊\'))

#设置属性
setattr(b1,\'sb\',True)
setattr(b1,\'show_name\',lambda self:self.name+\'sb\')
print(b1.__dict__)
print(b1.show_name(b1))

#删除属性
delattr(b1,\'addr\')
delattr(b1,\'show_name\')
delattr(b1,\'show_name111\')#不存在,则报错

print(b1.__dict__)


(6)类也是对象

class Foo(object):
    staticField="oldboy"
    def __init__(self):
        self.name=\'wupeiqi\'
    def func(self):
        return \'执行func结果\'

    @staticmethod
    def bar():
        return \'执行bar结果\'
f1=Foo()
print(getattr(Foo,\'staticField\'))
print(getattr(Foo,\'func\'))
print(getattr(Foo,\'func\')(f1))
print(getattr(Foo,\'bar\'))
print(getattr(Foo,\'bar\')())
输出结果为:
oldboy
<function Foo.func at 0x000000595A414048>
执行func结果
<function Foo.bar at 0x000000595A4140D0>
执行bar结果


(7)反射当前模块的属性
模块也是对象
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import sys
def s1():
    print \'s1\'
def s2():
    print \'s2\'
this_module = sys.modules[__name__]  #拿到当前模块对象(模块名,模块地址等)
hasattr(this_module,\'s1\')
print(hasattr(this_module,\'s1\'))
print(getattr(this_module,\'s2\'))
print(this_module.s1)
this_module.s1()
输出结果为:
True
<function s2 at 0x00000050178AAF28>
<function s1 at 0x00000050178AAD90>
s1

(8)反射其他模块的属性
导入其他模块,利用反射查找该模块是否存在某个方法
使用模块.函数调用
#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
程序目录:
    module_test.py
    index.py
当前文件:
    index.py
"""
import module_test as obj
#obj.test()
print(hasattr(obj,\'test\'))
getattr(obj,\'test\')()


Py有两种用途:模块  和  独立脚本
可以用__name__区分


(9)反射实例
反射当前模块实例:
import sys
def add():
    print(\'add\')
def change():
    print(\'change\')
def search():
    print(\'search\')
def delete():
    print(\'delete\')
this_module=sys.modules[__name__]
while True:
    cmd=input(\'>>:\').strip()
    if not cmd:continue
    if hasattr(this_module,cmd):
        func=getattr(this_module,cmd)
        func()
输出结果为:
>>:add
add
>>:search
search
>>:change1
>>:change
change


10.11.3 反射的好处
(1)实现可插拔机制

总之反射的好处就是,可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种“后期绑定”,即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能。

 

客户端:
在ftpclient.py文件中
class FtpClient:
    \'ftp客户端,但是还么有实现具体的功能\'
    def __init__(self,addr):
        print(\'正在连接服务器[%s]\' %addr)
        self.addr=addr

服务端:
在ftpserver.py文件中
import ftpclient
f1=ftpclient.FtpClient(\'192.168.1.1\')
if hasattr(f1,\'get\'):
    func_get=getattr(f1,\'get\')
    func_get()
else:
    print(\'---->不存在此方法\')
    print(\'处理其他的逻辑\')
输出结果为:
正在连接服务器[192.168.1.1]
---->不存在此方法
处理其他的逻辑
(2)动态导入模块(基于反射当前模块成员)
1 importlib导入模块  (官方推荐)
import importlib
t=importlib.import_module(\'time\')
print(t.time())
输出结果为:
1493022421.5157373

2 使用__import__导入模块 (官方不推荐)
m=input("请输入你要导入的模块:")
m1=__import__(m)
print(m1)
print(m1.time())
输出结果为:
请输入你要导入的模块time
<module \'time\' (built-in)>
1493022299.095836



 


10.12 内置attr系列
都属于内置函数

10.12.1 __setattr__函数
(1)设置属性时就会触发__setattr__函数的运行
设置属性时就会触发__setattr__函数的运行
class Foo:
    def __init__(self,name):
        self.name=name
    def __setattr__(self,key,value):
        print(\'setattr---key:%s,value:%s\'%(key,value))
f1=Foo("egon")
print(f1.__dict__)       #f1中并没有name=egon的赋值,原因在于触发了__setattr__函数,在__setattr__中 
                     #并没有赋值的语句
输出结果为:
setattr---key:name,value:egon
{}

(2)在__setattr__函数赋值实例的属性元素
在__setattr__函数赋值实例的属性元素
class Foo:
    def __init__(self,name):
        self.name=name
    def __setattr__(self,key,value):
        print(\'setattr---key:%s,value:%s\'%(key,value))
        #self.key=value               #
        #setattr(self,key_str,value)      #会引起递归错误
        #self.key_str=value
        self.__dict__[key]=value
f1=Foo("egon")
f1.age=18                   #是要是设置属性就会触发__setattr__函数
print(f1.__dict__)
输出结果为:
setattr---key:name,value:egon
setattr---key:age,value:18
{\'name\': \'egon\', \'age\': 18}

(3) __setattr__中可以设置一定条件才能完成赋值操作
__setattr__中可以设置一定条件才能完成赋值操作
列表中只能赋值字符串的值
class Foo:
    def __init__(self,name):
        self.name=name
    def __setattr__(self, key, value):
        if not isinstance(value,str):
            raise TypeError(\'must be str\')
        print(\'setattr--key:%s,value:%s\'%(key,value))
        self.__dict__[key]=value
f1=Foo("egon")
print(f1.__dict__)
f1.age=18
print(f1.__dict__)
输出结果为:
setattr--key:name,value:egon
{\'name\': \'egon\'}
TypeError: must be str     #报错

10.12.2 __delattr__函数
__delattr__函数会被”del 属性”触发,也可以添加删除的条件
class Foo:
    def __init__(self,name):
        self.name=name
    def __setattr__(self, key, value):
        if not isinstance(value,str):
            raise TypeError(\'must be str\')
        print(\'setattr--key:%s,value:%s\'%(key,value))
        self.__dict__[key]=value
    def __delattr__(self, item):
        print("del %s",item)
        print(type(item))
        #del self.item                #
        #delattr(self,item)            # 用这种方法del会死循环
        self.__dict__.pop(item)
f1=Foo("egon")
f1.age=\'18\'
print(f1.__dict__)
del f1.name
print(f1.__dict__)
输出结果为:
setattr--key:name,value:egon
setattr--key:age,value:18
{\'name\': \'egon\', \'age\': \'18\'}
<class \'str\'>
del name
{\'age\': \'18\'}


10.12.3 __getattr__函数
__getattr__函数:当要调用的属性或函数不存在的时候会触发getattr运行

class Foo:
    def __init__(self,x):
        self.name=x
    def __getattr__(self, item):
        print(\'getattr-->%s %s \'%(item,type(item)))
f=Foo("egon")
print(f.xxx)
输出结果为:
egon
getattr-->xxx <class \'str\'> 
None


10.12.4 attr系列举例

class Foo:
    x=1
    def __init__(self,y):
        self.y=y
    def __getattr__(self, item):
        print(\'----> from getattr:你找的属性不存在\')
    def __setattr__(self, key, value):
        print(\'----> from setattr\')
        # self.key=value #这就无限递归了,你好好想想
        # self.__dict__[key]=value #应该使用它
    def __delattr__(self, item):
        print(\'----> from delattr\')
        # del self.item #无限递归了
        self.__dict__.pop(item)
#__setattr__添加/修改属性会触发它的执行
f1=Foo(10)
print(f1.__dict__) # 因为你重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值
f1.z=3
print(f1.__dict__)
#__delattr__删除属性的时候会触发
f1.__dict__[\'a\']=3#我们可以直接修改属性字典,来完成添加/修改属性的操作
del f1.a
print(f1.__dict__)
#__getattr__只有在使用点调用属性且属性不存在的时候才会触发
f1.xxxxxx



10.13 定制自己的数据类型
定制自己的数据类型(二次加工标准类型)
针对类的二次加工是用继承方式
针对函数的二次加工是用授权方式
10.13.1 包装:基于继承定制数据类型
包装:python为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增/改写方法,这用到了我们刚学的继承/派生知识(其他的标准类型均可以通过下面的方式二次加工)

基于继承的原理,来制定自己的数据类型

自己定制自己的功能: 这个列表只能加数字
1 本来的列表是任何类型的数据都可以加入进去
class List(list):
    pass
l1=List([1,2,3])
l1.append(4)
print(l1)
l1.append("5")
print(l1)
输出结果为:
[1, 2, 3, 4]
[1, 2, 3, 4, \'5\']

2 继承list类,重新实现append方法,由于是继承方式,list的其他方法还是可以正常使用的
class List(list):
    def append(self, p_object):
        if not isinstance(p_object,int):
            raise TypeError(\'must be int\')
        super(List, self).append(p_object)
l1=List([1,2,3])
l1.append(4)
print(l1)
l1.append("5")
print(l1)
输出结果为:
[1,2,3,4]
Must be int      #报错

3 继承list类,重新实现append方法,重新实现insert方法。由于是继承方式,list的其他方法还是可以正常使用的
class List(list):
    def append(self, p_object):
        if not isinstance(p_object,int):
            raise TypeError(\'must be int\')
        super(List, self).append(p_object)
    def insert(self, index, p_object):
        if not isinstance(p_object,int):
            raise TypeError("must be int")
        super(List, self).insert(index,p_object)
l=List([1,2,3])
# print(l)
# l.append(4)
# print(l)
# l.append("5")
# print(l)
l.insert(0,0)
print(l)

class List(list):
    def append(self, p_object):
        if not isinstance(p_object,int):
            raise TypeError(\'must be int\')
        super().append(p_object)
l=List([1,2,3])
print(l)
l.append(4)
print(l)
l.append(\'444444444\') #报错

class List(list):
    def append(self, p_object):
        \'我改写的方法\'
        if not isinstance(p_object,str):
            print(\'只有字符串类型能被添加到列表中\')
            return
        # self.append(p_object) #进入无限递归
        super().append(p_object)
    def show_mid(self):
        \'我新增的方法\'
        index=int(len(self)/2)
        print(self[index])
l1=List(\'hello\')
l1.append(\'abc\')
print(l1,type(l1))
#数字无法添加成功
l1.append(1)
print(\'-->\',l1)
#基于标准类型新增了功能
l1.show_mid()

10.13.2 授权
   授权:授权是包装的一个特性,包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能。其他的则保持原样。授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。
   实现授权的关键点就是覆盖__getattr__方法。
   需要改写的可以改写,不需要的改变的用getattr授权实现:一般类的二次加工是用继承解决;针对函数的二次加工是用getattr授权实现。

import time
class FileHandle:
    def __init__(self,filename,mode=\'r\',encoding=\'utf-8\'):
        self.file=open(filename,mode,encoding=encoding)
    def write(self,line):
        t=time.strftime(\'%Y-%m-%d %T\')
        self.file.write(\'%s %s\' %(t,line))

    def __getattr__(self, item):
        return getattr(self.file,item)

f1=FileHandle(\'b.txt\',\'w+\')
f1.write(\'你好啊\')
f1.seek(0)
print(f1.read())
f1.close()



在日志文件每次写入的时候都写入当前时间
1 定义Open的函数,定义自己的write方法,Open的write方法中使用到open的write方法。
用open开启的文件句柄作为Open函数一个属性。
定义成功,可以使用Open写入文件
class Open:
    def __init__(self,filepath,m=\'r\',encode=\'utf-8\'):
        self.x=open(filepath,mode=m,encoding=encode)
        self.filepath=filepath
        self.mode=m
        self.encoding=encode
        
    def write(self,line):
        print(\'f 自己的write\',line)
        self.x.write(line)
f=Open(\'b.txt\',\'w\')
f.write(\'111111\n\')

2 时间模块
>>> import time
>>> time.strftime(\'%Y-%m-%d\')
\'2017-04-24\'
>>> time.strftime(\'%Y-%m-%d %X\')
\'2017-04-24 15:02:11\'

3 在日志文件中写入时间
import time
class Open:
    def __init__(self,filepath,m=\'r\',encode=\'utf-8\'):
        self.x=open(filepath,mode=m,encoding=encode)
        self.filepath=filepath
        self.mode=m
        self.encoding=encode

    def write(self,line):
        print(\'f 自己的write\',line)
        t=time.strftime(\'%Y-%m-%d %X\')
        self.x.write(\'%s %s\'%(t,line))
f=Open(\'b.txt\',\'w\')
f.write(\'111111\n\')

4 open中的其他函数如read,close,flush都通过getattr实现
import time
class Open:
    def __init__(self,filepath,m=\'r\',encode=\'utf-8\'):
        self.x=open(filepath,mode=m,encoding=encode)
        self.filepath=filepath
        self.mode=m
        self.encoding=encode
    def write(self,line):
        print(\'f 自己的write\',line)
        t=time.strftime(\'%Y-%m-%d %X\')
        self.x.write(\'%s %s\'%(t,line))
    def __getattr__(self, item):          #默认调用找不到的函数或变量时触发getattr
        # print(\'------>\',item,type(item))
        #print(hasattr(self.x,item))      # 判断self.x是否存在item此名称的变量或函数
        return getattr(self.x,item)       # 返回在self.x中的得到item名称的变量或函数的内存地址
f=Open(\'b.txt\',\'w\')
f.write(\'111111\n\')
f.write(\'222222\n\')
f.write(\'333333\n\')
f=Open(\'b.txt\',\'r+\')
print(f.read)
res=f.read()
print(res)
f.flush()
f.close()
输出结果为:
f 自己的write 111111
f 自己的write 222222
f 自己的write 333333
<built-in method read of _io.TextIOWrapper object at 0x000000C57CCEEC18>
2017-04-24 15:18:52 111111
2017-04-24 15:18:52 222222
2017-04-24 15:18:52 333333


作业:
# 基于授权定制自己的列表类型,要求定制的自己的__init__方法,
# class List():
#     def __init__(self,item):
#         self.l=list(item)
#         self.item=item
#
# l1=List([1,2,3,4])
# print(l1.l)
# 定制自己的append:只能向列表加入字符串类型的值
# class List():
#     def __init__(self,item):
#         self.l=list(item)
#         self.item=item
#     def append(self,item):
#         if not isinstance(item,str):
#             raise TypeError("must be str")
#         self.l.append(item)
#
# l1=List(["wen","yan"])
# print(l1.l)
# l1.append("jie")
# print(l1.l)
# 定制显示列表中间那个值的属性(提示:property)
# class List():
#     def __init__(self,item):
#         self.l=list(item)
#         self.item=item
#
#     @property
#     def middle(self):
#         m_index=int(len(self.l)/2)
#         return type(self.l[m_index])
#
#     def append(self,item):
#         if not isinstance(item,str):
#             raise TypeError("must be str")
#         self.l.append(item)
# l1=List(["wen","yan"])
# print(l1.l)
# l1.append("jie")
# print(l1.l)
# print(l1.middle)
# 其余方法都使用list默认的(提示:__getattr__加反射)
class List():
    def __init__(self,item):
        self.l=list(item)
        self.item=item
    def __str__(self):
        return str(self.l)
    @property
    def middle(self):
        m_index=int(len(self.l)/2)
        return type(self.l[m_index])

    def append(self,item):
        if not isinstance(item,str):
            raise TypeError("must be str")
        self.l.append(item)

    def __getattr__(self, item):
        return getattr(self.l,item)

l1=List(["wen","yan"])
print(l1)
l1.append("jie")
print(l1)
print(l1.middle)
print(l1.pop())
print(type(l1))


10.14 __getattribute__

回顾__getattr__函数
class Foo:
    def __init__(self,x):
        self.x=x

    def __getattr__(self, item):
        print(\'执行的是我\')
        # return self.__dict__[item]
f1=Foo(10)
print(f1.x)
f1.xxxxxx #不存在的属性访问,触发__getattr__



class Foo:
    def __init__(self,x):
        self.x=x

    def __getattribute__(self, item):
        print(\'不管是否存在,我都会执行\')

f1=Foo(10)
f1.x
f1.xxxxxx


#_*_coding:utf-8_*_
__author__ = \'Linhaifeng\'
class Foo:
    def __init__(self,x):
        self.x=x
    def __getattr__(self, item):
        print(\'执行的是我\')
        # return self.__dict__[item]
    def __getattribute__(self, item):
        print(\'不管是否存在,我都会执行\')
        raise AttributeError(\'哈哈\')
f1=Foo(10)
f1.x
f1.xxxxxx
#当__getattribute__与__getattr__同时存在,只会执行__getattrbute__,除非__getattribute__在执行过程中抛出异常AttributeError


10.15 描述符

10.15.1 描述符定义
描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议
__get__():调用一个属性时,触发
__set__():为一个属性赋值时,触发
__delete__():采用del删除属性时,触发

定义一个描述符:
class Foo: #在python3中Foo是新式类,它实现了三种方法,这个类就被称作一个描述符
    def __get__(self, instance, owner):
        pass
    def __set__(self, instance, value):
        pass
    def __delete__(self, instance):
        pass


10.15.2 描述符作用

描述符是干什么的:描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)

(1)引子
引子:描述符类产生的实例进行属性操作并不会触发三个方法的执行
class Foo:
    def __get__(self, instance, owner):
        print(\'触发get\')
    def __set__(self, instance, value):
        print(\'触发set\')
    def __delete__(self, instance):
        print(\'触发delete\')

#包含这三个方法的新式类称为描述符,由这个类产生的实例进行属性的调用/赋值/删除,并不会触发这三个方法
f1=Foo()
f1.name=\'egon\'
f1.name
del f1.name
#疑问:何时,何地,会触发这三个方法的执行


(2)描述符应用之何时何地
描述符应用之何时何地
#描述符Str
class Str:
    def __get__(self, instance, owner):
        print(\'Str调用\')
    def __set__(self, instance, value):
        print(\'Str设置...\')
    def __delete__(self, instance):
        print(\'Str删除...\')

#描述符Int
class Int:
    def __get__(self, instance, owner):
        print(\'Int调用\')
    def __set__(self, instance, value):
        print(\'Int设置...\')
    def __delete__(self, instance):
        print(\'Int删除...\')

class People:
    name=Str()
    age=Int()
    def __init__(self,name,age): #name被Str类代理,age被Int类代理,
        self.name=name
        self.age=age

#何地?:定义成另外一个类的类属性

#何时?:且看下列演示

p1=People(\'alex\',18)

#描述符Str的使用
p1.name
p1.name=\'egon\'
del p1.name

#描述符Int的使用
p1.age
p1.age=18
del p1.age

#我们来瞅瞅到底发生了什么
print(p1.__dict__)
print(People.__dict__)

#补充
print(type(p1) == People) #type(obj)其实是查看obj是由哪个类实例化来的
print(type(p1).__dict__ == People.__dict__)


10.15.3 描述符分为两种
描述符分两种

(1)数据描述符
一 数据描述符:至少实现了__get__()和__set__()

1 class Foo:
2     def __set__(self, instance, value):
3         print(\'set\')
4     def __get__(self, instance, owner):
5         print(\'get\')

(2)非数据描述符
二 非数据描述符:没有实现__set__()

1 class Foo:
2     def __get__(self, instance, owner):
3         print(\'get\')


10.15.4 描述符注意事项
一 描述符本身应该定义成新式类,被代理的类也应该是新式类
二 必须把描述符定义成这个类的类属性,不能为定义到构造函数中
三 要严格遵循该优先级,优先级由高到底分别是
1.类属性
2.数据描述符
3.实例属性
4.非数据描述符
5.找不到的属性触发__getattr__()

(1) 类属性>数据描述符
类属性>数据描述符
#描述符Str
class Str:
    def __get__(self, instance, owner):
        print(\'Str调用\')
    def __set__(self, instance, value):
        print(\'Str设置...\')
    def __delete__(self, instance):
        print(\'Str删除...\')

class People:
    name=Str()
    def __init__(self,name,age): #name被Str类代理,age被Int类代理,
        self.name=name
        self.age=age


#基于上面的演示,我们已经知道,在一个类中定义描述符它就是一个类属性,存在于类的属性字典中,而不是实例的属性字典

#那既然描述符被定义成了一个类属性,直接通过类名也一定可以调用吧,没错
People.name #恩,调用类属性name,本质就是在调用描述符Str,触发了__get__()

People.name=\'egon\' #那赋值呢,我去,并没有触发__set__()
del People.name #赶紧试试del,我去,也没有触发__delete__()
#结论:描述符对类没有作用-------->傻逼到家的结论

\'\'\'
原因:描述符在使用时被定义成另外一个类的类属性,因而类属性比二次加工的描述符伪装而来的类属性有更高的优先级
People.name #恩,调用类属性name,找不到就去找描述符伪装的类属性name,触发了__get__()

People.name=\'egon\' #那赋值呢,直接赋值了一个类属性,它拥有更高的优先级,相当于覆盖了描述符,肯定不会触发描述符的__set__()
del People.name #同上


(2) 描述符>实例属性
描述符>实例属性
#描述符Str
class Str:
    def __get__(self, instance, owner):
        print(\'Str调用\')
    def __set__(self, instance, value):
        print(\'Str设置...\')
    def __delete__(self, instance):
        print(\'Str删除...\')

class People:
    name=Str()
    def __init__(self,name,age): #name被Str类代理,age被Int类代理,
        self.name=name
        self.age=age


p1=People(\'egon\',18)

#如果描述符是一个数据描述符(即有__get__又有__set__),那么p1.name的调用与赋值都是触发描述符的操作,于p1本身无关了,相当于覆盖了实例的属性
p1.name=\'egonnnnnn\'
p1.name
print(p1.__dict__)#实例的属性字典中没有name,因为name是一个数据描述符,优先级高于实例属性,查看/赋值/删除都是跟描述符有关,与实例无关了
del p1.name


(3) 实例属性>非数据描述符
实例属性>非数据描述符
class Foo:
    def func(self):
        print(\'我胡汉三又回来了\')
f1=Foo()
f1.func() #调用类的方法,也可以说是调用非数据描述符
#函数是一个非数据描述符对象(一切皆对象么)
print(dir(Foo.func))
print(hasattr(Foo.func,\'__set__\'))
print(hasattr(Foo.func,\'__get__\'))
print(hasattr(Foo.func,\'__delete__\'))
#有人可能会问,描述符不都是类么,函数怎么算也应该是一个对象啊,怎么就是描述符了
#笨蛋哥,描述符是类没问题,描述符在应用的时候不都是实例化成一个类属性么
#函数就是一个由非描述符类实例化得到的对象
#没错,字符串也一样


f1.func=\'这是实例属性啊\'
print(f1.func)

del f1.func #删掉了非数据
f1.func()


(4) 再次验证:实例属性>非数据描述符
再次验证:实例属性>非数据描述符
class Foo:
    def __set__(self, instance, value):
        print(\'set\')
    def __get__(self, instance, owner):
        print(\'get\')
class Room:
    name=Foo()
    def __init__(self,name,width,length):
        self.name=name
        self.width=width
        self.length=length


#name是一个数据描述符,因为name=Foo()而Foo实现了get和set方法,因而比实例属性有更高的优先级
#对实例的属性操作,触发的都是描述符的
r1=Room(\'厕所\',1,1)
r1.name
r1.name=\'厨房\'



class Foo:
    def __get__(self, instance, owner):
        print(\'get\')
class Room:
    name=Foo()
    def __init__(self,name,width,length):
        self.name=name
        self.width=width
        self.length=length


#name是一个非数据描述符,因为name=Foo()而Foo没有实现set方法,因而比实例属性有更低的优先级
#对实例的属性操作,触发的都是实例自己的
r1=Room(\'厕所\',1,1)
r1.name
r1.name=\'厨房\'


(5)非数据属性>找不到 
非数据属性>找不到

class Foo:
    def func(self):
        print(\'我胡汉三又回来了\')

    def __getattr__(self, item):
        print(\'找不到了当然是来找我啦\',item)
f1=Foo()

f1.xxxxxxxxxxx


10.15.5 描述符使用
众所周知,python是弱类型语言,即参数的赋值没有类型限制,下面我们通过描述符机制来实现类型限制功能
(1)
class Str:
    def __init__(self,name):
        self.name=name
    def __get__(self, instance, owner):
        print(\'get--->\',instance,owner)
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        print(\'set--->\',instance,value)
        instance.__dict__[self.name]=value
    def __delete__(self, instance):
        print(\'delete--->\',instance)
        instance.__dict__.pop(self.name)


class People:
    name=Str(\'name\')
    def __init__(self,name,age,salary):
        self.name=name
        self.age=age
        self.salary=salary

p1=People(\'egon\',18,3231.3)

#调用
print(p1.__dict__)
p1.name

#赋值
print(p1.__dict__)
p1.name=\'egonlin\'
print(p1.__dict__)

#删除
print(p1.__dict__)
del p1.name
print(p1.__dict__)


(2)

class Str:
    def __init__(self,name):
        self.name=name
    def __get__(self, instance, owner):
        print(\'get--->\',instance,owner)
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        print(\'set--->\',instance,value)
        instance.__dict__[self.name]=value
    def __delete__(self, instance):
        print(\'delete--->\',instance)
        instance.__dict__.pop(self.name)


class People:
    name=Str(\'name\')
    def __init__(self,name,age,salary):
        self.name=name
        self.age=age
        self.salary=salary

#疑问:如果我用类名去操作属性呢
People.name #报错,错误的根源在于类去操作属性时,会把None传给instance

#修订__get__方法
class Str:
    def __init__(self,name):
        self.name=name
    def __get__(self, instance, owner):
        print(\'get--->\',instance,owner)
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        print(\'set--->\',instance,value)
        instance.__dict__[self.name]=value
    def __delete__(self, instance):
        print(\'delete--->\',instance)
        instance.__dict__.pop(self.name)


class People:
    name=Str(\'name\')
    def __init__(self,name,age,salary):
        self.name=name
        self.age=age
        self.salary=salary
print(People.name) #完美,解决


(3)

class Str:
    def __init__(self,name,expected_type):
        self.name=name
        self.expected_type=expected_type
    def __get__(self, instance, owner):
        print(\'get--->\',instance,owner)
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        print(\'set--->\',instance,value)
        if not isinstance(value,self.expected_type): #如果不是期望的类型,则抛出异常
            raise TypeError(\'Expected %s\' %str(self.expected_type))
        instance.__dict__[self.name]=value
    def __delete__(self, instance):
        print(\'delete--->\',instance)
        instance.__dict__.pop(self.name)


class People:
    name=Str(\'name\',str) #新增类型限制str
    def __init__(self,name,age,salary):
        self.name=name
        self.age=age
        self.salary=salary

p1=People(123,18,3333.3)#传入的name因不是字符串类型而抛出异常


(4)

class Typed:
    def __init__(self,name,expected_type):
        self.name=name
        self.expected_type=expected_type
    def __get__(self, instance, owner):
        print(\'get--->\',instance,owner)
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        print(\'set--->\',instance,value)
        if not isinstance(value,self.expected_type):
            raise TypeError(\'Expected %s\' %str(self.expected_type))
        instance.__dict__[self.name]=value
    def __delete__(self, instance):
        print(\'delete--->\',instance)
        instance.__dict__.pop(self.name)


class People:
    name=Typed(\'name\',str)
    age=Typed(\'name\',int)
    salary=Typed(\'name\',float)
    def __init__(self,name,age,salary):
        self.name=name
        self.age=age
        self.salary=salary

p1=People(123,18,3333.3)
p1=People(\'egon\',\'18\',3333.3)
p1=People(\'egon\',18,3333)


大刀阔斧之后我们已然能实现功能了,但是问题是,如果我们的类有很多属性,你仍然采用在定义一堆类属性的方式去实现,low,这时候我需要教你一招:独孤九剑

(1)

def decorate(cls):
    print(\'类的装饰器开始运行啦------>\')
    return cls

@decorate #无参:People=decorate(People)
class People:
    def __init__(self,name,age,salary):
        self.name=name
        self.age=age
        self.salary=salary

p1=People(\'egon\',18,3333.3)

(2)

def typeassert(**kwargs):
    def decorate(cls):
        print(\'类的装饰器开始运行啦------>\',kwargs)
        return cls
    return decorate
@typeassert(name=str,age=int,salary=float) #有参:1.运行typeassert(...)返回结果是decorate,此时参数都传给kwargs 2.People=decorate(People)
class People:
    def __init__(self,name,age,salary):
        self.name=name
        self.age=age
        self.salary=salary

p1=People(\'egon\',18,3333.3)


最终结果

class Typed:
    def __init__(self,name,expected_type):
        self.name=name
        self.expected_type=expected_type
    def __get__(self, instance, owner):
        print(\'get--->\',instance,owner)
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        print(\'set--->\',instance,value)
        if not isinstance(value,self.expected_type):
            raise TypeError(\'Expected %s\' %str(self.expected_type))
        instance.__dict__[self.name]=value
    def __delete__(self, instance):
        print(\'delete--->\',instance)
        instance.__dict__.pop(self.name)

def typeassert(**kwargs):
    def decorate(cls):
        print(\'类的装饰器开始运行啦------>\',kwargs)
        for name,expected_type in kwargs.items():
            setattr(cls,name,Typed(name,expected_type))
        return cls
    return decorate
@typeassert(name=str,age=int,salary=float) #有参:1.运行typeassert(...)返回结果是decorate,此时参数都传给kwargs 2.People=decorate(People)
class People:
    def __init__(self,name,age,salary):
        self.name=name
        self.age=age
        self.salary=salary

print(People.__dict__)
p1=People(\'egon\',18,3333.3)



10.15.6 描述符总结

描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod,@staticmethd,@property甚至是__slots__属性

描述父是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件.


10.15.7 利用描述符自制property

利用描述符原理完成一个自定制@property,实现延迟计算(本质就是把一个函数属性利用装饰器原理做成一个描述符:类的属性字典中函数名为key,value为描述符类产生的对象)

(1)@property回顾

class Room:
    def __init__(self,name,width,length):
        self.name=name
        self.width=width
        self.length=length

    @property
    def area(self):
        return self.width * self.length

r1=Room(\'alex\',1,1)print(r1.area)


(2)自制@property

class Lazyproperty:
    def __init__(self,func):
        self.func=func
    def __get__(self, instance, owner):
        print(\'这是我们自己定制的静态属性,r1.area实际是要执行r1.area()\')
        if instance is None:
            return self
        return self.func(instance) #此时你应该明白,到底是谁在为你做自动传递self的事情

class Room:
    def __init__(self,name,width,length):
        self.name=name
        self.width=width
        self.length=length

    @Lazyproperty #area=Lazyproperty(area) 相当于定义了一个类属性,即描述符
    def area(self):
        return self.width * self.length

r1=Room(\'alex\',1,1)
print(r1.area)


(3)实现延迟计算功能

class Lazyproperty:
    def __init__(self,func):
        self.func=func
    def __get__(self, instance, owner):
        print(\'这是我们自己定制的静态属性,r1.area实际是要执行r1.area()\')
        if instance is None:
            return self
        else:
            print(\'--->\')
            value=self.func(instance)
            setattr(instance,self.func.__name__,value) #计算一次就缓存到实例的属性字典中
            return value

class Room:
    def __init__(self,name,width,length):
        self.name=name
        self.width=width
        self.length=length

    @Lazyproperty #area=Lazyproperty(area) 相当于\'定义了一个类属性,即描述符\'
    def area(self):
        return self.width * self.length

r1=Room(\'alex\',1,1)
print(r1.area) #先从自己的属性字典找,没有再去类的中找,然后出发了area的__get__方法
print(r1.area) #先从自己的属性字典找,找到了,是上次计算的结果,这样就不用每执行一次都去计算



(4)延时计算再改

#缓存不起来了

class Lazyproperty:
    def __init__(self,func):
        self.func=func
    def __get__(self, instance, owner):
        print(\'这是我们自己定制的静态属性,r1.area实际是要执行r1.area()\')
        if instance is None:
            return self
        else:
            value=self.func(instance)
            instance.__dict__[self.func.__name__]=value
            return value
        # return self.func(instance) #此时你应该明白,到底是谁在为你做自动传递self的事情
    def __set__(self, instance, value):
        print(\'hahahahahah\')

class Room:
    def __init__(self,name,width,length):
        self.name=name
        self.width=width
        self.length=length

    @Lazyproperty #area=Lazyproperty(area) 相当于定义了一个类属性,即描述符
    def area(self):
        return self.width * self.length

print(Room.__dict__)
r1=Room(\'alex\',1,1)
print(r1.area)
print(r1.area) 
print(r1.area) 
print(r1.area) #缓存功能失效,每次都去找描述符了,为何,因为描述符实现了set方法,它由非数据描述符变成了数据描述符,数据描述符比实例属性有更高的优先级,因而所有的属性操作都去找描述符了


10.15.8 利用描述符自制classmethod

class ClassMethod:
    def __init__(self,func):
        self.func=func

    def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身,
        def feedback():
            print(\'在这里可以加功能啊...\')
            return self.func(owner)
        return feedback

class People:
    name=\'linhaifeng\'
    @ClassMethod # say_hi=ClassMethod(say_hi)
    def say_hi(cls):
        print(\'你好啊,帅哥 %s\' %cls.name)

People.say_hi()

p1=People()
p1.say_hi()
#疑问,类方法如果有参数呢,好说,好说

class ClassMethod:
    def __init__(self,func):
        self.func=func

    def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身,
        def feedback(*args,**kwargs):
            print(\'在这里可以加功能啊...\')
            return self.func(owner,*args,**kwargs)
        return feedback

class People:
    name=\'linhaifeng\'
    @ClassMethod # say_hi=ClassMethod(say_hi)
    def say_hi(cls,msg):
        print(\'你好啊,帅哥 %s %s\' %(cls.name,msg))

People.say_hi(\'你是那偷心的贼\')

p1=People()
p1.say_hi(\'你是那偷心的贼\')



10.15.9 利用描述符自制staticmethod

class StaticMethod:
    def __init__(self,func):
        self.func=func

    def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身,
        def feedback(*args,**kwargs):
            print(\'在这里可以加功能啊...\')
            return self.func(*args,**kwargs)
        return feedback

class People:
    @StaticMethod# say_hi=StaticMethod(say_hi)
    def say_hi(x,y,z):
        print(\'------>\',x,y,z)

People.say_hi(1,2,3)

p1=People()
p1.say_hi(4,5,6)


10.16 再看property
一个静态属性property本质就是实现了get,set,delete三种方法
用法1:
class Foo:
    @property
    def AAA(self):
        print(\'get的时候运行我啊\')

    @AAA.setter
    def AAA(self,value):
        print(\'set的时候运行我啊\')

    @AAA.deleter
    def AAA(self):
        print(\'delete的时候运行我啊\')

#只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter
f1=Foo()
f1.AAA
f1.AAA=\'aaa\'
del f1.AAA

用法2:
class Foo:
    def get_AAA(self):
        print(\'get的时候运行我啊\')

    def set_AAA(self,value):
        print(\'set的时候运行我啊\')

    def delete_AAA(self):
        print(\'delete的时候运行我啊\')
    AAA=property(get_AAA,set_AAA,delete_AAA) #内置property三个参数与get,set,delete一一对应

f1=Foo()
f1.AAA
f1.AAA=\'aaa\'
del f1.AAA


实例1:
class Goods:

    def __init__(self):
        # 原价
        self.original_price = 100
        # 折扣
        self.discount = 0.8

    @property
    def price(self):
        # 实际价格 = 原价 * 折扣
        new_price = self.original_price * self.discount
        return new_price

    @price.setter
    def price(self, value):
        self.original_price = value

    @price.deleter
    def price(self):
        del self.original_price


obj = Goods()
obj.price         # 获取商品价格
obj.price = 200   # 修改商品原价
print(obj.price)
del obj.price     # 删除商品原价


实例2:
#实现类型检测功能

#第一关:
class People:
    def __init__(self,name):
        self.name=name

    @property
    def name(self):
        return self.name

# p1=People(\'alex\') #property自动实现了set和get方法属于数据描述符,比实例属性优先级高,所以你这面写会触发property内置的set,抛出异常


#第二关:修订版

class People:
    def __init__(self,name):
        self.name=name #实例化就触发property

    @property
    def name(self):
        # return self.name #无限递归
        print(\'get------>\')
        return self.DouNiWan

    @name.setter
    def name(self,value):
        print(\'set------>\')
        self.DouNiWan=value

    @name.deleter
    def name(self):
        print(\'delete------>\')
        del self.DouNiWan

p1=People(\'alex\') #self.name实际是存放到self.DouNiWan里
print(p1.name)
print(p1.name)
print(p1.name)
print(p1.__dict__)

p1.name=\'egon\'
print(p1.__dict__)

del p1.name
print(p1.__dict__)


#第三关:加上类型检查
class People:
    def __init__(self,name):
        self.name=name #实例化就触发property

    @property
    def name(self):
        # return self.name #无限递归
        print(\'get------>\')
        return self.DouNiWan

    @name.setter
    def name(self,value):
        print(\'set------>\')
        if not isinstance(value,str):
            raise TypeError(\'必须是字符串类型\')
        self.DouNiWan=value

    @name.deleter
    def name(self):
        print(\'delete------>\')
        del self.DouNiWan

p1=People(\'alex\') #self.name实际是存放到self.DouNiWan里
p1.name=1



10.17 __setitem__,__getitem__,__delitem__
将对象作为字典进行操作属性
将对象的值以字典的形式赋值时会触发这些函数
__setattr__等是以字符串的形式赋值时触发,__setitem__一个是字典的形式,一个

class Foo:
    def __init__(self,name):
        self.name=name
    def __getitem__(self, item):
        print(\'__getitem__执行\')
        return self.__dict__[item]
    def __setitem__(self, key, value):
        print(\'__setitem__执行\')
        self.__dict__[key]=value
    def __delitem__(self, key):
        print(\'del obj[key]时,__delitem__执行\')
        self.__dict__.pop(key)
    def __delattr__(self, item):
        print("del obj.key时,__delattr__执行")
        self.__dict__.pop(item)
f1=Foo(\'wen\')
f1[\'age\']=18
f1[\'age1\']=19
del f1.age1
del f1[\'age\']
f1[\'name\']=\'wenyanjie\'
print(f1.__dict__)
print(f1[\'name\'])
输出结果为:
__setitem__执行
__setitem__执行
del obj.key时,__delattr__执行
del obj[key]时,__delitem__执行
__setitem__执行
{\'name\': \'wenyanjie\'}
__getitem__执行
wenyanjie



10.18 __str__,__repr__,__format__

改变对象的字符串显示__str__,__repr__
自定义格式化字符串__format__

注str函数或者print函数---->都是obj.__str__()
注repr函数或交互式解释器----->都是obj.__repr__()
如果__str__没有被定义,那么就会使用__repr__来代替输出
注意:这两方法的返回值必须是字符串,否则抛出异常。

__author__="Wenyanjie"
format_dict={
    \'nat\':\'{obj.name}-{obj.addr}-{obj.type}\',
    \'tna\':\'{obj.type}:{obj.name}:{obj.addr}\',
    \'tan\':\'{obj.type}/{obj.addr}/{obj.name}\',
}
class School:
    def __init__(self,name,addr,type):
        self.name=name
        self.addr=addr
        self.type=type
    def __repr__(self):
        return \'School (%s,%s)\' %(self.name,self.addr)
    def __str__(self):
        return \'(%s,%s)\'%(self.name,self.addr)
    def __format__(self, format_spec):
        if not format_spec or format_spec not in format_dict:
            format_spec=\'nat\'
        fmt=format_dict[format_spec]
        return fmt.format(obj=self)

s1=School(\'oldboy1\',\'北京\',\'私立\')
print(\'from repr:\',repr(s1))
print(\'from str:\',str(s1))
print(s1)
输出结果为:
from repr: School (oldboy1,北京)
from str: (oldboy1,北京)
(oldboy1,北京)

print(format(s1,\'nat\'))
print(format(s1,\'tna\'))
print(format(s1,\'tan\'))
print(format(s1,\'wenyanjie\'))
输出结果为:
oldboy1-北京-私立
私立:oldboy1:北京
私立/北京/oldboy1
oldboy1-北京-私立


函数__format__练习

date_dic={
    \'ymd\':\'{0.year}:{0.month}:{0.day}\',
    \'dmy\':\'{0.day}/{0.month}/{0.year}\',
    \'mdy\':\'{0.month}-{0.day}-{0.year}\'
}
class Date:
    def __init__(self,year,month,day):
        self.year=year
        self.month=month
        self.day=day
    def __format__(self, format_spec):
        if not format_spec or format_spec not in date_dic:
            format_spec=\'ymd\'
        fmt=date_dic[format_spec]
        return fmt.format(self)
d1=Date(2017,4,25)
print(format(d1))
print(format(d1,\'dmy\'))
print(\'{:mdy}\'.format(d1))
输出结果为:
2017:4:25
25/4/2017
4-25-2017



10.19 __slots__
__slots__定义
1 __slots__是什么:是一个类变量,变量值是可以是列表,元组,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)
2 引子:使用点号来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的)
3 为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__
  当你定义__slots__后,__slots__就会为实例使用一个更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个。
  字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__一个不好的地方就是我们不能再给实例添加新的属性了,只能使用在__slots__中定义的那些属性名。
4 注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再支持一些普通类特性了,比如继承。大多数情况下,你应该只在那些经常被使用到的用做数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象。
5 关于__slots__的一个常见误区是它可以作为一个封装工具是来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是他的初衷,更多的是用来作为一个内存优化工具。

下面这个例子中People类的实例对象只能有x属性且没有自己的__dict__
class People:
    __slots__=[\'x\']
p=People()
print(People.__dict__)
p.x=1
# print(p.__dict__)
print(People.__dict__)
#p.y=2


所有People产生的对象只能有x,y,z属性,并且没有__dict__
对象不会建立名称空间了,使用类的名称空间。
优点:节省内存,限制属性。
场景:一个类产生好多对象,对象有好多属性固定。可以使用__slots__。

class People:
    __slots__=[\'x\',\'y\',\'z\']
p=People()
print(People.__dict__)
p.x=1
p.y=2
p.z=3
print(p.x,p.y,p.z)
# print(p.__dict__)
p1=People()
p1.x=10
p1.y=20
p1.z=30
print(p1.x,p1.y,p1.z)
# print(p1.__dict__)
#   p与p1都没有属性字典__dict__了,统一归__slots__管,节省内存
输出结果为:
{\'__slots__\': [\'x\', \'y\', \'z\'], \'__doc__\': None, \'x\': <member \'x\' of \'People\' objects>, \'__module__\': \'__main__\', \'z\': <member \'z\' of \'People\' objects>, \'y\': <member \'y\' of \'People\' objects>}
1 2 3
10 20 30

10.20 __next__和__iter__实现迭代器协议


class Foo:
    def __init__(self,start):
        self.start=start
    def __iter__(self):
        return self
    def __next__(self):
        if self.start>10:
            raise StopIteration
        n=self.start
        self.start+=1
        return n
f=Foo(0)
# print(next(f))
# print(next(f))
# print(next(f))
# print(next(f))
# print(next(f))
for i in f:
    print(\'--->\',i)


实现range功能的Range类
class Range():
    def __init__(self,start,stop,le=1):
        self.start=start
        self.stop=stop
        self.le=le
    def __iter__(self):
        return self
    def __next__(self):
        if self.start>=self.stop:
            raise StopIteration
        n=self.start
        self.start+=self.le
        return n
f=Range(0,10,2)
for i in f:
    print("---->",i)


菲波那切数列
class Fib:
    def __init__(self):
        self._a=0
        self._b=1
    def __iter__(self):
        return self
    def __next__(self):
        self._a,self._b=self._b,self._a+self._b
        return self._a
f1=Fib()
# print(f1.__next__())
# print(next(f1))
# print(next(f1))
for i in f1:
    if i>100:
        break
    print("%s "%i,end=\'\')





10.21 __doc__
类的描述信息,该属性无法继承给子类

class Foo:
    \'我是Foo的描述信息\'
    pass
class Bar(Foo):
    pass
print(Foo.__doc__)
print(Bar.__doc__)
输出结果为:
我是Foo的描述信息
None


10.22 __module__和__class__
__module__表示当前操作的对象在那个模块
__class__表示当前操作的对象的类是什么

在lib.py文件中
class C:
    def __init__(self):
        self.name="wenyanjie"
在lib2.py文件中
from lib import C
obj=C()
print(obj.__module__)
print(obj.__class__)
输出结果为:
Lib           # 输出模块
<class \'lib.C\'>   # 输出类

10.23 __del__
析构方法,当对象在内存中被释放时,自动触发执行。
当此对象的引用为0时,文件运行完时,手动del删除对象时,都会触发__del__方法

注意:此方法一般无须定义,因为python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。

class Foo:
    def __del__(self):
        print(\'执行我啦\')
f1=Foo()
del f1
print(\'----->\')
输出结果为:
执行我啦
----->


class Foo:
    def __del__(self):
        print(\'执行我啦\')
f1=Foo()
print(\'----->\')
输出结果为:
----->
执行我啦

删除文件句柄时,默认关闭文件句柄
import time
class Open:
    def __init__(self,fliepath,mode=\'r\',encode=\'utf-8\'):
        self.f=open(fliepath,mode=mode,encoding=encode)
    def write(self):
        pass
    def __getattr__(self, item):
        return getattr(self.f,item)
    def __del__(self):
        print(\'---->del\')
        self.f.close()
f=Open(\'a.txt\',\'w\')
# del f
print("123")
print("123")
print("123")
print("123")
print("123")
输出结果为:
123
123
123
123
123
---->del

10.24 __enter__和__exit__

10.24.1 上下文管理协议

我们知道在操作文件对象的时候可以这么写:
with open(\'b.txt\',\'r\') as f:
‘代码块’

上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法

class Open:
    def __init__(self,name):
        self.name=name
    def __enter__(self):
        print(\'出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量\')
    def __exit__(self, exc_type, exc_val, exc_tb):
        print(\'with中的代码执行完毕时执行我啊\')
with Open(\'a.txt\') as f:
    print(\'---->执行代码块\')
输出结果为:
出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量
---->执行代码块
with中的代码执行完毕时执行我啊

实例:
# class Foo:
#     def __enter__(self):
#         print(\'enter\')
#     def __exit__(self, exc_type, exc_val, exc_tb):
#         print(\'exit\')
#         print(\'exc_type:\',exc_type)
#         print(\'exc_val:\',exc_val)
#         print(\'exc_tb:\',exc_tb)
# with Foo():
#     print(\'with foo的子代码块\')
# class Foo:
#     def __enter__(self):
#         print(\'enter\')
#         return
#     def __exit__(self, exc_type, exc_val, exc_tb):
#         print(\'exit\')
#         print(\'exc_type:\',exc_type)
#         print(\'exc_val:\',exc_val)
#         print(\'exc_tb:\',exc_tb)
# with Foo() as obj:
#     print(\'with foo的子代码块\')
#     print("obj:",obj)
class Foo:
    def __enter__(self):
        print(\'enter\')
        return 123456
    def __exit__(self, exc_type, exc_val, exc_tb):
        print(\'exit\')
        print(\'exc_type:\',exc_type)
        print(\'exc_val:\',exc_val)
        print(\'exc_tb:\',exc_tb)
with Foo() as obj:
    print(\'with foo的子代码块\')
    print("obj:",obj)

With不用关闭文件,为什么?因为__exit__里面执行了关闭文件的操作



10.24.2 __exit__处理异常
__exit__()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行。除非是在__exit__()中进行异常处理,并返回True
class Open:
    def __init__(self,name):
        self.name=name
    def __enter__(self):
        print(\'出现with语句,对象__enter__被触发,有返回值则赋值给as声明的变量\')
    def __exit__(self, exc_type, exc_val, exc_tb):
        print(\'with中代码块执行完毕时执行我啊\')
        print(exc_type)
        print(exc_val)
        print(exc_tb)
with Open(\'a.txt\') as f:
    print(\'----->执行代码块\')
    raise AttributeError(\'***着火啦,救火啦***\')
print(\'0\'*100)      #------------------------------->不会执行
输出结果为:
出现with语句,对象__enter__被触发,有返回值则赋值给as声明的变量
----->执行代码块
with中代码块执行完毕时执行我啊
<class \'AttributeError\'>
***着火啦,救火啦***
AttributeError: ***着火啦,救火啦***
<traceback object at 0x000000BBD8705648>


如果__exit__()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行

class Open:
    def __init__(self,name):
        self.name=name
    def __enter__(self):
        print(\'出现with语句,对象__enter__被触发,有返回值则赋值给as声明的变量\')
    def __exit__(self, exc_type, exc_val, exc_tb):
        print(\'with中代码块执行完毕时执行我啊\')
        print(exc_type)
        print(exc_val)
        print(exc_tb)
        return True
with Open(\'a.txt\') as f:
    print(\'----->执行代码块\')
    raise AttributeError(\'***着火啦,救火啦***\')
print(\'0\'*100)
输出结果为:
出现with语句,对象__enter__被触发,有返回值则赋值给as声明的变量
----->执行代码块
with中代码块执行完毕时执行我啊
<class \'AttributeError\'>
***着火啦,救火啦***
<traceback object at 0x000000DCD0454648>
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000


10.24.3 __exit__用途
用途或者好处:
1 使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预
2 在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处。

import time
class Open:
    def __init__(self,filepath,m=\'r\',encode=\'utf-8\'):
        self.x=open(filepath,mode=m,encoding=encode)
        self.filepath=filepath
        self.mode=m
        self.encoding=encode
    def __enter__(self):
        print("=========> 执行enter")
        return self
    def write(self,line):
        print(\'f 自己的write\',line)
        t=time.strftime(\'%Y-%m-%d %X\')
        self.x.write(\'%s %s\'%(t,line))
    def __getattr__(self, item):
        return getattr(self.x,item)
    def __exit__(self, exc_type, exc_val, exc_tb):
        print(\'exit\')
        print("exc_type",exc_type)
        print(\'exc_val\',exc_val)
        print(\'exc_tb\',exc_tb)
        self.x.close()
        return True

# f=Open(\'b.txt\',\'w\')
# f.write(\'111111\n\')
# f.write(\'222222\n\')
# f.write(\'333333\n\')
# f=Open(\'b.txt\',\'r+\')
# print(f.read)
# res=f.read()
# print(res)
# f.flush()
# f.close()
with Open(\'b.txt\',\'r\') as f:
    print("with f 执行:",f)
    res = f.read()
    print(res)
    raise NameError(\'名字没有定义\')
    print("**************")
输出结果为:
=========> 执行enter
with f 执行: <__main__.Open object at 0x000000CE1E00FA58>
2017-04-25 11:14:32 111111
2017-04-25 11:14:32 222222
2017-04-25 11:14:32 333333

exit
exc_type <class \'NameError\'>
exc_val 名字没有定义
exc_tb <traceback object at 0x000000CE1E022FC8>



10.25 __call__

对象加括号运行,会触发__call__方法方法执行

注意:构造方法的执行是由创建对象触发的,即:对象=类名();
而对于__call__方法的执行是由对象后加括号触发的,即:对象()或者类()

class Foo:
    def __init__(self):
        print(\'执行__init__\')
    def __call__(self, *args, **kwargs):
        print(\'执行__call__\',*args,**kwargs)
obj=Foo()
obj()
obj(1,2,3)
print(callable(Foo))
print(callable(obj))
输出结果为:
执行__init__
执行__call__
执行__call__ 1 2 3
True
True

callable函数是检查对象object是否可调用。类是可以调用的,对象只有写了__call__方法才是可被调用的。


10.26 __eq__,__hash__方法

去重name和sex相同的Person的对象:
class Person:
    def __init__(self,name,age,sex,weight):
        self.name = name
        self.sex = sex
        self.age = age
        self.weight = weight
    def __eq__(self, obj):
        if self.name == obj.name and  self.sex ==  obj.sex:
         return True
    def __hash__(self):
        #根据hash值是否相同来判断是否重复
        return (self.name,self.sex).__hash__()
a1=Person(\'alex\',18,\'male\',60)
a2=Person(\'alex\',30,\'male\',60)
print(set([a1,a2]))


10.26 metaclass元类以及自定义元类
10.26.1 引子

class Foo:
    pass
f1=Foo()    #f1是通过Foo类实例化的对象

python中一切皆是对象,类本身也是一个对象,当使用关键字class的时候,python解释器在加载class的时候就会创建了一个对象(这里的对象指的是类而非类的实例)
上例可以看出f1是由Foo这个类产生的对象,而Foo本身也是对象,那它又是由哪个类产生的呢?

#type函数可以查看类型,也可以用来查看对象的类,二者是一样的
class Foo:
    pass
f1=Foo()
print(type(f1))
print(type(Foo))
输出结果为:
<class \'__main__.Foo\'>    #表示 obj对象由Foo类创建
<class \'type\'>            #表示  Foo类是由type创建的





10.26.2 元类是什么

元类是类的类,是类的模板
元类是用来控制如何创建类的,正如类是创建对象的模板一样
元类的实例为类,正如类的实例为对象(f1对象是Foo类的一个实例,Foo类是type类的一个实例)
Type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实是type类实例化的对象
元类就是类的类,可以控制创建类的行为

抽象类的实现也是指定了一个元类

10.26.3 创建类的两种方式

(1)方式一:class
class Foo:
    def func(self):
        print(\'from func \')

(2)方式二:使用元类type
def func(self):
    print(\'from func\')
x=1
Foo=type(\'Foo\',(object,),{\'func\':func,\'x\':1})

#type成为元类,是所有类的类,利用type模拟class关键字的创建类的过程
def run(self):
    print(\'%s is running\' %self.name)
class_name=\'Bar\'
bases=(object,)
class_dic={
    \'x\':1,
    \'run\':run
}
Bar=type(class_name,bases,class_dic)
print(Bar)
print(type(Bar))
输出结果为:
<class \'__main__.Bar\'>
<class \'type\'>

s=type(\'spam\',(),{})
print(s)
print(s.__bases__)
print(s.__dict__)
相当于:
class Spam:
    pass
print(Spam.__dict__)



10.26.4 自定义元类
一个类没有声明自己的元类,默认它的元类就是type,除了使用元类type,用户也可以通过继承type来定义元类(顺便我们也可以看到元类是如何控制类的创建,工作的流程是什么)

元类就是类的类,可以控制创建类的行为

(1)自定义元类相当于

class Mymeta(type):
    pass
class Foo(metaclass=Mymeta):
    x=1
    def run(self):
        print(\'running\')
相当于:
Foo=Mymeta(\'Foo\',(object,),{\'x\':1,\'run\':run})

(2)自定义元类型让继承他的类必须写注释信息

class Mymeta(type):
    def __init__(self,class_name,class_bases,class_dic):
        for key in class_dic:
            if not callable(class_dic[key]):continue
            if not class_dic[key].__doc__:
                raise TypeError(\'没写注释,请写注释\')
class Foo(metaclass=Mymeta):
    x=1
    def run(self):
        \'run func\'
        print(\'running\')
print(Foo.__dict__)
输出结果为:
{\'x\': 1, \'__module__\': \'__main__\', \'run\': <function Foo.run at 0x00000004FEC46048>, \'__dict__\': <attribute \'__dict__\' of \'Foo\' objects>, \'__weakref__\': <attribute \'__weakref__\' of \'Foo\' objects>, \'__doc__\': None}


(3)如果自定义的类型中__init__不写任何代码,也是可以的

class Mymeta(type):
    def __init__(self,class_name,class_bases,class_dic):
        pass
class Foo(metaclass=Mymeta):
    x=1
    def run(self):
        \'run func\'
        print(\'running\')
print(Foo.__dict__)
输出结果为:
{\'__module__\': \'__main__\', \'run\': <function Foo.run at 0x000000AADC105048>, \'__doc__\': None, \'__dict__\': <attribute \'__dict__\' of \'Foo\' objects>, \'__weakref__\': <attribute \'__weakref__\' of \'Foo\' objects>, \'x\': 1}


(4)类的__init__不用返回值的实质:是因为调用了type的__call__
__new__会得到一个空对象

class Mymeta(type):
    def __init__(self,class_name,class_bases,class_dic):
        pass
    def __call__(self, *args, **kwargs):
        obj=self.__new__(self)
        self.__init__(obj,*args,**kwargs)
        return obj
class Foo(metaclass=Mymeta):
    x=1
    def __init__(self,name):
        self.name=name
    def run(self):
        \'run func\'
        print(\'running\')
f=Foo("egon")
print(f.name)
输出结果为:
egon


(5)元类是深度的魔法

 


http://www.cnblogs.com/weihuchao/p/6762521.html



http://www.cnblogs.com/linhaifeng/articles/6379069.html#_label1


十二 模块


12.1 什么是模块

   一个模块就是一个包含了python定义和声明的文件,文件名就是模块名字加上.py的后缀。
模块就是py文件

 


12.2 为什么使用模块
   如果你退出python解释器然后重新进入,那么你之前定义的函数或者变量都将丢失,因此我们通常将程序写到文件中以便永久保存下来,需要时就通过python test.py方式去执行,此时test.py被称为脚本script。

随着程序的发展,功能越来越多,为了方便管理,我们通常将程序分成一个个的文件,这样做程序的结构更清晰,方便管理。这时我们不仅仅可以把这些文件当做脚本去执行,还可以把他们当做模块来导入到其他的模块中,实现了功能的重复利用,




12.3 如何使用模块

12.3.1 import
示例文件:spam.py,文件名spam.py,模块名spam

 1 #spam.py
 2 print(\'from the spam.py\')
 3 
 4 money=1000
 5 
 6 def read1():
 7     print(\'spam->read1->money\',1000)
 8 
 9 def read2():
10     print(\'spam->read2 calling read\')
11     read1()
12 
13 def change():
14     global money
15     money=0


(1)初始化模块

模块可以包含可执行的语句和函数的定义,这些语句的目的是初始化模块,它们只在模块名第一次遇到导入import语句时才执行(import语句是可以在程序中的任意位置使用的,且针对同一个模块很import多次,为了防止你重复导入,python的优化手段是:第一次导入后就将模块名加载到内存了,后续的import语句仅是对已经加载大内存中的模块对象增加了一次引用,不会重新执行模块内的语句),如下 

 1 #test.py
 2 import spam #只在第一次导入时才执行spam.py内代码,此处的显式效果是只打印一次\'from the spam.py\',当然其他的顶级代码也都被执行了,只不过没有显示效果.
 3 import spam
 4 import spam
 5 import spam
 6 
 7 \'\'\'
 8 执行结果:
 9 from the spam.py

我们可以从sys.module中找到当前已经加载的模块,sys.module是一个字典,内部包含模块名与模块对象的映射,该字典决定了导入模块时是否需要重新导入。
import spam
import sys
print(sys.modules)



(2)模块的名称空间
每个模块都是一个独立的名称空间,定义在这个模块中的函数,把这个模块的名称空间当做全局名称空间,这样我们在编写自己的模块时,就不用担心我们定义在自己模块中全局变量会在被导入时,与使用者的全局变量冲突

#测试一:money与spam.money不冲突
#test.py
import spam 
money=10
print(spam.money)

\'\'\'
执行结果:
from the spam.py
1000
\'\'\'


#测试二:read1与spam.read1不冲突
#test.py
import spam
def read1():
    print(\'========\')
spam.read1()

\'\'\'
执行结果:
from the spam.py
spam->read1->money 1000
\'\'\'


#测试三:执行spam.change()操作的全局变量money仍然是spam中的
#test.py
import spam
money=1
spam.change()
print(money)

\'\'\'
执行结果:
from the spam.py
1
\'\'\'


(3)import导入模块做的三件事总结
首次导入模块spam时会做三件事:

1.为源文件(spam模块)创建新的名称空间,在spam中定义的函数和方法若是使用到了global时访问的就是这个名称空间。

2.在新创建的命名空间中执行模块中包含的代码,见初始导入import spam

1提示:导入模块时到底执行了什么?
2 
3 In fact function definitions are also ‘statements’ that are ‘executed’; the execution of a module-level function definition enters the function name in the module’s global symbol table.
4 事实上函数定义也是“被执行”的语句,模块级别函数定义的执行将函数名放入模块全局名称空间表,用globals()可以查看

3.创建名字spam来引用该命名空间
1 这个名字和变量名没什么区别,都是‘第一类的’,且使用spam.名字的方式可以访问spam.py文件中定义的名字,spam.名字与test.py中的名字来自两个完全不同的地方。

 

 

 




(4)为模块起别名
为模块名起别名,相当于m1=1;m2=m1 
1 import spam as sm
2 print(sm.money)

为已经导入的模块起别名的方式对编写可扩展的代码很有用,假设有两个模块xmlreader.py和csvreader.py,它们都定义了函数read_data(filename):用来从文件中读取一些数据,但采用不同的输入格式。可以编写代码来选择性地挑选读取模块,例如
1 if file_format == \'xml\':
2     import xmlreader as reader
3 elif file_format == \'csv\':
4     import csvreader as reader
5 data=reader.read_date(filename)


(5)在一行导入多个模块
不建议这麽做
1 import sys,os,re


12.3.2 from ... Import ...
(1)好处
对比import spam,会将源文件的名称空间\'spam\'带到当前名称空间中,使用时必须是spam.名字的方式
而from 语句相当于import,也会创建新的名称空间,但是将spam中的名字直接导入到当前的名称空间中,在当前名称空间中,直接使用名字就可以了
from spam import read1,read2
这样在当前位置直接使用read1和read2就好了,执行时,仍然以spam.py文件全局名称空间
#测试一:导入的函数read1,执行时仍然回到spam.py中寻找全局变量money
#test.py
from spam import read1
money=1000
read1()
\'\'\'
执行结果:
from the spam.py
spam->read1->money 1000
\'\'\'

#测试二:导入的函数read2,执行时需要调用read1(),仍然回到spam.py中找read1()
#test.py
from spam import read2
def read1():
    print(\'==========\')
read2()

\'\'\'
执行结果:
from the spam.py
spam->read2 calling read
spam->read1->money 1000
\'\'\'

如果当前有重名read1或者read2,那么会有覆盖效果。

#测试三:导入的函数read1,被当前位置定义的read1覆盖掉了
#test.py
from spam import read1
def read1():
    print(\'==========\')
read1()
\'\'\'
执行结果:
from the spam.py
==========
\'\'\'

需要特别强调的一点是:python中的变量赋值不是一种存储操作,而只是一种绑定关系,如下:
 1 from spam import money,read1
 2 money=100 #将当前位置的名字money绑定到了100
 3 print(money) #打印当前的名字
 4 read1() #读取spam.py中的名字money,仍然为1000
 5 
 6 \'\'\'
 7 from the spam.py
 8 100
 9 spam->read1->money 1000
10 \'\'\'

文件spam.py :
print(\'from the spam.py\')
money=1000
def read1():
    print("spam -> read1 ->money",money )
def read2():
    print(\'spam -> read2\')
    read1()
def change():
    global money
money=0

文件test2.py :
from spam import money,read1,read2
money=10
print(read1)
read1=123
print(read1)
print(money)
read2()
输出结果为:
from the spam.py
<function read1 at 0x000000647282BF28>
123
10
spam -> read2
spam -> read1 ->money 1000






(2)支持as

1 from spam import read1 as read



(3)支持多行

1 from spam import (read1,
2                   read2,
3                   money)



(4)覆盖问题

 from spam import * 把spam中所有的不是以下划线(_)开头的名字都导入到当前位置,大部分情况下我们的python程序不应该使用这种导入方式,因为*你不知道你导入什么名字,很有可能会覆盖掉你之前已经定义的名字。而且可读性极其的差,在交互式环境中导入时没有问题。

 1 from spam import * #将模块spam中所有的名字都导入到当前名称空间
 2 print(money)
 3 print(read1)
 4 print(read2)
 5 print(change)
 6 
 7 \'\'\'
 8 执行结果:
 9 from the spam.py
10 1000
11 <function read1 at 0x1012e8158>
12 <function read2 at 0x1012e81e0>
13 <function change at 0x1012e8268>
14 \'\'\'


可以使用__all__来控制*(用来发布新版本)
在spam.py中新增一行
__all__=[\'money\',\'read1\'] #这样在另外一个文件中用from spam import *就这能导入列表中规定的两个名字


(5)性能问题
考虑到性能的原因,每个模块只被导入一次,放入字典sys.module中,如果你改变了模块的内容,你必须重启程序,python不支持重新加载或卸载之前导入的模块,

有的同学可能会想到直接从sys.module中删除一个模块不就可以卸载了吗,注意了,你删了sys.module中的模块对象仍然可能被其他程序的组件所引用,因而不会被清楚。

特别的对于我们引用了这个模块中的一个类,用这个类产生了很多对象,因而这些对象都有关于这个模块的引用。

如果只是你想交互测试的一个模块,使用 importlib.reload(), e.g. import importlib; importlib.reload(modulename),这只能用于测试环境。

aa.py的初始内容:
def func1():
print(\'func1\')

执行test.py
1 import time,importlib
2 import aa
3 
4 time.sleep(20)
5 # importlib.reload(aa)
6 aa.func1()


在20秒的等待时间里,修改aa.py中func1的内容,等待test.py的结果。
打开importlib注释,重新测试


12.3.3 把模块当做脚本执行

我们可以通过模块的全局变量__name__来查看模块名:
当做脚本运行:
__name__ 等于\'__main__\'

当做模块导入:
__name__=

作用:用来控制.py文件在不同的应用场景下执行不同的逻辑
if __name__ == \'__main__\':

#fib.py

def fib(n):    # write Fibonacci series up to n
    a, b = 0, 1
    while b < n:
        print(b, end=\' \')
        a, b = b, a+b
    print()

def fib2(n):   # return Fibonacci series up to n
    result = []
    a, b = 0, 1
    while b < n:
        result.append(b)
        a, b = b, a+b
    return result

if __name__ == "__main__":
    import sys
    fib(int(sys.argv[1]))


执行:
1 #python fib.py <arguments>
2 python fib.py 50 #在命令行

 

12.3.4 模块搜索路径

 
在第一次导入某个模块时(比如spam),会先检查该模块是否已经被加载到内存中(当前执行文件的名称空间对应的内存),如果有则直接引用。如果没有,解释器则会查找同名的内建模块,如果还没有找到就从sys.path给出的目录列表中依次寻找spam.py文件。

不要用与内置函数和第三方模块相同的名称命名py文件,切记

sys.path的初始化的值来自于:
The directory containing the input script (or the current directory when no file is specified).
PYTHONPATH (a list of directory names, with the same syntax as the shell variable PATH).
The installation-dependent default.

需要特别注意的是:我们自定义的模块名不应该与系统内置模块重名。虽然每次都说,但是仍然会有人不停的犯错。 

在初始化后,python程序可以修改sys.path,路径放到前面的优先于标准库被加载。

1 >>> import sys
2 >>> sys.path.append(\'/a/b/c/d\')
3 >>> sys.path.insert(0,\'/x/y/z\') #排在前的目录,优先被搜索

注意:搜索时按照sys.path中从左到右的顺序查找,位于前的优先被查找,sys.path中还可能包含.zip归档文件和.egg文件,python会把.zip归档文件当成一个目录去处理,

1 #首先制作归档文件:zip module.zip foo.py bar.py
2 
3 import sys
4 sys.path.append(\'module.zip\')
5 import foo,bar
6 
7 #也可以使用zip中目录结构的具体位置
8 sys.path.append(\'module.zip/lib/python\')


至于.egg文件是由setuptools创建的包,这是按照第三方python库和扩展时使用的一种常见格式,.egg文件实际上只是添加了额外元数据(如版本号,依赖项等)的.zip文件。

需要强调的一点是:只能从.zip文件中导入.py,.pyc等文件。使用C编写的共享库和扩展块无法直接从.zip文件中加载(此时setuptools等打包系统有时能提供一种规避方法),且从.zip中加载文件不会创建.pyc或者.pyo文件,因此一定要事先创建他们,来避免加载模块是性能下降。


 
Sys.path中先找到为准


12.3.5 编译python文件

为了提高模块的加载速度,Python缓存编译的版本,每个模块在__pycache__目录的以module.version.pyc的形式命名,通常包含了python的版本号,如在CPython版本3.3,关于spam.py的编译版本将被缓存成__pycache__/spam.cpython-33.pyc,这种命名约定允许不同的版本,不同版本的Python编写模块共存。

Python检查源文件的修改时间与编译的版本进行对比,如果过期就需要重新编译。这是完全自动的过程。并且编译的模块是平台独立的,所以相同的库可以在不同的架构的系统之间共享,即pyc使一种跨平台的字节码,类似于JAVA火.NET,是由python虚拟机来执行的,但是pyc的内容跟python的版本相关,不同的版本编译后的pyc文件不同,2.5编译的pyc文件不能到3.5上执行,并且pyc文件是可以反编译的,因而它的出现仅仅是用来提升模块的加载速度的。

提示:

1.模块名区分大小写,foo.py与FOO.py代表的是两个模块

2.你可以使用-O或者-OO转换python命令来减少编译模块的大小

1 -O转换会帮你去掉assert语句
2 -OO转换会帮你去掉assert语句和__doc__文档字符串
3 由于一些程序可能依赖于assert语句或文档字符串,你应该在在确认需要的情况下使用这些选项。

3.在速度上从.pyc文件中读指令来执行不会比从.py文件中读指令执行更快,只有在模块被加载时,.pyc文件才是更快的

4.只有使用import语句是才将文件自动编译为.pyc文件,在命令行或标准输入中指定运行脚本则不会生成这类文件,因而我们可以使用compieall模块为一个目录中的所有模块创建.pyc文件

1 模块可以作为一个脚本(使用python -m compileall)编译Python源
2  
3 python -m compileall /module_directory 递归着编译
4 如果使用python -O -m compileall /module_directory -l则只一层
5  
6 命令行里使用compile()函数时,自动使用python -O -m compileall
7  
8 详见:https://docs.python.org/3/library/compileall.html#module-compileall

如果文件的修改时间改变,pyc文件才会重新生成

12.3.6 标准模块
python提供了一个标准模块库,一些模块被内置到解释器中,这些提供了不属于语言核心部分的操作的访问,但它们是内置的,无论是为了效率还是提供对操作系统原语的访问。这些模块集合是依赖于底层平台的配置项,如winreg模块只能用于windows系统。特别需要注意的是,sys模块内建在每一个python解释器

sys.ps1
sys.ps2
这俩只在命令行有效,得出的结果,标识了解释器是在交互式模式下。

变量sys.path是一个决定了模块搜索路径的字符串列表,它从环境变量PYTHONOATH中初始化默认路径,如果PYTHONPATH没有设置则从内建中初始化值,我们可以修改它
sys.path.append

os一种好的处理路径的方式
import os
os.path.normpath(path)  #规范化路径,转换path的大小写和斜杠

a=\'/Users/jieli/test1/\\\a1/\\\\aa.py/../..\'
print(os.path.normpath(a))
\'\'\'
打印结果:
\Users\jieli\test1
\'\'\'
#具体应用
import os,sys
possible_topdir = os.path.normpath(os.path.join(
    os.path.abspath(__file__),
    os.pardir, #上一级
    os.pardir,
    os.pardir
))
sys.path.insert(0,possible_topdir)


12.3.7 dir()函数

内建函数dir是用来查找模块中定义的名字,返回一个有序字符串列表
import spam
dir(spam)

如果没有参数,dir()列举出当前定义的名字

dir()不会列举出内建函数或者变量的名字,它们都被定义到了标准模块builtin中,可以列举出它们,
import builtins
dir(builtins)
 
 

十三 包
Packages are a way of structuring Python’s module namespace by using “dotted module names”
包是一种通过使用‘.模块名’来组织python模块名称空间的方式。

无论是import形式还是from...import形式,凡是在导入语句中(而不是在使用时)遇到带点的,都要第一时间提高警觉:这是关于包才有的导入语法

包的本质就是一个包含__init__.py文件的目录。
包A和包B下有同名模块也不会冲突,如A.a与B.a来自俩个命名空间

glance/                   #Top-level package

├── __init__.py      #Initialize the glance package

├── api                  #Subpackage for api

│   ├── __init__.py

│   ├── policy.py

│   └── versions.py

├── cmd                #Subpackage for cmd

│   ├── __init__.py

│   └── manage.py

└── db                  #Subpackage for db

    ├── __init__.py

    └── models.py


1 #文件内容
 2 
 3 #policy.py
 4 def get():
 5     print(\'from policy.py\')
 6 
 7 #versions.py
 8 def create_resource(conf):
 9     print(\'from version.py: \',conf)
10 
11 #manage.py
12 def main():
13     print(\'from manage.py\')
14 
15 #models.py
16 def register_models(engine):
17     print(\'from models.py: \',engine) 


13.1 注意事项:点的左边必须是包

1.关于包相关的导入语句也分为import和from ... import ...两种,但是无论哪种,无论在什么位置,在导入时都必须遵循一个原则:凡是在导入时带点的,点的左边都必须是一个包,否则非法。可以带有一连串的点,如item.subitem.subsubitem,但都必须遵循这个原则。

2.对于导入后,在使用时就没有这种限制了,点的左边可以是包,模块,函数,类(它们都可以用点的方式调用自己的属性)。

3.对比import item 和from item import name的应用场景:
如果我们想直接使用name那必须使用后者。


13.2 import 包

我们在与包glance同级别的文件中测试
1 import glance.db.models
2 glance.db.models.register_models(\'mysql\') 



13.3 from...import...

需要注意的是from后import导入的模块,必须是明确的一个不能带点,否则会有语法错误,如:from a import b.c是错误语法

我们在与包glance同级别的文件中测试 

from glance.db import models
models.register_models(\'mysql\')

from glance.db.models import register_models
register_models(\'mysql\')





13.4 __init__.py文件
不管是哪种方式,只要是第一次导入包或者是包的任何其他部分,都会依次执行包下的__init__.py文件(我们可以在每个包的文件内都打印一行内容来验证一下),这个文件可以为空,但是也可以存放一些初始化包的代码,即这个__init__也可以导入其他模块或包。



13.5 from .... import *中*的用法 
导入* 就是导入 __init__中的__all__里面的内容
在讲模块时,我们已经讨论过了从一个模块内导入所有*,此处我们研究从一个包导入所有*。
此处是想从包api中导入所有,实际上该语句只会导入包api下__init__.py文件中定义的名字,我们可以在这个文件中定义__all___:
1 #在__init__.py中定义
2 x=10
3 
4 def func():
5     print(\'from api.__init.py\')
6 
7 __all__=[\'x\',\'func\',\'policy\']

此时我们在于glance同级的文件中执行from glance.api import *就导入__all__中的内容(versions仍然不能导入)。


13.6 绝对导入和相对导入

(1)绝对导入和相对导入举例
我们的最顶级包glance是写给别人用的,然后在glance包内部也会有彼此之间互相导入的需求,这时候就有绝对导入和相对导入两种方式:

绝对导入:以glance作为起始
相对导入:用.或者..的方式最为起始(只能在一个包中使用,不能用于不同目录内)

例如:我们在glance/api/version.py中想要导入glance/cmd/manage.py

1 在glance/api/version.py
2 
3 #绝对导入
4 from glance.cmd import manage
5 manage.main()
6 
7 #相对导入
8 from ..cmd import manage
9 manage.main()

(2)注意绝对导入和相对导入的问题

测试结果:注意一定要在于glance同级的文件中测试
1 from glance.api import versions 

注意:在使用pycharm时,有的情况会为你多做一些事情,这是软件相关的东西,会影响你对模块导入的理解,因而在测试时,一定要回到命令行去执行,模拟我们生产环境,你总不能拿着pycharm去上线代码吧!!!

特别需要注意的是:对于包来说,应该遵循这样的原则,可以用import导入内置或者第三方模块,但是要绝对不要使用import来导入自定义包的子模块,应该使用from... import ...的绝对或者相对导入,且包的相对导入只能用from的形式。

比如我们想在glance/api/versions.py中导入glance/api/policy.py,有的同学一看这俩模块是在同一个目录下,十分开心的就去做了,它直接这么做
1 #在version.py中
2 
3 import policy
4 policy.get()

没错,我们单独运行version.py是一点问题没有的,运行version.py的路径搜索就是从当前路径开始的,于是在导入policy时能在当前目录下找到

但是你想啊,你子包中的模块version.py极有可能是被一个glance包同一级别的其他文件导入,比如我们在于glance同级下的一个test.py文件中导入version.py,如下
 1 from glance.api import versions
 2 
 3 \'\'\'
 4 执行结果:
 5 ImportError: No module named \'policy\'
 6 \'\'\'
 7 
 8 \'\'\'
 9 分析:
10 此时我们导入versions在versions.py中执行
11 import policy需要找从sys.path也就是从当前目录找policy.py,
12 这必然是找不到的
13 \'\'\'


13.7 单独导入包不会导入所有子模块
单独导入包名称时不会导入所有包含的所有子模块,如:

#在与glance同级的test.py中
import glance
glance.cmd.manage.main()

\'\'\'
执行结果:
AttributeError: module \'glance\' has no attribute \'cmd\'

\'\'\' 

解决方法: 可以在各级的__init__文件中导入模块和包
1 #glance/__init__.py
2 from . import cmd
3 
4 #glance/cmd/__init__.py
5 from . import manage

执行结果为:
1 #在于glance同级的test.py中
2 import glance
3 glance.cmd.manage.main()

千万别问:__all__不能解决吗,__all__是用于控制from...import * 


13.8 自定义包中可以直接import内置包
涉及模块导入的顺序

 



13.9 导入同级目录的包

 

(1)每一级的__init__文件都改

文件policy.py
def get():
    print(\'from policy.py\')
文件glance/api/__init__.py
 from . import policy
文件glance/__init__.py
from . import api
文件test.py
import glance
glance.api.policy.get()
输出结果为:
from policy.py


(2)包.的方式 调用方法
对于使用包的人来说,不用关心包的内容,直接就可以使用功能

文件policy.py
def get():
    print(\'from policy.py\')
文件glance/api/__init__.py不导入任何文件
文件glance/__init__.py
from glance.api.policy import get
文件test.py
import glance
glance.get()
输出结果为:
from policy.py


13.10 导入其他文件夹的包

 

文件policy.py
def get():
    print(\'from policy.py\')
文件glance/api/__init__.py不导入任何文件
文件glance/__init__.py
from glance.api.policy import get
文件test.py
import sys,os
base_dir=os.path.dirname(os.path.abspath(__file__))  #获取路径
sys.path.append(base_dir)                        #加入sys.path
from aaa import glance                          # 从aaa中导入glance包
glance.get()
输出结果为:
from policy.py




 



 


13.11 包内文件调用包内其他文件





 
包是用来导入的
软件是直接运行的







十四 一些模块


14.1 time


www.cnblogs.com/yuanchenqi/articles/6766020.html
http://www.cnblogs.com/linhaifeng/articles/6384466.html


14.1.1 time的三种表示方式
在python中,通常有这几种方式来表示时间:

(1)时间戳(timestamp)
通常来说,时间戳表示的是从1970年1月1日00:00:00开始按秒计算的偏移量。我们运行“type(time.time())”,返回的是float类型。

(2)格式化的时间字符串(Format String)
‘1988-03-16’

(3)结构化的时间(struct_time)
struct_time元组共有9个元素共九个元素:(年,月,日,时,分,秒,一年中第几周,一年中第几天,夏令时)


import time
print(time.time())                    # 时间戳:当前的时间戳
print(time.strftime("%Y-%m-%d %X"))   # 格式化的时间字符串
print(time.localtime())                #本地时区的struct_time
print(time.gmtime())                 #UTC时区的struct_time
输出结果为:
1493189903.0360186
2017-04-26 14:58:23
time.struct_time(tm_year=2017, tm_mon=4, tm_mday=26, tm_hour=14, tm_min=58, tm_sec=23, tm_wday=2, tm_yday=116, tm_isdst=0)
time.struct_time(tm_year=2017, tm_mon=4, tm_mday=26, tm_hour=6, tm_min=58, tm_sec=23, tm_wday=2, tm_yday=116, tm_isdst=0)


14.1.2 time格式的转换方式

其中计算机认识的时间只能是\'时间戳\'格式,而程序员可处理的或者说人类能看懂的时间有: \'格式化的时间字符串\',\'结构化的时间\' ,于是有了下图的转换关系

 


(1)time.localtime([secs])
将一个时间戳转换为当前时区的struct_time。secs参数未提供,则以当前时间为准。
print(time.localtime())
print(time.localtime(time.time()))
输出结果为:
time.struct_time(tm_year=2017, tm_mon=4, tm_mday=26, tm_hour=15, tm_min=2, tm_sec=41, tm_wday=2, tm_yday=116, tm_isdst=0)
time.struct_time(tm_year=2017, tm_mon=4, tm_mday=26, tm_hour=15, tm_min=2, tm_sec=41, tm_wday=2, tm_yday=116, tm_isdst=0)

(2)time.gmtime([secs]) 
和localtime()方法类似,gmtime()方法是将一个时间戳转换为UTC时区(0时区)的struct_time。
print(time.gmtime())
print(time.gmtime(time.time()))
输出结果为:
time.struct_time(tm_year=2017, tm_mon=4, tm_mday=26, tm_hour=7, tm_min=37, tm_sec=45, tm_wday=2, tm_yday=116, tm_isdst=0)
time.struct_time(tm_year=2017, tm_mon=4, tm_mday=26, tm_hour=7, tm_min=37, tm_sec=45, tm_wday=2, tm_yday=116, tm_isdst=0)
(3)time.mktime(t) 
将一个struct_time转化为时间戳。
print(time.mktime(time.localtime()))
输出结果为:
1493192331.0


(4)time.strftime(format[, t]) 
把一个代表时间的元组或者struct_time(如由time.localtime()和time.gmtime()返回)转化为格式化的时间字符串。如果t未指定,将传入time.localtime()。如果元组中任何一个元素越界,ValueError的错误将会被抛出。

print(time.strftime("%Y-%m-%d %X",time.localtime()))
print(time.strftime("%Y-%m-%d %X"))
输出结果为:
2017-04-26 15:42:09
2017-04-26 15:42:09

(5)time.strptime(string[, format])
把一个格式化时间字符串转化为struct_time。实际上它和strftime()是逆操作。
#在这个函数中,format默认为:"%a %b %d %H:%M:%S %Y"。

print(time.strptime(\'2017-04-26 15:42:09\',\'%Y-%m-%d %X\'))
输出结果为:
time.struct_time(tm_year=2017, tm_mon=4, tm_mday=26, tm_hour=15, tm_min=42, tm_sec=9, tm_wday=2, tm_yday=116, tm_isdst=-1)


 


(7)time.asctime([t]) 
把一个表示时间的元组或者struct_time表示为这种形式:\'Sun Jun 20 23:21:05 1993\'。
如果没有参数,将会将time.localtime()作为参数传入。
print(time.asctime(time.localtime()))
print(time.asctime())
输出结果为:
Wed Apr 26 15:50:43 2017
Wed Apr 26 15:50:43 2017


(8)time.ctime([secs])
把一个时间戳(按秒计算的浮点数)转化为time.asctime()的形式。如果参数未给或者为None的时候,将会默认time.time()为参数。它的作用相当于time.asctime(time.localtime(secs))。
print(time.ctime())
print(time.ctime(time.time()))
输出结果为:
Wed Apr 26 15:53:18 2017
Wed Apr 26 15:53:18 2017


14.1.3 time.sleep

线程推迟指定的时间运行,单位为秒。
time.sleep(5)


14.2 random

14.2.1 random.random()
大于0且小于1的小数
print(random.random())
输出结果为:
0.3727111444551693


14.2.2 random.randint(1,3)
大于等于1且小于等于3之间的整数
print(random.randint(1,3))
输出结果为:
3


14.2.3 random.randrange(1,3)
大于等于1且小于3之间的整数
print(random.randrange(1,3))
输出结果为:
2


14.2.4 random.choice([1,2])
1或者23或者[4,5]
print(random.choice([1,\'23\',[4,5]]))
输出结果为:
[4, 5]


14.2.5 random.sample([1,2,3,4],3)
列表元素任意2个组合
print(random.sample([1,\'23\',[4,5]],2))
输出结果为:
[\'23\', [4, 5]]


14.2.6 random.uniform(1,3)
大于1小于3的小数,如1.927109612082716 
print(random.uniform(1,3))
输出结果为:
1.6594925842174193


14.2.7 random.shuffle(item)
打乱item的顺序,相当于"洗牌"
item=[1,2,3,4,5]
random.shuffle(item)
print(item)
输出结果为:
[2, 4, 3, 1, 5]


14.2.8 验证码举例
大写字母与数字混合
def v_code():
    code=\'\'
    for i in range(5):
        num=random.randint(0,9)
        alf=chr(random.randint(65,90))
        add=random.choice([num,alf])
        code += str(add)
    return code
print(v_code())

大写,小写字母和数字混合
def v_code():
    code=\'\'
    for i in range(5):
        num=random.randint(0,9)
        alf=chr(random.randint(65,90))
        alf2=chr(random.randint(97, 122))
        add=random.sample([num,alf,alf2],1)
        code += str(add[0])
    return code
print(v_code())

def validata():
    mySeq=""
    for i in range(5):
        type=random.randint(1,3)
        if type==1:
            mySeq+=str(random.randint(0,9))
        if type==2:
            mySeq+=chr(random.randint(97,122))
        if type==3:
            mySeq+=chr(random.randint(65,90))
    return mySeq
print(validata())



14.3 hashlib 模块

14.3.1 hashlib的概念和特点

hash:一种算法 ,3.x里代替了md5模块和sha模块,主要提供 SHA1, SHA224, SHA256, SHA384, SHA512 ,MD5 算法
三个特点:
1.内容相同则hash运算结果相同,内容稍微改变则hash值则变
2.不可逆推
3.相同算法:无论校验多长的数据,得到的哈希值长度固定。

14.3.2 update

 1 import hashlib
 2  
 3 m=hashlib.md5()    # m=hashlib.sha256()
 4  
 5 m.update(\'hello\'.encode(\'utf8\'))
 6 print(m.hexdigest())  #5d41402abc4b2a76b9719d911017c592
 7  
 8 m.update(\'alvin\'.encode(\'utf8\'))
 9  
10 print(m.hexdigest())  #92a7e713c30abbb0319fa07da2a5c4af
11  
12 m2=hashlib.md5()
13 m2.update(\'helloalvin\'.encode(\'utf8\'))
14 print(m2.hexdigest()) #92a7e713c30abbb0319fa07da2a5c4af
15 
16 \'\'\'
17 注意:把一段很长的数据update多次,与一次update这段长数据,得到的结果一样
18 但是update多次为校验大文件提供了可能。
19 \'\'\'



以上加密算法虽然依然非常厉害,但时候存在缺陷,即:通过撞库可以反解。所以,有必要对加密算法中添加自定义key再来做加密。
1 import hashlib
2  
3 # ######## 256 ########
4  
5 hash = hashlib.sha256(\'898oaFs09f\'.encode(\'utf8\'))
6 hash.update(\'alvin\'.encode(\'utf8\'))
7 print (hash.hexdigest())#e79e68f070cdedcfe63eaf1a2e92c83b4cfb1b5c6bc452d214c1b7e77cdfd1c7


python 还有一个 hmac 模块,它内部对我们创建 key 和 内容 进行进一步的处理然后再加密:
1 import hmac
2 h = hmac.new(\'alvin\'.encode(\'utf8\'))
3 h.update(\'hello\'.encode(\'utf8\'))
4 print (h.hexdigest())#320df9832eab4c038b6c1d7ed73a5940


 




 


 


 



14.4 os模块
有os模块是与操作系统交互的一个接口

14.4.1 os.getcwd() 
获取当前工作目录,即当前python脚本工作的目录路径
import os
print(os.getcwd())
输出结果为:
C:\python_fullstack_wen\day33

14.4.2 os.chdir("dirname")  
改变当前脚本工作目录;相当于shell下cd
print(os.getcwd())
os.chdir("C:\python_fullstack_wen\day32")
print(os.getcwd())
输出结果为:
C:\python_fullstack_wen\day33
C:\python_fullstack_wen\day32


14.4.3 os.curdir  
返回当前目录: (\'.\')
print(os.curdir)
输出结果为:
.

14.4.4 os.pardir  
获取当前目录的父目录字符串名:(\'..\')
print(os.pardir)
输出结果为:
..

14.4.5 os.makedirs(\'dirname1/dirname2\')  
  
可生成多层递归目录

os.makedirs("aaa/bbb")
os.chdir("aaa/bbb")
print(os.getcwd())
输出结果为:
C:\python_fullstack_wen\day33\aaa\bbb

14.4.6 os.removedirs(\'dirname1\')    

若目录为空,则删除,并递归到上一级目录,如若也为空,则删除,依此类推

os.removedirs(\'aaa/bbb\')


14.4.7 os.mkdir(\'dirname\')    

生成单级目录;相当于shell中mkdir dirname
os.mkdir(\'aaa\')
os.chdir(\'aaa\')
print(os.getcwd())
输出结果为:
C:\python_fullstack_wen\day33\aaa


14.4.8 os.rmdir(\'dirname\')    

删除单级空目录,若目录不为空则无法删除,报错;相当于shell中rmdir dirname
os.rmdir(\'aaa\')

14.4.9 os.listdir(\'dirname\')    

列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印
print(os.listdir()) 不加参数显示当前目录中文件和目录列表
path=os.getcwd()
print(os.listdir(path))
输出结果为:
[\'hashlib1.py\', \'os_.py\', \'random-.py\', \'sys_.py\', \'time.py\', \'__pycache__\', \'模块.py\']

14.4.10 os.remove()  
删除一个文件
os.remove(\'a.txt\')


14.4.11 os.rename("oldname","newname")  
重命名文件/目录
os.rename(\'a.txt\',\'b.txt\')



14.4.12 os.stat(\'path/filename\')  
获取文件/目录信息
print(os.stat(\'b.txt\'))
输出结果为:
os.stat_result(st_mode=33206, st_ino=19421773393178877, st_dev=4068286715, st_nlink=1, st_uid=0, st_gid=0, st_size=0, st_atime=1493197243, st_mtime=1493197243, st_ctime=1493197243)

stat 结构:
st_mode: inode 保护模式
st_ino: inode 节点号。
st_dev: inode 驻留的设备。
st_nlink: inode 的链接数。
st_uid: 所有者的用户ID。
st_gid: 所有者的组ID。
st_size: 普通文件以字节为单位的大小;包含等待某些特殊文件的数据。
st_atime: 上次访问的时间。
st_mtime: 最后一次修改的时间。
st_ctime: 由操作系统报告的"ctime"。在某些系统上(如Unix)是最新的元数据更改的时间,在其它系统上(如Windows)是创建时间(详细信息参见平台的文档)。

 


14.4.13 os.sep    
输出操作系统特定的路径分隔符,win下为"\\",Linux下为"/"
print(os.sep)
输出结果为:
\


14.4.14 os.linesep   
输出当前平台使用的行终止符,win下为"\t\n",Linux下为"\n"




14.4.15 os.pathsep    
输出用于分割文件路径的字符串 win下为;,Linux下为:
print(os.pathsep)
输出结果为:
;

14.4.16 os.name    
输出字符串指示当前使用平台。win->\'nt\'; Linux->\'posix\'
print(os.name)
输出结果为:
nt

14.4.17 os.system("bash command")  
运行shell命令,直接显示


14.4.18 os.environ  
获取系统环境变量
print(os.environ)

14.4.19 os.path.abspath(path)  
返回path规范化的绝对路径
print(os.path.abspath(\'b.txt\'))
输出结果为:
C:\python_fullstack_wen\day33\b.txt

14.4.20 os.path.split(path)  
将path分割成目录和文件名二元组返回
filepath,filename=os.path.split(\'C:\python_fullstack_wen\day33\b.txt\')
print(filepath)
print(filename)
输出结果为:
C:\python_fullstack_wen
day3.txt


14.2.21 os.path.dirname(path)  
返回path的目录。其实就是os.path.split(path)的第一个元素
filepath=os.path.dirname(r"C:\python_fullstack_wen\day33\b.txt")
print(filepath)
输出结果为:
C:\python_fullstack_wen

14.2.22 os.path.basename(path)  
返回path最后的文件名。如何path以/或\结尾,那么就会返回空值。即os.path.split(path)的第二个元素
filename=os.path.basename(r\'C:\python_fullstack_wen\day33\b.txt\')
print(filename)
输出结果为:
b.txt

14.2.23 os.path.exists(path)  
如果path存在,返回True;如果path不存在,返回False
print(os.path.exists(\'b.txt\'))
print(os.path.exists(r\'C:\python_fullstack_wen\day33\b.txt\'))
print(os.path.exists(\'bb.txt\'))
print(os.path.exists(r\'C:\python_fullstack_wen\day33\'))    #可以检测目录存不存在
输出结果为:
True
True
False
True

14.2.24 os.path.isabs(path) 
如果path是绝对路径,返回True
print(os.path.isabs(\'b.txt\'))
print(os.path.isabs(\'C:\python_fullstack_wen\day33\b.txt\'))
输出结果为:
False
True


14.4.25 os.path.isfile(path)  
如果path是一个存在的文件,返回True。否则返回False
print(os.path.isfile(\'b.txt\'))
print(os.path.isfile(r\'C:\python_fullstack_wen\day33\b.txt\'))
print(os.path.isfile(r\'C:\python_fullstack_wen\day33\bb.txt\'))
输出结果为:
True
True
False


14.4.26 os.path.isdir(path)  
如果path是一个存在的目录,则返回True。否则返回False
print(os.path.isdir(r\'C:\python_fullstack_wen\day33\b.txt\'))
print(os.path.isdir(r\'C:\python_fullstack_wen\day33\'))
输出结果为:
False
True

14.4.27 os.path.join(path1[, path2[, ...]])  

将多个路径组合后返回,第一个绝对路径之前的参数将被忽略
path=os.path.join(\'C:\python_fullstack_wen\',\'day33\')
print(path)
输出结果为:
C:\python_fullstack_wen\day33


14.4.28 os.path.getatime(path)  

返回path所指向的文件或者目录的最后存取时间
last_time=os.path.getatime(r\'C:\python_fullstack_wen\day33\')
print(last_time)
输出结果为:
1493204136.6966121

14.4.29 os.path.getmtime(path)  

返回path所指向的文件或者目录的最后修改时间
last_time=os.path.getmtime(r\'C:\python_fullstack_wen\day33\')
print(last_time)
输出结果为:
1493204248.7941592

14.4.30 os.path.normpath(path)
在Linux和Mac平台上,该函数会原样返回path,在windows平台上会将路径中所有的字符转换为小写,
并将所有斜杠转换为反斜杠。规范化路径,只是针对womdows系统有效。

print(os.path.normcase(\'c:/windows\\system32\\\'))
输出结果为:
c:\windows\system32\


14.4.31 os.path.normpath(path)
规范化路径,如..和/
print(os.path.normpath(\'c://windows\\System32\\../Temp/\'))
a=\'/Users/jieli/test1/\\\a1/\\\aa.py/../..\'
print(os.path.normpath(a))
输出结果为:
c:\windows\Temp
\Users\jieli\test1

14.4.32 os.path.getsize(path) 

返回path中文件的大小

size=os.path.getsize(r\'C:\python_fullstack_wen\day33\b.txt\')
print(size)
输出结果为:
5  #字节大小


14.4.33 路径处理案例

import os,sys
possible_topdir=os.path.normpath(os.path.join(
    os.path.abspath(__file__),
    os.pardir,
    os.pardir,
    os.pardir
))
sys.path.insert(0,possible_topdir)
print(sys.path)



14.4.34 __file__方法

用__file__ 来获得运行脚本所在的路径是比较方便的。但这可能得到的是一个相对路径,比如在脚本test.py中写入:

print(__file__)
输出结果为:
C:/python_fullstack_wen/day33/os_.py

按相对路径./test.py来执行,则打印得到的是相对路径,
按绝对路径执行则得到的是绝对路径。
而按用户目录来执行(~/practice/test.py),则得到的也是绝对路径(~被展开)

所以为了得到绝对路径,我们需要 os.path.realpath(__file__)。
print(os.path.realpath(__file__))
输出结果为:
C:\python_fullstack_wen\day33\os_.py

获取绝对路径:os.apth.abspath(__file__)


14.4.35 os.wak(path)




14.5 sys模块

跟解释器打交道的

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

import sys,time
for i in range(50):
    sys.stdout.write(\'%s\r\' %(\'#\'*i))
    sys.stdout.flush()
    time.sleep(0.1)
\'\'\'
注意:在pycharm中执行无效,请到命令行中以脚本的方式执行
\'\'\'

14.6 shutil模块








14.7 struct模块
用于处理bytes个其他二进制数据类型的转换
14.7.1 struct.pack 把任意类型转化为bytes
把数字转成byte格式,并且还是是固定长度
import struct
a=struct.pack(\'i\',10240099)
print(a)
print(len(a))
输出结果为:
b\'c@\x9c\x00\'
4

http://www.cnblogs.com/weihuchao/p/6760252.html#_label3
14.7.2 struct.unpack 将bytes转换回来
将struct.pack转化的bytes数据转化回去
import struct
a=struct.pack(\'i\',10240099)
a_unpack=struct.unpack(\'i\',a)
print(a_unpack)
print(a_unpack[0])
输出结果为:
(10240099,)
10240099











14.8 shelve模块










14.9 xml模块








14.10 configparser模块







14.11 suprocess模块


 1 import  subprocess
 2 
 3 \'\'\'
 4 sh-3.2# ls /Users/egon/Desktop |grep txt$
 5 mysql.txt
 6 tt.txt
 7 事物.txt
 8 \'\'\'
 9 
10 res1=subprocess.Popen(\'ls /Users/jieli/Desktop\',shell=True,stdout=subprocess.PIPE)
11 res=subprocess.Popen(\'grep txt$\',shell=True,stdin=res1.stdout,
12                  stdout=subprocess.PIPE)
13 
14 print(res.stdout.read().decode(\'utf-8\'))
15 
16 
17 #等同于上面,但是上面的优势在于,一个数据流可以和另外一个数据流交互,可以通过爬虫得到结果然后交给grep
18 res1=subprocess.Popen(\'ls /Users/jieli/Desktop |grep txt$\',shell=True,stdout=subprocess.PIPE)
19 print(res1.stdout.read().decode(\'utf-8\'))


14.12 logging 模块
loggin模块有两个处理方式,一个是直接使用loggin处理,另一个是生成一个对象,用对象处理

14.12.1 直接使用loggin模块处理

(1)日志级别
写入日志信息的可以使用:
  logging.日志级别(日志信息)
日志级别的顺序是:debug<info<waring(默认)<error<critical

import logging
logging.debug(\'debug message\')
logging.info(\'info message\')
logging.warning(\'warnig message\')
logging.error(\'error message\')
logging.critical(\'critical message\')
输出结果为:
WARNING:root:warnig message    #WARNING:root: 为默认输出
ERROR:root:error message
CRITICAL:root:critical message

(2)使用loggin.basicConfig设置配置信息
日志写入文件,固定时间格式等设置:
参数level设置日志级别
参数format可以设置一条日志信息的打印格式
参数datafmt可以设置时间的显示格式
参数filename可以设置将日志输出成文件的文件名字,该文件指定之后控制台就不再显示
参数filemode可以设置写入文件的写入模式


import logging
logging.basicConfig(level=logging.DEBUG,
                    format="%(asctime)s [%(lineno)s] %(message)s ",
                    datefmt="%Y-%m-%d %H:%M:%S",
                    filename=\'logger\',
                    filemode=\'a\'
                    )
logging.debug(\'debug message\')
logging.info(\'info message\')
logging.warning(\'warnig message\')
logging.error(\'error message\')
logging.critical(\'critical message\')

import logging
myFormat = \'[ %(levelname)s : %(asctime)s ] %(pathname)s[ %(lineno)d ]  == > %(message)s\'
myDateFormat = \'%Y-%m-%d %X\'
logging.basicConfig(level=logging.INFO, format=myFormat, datefmt=myDateFormat, filename=\'log\', filemode=\'a\')
logging.debug(\'debug message\')
logging.info(\'info message\')
logging.warning(\'warning message\')
logging.error(\'error message\')
logging.critical(\'critical message\')


14.12.2 获取loggin的对象logger

上面的只能指定一个流,获取logging对象后可以为多个流设置日志

(1)获取loggin对象
   Logging.getLogger()
   可以指定名字,默认为root
(2)设置文件或者屏幕输出对象
   Logging.fileHandle(文件名字, mode=读写模式, encoding=编码, delay=布尔值)
   logging.StreamHandler()
(3)设置日志等级
   logging对象.setLevel(等级)
(4)设置打印格式
   FileHandler对象.setFormatter(格式)
StreamHandler对象.setFormatter(格式)
(5)绑定Handler对象给loggin对象
    Logging对象.addHandler(文件或者流Handle对象)
 
import logging
logger=logging.getLogger()
# print(logger)
#logger是一个对象
#<logging.RootLogger object at 0x000000F548807F28>
#日志输出到文件
fh=logging.FileHandler("logger2")
#日志输出到屏幕
sh=logging.StreamHandler()
fm=logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - \
   %(message)s")
fh.setFormatter(fm)
sh.setFormatter(fm)
logger.addHandler(fh)
logger.addHandler(sh)
logger.debug(\'debug message\')
logger.info(\'info message\')
logger.warning(\'warnig message\')
logger.error(\'error message\')
logger.critical(\'critical message\')

import logging
log = logging.getLogger()
fh = logging.FileHandler(\'log2\')
sh = logging.StreamHandler()
log.setLevel(logging.DEBUG)
myFormat = \'[ %(levelname)s : %(asctime)s ] %(pathname)s[ %(lineno)d ]  == > %(message)s\'
formatter = logging.Formatter(myFormat)
fh.setFormatter(formatter)
sh.setFormatter(formatter)
log.addHandler(fh)
log.addHandler(sh)
logging.debug(\'debug message\')

输出结果为:
2017-04-27 09:19:09,397 - root - WARNING -    warnig message
2017-04-27 09:19:09,398 - root - ERROR -    error message
2017-04-27 09:19:09,398 - root - CRITICAL -    critical message



 




14.13 re模块

http://deerchao.net/tutorials/regex/regex.htm


就其本质而言,正则表达式(或RE)是一种小型的、高度专业化的编程语言,(在Python中)它内嵌在python中,并通过re模块实现。正则表达式模式被编译成一系列的字节码,然后由C编写的匹配引擎执行。

字符匹配(普通字符,元字符):
14.13.1 普通字符
大多数字符和字母都会和自身匹配

print(re.findall(\'wen\',\'wenyanjie\'))
输出结果为:
[\'wen\']

14.13.2 元字符
. ^ $ * + ? { } [ ] | ( ) \

(1)元字符之.
通配符,匹配除换行符\n之外的任意字符
ret=re.findall(\'a..in\',\'helloalvin\')
print(ret)
输出结果为:
[\'alvin\']


(2)元字符之^
以什么开头,匹配字符串的开始
ret=re.findall(\'^a...n\',\'alvinhelloawwwn\')
print(ret)
输出结果为:
[\'alvin\']


(3)元字符之$
以什么结尾,匹配字符串的结束
ret=re.findall(\'a...n$\',\'alvinhelloawwwn\')
print(ret)
输出结果为:
[\'awwwn\']



 关于重复的功能
(4)元字符之*
重复0到无穷次  属于贪婪匹配
ret=re.findall(\'abc*\',\'abcccc\')
print(ret)
输出结果为:
[\'abcccc\']


(5)元字符之+
重复1到无穷次  属于贪婪匹配 
ret=re.findall("abc+","abcccc")
print(ret)
输出结果为:
[\'abcccc\']


(6)元字符之?
?号代表重复0次或1次
ret=re.findall(\'abc?\',\'abcccc\')
print(ret)
输出结果为:
[\'abc\']

(7)元字符之{}
重复的次数是可以自己写的
{6} 可以重复6次  
{1,6}  可以重复1次到6次
{1,}   可以重复1次到多次

ret=re.findall(\'abc{1,4}\',\'abccc\')
print(ret)
输出结果为:
[\'abccc\']

 贪婪匹配和惰性匹配
注意:前面的*,+,?等都是贪婪匹配,也就是尽可能匹配,后面加?号使其变成惰性匹配
ret=re.findall(\'abc*?\',\'abccccccc\')
print(ret)
输出结果为:
[\'ab\']

ret=re.findall(\'abc+?\',\'abccccc\')
print(ret)
输出结果为:
[\'abc\']


(8)元字符之字符集[]

8.1 字符集[]
[]是或的意思
ret=re.findall(\'a[bc]d\',\'acd\')
print(ret)
输出结果为:
[\'acd\']


8.2 字符集[.*+] 相当于普通字符
字符集中没有特殊符号了
ret=re.findall(\'[.*+]\',\'a.cd+\')
print(ret)
输出结果为:
[\'.\', \'+\']

不过可以在[]外使用
ret=re.findall(\'q[a-z]*\',\'quaaffewf\')
print(ret)
#[\'quaaffewf\']

ret=re.findall(\'q[a-z]+\',\'quaaffewf\')
print(ret)
#[\'quaaffewf\']

ret=re.findall(\'q[a-z].\',\'quaaffewf\')
print(ret)
#[\'qua\']


8.3 字符集[-]
表示范围
ret=re.findall(\'[a-z]\',\'acd\')
print(ret)
输出结果为:
[\'a\', \'c\', \'d\']

8.4 字符集[^]
表示非, [^x] 匹配除了x以外的任意字符
ret=re.findall(\'[^ab]\',\'45dfg3\')
print(ret)
输出结果为:
[\'4\', \'5\', \'d\', \'f\', \'g\', \'3\']


8.5 字符集[\]
转义
A 反斜杠后边跟普通字符实现特殊功能
ret=re.findall(\'[\d]+\',\'12sdfhu334\')
print(ret)
输出结果为:
[\'12\', \'334\']

B 反斜杠后面跟元字符表示去除特殊功能
ret=re.findall(\'[\.]\',\'.werfui435\')
print(ret)
输出结果为:
[\'.\']
(9)元字符之\
转义
反斜杠后边跟元字符去除特殊功能,比如\.
反斜杠后边跟普通字符实现特殊功能,比如\d


(a 反斜杠后边跟元字符去除特殊功能)
匹配点号
ret=re.findall("www\.baidu",\'www.baidu\')
print(ret)
输出结果为:
[\'www.baidu\']


(b 反斜杠后边跟普通字符实现特殊功能)
9.1 \d
匹配数字,匹配任何十进制数,它相当于类[0-9]
ret=re.findall(\'\d+\',\'wehu234bui24\')
print(ret)
输出结果为:
[\'234\', \'24\']
9.2 \D 
匹配任何非数字字符,它相当于类[^0-9]
ret=re.findall(\'\D+\',\'wen?*%#$uh876234sdf\')
print(ret)
输出结果为:
[\'wen?*%#$uh\', \'sdf\']

9.3 \s 

匹配任何空白字符,它相当于类[\t\n\r\f\v]
ret=re.findall(\'\s\',\'hello wen !\')
print(ret)
输出结果为:
[\' \', \' \']
9.4 \S
匹配任何非空白字符串,它相当于类[^\t\n\r\f\v]

ret=re.findall(\'\S+\',\'hello wen !\')
print(ret)
输出结果为:
[\'hello\', \'wen\', \'!\']

9.5 \w
匹配任何字母数字字符,它相当于类[a-zA-Z0-9_]
ret=re.findall(\'\w+\',\'hello wen !_\')
print(ret)
输出结果为:
[\'hello\', \'wen\', \'_\']


9.6 \W
匹配任何非字母数字字符,它相当于类[^a-zA-Z0-9_]
ret=re.findall(\'\W+\',\'hello wen !_\')
print(ret)
输出结果为:
[\' \', \' !\']

9.7 \b 
匹配一个特殊字符的边界,比如空格,&,#等

ret=re.findall(r"I\b","hello I am LIST")
print(ret)
输出结果为:
[\'I\']


9.8 \B
匹配不是单词开头或结束的位置
ret=re.findall(r\'I\B\',\'hello I am LST\')
print(ret)
输出结果为:
[]

(原生字符串 r)
元字符\和字符\
# ret=re.findall(\'c\1\',\'abc\1e\')
# print(ret)
#结果为:[\'c\x01\']
# ret=re.findall(\'c\\1\',\'abc\1e\')
# print(ret)
#结果为:报错
# ret=re.findall(\'c\\\\1\',\'abc\1e\')
# print(ret)
#结果为[]
# ret=re.findall(r\'c\\1\',\'abc\1e\')
# print(ret)
#结果为:[]
# ret=re.findall(r\'c\\1\',r\'abc\1e\')
# print(ret)
#输出结果为:[\'c\\1\']

有\b字符匹配的b
# ret=re.findall("\bblow","blow")
# print(ret)
#输出结果为:[]
# ret=re.findall(r\'\bblow\',\'blow\')
# print(ret)
#输出结果为:[\'blow\']


图示:
 

ret=re.findall(\'a\\\\k\',\'a\k\')
print(ret)
#输出结果为:[\'a\\k\']
ret=re.findall(r\'a\\k\',\'a\k\')
print(ret)
#输出结果为:[\'a\\k\']
 


(10)元字符之|

ret=re.findall("ka|b","kakbgwyd")
print(ret)
输出结果为:
[\'ka\', \'b\']

(11)元字符之分组()




ret=re.findall(r\'(ad+)\',\'add\')
print(ret)
#输出结果为:[\'add\']

有名分组:
ret=re.search(\'(?P<id>\d{2})/(?P<name>\w{3})\',\'23/com\')
print(ret)
#输出结果为:<_sre.SRE_Match object; span=(0, 6), match=\'23/com\'>
print(ret.group())
#输出结果为:23/com
print(ret.group(\'id\'))
#输出结果为:23

 

14.13.3 re.findall方法
找到所有的匹配字符串,以列表形式返回
#返回所有满足匹配条件的结果,放在列表里
import re
ret=re.findall(\'a\',\'alvin yuan\')
print(ret)
输出结果为:
[\'a\', \'a\']

14.13.4 re.search方法

#函数会在字符串内查找模式匹配,只到找到第一个匹配然后返回一个包含匹配信息的对象,该对象可以
# 通过调用group()方法得到匹配的字符串,如果字符串没有匹配,则返回None。
re.search(\'a\',\'alvin yuan\').group()

import re
ret=re.search(\'\d+\',\'wew23wfe234f\').group()
print(ret)
输出结果为:
23


14.13.5 re.match 方法
字符串的开头是否能匹配正则表达式。返回_sre.SRE_Match对象,如果不能匹配返回None。如果匹配的话,res.string可以获得原始的字符串,并不是匹配的字符串。

#同search,不过尽在字符串开始处进行匹配
import re
res=re.match(\'a\',\'abc\')
print(res)
print(res.group())
输出结果为:
<_sre.SRE_Match object; span=(0, 1), match=\'a\'>
a

14.13.6 re.split 方法
将字符串进行分割,maxsplit参数指的是分割的最大次数,如果不写,就默认进行所有的分割,

#先按\'a\'分割得到\'\'和\'bcd\',在对\'\'和\'bcd\'分别按\'b\'分割
import re
ret=re.split(\'[ab]\',\'abcd\')
ret2=re.split(\'[ab]\',\'abcd\',1)
print(ret)
print(ret2)
输出结果为:
[\'\', \'\', \'cd\']
[\'\', \'bcd\']

14.13.7 re.sub 方法
对匹配的字符串进行操作
re.sub(pattern, repl, string, count=0, flags=0)  使用repl替换string中pattern匹配到的部分;
                                                 这里repl可以是一个函数,参数是匹配对象,返回要替代的串

操作1: 用其他字符串调换匹配字符串
import re
ret=re.sub(\'\d\',\'abc\',\'alvin5yuan6\',1)
print(ret)
输出结果为:
alvinabcyuan6

操作2:使用函数作为参数
 

14.13.8 re.subn 方法

ret=re.subn(\'\d\',\'abc\',\'alvin5yuan6\')
print(ret)#(\'alvinabcyuanabc\', 2)



14.13.9 re.compile 方法
预编译一个正则表达式,返回一个表达式对象
import re
obj=re.compile(\'\d{3}\')
ret=obj.search(\'abc123eeee\')
print(ret.group())
输出结果为:
123

14.13.10 re.finditer 方法
返回一个顺序,用于访问每一个匹配结果的迭代器。

import re
ret=re.finditer(\'\d\',\'ds3sy4784a\')
print(ret)        #<callable_iterator object at 0x10195f940>
print(next(ret).group())
print(next(ret).group())
输出结果为:
<callable_iterator object at 0x000000166DBCBA58>
3
4784



14.13.11 re模块举例


import re
ret=re.findall(\'www.(baidu|oldboy).com\',\'www.oldboy.com\')
print(ret)#[\'oldboy\']     这是因为findall会优先把匹配结果组里内容返回,如果想要匹配结果,取消权限即可
ret=re.findall(\'www.(?:baidu|oldboy).com\',\'www.oldboy.com\')
print(ret)#[\'www.oldboy.com\']

import re
print(re.findall("<(?P<tag_name>\w+)>\w+</(?P=tag_name)>","<h1>hello</h1>"))
print(re.search("<(?P<tag_name>\w+)>\w+</(?P=tag_name)>","<h1>hello</h1>"))
print(re.search(r"<(\w+)>\w+</\1>","<h1>hello</h1>"))

#匹配出所有的整数
import re
#ret=re.findall(r"\d+{0}]","1-2*(60+(-40.35/5)-(-4*3))")
ret=re.findall(r"-?\d+\.\d*|(-?\d+)","1-2*(60+(-40.35/5)-(-4*3))")
ret.remove("")
print(ret)


14.13.12 计算器作业

 




14.14 join等序列化模块

14.14.1 json
(1)json定义
可以通过json模块来讲数据转化为json格式数据,并且存储
还可以直接读取文件并将json内容转化为对应的python格式数据

(2)dumps和loads方法
将数据转为json字符串
   Json.dumps(需要转的数据)
将json数据转化为对象的python数据
  Json.loads(json字符串)
其中对应的有dump()和load()方法,这两有两参数,用于直接打开文件句柄来读或者写

import json
dit={\'name\':\'whc\',\'age\':23,\'sex\':\'male\'}
data=json.dumps(dit)
#  \'{"name": "whc", "age": 23, "sex": "male"}\'
print(type(data))
# <class \'str\'>
f=open(\'json\',\'w\')
f.write(data)
f.close()
f=open(\'json\')
new_data=json.loads(f.read())
# {\'name\': \'whc\', \'age\': 23, \'sex\': \'male\'}


(3)dump和load方法
方法dump:1,转成json字符串 2 将json字符串写入f里

import json
dic={\'name\':\'whc\',\'age\':23,\'sex\':\'male\'}
json.dump(dic,open(\'json\',\'w\'))
newData=json.load(open(\'json\'))


 




import json
d={\'name\':"egon"}
f=open(\'json2.txt\',\'w\')
json.dump(d,f)
# 1 转成json字符串  2 将json字符串写入f里
f.close()


 


符合json数据格式的要求 就可以作为json数据类型处理


 



14.14.2 pickle


 
 

14.15 subprocess模块


14.15.1 得到进程的正常输出
>>> import subprocess
>>> p=subprocess.Popen(\'ls\',shell=True,stdout=subprocess.PIPE)
>>> p
<subprocess.Popen object at 0x7f05e0f36f50>
>>> p.stdout.read()
\'1.txt\nanaconda-ks.cfg\nDesktop\ngroup\ninitial-setup-ks.cfg\npasswd\ntest.txt\nyan.txt\n\'
>>> p.stdout.read()
\'\'

14.15.2 得到进程的错误输出

>>> p=subprocess.Popen(\'lsls\',shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
>>> p.stdout.read()
\'\'
>>> p.stderr.read()
\'/bin/sh: lsls: \xe6\x9c\xaa\xe6\x89\xbe\xe5\x88\xb0\xe5\x91\xbd\xe4\xbb\xa4\n\'

14.15.3 进程的输出进行转码新显示
>>> p=subprocess.Popen(\'ls\',shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
>>> data=p.stdout.read()
>>> data.decode(\'gbk\')
u\'1.txt\nanaconda-ks.cfg\nDesktop\ngroup\ninitial-setup-ks.cfg\npasswd\ntest.txt\nyan.txt\n\'




十五 异常处理

15.1 错误和异常

(1)错误分两种
程序中难免出现错误,而错误分成两种:
1 语法错误
这个错误,会被python解释器的语法检测,必须在程序执行前就改正

语法错误示范:
#语法错误示范一
if
#语法错误示范二
def test:
    pass
#语法错误示范三
class Foo
    pass
#语法错误示范四
print(haha

2 逻辑错误
逻辑错误示范1
#用户输入不完整(比如输入为空)或者输入非法(输入不是数字)
num=input(">>: ")
int(num)
逻辑错误示范2
#无法完成计算
res1=1/0
res2=1+\'str\'

(2)异常

异常就是程序运行时发生错误的信号。

在python中,错误触发的异常如下
 


(3)python中的异常类

在python中不同的异常可以不同的类型去标识,(python中统一了类与类型,类型即类),不同的类对象标识不同的异常,一个异常标识一种错误

AttributeError  试图访问一个对象没有的属性,比如foo.x,但是foo没有属性x
IOError 输入/输出异常;基本上是无法打开文件
ImportError 无法引入模块或包;基本上是路径问题或名称错误
IndentationError 语法错误(的子类) ;代码没有正确对齐
IndexError 下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5]
KeyError 试图访问字典里不存在的键
KeyboardInterrupt Ctrl+C被按下
NameError 使用一个还未被赋予对象的变量
SyntaxError Python代码非法,代码不能编译(个人认为这是语法错误,写错了)
TypeError 传入对象类型与要求的不符合
UnboundLocalError 试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量,
导致你以为正在访问它
ValueError 传入一个调用者不期望的值,即使值的类型是正确的



15.2 异常处理

15.2.1 异常处理定义

  Python解释器检测到错误,触发异常(也允许程序员自己触发异常)
   程序员编写特定的代码,专门用来捕捉这个异常(这段代码与程序逻辑无关,与异常处理有关)
如果捕捉成功则进入另外一个处理分支,执行你为其定制的逻辑,使程序不会崩溃,这就是异常处理



15.2.2 为何要进行异常处理

  python解析器去执行程序,检测到了一个错误时,触发异常,异常触发后且没被处理的情况下,程序就在当前异常处终止,后面的代码不会运行,谁会去用一个运行着突然就崩溃的软件。
  所以你必须提供一种异常处理机制来增强你程序的健壮性与容错性

  首先须知,异常是由程序的错误引起的,语法上的错误跟异常处理无关,必须在程序运行前就修正

15.2.3 使用if进行异常处理
If是提前预防错误
1.if判断式的异常处理只能针对某一段代码,对于不同的代码段的相同类型的错误你需要写重复的if来进行处理。

2.在你的程序中频繁的写与程序本身无关,与异常处理有关的if,可读性极其的差

3.这是可以解决异常的,只是存在1,2的问题,所以,千万不要妄下定论if不能用来异常处理。如果你不服,那你想想在没有学习try...except之前,你的程序难道就没有异常处理,而任由其崩溃么



15.2.4 try...except...进行异常处理

(1)基本语法
try:
     被检测的代码块
 except 异常类型 as e:
     try中一旦检测到异常,就执行这个位置的逻辑


(2)异常类只能处理指定异常
异常类只能用来处理指定的异常情况,如果非指定异常则无法处理。
1 # 未捕获到异常,程序直接报错
2  
3 s1 = \'hello\'
4 try:
5     int(s1)
6 except IndexError as e:
7     print e


(3)except多分支
1 s1 = \'hello\'
2 try:
3     int(s1)
4 except IndexError as e:
5     print(e)
6 except KeyError as e:
7     print(e)
8 except ValueError as e:
9     print(e)


(4)Exception万能异常处理
在python的异常中,有一个万能异常:Exception,他可以捕获任意异常
1 s1 = \'hello\'
2 try:
3     int(s1)
4 except Exception as e:
5     print(e)

如果什么异常,都要统一丢弃,或者使用同一段代码逻辑去处理他们,要就使用Exception万能异常处理
如果不同的异常需要定制不同的处理逻辑,那就需要用到多分支,最后一个分支为Exception万能异常处理就行

(5)else和finally
异常处理try中的else : try内代码没有异常就会执行else的语句
异常处理try中的finally: 无论是否触发异常,都会执行该模块,通常是进行清理工作。
 1 s1 = \'hello\'
 2 try:
 3     int(s1)
 4 except IndexError as e:
 5     print(e)
 6 except KeyError as e:
 7     print(e)
 8 except ValueError as e:
 9     print(e)
10 #except Exception as e:
11 #    print(e)
12 else:
13     print(\'try内代码块没有异常则执行我\')
14 finally:
15     print(\'无论异常与否,都会执行该模块,通常是进行清理工作\') 


(6)raise主动触发异常

1 #_*_coding:utf-8_*_
2 __author__ = \'Linhaifeng\'
3 
4 try:
5     raise TypeError(\'类型错误\')
6 except Exception as e:
7     print(e)


(7)自定义异常

 1 #_*_coding:utf-8_*_
 2 __author__ = \'Linhaifeng\'
 3 
 4 class EgonException(BaseException):
 5     def __init__(self,msg):
 6         self.msg=msg
 7     def __str__(self):     #print打印对象就会输出异常的内容,需要靠__str__
 8         return self.msg
 9 
10 try:
11     raise EgonException(\'类型错误\')
12 except EgonException as e:
13     print(e) 


(8)断言

 # assert 条件
assert 1 == 1
assert 1 == 2



15.2.5 使用try异常处理的好处

try..except这种异常处理机制就是取代if那种方式,让你的程序在不牺牲可读性的前提下增强健壮性和容错性

异常处理中为每一个异常定制了异常类型(python中统一了类与类型,类型即类),对于同一种异常,一个except就可以捕捉到,可以同时处理多段代码的异常(无需‘写多个if判断式’)减少了代码,增强了可读性

使用try..except的方式
1:把错误处理和真正的工作分开来
2:代码更易组织,更清晰,复杂的工作任务更容易实现;
3:毫无疑问,更安全了程序,不至于由于一些小的疏忽而使意外崩溃了;


15.2.6 什么时候用异常处理

try...except应该尽量少用,因为它本身就是你附加给你的程序的一种异常处理的逻辑,与你的主要的工作是没有关系的
这种东西加的多了,会导致你的代码可读性变差

而且异常处理本就解决不了你的逻辑问题,只有在有些异常无法预知的情况下,才应该加上try...except,其他的逻辑错误应该尽量修正



十六 软件开发规范+ATM购物车例子

ATM+购物

 








十六 socket编程

16.1 客户端/服务器架构

即C/S架构,包括

1.硬件C/S架构(打印机)

2.软件C/S架构(web服务)

美好的愿望:

最常用的软件服务器是 Web 服务器。一台机器里放一些网页或 Web 应用程序,然后启动 服务。这样的服务器的任务就是接受客户的请求,把网页发给客户(如用户计算机上的浏览器),然后等待下一个客户请求。这些服务启动后的目标就是“永远运行下去”。虽然它们不可能实现这样的目标,但只要没有关机或硬件出错等外力干扰,它们就能运行非常长的一段时间。 
 

C/S架构与socket的关系:
我们学习socket就是为了完成C/S架构的开发

16.2 osi七层


须知一个完整的计算机系统是由硬件、操作系统、应用软件三者组成,具备了这三个条件,一台计算机系统就可以自己跟自己玩了
你就需要上网了(访问个黄色网站,发个黄色微博啥的),互联网的核心就是由一堆协议组成,协议就是标准,全世界人通信的标准是英语,如果把计算机比作人,互联网协议就是计算机界的英语。所有的计算机都学会了互联网协议,那所有的计算机都就可以按照统一的标准去收发信息从而完成通信了。人们按照分工不同把互联网协议从逻辑上划分了层级,详见我另一篇博客

网络通信原理:http://www.cnblogs.com/linhaifeng/articles/5937962.html


为何学习socket一定要先学习互联网协议:
1.首先:本节课程的目标就是教会你如何基于socket编程,来开发一款自己的C/S架构软件
2.其次:C/S架构的软件(软件属于应用层)是基于网络进行通信的
3.然后:网络的核心即一堆协议,协议即标准,你想开发一款基于网络通信的软件,就必须遵循这些标准。
4.最后:就让我们从这些标准开始研究,开启我们的socket编程之旅

 

TCP/IP协议族包括运输层、网络层、链路层。现在你知道TCP/IP与UDP的关系了吧。

编程就是实现某项功能

16.3 socket层
在图1中,我们没有看到Socket的影子,那么它到底在哪里呢?还是用图来说话,一目了然
 

16.4 socket是什么

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。

注意:
也有人将socket说成ip+port,ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序,ip地址是配置到网卡上的,而port是应用程序开启的,ip与port的绑定就标识了互联网中独一无二的一个应用程序

而程序的pid是同一台机器上不同进程或者线程的标识


16.5 套接字发展史及分类

16.5.1 套接字起源
套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或 IPC。套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。 

16.5.2 基于文件类型的套接字
基于文件类型的套接字家族
套接字家族的名字:AF_UNIX
unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信

16.5.3 基于网络类型的套接字
基于网络类型的套接字家族
套接字家族的名字:AF_INET
(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)


16.6 套接字工作流程

16.6.1 套接字工作流程图示
一个生活中的场景。你要打电话给一个朋友,先拨号,朋友听到电话铃声后提起电话,这时你和你的朋友就建立起了连接,就可以讲话了。等交流结束,挂断电话结束此次交谈。    生活中的场景就解释了这工作原理,也许TCP/IP协议族就是诞生于生活中,这也不一定。

 


 


先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束


16.6.2 socket()模块函数用法
socket()模块函数用法

 1 import socket
 2 socket.socket(socket_family,socket_type,protocal=0)
 3 socket_family 可以是 AF_UNIX 或 AF_INET。socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。protocol 一般不填,默认值为 0。
 4 
 5 获取tcp/ip套接字
 6 tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 7 
 8 获取udp/ip套接字
 9 udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
10 
11 由于 socket 模块中有太多的属性。我们在这里破例使用了\'from module import *\'语句。使用 \'from socket import *\',我们就把 socket 模块里的所有属性都带到我们的命名空间里了,这样能 大幅减短我们的代码。
12 例如tcpSock = socket(AF_INET, SOCK_STREAM)

16.6.3 服务端套接字函数
服务端套接字函数
s.bind()    绑定(主机,端口号)到套接字
s.listen()  开始TCP监听
s.accept()  被动接受TCP客户的连接,(阻塞式)等待连接的到来

16.6.4 客户端套接字函数

客户端套接字函数
s.connect()     主动初始化TCP服务器连接
s.connect_ex()  connect()函数的扩展版本,出错时返回出错码,而不是抛出异常

16.6.5 公共用途的套接字函数
公共用途的套接字函数
s.recv()            接收TCP数据    # recv是用户态的应用程序发起的
s.send()            发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall()         发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom()        接收UDP数据
s.sendto()          发送UDP数据
s.getpeername()     连接到当前套接字的远端的地址
s.getsockname()     当前套接字的地址
s.getsockopt()      返回指定套接字的参数
s.setsockopt()      设置指定套接字的参数
s.close()           关闭套接字

16.6.6 面向锁的套接字方法

面向锁的套接字方法
s.setblocking()     设置套接字的阻塞与非阻塞模式
s.settimeout()      设置阻塞套接字操作的超时时间
s.gettimeout()      得到阻塞套接字操作的超时时间


16.6.7 面向文件的套接字的函数

面向文件的套接字的函数
s.fileno()          套接字的文件描述符
s.makefile()        创建一个与该套接字相关的文件




16.7 基于TCP的套接字
16.7.1 tcp服务端套接字
:tcp服务端
1 ss = socket() #创建服务器套接字
2 ss.bind()      #把地址绑定到套接字
3 ss.listen()      #监听链接
4 inf_loop:      #服务器无限循环
5     cs = ss.accept() #阻塞,接受客户端链接
6     comm_loop:         #通讯循环
7         cs.recv()/cs.send() #对话(接收与发送)
8     cs.close()    #关闭客户端套接字
9 ss.close()        #关闭服务器套接字(可选)


16.7.2 tcp客户端套接字
:tcp客户端
1 cs = socket()    # 创建客户套接字
2 cs.connect()    # 尝试连接服务器
3 comm_loop:        # 通讯循环
4     cs.send()/cs.recv()    # 对话(发送/接收)
5 cs.close()            # 关闭客户套接字


16.7.3 tcp的socket程序举例
socket通信流程与打电话流程类似,我们就以打电话为例来实现一个low版的套接字通信
服务端:
#_*_coding:utf-8_*_
__author__ = \'Linhaifeng\'
import socket
ip_port=(\'127.0.0.1\',9000)  #电话卡
BUFSIZE=1024                #收发消息的尺寸
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机
s.bind(ip_port) #手机插卡
s.listen(5)     #手机待机
conn,addr=s.accept()            #手机接电话
# print(conn)
# print(addr)
print(\'接到来自%s的电话\' %addr[0])
msg=conn.recv(BUFSIZE)             #听消息,听话
print(msg,type(msg))
conn.send(msg.upper())          #发消息,说话
conn.close()                    #挂电话
s.close()                       #手机关机

客户端:
#_*_coding:utf-8_*_
__author__ = \'Linhaifeng\'
import socket
ip_port=(\'127.0.0.1\',9000)
BUFSIZE=1024
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect_ex(ip_port)           #拨电话
s.send(\'linhaifeng nb\'.encode(\'utf-8\'))         #发消息,说话(只能发送字节类型)
feedback=s.recv(BUFSIZE)                           #收消息,听话
print(feedback.decode(\'utf-8\'))
s.close()                                       #挂电话


16.7.4 tcp的socket程序改进实例
上述流程的问题是,服务端只能接受一次链接,然后就彻底关闭掉了,实际情况应该是,服务端不断接受链接,然后循环通信,通信完毕后只关闭链接,服务器能够继续接收下一次链接,下面是修改版
服务端:

import socket
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #建立socket对象
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #closesocket(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket:
phone.bind((\'127.0.0.1\',8080)) #socket对象绑定ip,端口

phone.listen(5)           #监听  listen(5)为连接池,可以缓存5个连接,5需要写入配置文件
print(\'starting...\')
while True:    #链接循环
    conn,addr=phone.accept() #等待客户端链接

    print(\'电话线路是\',conn)
    print(\'客户端的手机号是\',addr)
    while True:  #通信循环
        try:    # 防止客户端挂掉或断开,服务端受影响
            data=conn.recv(1024)         #接收数据  1024为每次接收最大的容量值,此值要结合内存的大小设置
            if not data:break         #Linux系统:客户端断开,不报异常,但会一直发空,收空
            print(\'客户端发来的消息是\',data)
            msg=data.upper()
            conn.send(msg)      #发送数据
            print(\'服务端发出数据\',msg)
        except Exception:
            break
    conn.close()               #关闭链接
phone.close()              #关闭socket对象

客户端:
import socket
ip_port=(\'127.0.0.1\',8080)
BUFSIZE=1024
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #建立socket对象
phone.connect(ip_port)   #socket对象建立链接
while True:
    msg=input(">>>:").strip()
    if not msg: continue
    #if len(msg)==0 :continue
    print(\'客户端发出数据:\',msg)
    phone.send(msg.encode(\'utf-8\')) #socket对象发送数据
    data=phone.recv(BUFSIZE)              #socket对象接收数据
    print(\'接收到服务端的数据\',data.decode(\'utf-8\'))
phone.close()                      #关闭socket链接


16.7.4 遇到问题
服务端问题:
 

这个是由于你的服务端仍然存在四次挥手的time_wait状态在占用地址(如果不懂,请深入研究1.tcp三次握手,四次挥手 2.syn洪水攻击 3.服务器高并发情况下会有大量的time_wait状态的优化方法)

解决方法:
方法1:
#加入一条socket配置,重用ip和端口
phone=socket(AF_INET,SOCK_STREAM)
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
phone.bind((\'127.0.0.1\',8080))
# SOL_SOCKET,SO_REUSEADDR 都属于socket模块中

方法2:
发现系统存在大量TIME_WAIT状态的连接,通过调整linux内核参数解决,
vi /etc/sysctl.conf
编辑文件,加入以下内容:
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30
然后执行 /sbin/sysctl -p 让参数生效。
net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
net.ipv4.tcp_fin_timeout 修改系統默认的 TIMEOUT 时间


16.7.5 模仿ssh远程执行命令的socket程序

注意此例子中subprocess.Popen执行的结果的编码是以前所在的系统为准的,如果是windows,那么res.stdout.read()读出的就是GBK编码的,在接收端需要用GBK解码。且只能从管道里读一次结果

服务端:
import socket
import subprocess
ip_port=(\'127.0.0.1\',8080)
BUFSIZE=1024
tcp_socket_server=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_socket_server.bind(ip_port)
tcp_socket_server.listen(5)
while True:
    conn,addr=tcp_socket_server.accept()
    print(\'客户端是:\',addr)
    while True:
        try:
            cmd=conn.recv(BUFSIZE)
            if not cmd:break

            act_res=subprocess.Popen(cmd.decode(\'utf-8\'),
                                     shell=True,
                                     stdout=subprocess.PIPE,
                                     stdin=subprocess.PIPE,
                                     stderr=subprocess.PIPE)
            act_err=act_res.stderr.read()
            if act_err:
                ret=act_err
            else:
                ret=act_res.stdout.read()
            conn.sendall(ret)
        except Exception:
            break
    conn.close()
tcp_socket_server.close()

客户端:
import socket
BUFSIZE=1024
ip_port=(\'127.0.0.1\',8080)

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect(ip_port)

while True:
    msg=input(\'>>:\').strip()
    if not msg:continue
    if msg==\'quit\':break

    s.send(msg.encode(\'utf-8\'))
    act_res=s.recv(BUFSIZE)

    print(act_res.decode(\'gbk\'),end=\'\')


此例会发生粘包问题,在粘包章节解决。



16.8 基于UDP的套接字

16.8.1 udp服务端套接字
Udp服务端套接字
1 ss = socket()   #创建一个服务器的套接字
2 ss.bind()       #绑定服务器套接字
3 inf_loop:       #服务器无限循环
4     cs = ss.recvfrom()/ss.sendto() # 对话(接收与发送)
5 ss.close()                         # 关闭服务器套接字

Recvfrom收到一个元组,前面是内容,后面是一个ip+端口的元组
Sendto发送内容,在发一个ip+端口与的元组  ip+端口都是客户端的

TCP服务端不能实现并发的功能,UDP服务端可以实现:udp不用建立链接

发送数据时,如果是字符串需要编码encode然后才能发,如果是数字格式,需要将字符串转化成字符串然后编码encode之后才能发。 收发数据的时候只能是字节格式 


16.8.2 udp客户端套接字
Udp客户端
cs = socket()   # 创建客户套接字
comm_loop:      # 通讯循环
    cs.sendto()/cs.recvfrom()   # 对话(发送/接收)
cs.close()                      # 关闭客户套接字


16.8.3 udp套接字简单实例
Udp服务端:
#_*_coding:utf-8_*_
__author__ = \'Linhaifeng\'
import socket
ip_port=(\'127.0.0.1\',9000)
BUFSIZE=1024
udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
udp_server_client.bind(ip_port)
while True:
    msg,addr=udp_server_client.recvfrom(BUFSIZE)
    print(msg,addr)
    udp_server_client.sendto(msg.upper(),addr)

Udp客户端:
#_*_coding:utf-8_*_
__author__ = \'Linhaifeng\'
import socket
ip_port=(\'127.0.0.1\',9000)
BUFSIZE=1024
udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
while True:
    msg=input(\'>>: \').strip()
    if not msg:continue
    udp_server_client.sendto(msg.encode(\'utf-8\'),ip_port)
    back_msg,addr=udp_server_client.recvfrom(BUFSIZE)
    print(back_msg.decode(\'utf-8\'),addr)


16.8.4 qq聊天
qq聊天(由于udp无连接,所以可以同时多个客户端去跟服务端通信)

Udp服务端:
#_*_coding:utf-8_*_
__author__ = \'Linhaifeng\'
import socket
ip_port=(\'127.0.0.1\',8081)
udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #买手机
udp_server_sock.bind(ip_port)
while True:
    qq_msg,addr=udp_server_sock.recvfrom(1024)
    print(\'来自[%s:%s]的一条消息:\033[1;44m%s\033[0m\' %(addr[0],addr[1],qq_msg.decode(\'utf-8\')))
    back_msg=input(\'回复消息: \').strip()
    udp_server_sock.sendto(back_msg.encode(\'utf-8\'),addr)

Udp客户端1:
#_*_coding:utf-8_*_
__author__ = \'Linhaifeng\'
import socket
BUFSIZE=1024
udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
qq_name_dic={
    \'狗哥alex\':(\'127.0.0.1\',8081),
    \'瞎驴\':(\'127.0.0.1\',8081),
    \'一棵树\':(\'127.0.0.1\',8081),
    \'武大郎\':(\'127.0.0.1\',8081),
}
while True:
    qq_name=input(\'请选择聊天对象: \').strip()
    while True:
        msg=input(\'请输入消息,回车发送: \').strip()
        if msg == \'quit\':break
        if not msg or not qq_name or qq_name not in qq_name_dic:continue
        udp_client_socket.sendto(msg.encode(\'utf-8\'),qq_name_dic[qq_name])
        back_msg,addr=udp_client_socket.recvfrom(BUFSIZE)
        print(\'来自[%s:%s]的一条消息:\033[1;44m%s\033[0m\' %(addr[0],addr[1],back_msg.decode(\'utf-8\')))
udp_client_socket.close()

Udp客户端2:
#_*_coding:utf-8_*_
__author__ = \'Linhaifeng\'
import socket
BUFSIZE=1024
udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
qq_name_dic={
    \'狗哥alex\':(\'127.0.0.1\',8081),
    \'瞎驴\':(\'127.0.0.1\',8081),
    \'一棵树\':(\'127.0.0.1\',8081),
    \'武大郎\':(\'127.0.0.1\',8081),
}
while True:
    qq_name=input(\'请选择聊天对象: \').strip()
    while True:
        msg=input(\'请输入消息,回车发送: \').strip()
        if msg == \'quit\':break
        if not msg or not qq_name or qq_name not in qq_name_dic:continue
        udp_client_socket.sendto(msg.encode(\'utf-8\'),qq_name_dic[qq_name])
        back_msg,addr=udp_client_socket.recvfrom(BUFSIZE)
        print(\'来自[%s:%s]的一条消息:\033[1;44m%s\033[0m\' %(addr[0],addr[1],back_msg.decode(\'utf-8\')))
udp_client_socket.close()


16.8.5 时间服务器
:ntp服务端
#_*_coding:utf-8_*_
__author__ = \'Linhaifeng\'
from socket import *
from time import strftime
ip_port=(\'127.0.0.1\',9000)
bufsize=1024
tcp_server=socket(AF_INET,SOCK_DGRAM)
tcp_server.bind(ip_port)
while True:
    msg,addr=tcp_server.recvfrom(bufsize)
    print(\'===>\',msg)
    if not msg:
        time_fmt=\'%Y-%m-%d %X\'
    else:
        time_fmt=msg.decode(\'utf-8\')
    back_msg=strftime(time_fmt)
    tcp_server.sendto(back_msg.encode(\'utf-8\'),addr)
tcp_server.close()

:ntp客户端
#_*_coding:utf-8_*_
__author__ = \'Linhaifeng\'
from socket import *
ip_port=(\'127.0.0.1\',9000)
bufsize=1024
tcp_client=socket(AF_INET,SOCK_DGRAM)
while True:
    msg=input(\'请输入时间格式(例%Y %m %d)>>: \').strip()
    tcp_client.sendto(msg.encode(\'utf-8\'),ip_port)
    data=tcp_client.recv(bufsize)
    print(data.decode(\'utf-8\'))
tcp_client.close()


 


 




16.9 基于TCP和UDP的socket对比

16.9.1 socket收发消息的原理图示
收发消息的原理如图:
发消息,都是讲数据发送到已端的发送缓冲中,收消息都是已端的缓冲区中收
(1) tcp:send发消息,recv收消息
(2) udp:sendto发消息,recvfrom收消息

 





16.9.2 send与sendinto 

 有 tcp是基于数据流的,而udp是基于数据报的
(1)send(bytes_data):发送数据流,数据流bytes_data若为空,自己这段的缓冲区也为空,操作系统不会控制tcp协议发空包
(2)sendinto(bytes_data,ip_port):发送数据报,byte_data为空,还有ip_port,所有即便是发送空的bytes_data,数据报其实也不是空的,自己这端的缓冲区收到内容,操作系统就会控制udp协议发包。


16.9.3 recv与recvfrom的区别
接收消息
1 tcp协议
(1)如果收消息缓冲区里的数据为空,那么recv就会阻塞(阻塞很简单,就是一直在等着收)
(2)只不过tcp协议的客户端send一个空数据就是真的空数据,客户端即使有无穷个send空,也跟没有一个样。
(3)tcp基于链接通信
---基于链接,则需要listen(backlog),指定半连接池的大小
---基于链接,必须先运行的服务端,然后客户端发起链接请求
---对于mac系统,如果一段断开了链接,那另外一端的链接也跟着完蛋,recv将不会阻塞,将循环收到空
  (解决方法:服务端通信循环内加异常处理,捕捉到异常后就break掉通讯循环)


 


16.10 粘包及粘包解决
只有TCP有粘包现象,UDP永远不会粘包。

16.10.1 socket收发消息的原理

 

(1)Tcp是面向流的协议,容易发生粘包
发送端可以是1K1K的发送数据,而接收端的应用程序可以2K2K的提走数据,也许可能是3K6K的提走数据,也就是说,应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多多少字节对应用程序是不可见的,因此Tcp协议是面向流的协议,也容易出现粘包。

(2)udp是面向消息的协议
UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。怎样定义消息呢?可以认为对方一次性write/send的数据为一个消息,需要明白的是当对方send一条信息的时候,无论底层怎样分段分片,TCP协议层会把构成整条消息的数据段排序完成后才呈现在内核缓冲区。

例如基于tcp的套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束

16.10.2 粘包定义

所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。


16.10.3 造成粘包的原因

此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。

TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。

UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。

tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头,实验略

udp的recvfrom是阻塞的,一个recvfrom(x)必须对一个一个sendinto(y),收完了x个字节的数据就算完成,若是y>x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠

tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。

16.10.4 粘包的发生情况

两种情况下会发生粘包:
(1)客户端产生:发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
(2)服务端产生:接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包) 


16.10.5 拆包的发生情况

当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。

补充问题一:为何tcp是可靠传输,udp是不可靠传输
基于tcp的数据传输请参考我的另一篇文章http://www.cnblogs.com/linhaifeng/articles/5937962.html,tcp在数据传输时,发送端先把数据发送到自己的缓存中,然后协议控制将缓存中的数据发往对端,对端返回一个ack=1,发送端则清理缓存中的数据,对端返回ack=0,则重新发送数据,所以tcp是可靠的
而udp发送数据,对端是不会返回确认信息的,因此不可靠

补充问题二:send(字节流)和recv(1024)及sendall
recv里指定的1024意思是从缓存里一次拿出1024个字节的数据
send的字节流是先放入己端缓存,然后由协议控制将缓存内容发往对端,如果待发送的字节流大小大于缓存剩余空间,那么数据丢失,用sendall就会循环调用send,数据不会丢失


16.10.6 解决粘包
问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。

1 最low的方法
 
2 较low的方法
程序的运行速度远快于网络传输速度,所以在发送一段字节前,先用send去发送该字节流长度,这种方式会放大网络延迟带来的性能损耗


服务端:
[root@python_lb ~]# cat zhanbao_server.py 
#coding:utf-8
import socket,subprocess

ip_port=(\'192.168.2.2\',8000)
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

s.bind(ip_port)
s.listen(5)

while True:
    conn,addr=s.accept()
    print(\'客户端是\',addr)
    while True:
        try:
            msg=conn.recv(1024)
            if not msg:break
            res=subprocess.Popen(msg.decode(\'utf-8\'),
                                 shell=True,
                                 stdin=subprocess.PIPE,
                                 stderr=subprocess.PIPE,
                                 stdout=subprocess.PIPE)
            err=res.stderr.read()
            if err:
                ret=err
            else:
                ret=res.stdout.read()
            data_length=len(ret)
            conn.send(str(data_length).encode(\'utf-8\'))
            data=conn.recv(1024).decode(\'utf-8\')
            if data==\'recv_ready\':
                conn.sendall(ret)
        except Exception:
            break
    conn.close()
s.close()

客户端:
import socket,time
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex((\'192.168.2.2\',8000))

while True:
    msg=input(\'>>:\').strip()
    if not msg:continue
    if msg==\'quit\':break

    s.send(msg.encode(\'utf-8\'))
    length=int(s.recv(1024).decode(\'utf-8\'))
    s.send(\'recv_ready\'.encode(\'utf-8\'))

    recv_size=0
    recv_data=b\'\'
    while recv_size < length:
        data=s.recv(10)
        recv_size+=len(data)
        recv_data+=data
    print(recv_data.decode(\'utf-8\'))

    # recv_size=0
    # recv_data=b\'\'
    # print(\'length:\',length)
    # while recv_size < length:
    #     recv_data+=s.recv(10)
    #     print(recv_size)
    #     recv_size=len(recv_data)
    #     print(recv_size)
    # print(recv_data)

s.close()



3最好的方法:自定义报头解决粘包问题

16.10.7 自定义报头解决粘包问题

自定义报头:固定长度 + 对将要发送数据的描述信息

(1)简单自定义报头
服务端:
[root@python_lb ~]# cat baotou_server.py 
#coding:utf-8
import socket,struct,subprocess

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
ip_port=(\'192.168.2.2\',8081)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(ip_port)
phone.listen(5)
while True:
    conn,addr=phone.accept()                                                                
    print(\'客户端是:\',addr)
    while True:
        try:
            cmd=conn.recv(1024)
            res=subprocess.Popen(cmd.decode(\'utf-8\'),
                                 shell=True,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE)
            out_res=res.stdout.read()
            err_res=res.stderr.read()
            data_size=len(out_res)+len(err_res)
            #发送报头
            conn.send(struct.pack(\'i\',data_size))
            #发送数据部分
            conn.send(out_res)
            conn.send(err_res)
        except Exception:
            break
    conn.close()
phone.close()

客户端:
import socket,struct
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

ip_port=(\'192.168.2.2\',8081)
phone.connect_ex(ip_port)

while True:
    cmd=input(\'>>:\').strip()
    if not cmd:continue
    phone.send(bytes(cmd,encoding=\'utf-8\'))
    #收报头
    baotou=phone.recv(4)
    data_size=struct.unpack(\'i\',baotou)[0]
    #收数据
    recv_size=0
    recv_data=b\'\'
    while recv_size < data_size:
        data=phone.recv(1024)
        recv_size+=len(data)
        recv_data+=data
    print(recv_data.decode(\'utf-8\'))
phone.close()

(2)完整的自定义报头

服务端:
[root@python_lb ~]# cat baotou2_server.py 
#coding:utf-8 
import socket,struct,json,subprocess
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
ip_port=(\'192.168.2.2\',8080)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(ip_port)
phone.listen(5)

while True:
    conn,addr=phone.accept()
    print(\'client addr:\',addr)
    while True:
        try:
           cmd=conn.recv(1024)
           res=subprocess.Popen(cmd.decode(\'utf-8\'),
                                shell=True,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE)
           out_res=res.stdout.read()
           err_res=res.stderr.read()
           data_size=len(out_res)+len(err_res)
           head_dic={\'data_size\':data_size,\'describe\':\'baotou\'}
           head_json=json.dumps(head_dic)
           head_bytes=head_json.encode(\'utf-8\')

           #1 先发报头的长度
           head_len=len(head_bytes)
           conn.send(struct.pack(\'i\',head_len))
           #2 再发送报头
           conn.send(head_bytes)
           #3 最后发送数据部分
           conn.send(out_res)
           conn.send(err_res)

        except Exception:
            break
    conn.close()
phone.close()

客户端:
import socket,struct,json,sys,time
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
ip_port=(\'192.168.2.2\',8080)
phone.connect(ip_port)

while True:
    cmd=input(\'>>:\').strip()
    if not cmd:continue
    phone.send(bytes(cmd,encoding=\'utf-8\'))
    #1 先收报头的长度
    head_struct=phone.recv(4)
    head_len=struct.unpack(\'i\',head_struct)[0]
    #2 再收报头
    head_bytes=phone.recv(head_len)
    head_json=head_bytes.decode(\'utf-8\')
    head_dic=json.loads(head_json)
    print(head_dic)
    data_size=head_dic[\'data_size\']
    #3 收数据
    recv_size=0
    recv_data=b\'\'
    while recv_size < data_size:
        data=phone.recv(10)
        recv_size+=len(data)
        recv_data+=data
        chushu=recv_size / data_size
        s = "\r%d%% %s" % (chushu*100, "#" * int(chushu*100))
        sys.stdout.write(s)
        time.sleep(0.5)
        sys.stdout.flush()
    print()
    print(recv_data.decode(\'utf-8\'))
phone.close()



服务端:
 
 

客户端:
 
 


16.11 socketserver并发

 

16.11.1 经典实现

(1)基于TCP实现socketserver并发

服务端:
import socketserver
class FTPserver(socketserver.BaseRequestHandler):
    # 自己新建关于socketserver的类必须继承socketserver.BaseRequestHandler
    # 此类中必须要有handle函数
    def handle(self):
        print(self)
        print(self.request)  # self.request 相当于之前的conn
        while True:
            data=self.request.recv(1024)
            print(data)
            self.request.send(data.upper())
if __name__==\'__main__\':
    obj=socketserver.ThreadingTCPServer((\'127.0.0.1\',8080),FTPserver)
    #socketserver的多线程
    obj.serve_forever()  #每个线程的链接循环
    # FTPserver类负责通讯循环

客户端:
import socket
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect((\'127.0.0.1\',8080))
while True: #通信循环
    msg=input(\'>>:\').strip()
    if not msg:continue
    phone.send(msg.encode(\'utf-8\'))
    data=phone.recv(1024)
    print(data)
phone.close()


(2)基于UDP实现的socketserver并发
服务端:
import socketserver
class MyServer(socketserver.BaseRequestHandler):
    def handle(self):
        print(self.request)
        print(\'收到客户端的消息是:\',self.request[0])
        self.request[1].sendto(self.request[0].upper(),self.client_address)
if __name__==\'__main__\':
    s=socketserver.ThreadingUDPServer((\'127.0.0.1\',9001),MyServer)
    s.serve_forever()

客户端:
import socket
ip_port=(\'127.0.0.1\',9001)
BUFSIZE=1024
udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
while True:
    msg=input(\'>>:\').strip()
    if not msg:continue
    udp_server_client.sendto(msg.encode(\'utf-8\'),ip_port)
    back_msg,addr=udp_server_client.recvfrom(BUFSIZE)
    print(back_msg.decode(\'utf-8\'),addr)

16.11.2 继承关系


 



 





 




 





 



16.11.3 ftp上传下载并发实现

(1)基于TCP的ftp并发实现
服务端:
import socketserver
import struct
import json
import os
class FtpServer(socketserver.BaseRequestHandler):
    coding=\'utf-8\'
    server_dir=\'file_upload\'
    max_packet_size=1024
    BASE_DIR=os.path.dirname(os.path.abspath(__file__))
    def handle(self):
        print(self.request)
        while True:
            data=self.request.recv(4)
            data_len=struct.unpack(\'i\',data)[0]
            head_json=self.request.recv(data_len).decode(self.coding)
            head_dic=json.loads(head_json)
            # print(head_dic)
            cmd=head_dic[\'cmd\']
            if hasattr(self,cmd):
                func=getattr(self,cmd)
                func(head_dic)
    def put(self,args):
        file_path = os.path.normpath(os.path.join(
            self.BASE_DIR,
            self.server_dir,
            args[\'filename\']
        ))
        filesize = args[\'filesize\']
        recv_size = 0
        print(\'----->\', file_path)
        with open(file_path, \'wb\') as f:
            while recv_size < filesize:
                recv_data = self.request.recv(self.max_packet_size)
                f.write(recv_data)
                recv_size += len(recv_data)
                print(\'recvsize:%s filesize:%s\' % (recv_size, filesize))
ftpserver=socketserver.ThreadingTCPServer((\'127.0.0.1\',8080),FtpServer)
ftpserver.serve_forever()

客户端:
import socket
import struct
import json
import os
class MYTCPClient:
    address_family = socket.AF_INET
    socket_type = socket.SOCK_STREAM
    allow_reuse_address = False
    max_packet_size = 8192
    coding=\'utf-8\'
    request_queue_size = 5
    def __init__(self, server_address, connect=True):
        self.server_address=server_address
        self.socket = socket.socket(self.address_family,
                                    self.socket_type)
        if connect:
            try:
                self.client_connect()
            except:
                self.client_close()
                raise
    def client_connect(self):
        self.socket.connect(self.server_address)
    def client_close(self):
        self.socket.close()
    def run(self):
        while True:
            inp=input(">>: ").strip()
            if not inp:continue
            l=inp.split()
            cmd=l[0]
            if hasattr(self,cmd):
                func=getattr(self,cmd)
                func(l)
    def put(self,args):
        cmd=args[0]
        filename=args[1]
        if not os.path.isfile(filename):
            print(\'file:%s is not exists\' %filename)
            return
        else:
            filesize=os.path.getsize(filename)
        head_dic={\'cmd\':cmd,\'filename\':os.path.basename(filename),\'filesize\':filesize}
        print(head_dic)
        head_json=json.dumps(head_dic)
        head_json_bytes=bytes(head_json,encoding=self.coding)

        head_struct=struct.pack(\'i\',len(head_json_bytes))
        self.socket.send(head_struct)
        self.socket.send(head_json_bytes)
        send_size=0
        with open(filename,\'rb\') as f:
            for line in f:
                self.socket.send(line)
                send_size+=len(line)
                print(send_size)
            else:
                print(\'upload successful\')
client=MYTCPClient((\'192.168.2.2\',8080))
client.run()


(2)基于UTP的ftp并发实现例子










16.12 ftp详细功能作业


 




 


操作系统:操作硬件的软件

http://www.cnblogs.com/yuanchenqi/articles/6248025.html







 



十七 操作系统简介 


 
http://www.cnblogs.com/yuanchenqi/articles/6806707.html

十八 多线程与多进程

http://www.cnblogs.com/yuanchenqi/articles/6755717.html


18.1 进程与线程的概念

18.1.1 进程

考虑一个场景:浏览器,网易云音乐以及notepad++ 三个软件只能顺序执行是怎样一种场景呢?另外,假如有两个程序A和B,程序A在执行到一半的过程中,需要读取大量的数据输入(I/O操作),而此时CPU只能静静地等待任务A读取完数据才能继续执行,这样就白白浪费了CPU资源。你是不是已经想到在程序A读取数据的过程中,让程序B去执行,当程序A读取完数据之后,让程序B暂停。聪明,这当然没问题,但这里有一个关键词:切换。

既然是切换,那么这就涉及到了状态的保存,状态的恢复,加上程序A与程序B所需要的系统资源(内存,硬盘,键盘等等)是不一样的。自然而然的就需要有一个东西去记录程序A和程序B分别需要什么资源,怎样去识别程序A和程序B等等(比如读书)。

进程定义:
进程就是一个程序在一个数据集上的一次动态执行过程。进程一般由程序、数据集、进程控制块三部分组成。我们编写的程序用来描述进程要完成哪些功能以及如何完成;数据集则是程序在执行过程中所需要使用的资源;进程控制块用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志。

并行的概念:跑在多核CPU上的进程们
并发:单核CPU上进行切换执行的进程们

并发中系统切换进程的两种状态:
(1) 出现IO操作
(2) 国定时间


18.1.2 线程

线程的出现是为了降低上下文切换的消耗,提高系统的并发性,并突破一个进程只能干一样事的缺陷,使到进程内并发成为可能。

假设,一个文本程序,需要接受键盘输入,将内容显示在屏幕上,还需要保存信息到硬盘中。若只有一个进程,势必造成同一时间只能干一样事的尴尬(当保存时,就不能通过键盘输入内容)。若有多个进程,每个进程负责一个任务,进程A负责接收键盘输入的任务,进程B负责将内容显示在屏幕上的任务,进程C负责保存内容到硬盘中的任务。这里进程A,B,C间的协作涉及到了进程通信问题,而且有共同都需要拥有的东西——-文本内容,不停的切换造成性能上的损失。若有一种机制,可以使任务A,B,C共享资源,这样上下文切换所需要保存和恢复的内容就少了,同时又可以减少通信所带来的性能损耗,那就好了。是的,这种机制就是线程。
线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序计数器、寄存器集合和堆栈共同组成。线程的引入减小了程序并发执行时的开销,提高了操作系统的并发性能。线程没有自己的系统资源。


18.1.3 进程与线程的关系
进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。或者说进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
线程则是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。
 

进程和线程的关系:
(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
(2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。
(3)CPU分给线程,即真正在CPU上运行的是线程。


进程:资源管理单位(存放线程的容器)
线程:最小执行单位

18.1.4 并行和并发
并行处理(Parallel Processing)是计算机系统中能同时执行两个或更多个处理的一种计算方法。并行处理可同时工作于同一程序的不同方面。并行处理的主要目的是节省大型和复杂问题的解决时间。

并发处理(concurrency Processing):指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机(CPU)上运行,但任一个时刻点上只有一个程序在处理机(CPU)上运行。

并发的关键是你有处理多个任务的能力,不一定要同时。并行的关键是你有同时处理多个任务的能力。所以说,并行是并发的子集
 


18.1.5 同步和异步
在计算机领域,同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;

异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。举个例子,打电话时就是同步通信,发短息时就是异步通信。


18.2 threading模块

18.2.1 线程对象的创建

(1)threading.Thread类直接实例化创建线程
实例:
import threading
import time
def countNum(n): #定义某个线程要运行的函数
    print(\'running on number:%s \'%n)
    time.sleep(3)
    print(\'time2\',time.time()-t)
if __name__ ==\'__main__\':
    t=time.time()
    t1=threading.Thread(target=countNum,args=(23,)) #生成一个线程实例
    t2=threading.Thread(target=countNum,args=(34,))
    t1.start() #启动线程
    t2.start()
    print(\'ending!\')
    print("time1:",time.time()-t)
输出结果为:
running on number:23 
running on number:34 
ending!
time1: 0.0009746551513671875
time2 3.0013554096221924
time2 3.0013554096221924


 


(2)threading.Thread类继承式创建线程
实例:
#继承Thread式创建线程
import threading
import time
class MyThread(threading.Thread):
    def __init__(self,num):
        threading.Thread.__init__(self)
        self.num=num
    def run(self):   # run函数类似于socket函数中的handle函数,必须定义,默认触发
        print("running on number:%s"% self.num)
        time.sleep(3)
        print("time1:", time.time() - t)
t=time.time()
t1=MyThread(56)
t2=MyThread(78)
t1.start()
t2.start()
print(\'ending\')
print("time1:",time.time()-t)
输出结果为:
running on number:56
running on number:78
ending
time1: 0.0
time1: 3.0010547637939453
time1: 3.0010547637939453


18.2.2 threading.Thread类的实例方法

(1)join()方法
join():在子线程完成运行之前,这个子线程的父线程将一直被阻塞。
      线程对象调用
import  threading,time
class MyThread(threading.Thread):
    def __init__(self,num):
        threading.Thread.__init__(self)
        self.num=num
    def run(self):
        print("running on number:%s"%self.num)
        time.sleep(3)
t1=MyThread(56)
t2=MyThread(78)
print(time.time())
# t1.start()
# t2.start()
# t1.join()
# t2.join()
t1.start()
t1.join()
t2.start()
t2.join()
print("ending")
print(time.time())
输出结果为:
1494227957.9458869
running on number:56
running on number:78
ending
1494227963.9491796 #运行了6秒


(2)setDaemon(True)方法
setDaemon(True):
 将线程声明为守护线程,必须在start() 方法调用之前设置,如果不设置为守护线程程序会被无限挂起。
 当我们在程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程 就分兵两路,分别运行,那么当主线程完成想退出时,会检验子线程是否完成。如果子线程未完成,则主线程会等待子线程完成后再退出。但是有时候我们需要的是只要主线程完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以 用setDaemon方法啦\'\'\'

守护线程作用一般是监听,守护进程也是。

关于Setdaemon:程序直到不存在非守护线程时退出

import threading
from time import ctime,sleep
import time
def Music(name):
    print("Begin listening to {name}.{time}".format(name=name,time=ctime()))
    sleep(3)
    print("End listening {name}.{time}".format(name=name,time=ctime()))

def Blog(title):
    print("Begin recording the {title}.{time}".format(title=title,time=ctime()))
    sleep(5)
    print("End recording {title}.{time}".format(title=title,time=ctime()))
threads=[]
t1=threading.Thread(target=Music,args=(\'FILL ME\',))
t2=threading.Thread(target=Blog,args=(\'PAPER\',))
threads.append(t1)
threads.append(t2)

if __name__=="__main__":
    t2.setDaemon(True) #注意:一定要在start之前设置
    for t in threads:
        t.start()
    # for t in threads:
    #     t.join()
    t1.join()
print("all over %s"%ctime())
输出结果为:
Begin listening to FILL ME.Mon May  8 15:34:38 2017
Begin recording the PAPER.Mon May  8 15:34:38 2017
End listening FILL ME.Mon May  8 15:34:41 2017
all over Mon May  8 15:34:41 2017

(3)其他方法
Thread实例对象的方法
  # isAlive(): 返回线程是否活动的。
  # getName(): 返回线程名。
  # setName(): 设置线程名。

threading模块提供的一些方法:
  # threading.currentThread(): 返回当前的线程变量。
  # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

设置进程名称:
 

 



18.3 GIL(全局解释器锁)

18.3.1 GIL的定义

Python中的线程是操作系统的原生线程,Python虚拟机使用一个全局解释器锁(Global Interpreter Lock)来互斥线程对Python虚拟机的使用。为了支持多线程机制,一个基本的要求就是需要实现不同线程对共享资源访问的互斥,所以引入了GIL。
GIL:在一个线程拥有了解释器的访问权之后,其他的所有线程都必须等待它释放解释器的访问权,即使这些线程的下一条指令并不会互相影响。
在调用任何Python C API之前,要先获得GIL
GIL缺点:多处理器退化为单处理器;优点:避免大量的加锁解锁操作


18.3.2 DIL的早期设计
Python支持多线程,而解决多线程之间数据完整性和状态同步的最简单方法自然就是加锁。 于是有了GIL这把超级大锁,而当越来越多的代码库开发者接受了这种设定后,他们开始大量依赖这种特性(即默认python内部对象是thread-safe的,无需在实现时考虑额外的内存锁和同步操作)。慢慢的这种实现方式被发现是蛋疼且低效的。但当大家试图去拆分和去除GIL的时候,发现大量库代码开发者已经重度依赖GIL而非常难以去除了。有多难?做个类比,像MySQL这样的“小项目”为了把Buffer Pool Mutex这把大锁拆分成各个小锁也花了从5.5到5.6再到5.7多个大版为期近5年的时间,并且仍在继续。MySQL这个背后有公司支持且有固定开发团队的产品走的如此艰难,那又更何况Python这样核心开发和代码贡献者高度社区化的团队呢?

18.3.3 GIL的影响
Python有多进程并行,但线程不能并行。每个进程有个锁,一次只能执行1个线程。由于GIL,导致同一时刻,同一进程只能有一个线程在执行。

无论你启多少个线程,你有多少个cpu, Python在执行一个进程的时候会淡定的在同一时刻只允许一个线程运行。
所以,python是无法利用多核CPU实现多线程的。 Python可以利用多核CPU实现多进程。
这样,python对于计算密集型的任务开多线程的效率甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。

 


实例:
from threading import Thread
import time
def counter():
    i = 0
    for _ in range(200000000):
        i=i+1
    return True
def main():
    l=[]
    start_time=time.time()

    for i in range(2):
        t=Thread(target=counter)
        t.start()
        l.append(t)
        t.join()
    # for t in l:
    #     t.join()
    end_time=time.time()
    print("Total time:{}".format(end_time-start_time))
if __name__==\'__main__\':
    main()
输出结果为:
#串行:Total time:18.066075086593628
#并发:Total time:18.092113494873047


18.3.4 使用multiprocessing解决DIL问题的方法
用multiprocessing替代Thread multiprocessing库的出现很大程度上是为了弥补thread库因为GIL而低效的缺陷。它完整的复制了一套thread所提供的接口方便迁移。唯一的不同就是它使用了多进程而不是多线程。每个进程有自己的独立的GIL,因此也不会出现进程之间的GIL争抢。

实例:
from multiprocessing import Process
import time
def counter():
    i = 0
    for _ in  range(200000000):
        i = i + 1
    return True
def main():
    l=[]
    start_time=time.time()
    for _ in range(2):
        t=Process(target=counter)
        t.start()
        l.append(t)
        t.join()
    # for t in l:
    #     t.join()
    end_time = time.time()
    print("Total time:{}".format(end_time-start_time))
if __name__ == "__main__":
    main()
输出结果为:
串行:Total time:18.10815715789795
并发:Total time:11.655998945236206



当然multiprocessing也不是万能良药。它的引入会增加程序实现时线程间数据通讯和同步的困难。就拿计数器来举例子,如果我们要多个线程累加同一个变量,对于thread来说,申明一个global变量,用thread.Lock的context包裹住三行就搞定了。而multiprocessing由于进程之间无法看到对方的数据,只能通过在主线程申明一个Queue,put再get或者用share memory的方法。这个额外的实现成本使得本来就非常痛苦的多线程程序编码,变得更加痛苦了。

总结:因为GIL的存在,只有IO Bound场景下得多线程会得到较好的性能 - 如果对并行计算性能较高的程序可以考虑把核心部分也成C模块,或者索性用其他语言实现 - GIL在较长一段时间内将会继续存在,但是会不断对其进行改进。

所以对于GIL,既然不能反抗,那就学会去享受它吧!


18.3.5 其他方法解决GIL问题

(1) multiprocessing模块
(2)Python使用多核,开进程,可以解决GIL问题。但是弊端是,开销大而且切换复杂
(3)解决GIL着重点是使用: 协程 + 多进程  。协程的效率比进程和线程都高
(4)解决GIL的另一个方向:IO多路复用


18.3.6 总结GIL
GIL(全局解释器锁)
加在cpython解释器上
计算密集型:一直在使用CPU
IO:存在大量IO操作

对于计算密集型任务:Python的多线程并没有用
对于IO密集型任务:Python的多线程是有意义的



18.4 同步锁(Lock)
加锁后执行是串行

锁通常被用来实现对共享资源的同步访问。为每一个共享资源创建一个Lock对象,当你需要访问资源时,调用acquire方法来获取锁对象(如果其他线程已经获得了该锁,则当前线程等待其被释放),待资源访问后,再调用release方法释放锁。
同步锁的操作:
lock=threading.Lock()
lock.acquire()
lock.release()
实例:
import time
import threading
def addNum():
    global num #在每个线程中都获取这个全局变量
    #num-=1
    lock.acquire()
    temp=num
    time.sleep(0.0001)
    num =temp-1  # 对此公共变量进行-1操作
    lock.release()
num = 100  #设定一个共享变量
thread_list = []
lock=threading.Lock()
for i in range(100):
    t = threading.Thread(target=addNum)
    t.start()
    thread_list.append(t)
for t in thread_list: #等待所有线程执行完毕
    t.join()
print(\'Result: \', num)

同步锁操作:
import threading
R=threading.Lock()
R.acquire()
\'\'\'
对公共数据的操作
\'\'\'
R.release()


扩展思考:
1、为什么有了GIL,还需要线程同步?
多线程环境下必须存在资源的竞争,那么如何才能保证同一时刻只有一个线程对共享资源进行存取?
加锁, 对, 加锁可以保证存取操作的唯一性, 从而保证同一时刻只有一个线程对共享数据存取.

通常加锁也有2种不同的粒度的锁:
    coarse-grained(粗粒度): python解释器层面维护着一个全局的锁机制,用来保证线程安全。
                            内核级通过GIL实现的互斥保护了内核的共享资源。
    fine-grained(细粒度):   那么程序员需要自行地加,解锁来保证线程安全,
                            用户级通过自行加锁保护的用户程序的共享资源。

 2、GIL为什么限定在一个进程上?
 
 你写一个py程序,运行起来本身就是一个进程,这个进程是有解释器来翻译的,所以GIL限定在当前进程;
 如果又创建了一个子进程,那么两个进程是完全独立的,这个字进程也是有python解释器来运行的,所以
 这个子进程上也是受GIL影响的                
\'\'\'


18.5 死锁与递归锁

18.5.1 死锁

所谓死锁:是指两个或者两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,他们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

import threading
import time
mutexA=threading.Lock()
mutexB=threading.Lock()
class MyThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
    def run(self):
        self.fun1()
        self.fun2()
    def fun1(self):
        mutexA.acquire() #如果锁被占用,等待锁的释放
        print("I am %s ,get res:%s --- %s "%(self.name,"ResA",time.time()))
        mutexB.acquire()
        print("I am %s ,get res:%s --- %s"%(self.name,"ResB",time.time()))
        mutexA.release()
        mutexB.release()
    def fun2(self):
        mutexB.acquire()
        print("I am %s ,get res:%s --- %s"%(self.name,"ResB",time.time()))
        time.sleep(0.2)
        mutexA.acquire()
        print("I am %s ,get res:%s --- %s"%(self.name,"ResA",time.time()))
        mutexA.release()
        mutexB.release()
if __name__ =="__main__":
    print("start---------------%s"%time.time())
    for i in range(0,10):
        my_thread=MyThread()
        my_thread.start()
输出结果为:
start---------------1494312892.4097893
I am Thread-1 ,get res:ResA --- 1494312892.4097893 
I am Thread-1 ,get res:ResB --- 1494312892.4097893
I am Thread-1 ,get res:ResB --- 1494312892.4097893
I am Thread-2 ,get res:ResA --- 1494312892.4097893 #然后死锁

18.5.2 递归锁
在Python中为了支持在统一线程中多次请求同一资源,python提供了可重入锁RLock。这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁。

import threading
import time
mutex=threading.RLock()
class MyThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
    def run(self):
        self.fun1()
        self.fun2()
    def fun1(self):
        mutex.acquire() 
        print("I am %s ,get res:%s --- %s "%(self.name,"ResA",time.time()))
        mutex.acquire()
        print("I am %s ,get res:%s --- %s"%(self.name,"ResB",time.time()))
        mutex.release()
        mutex.release()
    def fun2(self):
        mutex.acquire()
        print("I am %s ,get res:%s --- %s"%(self.name,"ResB",time.time()))
        time.sleep(0.2)
        mutex.acquire()
        print("I am %s ,get res:%s --- %s"%(self.name,"ResA",time.time()))
        mutex.release()
        mutex.release()
if __name__ =="__main__":
    print("start---------------%s"%time.time())
    for i in range(0,10):
        my_thread=MyThread()
        my_thread.start()



18.6 event对象(线程通信)
18.6.1 event对象原理
线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。
(1)在初始情况下,Event对象中的信号标志被设置为假。
(2)如果线程等待一个Event对象,而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。
(3)一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。
(4)如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件,继续执行。

有threading库中的Event对象:
: envent.isSet() :返回event的状态值
: event.wait() :如果event.isSet()==False将阻塞线程
: event.set() : 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态,等待操作系统调用
:event.clear() :恢复event的状态值为False

 


18.6.2 event对象实例

可以考虑一种应用场景(仅仅作为说明),例如,我们有多个线程从Redis队列中读取数据来处理,这些线程都要尝试去连接Redis的服务,一般情况下,如果Redis连接不成功,在各个线程的代码中,都会去尝试重新连接。如果我们想要在启动时确保Redis服务正常,才让那些工作线程去连接Redis服务器,那么我们就可以采用threading.Event机制来协调各个工作线程的连接操作:主线程去尝试连接Redis服务器,如果正常的话,触发事件,各工作线程会尝试连接Redis服务。

实例:
import threading
import time
import logging
logging.basicConfig(level=logging.DEBUG,format=\'(%(threadName)-10s) %(message)s\',)
def worker(event):
    logging.debug(\'Waiting for redis ready...\')
    event.wait()
    logging.debug(\'redis ready,and connect to redis server and do some work [%s]\',time.ctime())
    time.sleep(1)
def main():
    redis_ready=threading.Event()
    t1=threading.Thread(target=worker,args=(redis_ready,),name=\'t1\')
    t1.start()
    t2=threading.Thread(target=worker,args=(redis_ready,),name=\'t2\')
    t2.start()
    logging.debug(\'first of all,check redis server,make sure it is ok,and then trigger the redis ready event\')
    time.sleep(3)
    redis_ready.set()
if __name__==\'__main__\':
    main()
输出结果为:
(t1        ) Waiting for redis ready...
(t2        ) Waiting for redis ready...
(MainThread) first of all,check redis server,make sure it is ok,and then trigger the redis ready event #等待3秒
(t1        ) redis ready,and connect to redis server and do some work [Tue May  9 17:25:57 2017]
(t2        ) redis ready,and connect to redis server and do some work [Tue May  9 17:25:57 2017]




18.6.3 超时参数
threading.Event的wait方法还接受一个超时参数,默认情况下如果事件一致没有发生,wait方法会一直阻塞下去,而加入这个超时参数之后,如果阻塞时间超过这个参数设定的值之后,wait方法会返回。对应于上面的应用场景,如果Redis服务器一致没有启动,我们希望子线程能够打印一些日志来不断地提醒我们当前没有一个可以连接的Redis服务,我们就可以通过设置这个超时参数来达成这样的目的:

def worker(event):
    while not event.is_set():
        logging.debug(\'Waiting for redis ready...\')
        event.wait(2)
    logging.debug(\'redis ready, and connect to redis server and do some work [%s]\', time.ctime())
    time.sleep(1)

这样,我们就可以在等待Redis服务启动的同时,看到工作线程里正在等待的情况。




18.7 Semaphore信号量
控制最大可以拿锁的线程的数量

Semaphone管理一个内置的计数器
每当调用acquire()时内置计数器-1
调用release()时内置计数器+1
计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()

实例:(同时只有5个线程可以获得semaphore,即可以限制最大连接数为5)
import threading
import time
semaphore=threading.Semaphore(5)
def func():
    semaphore.acquire()
    print(threading.currentThread().getName() + \' get semaphore\')
    time.sleep(2)
    semaphore.release()
for i in range(20):
    t1=threading.Thread(target=func)
    t1.start()
输出结果为:
Thread-1 get semaphore  #每2秒 打印出 5条
Thread-2 get semaphore 
Thread-3 get semaphore
Thread-4 get semaphore
Thread-5 get semaphore
Thread-8 get semaphore
Thread-6 get semaphore
Thread-7 get semaphore
Thread-9 get semaphore
Thread-10 get semaphore
Thread-11 get semaphore
Thread-12 get semaphore
Thread-14 get semaphore
Thread-13 get semaphore
Thread-15 get semaphore
Thread-16 get semaphore
Thread-17 get semaphore
Thread-18 get semaphore
Thread-19 get semaphore
Thread-20 get semaphore

应用:连接池


18.8 多线程利器--队列(queue)

18.8.1 queue(队列)方法

(1)列表是不安全的数据结构
两个线程对列表共同操作,会有冲突,因此列表是不安全的数据结构
对列是安全的,可以用来线程操作数据

import threading,time
li=[1,2,3,4,5]
def pri():
    while li:
        a=li[-1]
        print(a)
        time.sleep(1)
        try:
            li.remove(a)
        except Exception as e:
            print(\'-----\',a,e)
t1=threading.Thread(target=pri,args=())
t1.start()
t2=threading.Thread(target=pri,args=())
t2.start()
输出结果为:
5
5
4
----- 5 list.remove(x): x not in list
4
3
----- 4 list.remove(x): x not in list
3
2
----- 3 list.remove(x): x not in list
2
1
----- 2 list.remove(x): x not in list
1
----- 1 list.remove(x): x not in list


(2)队列方法
queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.

1 创建一个“队列”对象
import queue
q = queue.Queue(maxsize = 10)
queue.Queue类即是一个队列的同步实现。队列长度可为无限或者有限。可通过Queue的构造函数的可选参数maxsize来设定队列长度。如果maxsize小于1就表示队列长度无限。

2 将一个值放入队列中
q.put(10)
调用队列对象的put()方法在队尾插入一个项目。
put()有两个参数,第一个item为必需的,为插入项目的值;第二个block为可选参数,默认为1。如果队列当前为空且block为1,put()方法就使调用线程暂停,直到空出一个数据单元。如果block为0,put方法将引发Full异常。


3 将一个值从队列中取出
q.get()
调用队列对象的get()方法从队头删除并返回一个项目。可选参数为block,默认为True。如果队列为空且block为True,
get()就使调用线程暂停,直至有项目可用。如果队列为空且block为False,队列将引发Empty异常。

4 此包中的常用方法(q = Queue.Queue()):
q.qsize() 
返回队列的大小
q.empty() 
如果队列为空,返回True,反之False
q.full() 
如果队列满了,返回True,反之False。q.full 与 maxsize 大小对应
q.get([block[, timeout]]) 
获取队列,timeout等待时间
q.get_nowait() 
相当q.get(False)。
q.put_nowait(item) 
相当q.put(item, False)
q.task_done() 
在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号
q.join() 
实际上意味着等到队列为空,再执行别的操作 一般和task_done一起使用

(3)三种队列
Python Queue模块有三种队列及构造函数:
1、Python Queue模块的FIFO队列先进先出。   class queue.Queue(maxsize)
2、LIFO类似于堆,即先进后出。               class queue.LifoQueue(maxsize)
3、还有一种是优先级队列级别越低越先出来。        class queue.PriorityQueue(maxsize)

# 队列的后进先出模式
import queue
q=queue.LifoQueue()
q.put(12)
q.put("hello")
q.put({"name":"wen"})
while 1:
    data = q.get()
    print(data)
    print("------")

# 按优先级模式输出,数字越小优先级越高
import queue
q=queue.PriorityQueue()
q.put([3,12])
q.put([2,"hello"])
q.put([4,{"name":"wen"}])
while 1:
    data = q.get()
    print(data[1])
    print("------")


(4)实例
#默认队列是先进先出
#并且q队列空了,就会在get卡住,不会输出,不会报错
import queue
q=queue.Queue()
q.put(12)
q.put("hello")
q.put({"name":"wen"})
while 1:
    data = q.get()
    print(data)
    print("------")
输出结果为:
12
------
hello
------
{\'name\': \'wen\'}
------ #会卡住


# 结果没有任何输出,并卡住
#是在put第4个值的时候卡住,因为队列设置是3个值
import queue
q=queue.Queue(3)
q.put(12)
q.put("hello")
q.put({"name":"wen"})
q.put(34)
while 1:
    data = q.get()
    print(data)
    print("------")
输出结果为:
#直接卡住


#get put的参数block=False时 是否阻塞
import queue
q=queue.Queue(3)
q.put(12)
q.put("hello")
q.put({"name":"wen"})
# q.put(34,False)
while 1:
    data = q.get(block=False)
    print(data)
    print("------")


# join和take_done方法
import queue
q = queue.Queue(5)
q.put(10)
q.put(20)
print(q.get())
q.task_done()
print(q.get())
q.task_done()

q.join()

print("ending!")



#各种queue方法
import queue
q=queue.Queue(3)
q.put(12)
q.put("hello")
q.put({"name":"wen"})
print(q.qsize())
print(q.empty())
print(q.full())
q.put_nowait(56) # 相当于 q.put(56,block=False)
输出结果为:
3
False
True
queue.Full  #报错

18.8.2 应用:生产者消费者模型

(1)为什么要使用生产者和消费者模式

在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。

(2)什么是生产者消费者模式

生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

这就像,在餐厅,厨师做好菜,不需要直接和客户交流,而是交给前台,而客户去饭菜也不需要不找厨师,直接去前台领取即可,这也是一个结耦的过程。

(3)生产者消费者模型实例

import time,random
import queue,threading
q = queue.Queue()
def Producer(name):
  count = 0
  while count <10:
    print("making........")
    time.sleep(random.randrange(3))
    q.put(count)
    print(\'Producer %s has produced %s baozi..\' %(name, count))
    count +=1
    #q.task_done()
    #q.join()
    print("ok......")
def Consumer(name):
  count = 0
  while count <10:
    time.sleep(random.randrange(4))
    if not q.empty():
        data = q.get()
        #q.task_done()
        #q.join()
        print(data)
        print(\'\033[32;1mConsumer %s has eat %s baozi...\033[0m\' %(name, data))
    else:
        print("-----no baozi anymore----")
    count +=1

p1 = threading.Thread(target=Producer, args=(\'A\',))
c1 = threading.Thread(target=Consumer, args=(\'B\',))
# c2 = threading.Thread(target=Consumer, args=(\'C\',))
# c3 = threading.Thread(target=Consumer, args=(\'D\',))
p1.start()
c1.start()
# c2.start()
# c3.start()


18.9 multiprocessing模块多进程管理

18.9.1 multiprocessing模块作用

Multiprocessing is a package that supports spawning processes using an API similar to the threading module. The multiprocessing package offers both local and remote concurrency,effectively side-stepping the Global Interpreter Lock by using subprocesses instead of threads. Due to this, the multiprocessing module allows the programmer to fully leverage multiple processors on a given machine. It runs on both Unix and Windows.

由于GIL的存在,python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程。

multiprocessing包是Python中的多进程管理包。与threading.Thread类似,它可以利用multiprocessing.Process对象来创建一个进程。该进程可以运行在Python程序内部编写的函数。该Process对象与Thread对象的用法相同,也有start(), run(), join()的方法。此外multiprocessing包中也有Lock/Event/Semaphore/Condition类 (这些对象可以像多线程那样,通过参数传递给各个进程),用以同步进程,其用法与threading包中的同名类一致。所以,multiprocessing的很大一部份与threading使用同一套API,只不过换到了多进程的情境。


18.9.2 python的进程调用

# Process类调用  开的是进程

from multiprocessing import Process
import time
def f(name):
    print(\'hello\', name,time.ctime())
    time.sleep(1)

if __name__ == \'__main__\':
    p_list=[]
    for i in range(3):
        p = Process(target=f, args=(\'alvin:%s\'%i,))
        p_list.append(p)
        p.start()
    for i in p_list:
        p.join()
    print(\'end\')


# 继承Process类调用  开的是进程
from multiprocessing import Process
import time

class MyProcess(Process):
    def __init__(self):
        super(MyProcess, self).__init__()
        # self.name = name
    def run(self):
        print (\'hello\', self.name,time.ctime())
        time.sleep(1)
if __name__ == \'__main__\':
    p_list=[]
    for i in range(3):
        p = MyProcess()
        p.start()
        p_list.append(p)
    for p in p_list:
        p.join()
    print(\'end\')


18.9.3 process类

构造方法:
Process([group [, target [, name [, args [, kwargs]]]]])

  group: 线程组,目前还没有实现,库引用中提示必须是None; 
  target: 要执行的方法; 
  name: 进程名; 
  args/kwargs: 要传入方法的参数。

实例方法:
  is_alive():返回进程是否在运行。
  join([timeout]):阻塞当前上下文环境的进程程,直到调用此方法的进程终止或到达指定的timeout(可选参数)。
  start():进程准备就绪,等待CPU调度
  run():strat()调用run方法,如果实例进程时未制定传入target,这star执行t默认run()方法。
  terminate():不管任务是否完成,立即停止工作进程
属性:
daemon:和线程的setDeamon功能一样  
 
  name:进程名字。
  pid:进程号。


18.9.4 os.getpid得到进程号举例

from multiprocessing import Process
import os
import time
def info(name):
    print("name:",name)
    print("parent process:",os.getppid())  #父进程ID
    print("process id:",os.getpid())       #这个程序执行的进程ID
    print("------------")
    time.sleep(1)
def foo(name):
    info(name)
if __name__=="__main__":
    info(\'main process line\')
    p1=Process(target=info,args=(\'wen\',))
    p2=Process(target=info,args=(\'yan\',))
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    print("ending")
输出结果为:
name: main process line
parent process: 6432
process id: 5704
------------
name: wen
name: yan
parent process: 5704
process id: 96
------------
parent process: 5704
process id: 5108
------------
ending



通过tasklist(Win)或者ps -elf |grep(linux)命令检测每一个进程号(PID)对应的进程名

http://www.cnblogs.com/yuanchenqi/articles/6248025.html


进程间通讯

import queue,time
import multiprocessing

def foo(q):
    print("son process:",id(q))
    q.put(123)
    q.put("wen")
if __name__ == \'__main__\':
    #q=queue.Queue()  #线程队列
    q=multiprocessing.Queue()  #进程队列,子进程是拷贝了主进程的q队列
    p=multiprocessing.Process(target=foo,args=(q,))
    p.start()
    print("main process:",id(q))
    print(q.get())
print(q.get())

 


 

from multiprocessing import Process,Pipe
def f(conn):
    conn.send([12,{"name":"yuan"},\'hello\'])
    response=conn.recv()
    print("response",response)
    conn.close()
    print("q_ID2:",id(conn))
if __name__=="__main__":
    parent_conn,child_conn=Pipe()   #双向管道
    print("q_ID1:",id(child_conn))
    p=Process(target=f,args=(child_conn,))
    p.start()
    print(parent_conn.recv())
    parent_conn.send("儿子你好!")
    p.join()


 

from multiprocessing import Process, Manager

def f(d, l,n):

    d[n] = \'1\'    #{0:"1"}
    d[\'2\'] = 2    #{0:"1","2":2}

    l.append(n)    #[0,1,2,3,4,   0,1,2,3,4,5,6,7,8,9]
    #print(l)


if __name__ == \'__main__\':

    with Manager() as manager:

        d = manager.dict()#{}

        l = manager.list(range(5))#[0,1,2,3,4]

        p_list = []

        for i in range(10):
            p = Process(target=f, args=(d,l,i))
            p.start()
            p_list.append(p)

        for res in p_list:
            res.join()

        print(d)
        print(l)





 


 
 
from  multiprocessing import Process,Pool
import time,os

def Foo(i):

    time.sleep(1)
    print(i)
    print("son",os.getpid())

    return "HELLO %s"%i


def Bar(arg):
    print(arg)
    # print("hello")
    # print("Bar:",os.getpid())

if __name__ == \'__main__\':

    pool = Pool(5)
    print("main pid",os.getpid())
    for i in range(100):
        #pool.apply(func=Foo, args=(i,))  #同步接口
        #pool.apply_async(func=Foo, args=(i,))

        #回调函数:  就是某个动作或者函数执行成功后再去执行的函数

        pool.apply_async(func=Foo, args=(i,),callback=Bar)

    pool.close()
    pool.join()         # join与close调用顺序是固定的

    print(\'end\')




 







18.10 协程

协程: 协作式,----非抢占式的程序
   Yield(协程)
   用户态的切换
   Key:什么时候切换
   协程主要解决的也是IO操作的
   协程:本质上就是一个线程
       协程的优势:
                  1 没有切换的消耗。原因是协程不需要抢占,只是需要指定执行就行
                  2 没有锁的概念
       有一个问题:能用多核吗?可以采用多进程+协程,一个很好的解决并发的方案



协程,又称微线程,纤程。英文名Coroutine.
优点1:协程极高的执行效率。因为子程序切换不是线程切换,而是程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势越明显
优点2:不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
因为协程是一个线程执行,那怎么利用多核CPU呢? 最简单的方法就是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。


http://www.cnblogs.com/linhaifeng/articles/6817679.html#_label1
18.10.1 yield与协程

生成器复习:
 

def f():
    print("ok")
    s=yield 6
    print(s)
    print("ok2")
    yield
gen=f()
# print(gen)
# next(gen)
RET=gen.__next__()
print(RET)
# next(gen)
gen.send(5)


def consumer(name):
    print("--->ready to eat baozi...")
    while True:
        new_baozi = yield
        print("[%s] is eating baozi %s" % (name,new_baozi))
        time.sleep(1)
def producer():
    r = con.__next__()
    r = con2.__next__()
    n = 0
    while 1:
        time.sleep(1)
        print("\033[32;1m[producer]\033[0m is making baozi %s and %s" %(n,n+1) )
        con.send(n)
        con2.send(n+1)
        n +=2
if __name__ == \'__main__\':
    con = consumer("c1")
    con2 = consumer("c2")
    producer()




import time
"""
传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。
如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高。
"""
# 注意到consumer函数是一个generator(生成器):
# 任何包含yield关键字的函数都会自动成为生成器(generator)对象

def consumer():
    r = \'\'
    while True:
        # 3、consumer通过yield拿到消息,处理,又通过yield把结果传回;
        #    yield指令具有return关键字的作用。然后函数的堆栈会自动冻结(freeze)在这一行。
        #    当函数调用者的下一次利用next()或generator.send()或for-in来再次调用该函数时,
        #    就会从yield代码的下一行开始,继续执行,再返回下一次迭代结果。通过这种方式,迭代器可以实现无限序列和惰性求值。
        n = yield r
        if not n:
            return
        print(\'[CONSUMER] ←← Consuming %s...\' % n)
        time.sleep(1)
        r = \'200 OK\'
def produce(c):
    # 1、首先调用c.next()启动生成器
    next(c)
    n = 0
    while n < 5:
        n = n + 1
        print(\'[PRODUCER] →→ Producing %s...\' % n)
        # 2、然后,一旦生产了东西,通过c.send(n)切换到consumer执行;
        cr = c.send(n)
        # 4、produce拿到consumer处理的结果,继续生产下一条消息;
        print(\'[PRODUCER] Consumer return: %s\' % cr)
    # 5、produce决定不生产了,通过c.close()关闭consumer,整个过程结束。
    c.close()
if __name__==\'__main__\':
    # 6、整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。
    c = consumer()
    produce(c)
    
    
\'\'\'
result:

[PRODUCER] →→ Producing 1...
[CONSUMER] ←← Consuming 1...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] →→ Producing 2...
[CONSUMER] ←← Consuming 2...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] →→ Producing 3...
[CONSUMER] ←← Consuming 3...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] →→ Producing 4...
[CONSUMER] ←← Consuming 4...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] →→ Producing 5...
[CONSUMER] ←← Consuming 5...
[PRODUCER] Consumer return: 200 OK
\'\'\'

 
yield可以实现并行,但是并没有办法监听

18.10.2 greenlet实现协程
greelet机制的主要思想是:生成器函数或者协程函数中的yield语句挂起函数的执行,直到稍后使用next()或send()操作进行恢复为止。可以使用一个调度器循环在一组生成器函数之间协作多个任务。greentlet是python中实现我们所谓的"Coroutine(协程)"的一个基础库.

动作的发起是greenlet对象的swith的接口

from greenlet import greenlet
def test1():
    print (12)
    gr2.switch()
    print (34)
    gr2.switch()
def test2():
    print (56)
    gr1.switch()
    print (78)
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()


18.10.3 gevent:基于greenlet的框架

: greenlet模块实现协程
Python通过yield提供了对协程的基本支持,但是不完全。而第三方的gevent为Python提供了比较完善的协程支持。
gevent是第三方库,通过greenlet实现协程,其基本思想是:
当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。
由于切换是在IO操作时自动完成,所以gevent需要修改Python自带的一些标准库,这一过程在启动时通过monkey patch完成:
import gevent
import time
def foo():
    print("running in foo")
    gevent.sleep(2)
    print("switch to foo again")
def bar():
    print("switch to bar")
    gevent.sleep(5)
    print("switch to bar again")
start=time.time()
gevent.joinall(
    [gevent.spawn(foo),
    gevent.spawn(bar)]
)
print(time.time()-start)



当然,实际代码里,我们不会用gevent.sleep()去切换协程,而是在执行到IO操作时,gevent自动切换,代码如下:
from gevent import monkey
monkey.patch_all()
import gevent
from urllib import request
import time
def f(url):
    print(\'GET: %s\' % url)
    resp = request.urlopen(url)
    data = resp.read()
    print(\'%d bytes received from %s.\' % (len(data), url))
start=time.time()
gevent.joinall([
        gevent.spawn(f, \'https://itk.org/\'),
        gevent.spawn(f, \'https://www.github.com/\'),
        gevent.spawn(f, \'https://zhihu.com/\'),
])
# f(\'https://itk.org/\')
# f(\'https://www.github.com/\')
# f(\'https://zhihu.com/\')
print(time.time()-start)







18.11 IO模型

18.11.1 IO模型简介

IO Model 一共5种:
Blocking IO(阻塞IO)
Nonblocking IO (阻塞IO)
IO multiplexing (IO多路复用)
Signal driven IO
Asynchronous IO

IO发生时涉及的对象:
对于一个network IO(这里我们以read举例),它会涉及到两个系统对象,一个是调用这个IO的process(or thread),另一个就是系统内核(kernel)。

IO发生时涉及的步骤:
当一个read操作发生时,它会经历两个阶段:
1 等待数据准备(Waiting for the data to be ready)
2 将数据从内核拷贝到进程中(Copying the data from the kernal to the process)
IO Model的区别就是在这两个阶段上各有不同的情况。

寻址空间32位有4个G,内核态会有1个G,用户态会有3个G


18.11.2 blocking IO(阻塞IO)

在linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程大概是这样:

 
当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。


18.11.3 non-blocking IO(非阻塞IO)

Linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子的:
 
从图中可以看出,1 当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。2 从用户进程角度讲,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断是一个error时,它就知道数据还没有准备好,于是他可以再次发送read操作。3 一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。所以,用户进程其实是需要不断的主动询问kernel数据好了没有。

注意:
在网络IO时候,非阻塞IO也会进行recvfrom系统调用,检查数据是否准备好,与阻塞IO不一样,“非阻塞将大的整片时间的阻塞分成N多的小阻塞,所以进程不断地有机会被CPU光顾”。即每次recvfrom系统调用之间,cpu的权限还在进程手中,这段时间是可以做其他事情的,也就是说非阻塞的recvform系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。重复上面的过程,循环往复的进行recvform系统调用。这个过程通常被称之为轮询。轮询检查内核,直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。


举例:
import time
import socket
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk.setsockopt
sk.bind((\'127.0.0.1\',6667))
sk.listen(5)
sk.setblocking(False)
while True:
    try:
        print (\'waiting client connection .......\')
        connection,address = sk.accept()   # 进程主动轮询
        print("+++",address)
        client_messge = connection.recv(1024)
        print(str(client_messge,\'utf8\'))
        connection.close()
    except Exception as e:
        print (e)
        time.sleep(4)

#############################client

import time
import socket
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

while True:
    sk.connect((\'127.0.0.1\',6667))
    print("hello")
    sk.sendall(bytes("hello","utf8"))
    time.sleep(2)
    break

优点:能够在等待任务完成的时间里干其他活了(包括提交其他任务,也就是“后台”可以有多个任务在同时执行)
缺点:任务完成的响应延迟增大了,因为每过一段才去轮询一次read操作,而任务可能在两次轮询之间的任意时间完成。这会导致整体数据吞吐量的降低。



18.11.4 IO multiplexing(IO多路复用)
(1)IO多路复用原理
IO multiplexing(IO多路复用): select,epoll模型
有些地方也称为这种IO方式为event driven IO。Select/epoll的好处就在于单个process就可以同时处理多个网络连接IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。它的流程如图:
 

当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。

这个图和blocking IO的图其实并没有太大的不同,事实上,还更差一些。因为这里需要使用两个system call(select和recvfrom),而blocking IO只调用了一个system call(recvfrom)。当时,用select的优势在于它可以同时处理多个connection。(多说一句,所以,如果处理的链接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading +blocking IO的web server性能更好,可能延迟还更大。Select/epoll的优势并不是对于单个链接能处理得更快,而是在于能处理更多的链接)

在IO multiplexing Model中,实际上,对于每一个socket,一般都是设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。

注意1:select函数返回结果中如果有文件可读了,那么进程就可以通过调用accept()或recv()来让kernel将位于内核中准备到的数据copy到用户

注意2:select的优势在于可以处理多个链接,不适用于单个链接。

实例:
#***********************server.py
import socket
import select
sk=socket.socket()
sk.bind(("127.0.0.1",8801))
sk.listen(5)
inputs=[sk,]
while True:
    r,w,e=select.select(inputs,[],[],5)
    print(len(r))
    for obj in r:
        if obj==sk:
            conn,add=obj.accept()
            print(conn)
            inputs.append(conn)
        else:
            data_byte=obj.recv(1024)
            print(str(data_byte,\'utf8\'))
            inp=input(\'回答%s号客户>>>\'%inputs.index(obj))
            obj.sendall(bytes(inp,\'utf8\'))
    print(\'>>\',r)
#***********************client.py
import socket
sk=socket.socket()
sk.connect((\'127.0.0.1\',8801))
while True:
    inp=input(">>>>")
    sk.sendall(bytes(inp,"utf8"))
    data=sk.recv(1024)
    print(str(data,\'utf8\'))


(2)使用select实现并发实例
1 Select机制

 


 



2 使用select实现并发
服务端:
import time
import select
import socket
sock=socket.socket()
sock.bind((\'127.0.0.1\',8800))
sock.listen(5)
sock.setblocking(False)
inputs=[sock,]
while 1:
    r,w,e=select.select(inputs,[],[])
    # 监听有变化的套接字 inputs=[sock,conn1,conn2...]
    for obj in r: #第一次[sock,] 第二次[]
        if obj==sock:
            conn,addr=obj.accept()
            print("conn",conn)
            inputs.append(conn) #input=[sock,conn]
        else:
            data=obj.recv(1024)
            print(data.decode(\'utf8\'))
            send_data=input(">>>")
            obj.send(send_data.encode(\'utf8\'))

客户端:
import socket
sock=socket.socket()
sock.connect_ex((\'127.0.0.1\',8800))
while 1:
    data=input("input>>>:")
    sock.send(data.encode("utf-8"))
    rece_data=sock.recv(1024)
    print(rece_data.decode(\'utf-8\'))
sock.close()


3 select机制实现并发:异常的解决

 


(3) selectors模块
 

import selectors
import socket
sel = selectors.DefaultSelector()
def accept(sock, mask):
    conn, addr = sock.accept()  # Should be ready
    print(\'accepted\', conn, \'from\', addr)
    conn.setblocking(False)
    sel.register(conn, selectors.EVENT_READ, read)
def read(conn, mask):
    data = conn.recv(1000)  # Should be ready
    if data:
        print(\'echoing\', repr(data), \'to\', conn)
        conn.send(data)  # Hope it won\'t block
    else:
        print(\'closing\', conn)
        sel.unregister(conn)
        conn.close()
sock = socket.socket()
sock.bind((\'localhost\', 1234))
sock.listen(100)
sock.setblocking(False)
sel.register(sock, selectors.EVENT_READ, accept) #注册功能
while True:
    events = sel.select()
    for key, mask in events:
        callback = key.data
        callback(key.fileobj, mask)

 

 

 
 

服务端:
import selectors
import socket
sock=socket.socket()
sock.bind((\'127.0.0.1\',8800))
sock.listen(5)
sock.setblocking(False)
sel=selectors.DefaultSelector()
def read(conn,mask):
    try:
        data=conn.recv(1024)
        print(data.decode("utf-8"))
        data2=input(">>>")
        conn.send(data2.encode("utf-8"))
    except Exception as e:
        sel.unregister(conn)
def accept(sock,mask):
    conn,addr=sock.accept()
    print("conn",conn)
    sel.register(conn,selectors.EVENT_READ,read)

sel.register(sock,selectors.EVENT_READ,accept)

while 1:
    print("wating...")
    events=sel.select()
    for key,mask in events:
        print(key.fileobj)
        print(key.data)
        func=key.data
        obj=key.fileobj
        func(obj,mask)
客户端:
import socket
sock=socket.socket()
sock.connect(("127.0.0.1",8800))
while 1:
    data=input("input>>>")
    sock.send(data.encode("utf-8"))
    rece_data=sock.recv(1024)
    print(rece_data.decode("utf-8"))
sock.close()



 


 



(4)多种机制:IO多路复用实现
Windows:select
Linux:select poll epoll

select的缺点:
1 每次调用select都要将所有的fd(文件描述符)拷贝到内核空间,导致效率下降
2 遍历所有的fd,是否有数据访问;(最重要的问题)
3 最大连接数(1024)

Poll:最大连接数没有限制

Epoll:第一个函数:创建epoll句柄,将所有的fd(文件描述符)
                 ,但是只需拷贝一次
  回调函数:某一个函数或者某一个动作成功完成后会触发的函数 
                为所有的fd绑定一个回调函数,一旦有数据访问,触发该回调函数,
                回调函数将fd放到链表中。
  第三个函数:判断链表是否为空
   最大连接数没有上限

18.11.5 Asynchronous I/O(异步IO)
Linux下的asynchronous IO其实用得很少。先看一下他的流程:
 

用户进程发起read操作之后,立刻就可以开始去做其他的事。而另一方面,从kernel的角度,当它受到了一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。


18.11.6 IO模型比较分析
(1)阻塞模型和非阻塞模型比较(blocking vs non-blocking)
调用blocking IO会一直block住(阻塞住)对应的进程直到操作完成,而non-blocking IO在kernel还在准备数据的情况下会立刻返回。

(2)同步模型和异步模型比较(synchronous IO和asynchronous IO)
同步模型synchronous IO做IO operation的时候会将process阻塞。按照这个定义,之前所述的blocking IO,non-blocking IO,IO multiplexing都属于synachronous IO。

注意: non-blocking IO并没有被block啊,这里需要好好理解,定义中所指的“IO operation”是指真实的IO操作,就是例子中的recvfrom这个system call。Non-blocking IO在执行recvfrom这个system call的时候,如果kernel的数据没有准备好,这时候不会block进程。但是,当kernel中数据准备好的时候,recvfrom会将数据从kernel拷贝到用户内存中,这个时候进程是被block了,在这段时间内,进程是被block的。而asynchronous IO则不一样,当进程发起IO操作之后,就直接返回再也不理睬了,直到kernel发送一个信号,告诉进程说IO完成。在这整个过程中,进程完全没有被block。


各个IO Model的比较如图所示:
 
经过上面的介绍,会发现non-blocking IO和asynchronous IO的区别还是很明显的。在non-blocking IO中,虽然进程大部分时间都不会被block,但是它仍然要求进程去主动的check,并且当数据准备完成以后,也需要进程主动的再次调用recvfrom来将数据拷贝到用户内存。而asynchronous IO则完全不同。它就像是用户进程将整个IO操作交给了他人(kernel)完成,然后他人做完后发信号通知。在此期间,用户进程不需要去检查IO操作的状态,也不需要主动的去拷贝数据。



对于文件描述符(套接字对象的实质):
(1) 是一个非零整数,不会变
(2) 收发数据的时候,对于接受端而言,数据先到内核空间,然后copy到用户空间,同时,内核空间数据清除


IO模块
1 阻塞IO:全程阻塞
2 非阻塞IO:发送多次系统调用
优点 Wait for data时无阻塞
缺点:1 系统调用太多   
      2 数据不是实时接收的
两个阶段:wait for data:非阻塞
           Copy data:阻塞
3 IO多路复用(监听多个链接)
 IO多路复用的特点:
(1)全程(wait for data,copy)阻塞
(2)能监听多个文件描述符
(3)可以实现并发
4 
同步IO:阻塞IO,非阻塞IO,io多路复用
异步IO:异步IO



十九 HTML协议


 

import socket

def main():

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind((\'localhost\',8081))
    sock.listen(5)

    while True:
        print("server is working.....")
        conn, address = sock.accept()

        request = conn.recv(1024)

        conn.sendall(bytes("helo","utf8"))
        conn.close()
if __name__ == \'__main__\':
    main()


sock=socket.socket()
sock.bind((\'127.0.0.1\',8800))
sock.listen(5)
while True:
    conn,addr=sock.accept()
    dat=conn.recv(1024)
    print(\'-----\',dat.decode(\'utf8\'))
    with open(\'index.html\')as f:
        data=f.read()
    conn.send(b\'HTTP/1.1 201 OK\r\n\r\n%s\'%data.encode(\'gbk\'))
    conn.close()

 

 



二十 HTML语言

20.1 html是什么

超文本标记语言(Hypertext Markup Language,HTML)通过标签语言来标记要显示的网页中的各个部分。一套规则,浏览器认识的规则

浏览器按顺序渲染网页文件,然后根据标记符解释和显示内容。但需要注意的是,对于不同的浏览器,对同一标签可能会有不完全相同的解释(兼容性)

静态网页文件扩展名:.html 或 .htm

HTML 不是一种编程语言,而是一种标记语言 (markup language)
HTML 使用标记标签来描述网页

20.2 html结构

 

<!DOCTYPE html> 告诉浏览器使用什么样的html或者xhtml来解析html文档
<html></html>是文档的开始标记和结束标记。此元素告诉浏览器其自身是一个 HTML 文档,在它们之间是文档的头部<head>和主体<body>。
<head></head>元素出现在文档的开头部分。<head>与</head>之间的内容不会在浏览器的文档窗口显示,但是其间的元素有特殊重要的意义。
<title></title>定义网页标题,在浏览器标题栏显示。 
<body></body>之间的文本是可见的网页主体内容


20.3 html标签格式

 

标签的语法:
<标签名 属性1=“属性值1” 属性2=“属性值2”……>内容部分</标签名>
<标签名 属性1=“属性值1” 属性2=“属性值2”…… />


20.4 常用标签

20.4.1 <!DOCTYPE>标签
<!DOCTYPE> 声明位于文档中的最前面的位置,处于 <html> 标签之前。此标签可告知浏览器文档使用哪种 HTML 或 XHTML 规范。

作用:声明文档的解析类型(document.compatMode),避免浏览器的怪异模式。

document.compatMode:

BackCompat:怪异模式,浏览器使用自己的怪异模式解析渲染页面。
CSS1Compat:标准模式,浏览器使用W3C的标准解析渲染页面。
这个属性会被浏览器识别并使用,但是如果你的页面没有DOCTYPE的声明,那么compatMode默认就是BackCompat


20.4.2 <head>内常用标签

(1)<meta>标签
meta介绍
<meta>元素可提供有关页面的元信息(meta-information),针对搜索引擎和更新频度的描述和关键词。
<meta>标签位于文档的头部,不包含任何内容。
<meta>提供的信息是用户不可见的

meta标签的组成:meta标签共有两个属性,它们分别是http-equiv属性和name 属性,不同的属性又有不同的参数值,这些不同的参数值就实现了不同的网页功能。 

(1)name属性: 主要用于描述网页,与之对应的属性值为content,content中的内容主要是便于搜索引擎机器人查找信息和分类信息用的。
<meta name="keywords" content="meta总结,html meta,meta属性,meta跳转">
<meta name="description" content="老男孩培训机构是由一个很老的男孩创建的">

针对关键字搜索:必须name="keywords"  conn=”关键字内容”
 

(2)http-equiv属性:相当于http的文件头作用,它可以向浏览器传回一些有用的信息,以帮助正确地显示网页内容,与之对应的属性值为content,content中的内容其实就是各个参数的变量值。

<meta http-equiv="Refresh" content="2;URL=https://www.oldboy.com"> //(注意后面的引号,分别在秒数的前面和网址的后面)
<meta http-equiv="content-Type" charset=UTF8">   # 告诉客户端内容编码
<meta http-equiv = "X-UA-Compatible" content = "IE=EmulateIE7" /> 


(2)非</meta>标签

 <title>oldboy</title> #标题
    <link rel="icon" href="http://www.jd.com/favicon.ico">  #标题上的图标链接
    <link rel="stylesheet" href="css.css">
    <script src="hello.js"></script> 


20.4.3 <body>内常用标签

 
(1) 基本标签(块级标签和内联标签)

<hn>: n的取值范围是1~6; 从大到小. 用来表示标题.
<p>: 段落标签. 包裹的内容被换行.并且也上下内容之间有一行空白.
<b> <strong>: 加粗标签.
<strike>: 为文字加上一条中线.
<em>: 文字变成斜体.
 

<sup>和<sub>: 上角标 和 下角标.
<br>:换行.
<hr>:水平线

特殊字符:
      < &gt;&quot;©®
	
 

 


(2)<div>和<span>
<div></div> : <div>只是一个块级元素,并无实际的意义。主要通过CSS样式为其赋予不同的表现. 
<span></span>: <span>表示了内联行(行内元素),并无实际的意义,主要通过CSS样式为其赋予不同的表现.

块级元素与行内元素的区别
所谓块元素,是以另起一行开始渲染的元素,行内元素则不需另起一行。如果单独在网页中插入这两个元素,不会对页面产生任何的影响。
这两个元素是专门为定义CSS样式而生的。

 


(3)图形标签<img>

src: 要显示图片的路径.
alt: 图片没有加载成功时的提示.
title: 鼠标悬浮时的提示信息.
width: 图片的宽
height:图片的高 (宽高两个属性只用一个会自动等比缩放.)

 

 


(4)超链接标签(锚标签): <a> </a>

什么是超级链接?
所谓的超链接是指从一个网页指向一个目标的连接关系,这个目标可以是另一个网页,也可以是相同网页上的不同位置,还可以是一个图片,一个电子邮件地址,一个文件,甚至是一个应用程序

什么是URL?
URL是统一资源定位器(Uniform Resource Locator)的缩写,也被称为网页地址,是因特网上标准的资源的地址。
URL举例
http://www.sohu.com/stu/intro.html
http://222.172.123.33/stu/intro.html

URL地址由4部分组成
第1部分:为协议:http://、ftp://等 
第2部分:为站点地址:可以是域名或IP地址
第3部分:为页面在站点中的目录:stu
第4部分:为页面名称,例如 index.html
各部分之间用“/”符号隔开

<a href="" target="_blank" >click</a>
href属性指定目标网页地址。该地址可以有几种类型:
    绝对 URL - 指向另一个站点(比如 href="http://www.jd.com)
    相对 URL - 指当前站点中确切的路径(href="index.htm")
    锚 URL - 指向页面中的锚(href="#top")

普通链接:
 
图片链接跳转:
 
本地了解跳转:
 
页面内寻找(锚与id):
 
页面内寻找拓展,同样的p标签不同的id和样式
 
 


(5)列表标签

<ul>: 无序列表 [type属性:disc(实心圆点)(默认)、circle(空心圆圈)、square(实心方块)]
 
 


<ol>: 有序列表
         <li>:列表中的每一项.
 
 


<dl>  定义列表
         <dt> 列表标题
         <dd> 列表项

 
 

(6)表格标签: <table>

表格概念
表格是一个二维数据空间,一个表格由若干行组成,一个行又有若干单元格组成,单元格里可以包含文字、列表、图案、表单、数字符号、预置文本和其它的表格等内容。
表格最重要的目的是显示表格类数据。表格类数据是指最适合组织为表格格式(即按行和列组织)的数据。
表格的基本结构:
<table>
         <tr>
                <td>标题</td>
                <td>标题</td>
         </tr>
         
         <tr>
                <td>内容</td>
                <td>内容</td>
         </tr>
</table>

 
 


属性:
<tr>: table row 
<th>: table head cell  
<td>: table data cell
属性:
    border: 表格边框.
    cellpadding: 内边距 (内容与边框)
    cellspacing: 外边距. (边框与边框)
    width: 像素 百分比.(最好通过css来设置长宽)
    rowspan:  单元格竖跨多少行
    colspan:  单元格横跨多少列(即合并单元格)



<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<table border="1"  bordercolor="blue" cellspacing="0">
    <tr>
        <th colspan="3">星期一菜谱</th>
    </tr>
    <tr>
        <td rowspan="2">素菜</td>
        <td>青草茄子</td>
        <td>花椒扁豆</td>
    </tr>
    <tr>
        <td>小葱豆腐</td>
        <td>烧白菜</td>
    </tr>
    <tr>
        <td rowspan="2">荤菜</td>
        <td>油焖大虾</td>
        <td>海参鱼翅</td>
    </tr>
    <tr>
        <td>红烧肉
            <img src="rou.png" width="100px" height="100px"  alt="红烧肉" title="我的红烧肉">
        </td>
        <td>烤全羊</td>
    </tr>
</table>
</body>
</html>




(7)表单标签: <form>
功能:表单用于向服务器传输数据,从而实现用户与Web服务器的交互
表单能够包含input系列标签,比如文本字段、复选框、单选框、提交按钮等等。
表单还可以包含textarea、select、fieldset和 label标签


表单属性
     action: 表单提交到哪.一般指向服务器端一个程序,程序接收到表单提交过来的数据(即表单元素值)作相应处理,比如https://www.sogou.com/web
     method: 表单的提交方式 post/get默认取值就是get

表单元素
基本概念:
HTML表单是HTML元素中较为复杂的部分,表单往往和脚本、动态页面、数据处理等功能相结合,因此它是制作动态网站很重要的内容。
表单一般用来收集用户的输入信息
表单工作原理:
访问者在浏览有表单的网页时,可填写必需的信息,然后按某个按钮提交。这些信息通过Internet传送到服务器上。 
服务器上专门的程序对这些数据进行处理,如果有错误会返回错误信息,并要求纠正错误。当数据完整无误后,服务器反馈一个输入完成的信息


不同类型的input标签
<1> 表单类型
type:        text 文本输入框
             password 密码输入框
             radio 单选框
             checkbox 多选框  
             submit 提交按钮            
             button 按钮(需要配合js使用.) button和submit的区别?
             file 提交文件:form表单需要加上属性enctype="multipart/form-data" 
            
            上传文件注意两点:
                     1 请求方式必须是post
                     2 enctype="multipart/form-data"

 <2> 表单属性

 name:    表单提交项的键.
           注意和id属性的区别:name属性是和服务器通信时使用的名称;
           而id属性是浏览器端使用的名称,该属性主要是为了方便客户端编程,而在css和javascript中使用的

value:    表单提交项的值.对于不同的输入类型,value 属性的用法也不同:
                type="button", "reset", "submit" - 定义按钮上的显示的文本
                type="text", "password", "hidden" - 定义输入字段的初始值
                type="checkbox", "radio", "image" - 定义与输入相关联的值
checked:  radio 和 checkbox 默认被选中
readonly: 只读. text 和 password
disabled: 对所用input都好使.

select标签
<select> 下拉选标签属性
          name:表单提交项的键.
          size:选项个数
          multiple:multiple 
                 <optgroup>为每一项加上分组
                  <option> 下拉选中的每一项 属性:
                       value:表单提交项的值.   
                       selected: selected下拉选默认被选中


<textarea> 多行文本框

<form id="form1" name="form1" method="post" action="">
        <textarea cols=“宽度” rows=“高度” name=“名称”>
                   默认内容
        </textarea>
</form>


<label>标签

定义:<label> 标签为 input 元素定义标注(标记)。
说明:
1 label 元素不会向用户呈现任何特殊效果。
2 <label> 标签的 for 属性值应当与相关元素的 id 属性值相同

<form method="post" action="">
        <label for=“username”>用户名</label>
        <input type=“text” name=“username” id=“username” size=“20” />
</form>


<fieldset>标签

<fieldset>
    <legend>登录吧</legend>
    <input type="text">
</fieldset>


表单标签举例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>用户注册</h1>
<form action="" method="post">
    <!--action指定处理表单数据的服务器文件
    method上传数据的方式get或post -->
    <p>姓名:<input type="text" name="username" value="yuan"></p>
    <p>密码:<input type="password" name="pwd"></p>
    <p>爱好:篮球<input type="checkbox" name="aihao" value="baskteball" checked="checked">
        爱好:足球<input type="checkbox" name="aihao" value="football">
        爱好:乒乓球<input type="checkbox" name="aihao" value="pingpongball"></p>
    <!--三个爱好的name是一样的变量名。将选中的键的value值作为列表,传到后端。例-->
    <!--{"":"","aihao":"["baskteball","football"]"}-->
    <p>性别: 男<input type="radio" name="sex" value="man"> 女<input type="radio" name="sex" value="women"> 其他<input type="radio" name="sex" value="other"> </p>
    <!--{"sex":"man"} name相同的单选框只能选1个值-->
    <p><input type="button" value="button"></p>
    <!--button按钮需要绑定事件触发时-->

    <p><input type="file" name="filename"></p>
    <!--上传文件-->
    <p><input type="hidden" name="data" value="默认值"></p>
    <!--hidden是隐藏值,最后提交给服务端-->

    <select name="province" size="2" multiple="multiple">
        <optgroup label="中国">
            <option value="shanxi" selected>山西省</option>
            <option value="beijing" selected>北京市</option>
            <option value="shenzhen">深圳市</option>
        </optgroup>
    </select>
    <!--name为键,value为值--
    size="3" 显示几个
    multiple="multiple" 多选
    selected 默认值
    optgroup 分组
    -->
    <p>
        简介:
        <textarea name="jianjie" rows="8" cols="32"></textarea>
    </p>
    <label for="username">用户名</label>
    <input type="text" name="username" id="username" >
    <!--#将“用户名”和整个text绑定了-->
    <fieldset>
        <legend>登录吧</legend>
        <input type="text">
    </fieldset>
    <!--加外边框-->
    <p><input type="submit" value="submit"></p>
    <!--submit提交按钮的value值是显示在按钮上的值-->
</form>
</body>
</html>
输出结果为:
 







二十一 前端基础之CSS


http://www.cnblogs.com/yuanchenqi/articles/6856399.html 


 

21.1 CSS 语法

CSS 规则由两个主要的部分构成:选择器,以及一条或多条声明。
  selector {
                  property: value;
                  property: value;
             ...  property: value
          }
例如:
h1 {color:red; font-size:14px;}

 


21.2 css的四种引入方式 

21.2.1行内式
行内式是在标记的style属性中设定CSS样式。这种方式没有体现出CSS的优势,不推荐使用。
 <p style="color: red;background-color:green ">hello wenyanjie</p>
CSS和HTML没解耦,不好

21.2.2嵌入式
嵌入式是将CSS样式集中写在网页的<head></head>标签对的<style></style>标签对中。格式如下:
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        p{
            background-color: #2b99ff;
        }
    </style>
</head>

 

21.2.3链接式
将一个.css文件引入到HTML文件中 (常用)
<link href="mystyle.css" rel="stylesheet" type="text/css"/>

21.2.4导入式
  将一个独立的.css文件引入HTML文件中,导入式使用CSS规则引入外部CSS文件,<style>标记也是写在<head>标记中,使用的语法如下: 
<style type="text/css">
          @import"mystyle.css"; 此处要注意.css文件的路径
</style> 
加载顺序:先加载内容,再加载样式。并且又引用css文件的数量限制

注意:
    导入式会在整个网页装载完后再装载CSS文件,因此这就导致了一个问题,如果网页比较大则会儿出现先显示无样式的页面,闪烁一下之后,再出现网页的样式。这是导入式固有的一个缺陷。使用链接式时与导入式不同的是它会以网页文件主体装载前装载CSS文件,因此显示出来的网页从一开始就是带样式的效果的,它不会象导入式那样先显示无样式的网页,然后再显示有样式的网页,这是链接式的优点。


21.3 css选择器

21.3.1 基本选择器
基本选择器:
1 标签选择器
格式: p{color:green;}
2 id选择器
格式:info{font_size:30px;}
3 class选择器
.info { backgroup:#ff0; }
4 通配选择器
* {margin:0;padding:0;}



选择器举例:
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <style>
            /*通配选择器*/
            *{
                color: blueviolet;
                margin: 0px;
                /*所有的边距都修改为0*/
            }
            div{
                width: 100px;
                height: 30px;
                background-color: aqua;
            }
            /*标签选择器*/
            p{
                color: red;
            }
            /*Class选择器*/
            .c{
                color: darkblue;
            }
            /*id选择器*/
            #p3{
                color: gold;
            }
        </style>
    </head>
    <body>
        <p>只是p标签</p>
        <p id="p3">是id为p3标签</p>
        <p class="c">是class选择器</p>
        <div>是div标签</div>
    </body>
</html>
输出结果为:
 





Class选择器和id选择器合用:解决页码
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <style>
            .page{
                display: inline-block;
                width: 10px;
                height: 20px;
                background-color: gray;
                color: blue;
            }
            #okpage{
                 background-color: red;
            }
        </style>
    </head>
    <body>
        <ul>
            <li class="page">1</li>
            <li class="page">2</li>
            <li class="page">3</li>
            <li class="page" id="okpage">4</li>
            <li class="page">5</li>
            <li class="page">6</li>
            <li class="page">7</li>
        </ul>
    </body>
</html>
输出结果为:
 



21.3.2 组合选择器
一个标签里面嵌套这其他标签

E,F   多元素选择器,同时匹配所有E元素或F元素,E和F之间用逗号分隔      :div,p { color:#f00; }
E F   后代元素选择器,匹配所有属于E元素后代的F元素,E和F之间用空格分隔 :li a { font-weight:bold;}
E > F   子元素选择器,匹配所有E元素的子元素F            :div > p { color:#f00; }
E + F   毗邻元素选择器,匹配所有紧随E元素之后的同级元素F  :div + p { color:#f00; } 
E ~ F   普通兄弟选择器(以破折号分隔)                 :.div1 ~ p{font-size: 30px; }

注意,关于标签嵌套:
   一般,块级元素可以包含内联元素或某些块级元素,但内联元素不能包含块级元素,它只能包含其它内联元素。需要注意的是,p标签不能包含块级标签。


组合选择器举例:
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>

        <style>
            *{
               margin: 0px;
            }
            /*后代选择器*/
            .outer p{
                color: red;
            }
            /*子代选择器*/
            .outer>p{
                color: burlywood;
            }
            /*毗邻选择器*/
            .outer+p{
                color: firebrick;
            }
            /*兄弟选择器*/
            .outer~p{
                color: gray;
            }
        </style>
    </head>
    <body>
        <p>outer上的紧邻p标签中的内容</p>
        <div class="outer">
            <div>
                outer下的div标签中内容
                <p>outer下的div中p标签中内容</p>
            </div>
            <p>outer下p标签中内容</p>
        </div>
        <p>outer下紧邻的p标签中的内容</p>
        <div>outer下紧邻的div标签中的内容</div>
        <p>outer下紧邻的第二个p标签中的内容</p>
    </body>
</html>
输出结果为:
 




并且的关系: p标签并且class选择器iteml
 
 


21.3.3 属性选择器(用来匹配)

E[att]          匹配所有具有att属性的E元素,不考虑它的值。(注意:E在此处可以省略。
                比如“[cheacked]”。以下同。)   p[title] { color:#f00; }
E[att=val]      匹配所有att属性等于“val”的E元素   div[class=”error”] { color:#f00; }
E[att~=val]     匹配所有att属性具有多个空格分隔的值、其中一个值等于“val”的E元素
                td[class~=”name”] { color:#f00; }
E[attr^=val]    匹配属性值以指定值开头的每个元素                    
                div[class^="test"]{background:#ffff00;}
E[attr$=val]    匹配属性值以指定值结尾的每个元素    div[class$="test"]{background:#ffff00;}
E[attr*=val]    匹配属性值中包含指定值的每个元素    div[class*="test"]{background:#ffff00;}


Element(元素)


<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <style>
            /*匹配id选择器为po的*/
            [po]{
                color: blue;
            }
            /*匹配id选择器为po,且po的值为p2的*/
            [po=p2]{
                color: red;
            }
            /*匹配标签p并且id选择器po且po的值为p2的就匹配上*/
            p[po=p2]{
                color: gold;
            }
            /*匹配class选择器cl的后代选择器id选择器为po且po的值中为p2就匹配上*/
            .c1 [po="p2"]{
                color: salmon;
            }
            /*匹配class选择器cl并且id选择器po且po的值中为p2就匹配上*/
            .c1[po="p2"]{
                color: yellow;
            }
            /*匹配class选择器cl并且id选择器po且po的值中有p这个元素就匹配上*/
           .c1[po*="p"]{
                color: salmon;
            }
            .c1{
                color: red;
            }
            .btn{
                font-size: 30px;
            }
            /*匹配id选择器po且po的值中有p这个值就匹配上*/
            [po~="p"]{
                color: green;
            }
        </style>
    </head>
    <body>
        <div po="p1 p" class="c1">p1</div>
        <div po="p2" class="c1 btn">p2</div>
        <!--class="c1 btn" 两个可以同时匹配上-->
        <div class="c1">p3
        </div>
        <p po="p2">pp</p>
    </body>
</html>





21.3.4 伪类

(1)anchor伪类
anchor伪类:专用于控制链接的显示效果

a:link(没有接触过的链接),用于定义了链接的常规状态。
a:hover(鼠标放在链接上的状态),用于产生视觉效果。
a:visited(访问过的链接),用于阅读文章,能清楚的判断已经访问过的链接。
a:active(在链接上按下鼠标时的状态),用于表现鼠标按下时的链接状态。
        
伪类选择器 : 伪类指的是标签的不同状态:
a ==> 点过状态 没有点过的状态 鼠标悬浮状态 激活状态
a:link {color: #FF0000} /* 未访问的链接 */
a:visited {color: #00FF00} /* 已访问的链接 */
a:hover {color: #FF00FF} /* 鼠标移动到链接上 */
a:active {color: #0000FF} /* 选定的链接 */ 

格式: 标签:伪类名称{ css代码; }
用伪类进行控制:只能控制自己和子类,不能控制兄弟标签或选择器

a:hover举例:
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <style>
            .top{
                background-color: rebeccapurple;
                width: 100px;
                height: 100px;
            }
            .bottom{
                background-color: green;
                width: 100px;
                height: 100px;
            }
            .outer:hover .bottom{
                background-color: yellow;
            }
 /*注意:一定是outer:hover  控制outer里某一个标签,否则无效*/
            .top:hover+.bottom{
                background-color: aqua;
            }
        </style>
    </head>
    <body>
        <div class="outer">
            <div class="top">top</div>
            <div class="bottom">bottom</div>
        </div>
    </body>
</html>

(2)before after伪类 

 :before    p:before       在每个<p>元素之前插入内容     
 :after     p:after        在每个<p>元素之后插入内容     

例:p:before{content:"hello";color:red;display: block;}

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <style>
            .add:after{
                content:"欢迎"
            }
        </style>
    </head>
    <body>
        <div class="add">
            hello wen
        </div>
    </body>
</html>
输出结果为:
 


 


21.3.5 选择器的优先级

(1)css的继承
继承是CSS的一个主要特征,它是依赖于祖先-后代的关系的。继承是一种机制,它允许样式不仅可以应用于某个特定的元素,还可以应用于它的后代。例如一个BODY定义了的颜色值也会应用到段落的文本中。
body{color:red;}       <p>helloyuan</p>

这段文字都继承了由body {color:red;}样式定义的颜色。然而CSS继承性的权重是非常低的,是比普通元素的权重还要低的0。
p{color:green}

发现只需要给加个颜色值就能覆盖掉它继承的样式颜色。由此可见:任何显示申明的规则都可以覆盖其继承样式。 

    此外,继承是CSS重要的一部分,我们甚至不用去考虑它为什么能够这样,但CSS继承也是有限制的。有一些属性不能被继承,如:border, margin, padding, background等。
div{
  border:1px solid #222
}
<div>hello <p>yuan</p> </div>



(2)css的优先级
所谓CSS优先级,即是指CSS样式在浏览器中被解析的先后顺序。
样式表中的特殊性描述了不同规则的相对权重,它的基本规则是:
1 内联样式表的权值最高               style=""------------1000;
2 统计选择符中的ID属性个数。       #id --------------100
3 统计选择符中的CLASS属性个数。 .class -------------10
4 统计选择符中的HTML标签名个数。 p ---------------1
按这些规则将数字符串逐位相加,就得到最终的权重,然后在比较取舍时按照从左到右的顺序逐位比较。

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <style>
            div{
                background-color:fuchsia;
            }
            .c2{
                background-color: blueviolet;
            }
            .c1{
                background-color: darkred;
            }
            #id1{
                background-color:blue;
            }
        </style>
    </head>
    <body>
        <div class="c1 c2" id="id1" style="background-color: aqua" >
            DIV
            <p>p</p>
        </div>
        <p>p</p>
    </body>
</html>



1、文内的样式优先级为1,0,0,0,所以始终高于外部定义。
2、有!important声明的规则高于一切。
3、如果!important声明冲突,则比较优先权。
4、如果优先权一样,则按照在源码中出现的顺序决定,后来者居上。
5、由继承而得到的样式没有specificity的计算,它低于一切其它规则(比如全局选择符*定义的规则)
 



计算优先级:
 
 


21.4 css属性操作

21.4.1 css text 文本属性

(1)文本颜色:color
颜色属性被用来设置文字的颜色。
颜色是通过CSS最经常的指定:
十六进制值 - 如: #FF0000
一个RGB值 - 如: RGB(255,0,0)
颜色的名称 - 如:  red
p { color: rebeccapurple;  }

 

opacity透明度

(2)水平对齐方式
text-align 属性规定元素中的文本的水平对齐方式。
left      把文本排列到左边。默认值:由浏览器决定。
right    把文本排列到右边。
center 把文本排列到中间。
justify 实现两端对齐文本效果。

 



 

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>css</title>
<style>
        h2 {text-align:center;}
        p.publish_time {text-align:right;}
        p.content {text-align:justify;}
</style>
</head>

<body>
<h1>CSS text-align 水平居中</h1>
<p class="publish_time">2017 年 5 月 17 号</p>
<p class="content">
    有个落拓不得志的中年人每隔三两天就到教堂祈祷,而且他的祷告词几乎每次都相同。第一次他到教堂时,
    跪在圣坛前,虔诚地低语:“上帝啊,请念在我多年来敬畏您的份上。让我中一次彩票吧!阿门。”
    几天后,他又垂头丧气回到教堂,同样跪着祈祷:“上帝啊,为何不让我中彩票?我愿意更谦卑地来
    服侍你,求您让我中一次彩票吧!阿门。”又过了几天,他再次出现在教堂,同样重复他的祈祷。如此周而
    复始,不间断地祈求着。到了最后一次,他跪着:“我的上帝,为何您不垂听我的祈求?让我中一次彩票吧!
    只要一次,让我解决所有困难,我愿终身奉献,专心侍奉您……”就在这时,圣坛上发出一阵宏伟庄严的声
    音:“我一直垂听你的祷告。可是最起码?你也该先去买一张彩票吧!”</p>
<p><b>注意:</b> 重置浏览器窗口大小查看 "justify" 是如何工作的。</p>
</body>
</html>



(3)文本其它属性

font-size: 10px;     字体大小
line-height: 200px;   文本行高 通俗的讲,文字高度加上文字上下的空白区域的高度 50%:基于字体大小的百分比。可以做垂直居中。
vertical-align:-4px  设置元素内容的垂直对齐方式 ,只对行内元素有效,对块级元素无效
text-decoration:none       text-decoration 属性用来设置或删除文本的装饰。主要是用来删除链接的下划线
font-family: \'Lucida Bright\'
font-weight: lighter/bold/border/
font-style: oblique
text-indent: 150px;      首行缩进150px
letter-spacing: 10px;  字母间距
word-spacing: 20px;  单词间距
text-transform: capitalize/uppercase/lowercase ; 文本转换,用于所有字句变成大写或小写字母,或每个单词的首字母大写

 
溢出的变成滚动条

12.4.2 背景属性

(1)属性介绍
background-color 背景颜色
background-image  背景图像
background-repeat  默认反复铺满
background-position  移动图片的位置

background-color: cornflowerblue
background-image: url(\'1.jpg\');
background-repeat: no-repeat;(repeat:平铺满)
background-position: right top(20px 20px);

简写:
background:#ffffff url(\'1.png\') no-repeat right top;


 


 


Body塌陷问题



 


 


12.4.3边框属性

(1)属性介绍

border-width    边框厚度
border-style (required)  边框类型
border-color    具体颜色


 

border-style: solid;
border-color: chartreuse;
border-width: 20px;

简写:
border: 30px rebeccapurple solid;

边框-单独设置各边
border-top-style:dotted;
border-right-style:solid;
border-bottom-style:dotted;
border-left-style:none;


12.4.4 列表属性


list-style-type      设置列表项标志的类型。
list-style-image    将图象设置为列表项标志。
list-style-position  设置列表中列表项标志的位置。
list-style          简写属性。用于把所有用于列表的属性设置于一个声明中

ist-style-type属性指定列表项标记的类型:
ul { list-style-type: square; }
使用图像来替换列表项的标记:
ul {
     list-style-image: url(\'\');
            }


 


 

无样式,有列表之实质:常用
 



12.4.5 dispaly属性
none
block
inline
inline-block


(1)none(隐藏某标签)
p{display:none;}

注意与visibility:hidden的区别:
   visibility:hidden可以隐藏某个元素,但隐藏的元素仍需占用与未隐藏之前一样的空间。也就是说,该元素虽然被隐藏了,但仍然会影响布局。
   display:none可以隐藏某个元素,且隐藏的元素不会占用任何空间。也就是说,该元素不但被隐藏了,而且该元素原本占用的空间也会从页面布局中消失。

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <style>
            div{
                width: 200px;
                height: 100px;
                background-color: forestgreen;
            }
            .son{
                width: 100px;
                height: 50px;
                background-color: red;
            }
            .father:hover .son{
                display: none;
            }
        </style>
    </head>
    <body>
        <div class="father">hello
            <div class="son">

            </div>
        </div>
    </body>
</html>
输出结果为:
鼠标处在绿色区域的时候,红色会消失
  


(2)block(内联标签设置为块级标签)

block(内联标签设置为块级标签)
span {display:block;}
注意:一个内联元素设置为display:block是不允许有它内部的嵌套块元素。



(3)inline(块级标签设置为内联标签)
inline(块级标签设置为内联标签)
li {display:inline;}



(4)inline-block
即有内联的不占郑航,有有块级的设置大小属性等
display:inline-block可做列表布局,其中的类似于图片间的间隙小bug可以通过如下设置解决:
#outer{
            border: 3px dashed;
            word-spacing: -5px;
        }





12.4.6外边距(margine)和内边距(padding)

 


(1)边距属性总结
margin:        用于控制元素与元素之间的距离;margin的最基本用途就是控制元素周围空间的间隔,从视觉角度上达到相互隔开的目的。
padding:       用于控制内容与边框之间的距离;   
Border(边框)    围绕在内边距和内容外的边框。
Content(内容)   盒子的内容,显示文本和图像。

Margin:改变位置,不影响元素大小
Padding:会改变元素的大小


(2)margin属性
元素的宽度和高度
重要: 当您指定一个CSS元素的宽度和高度属性时,你只是设置内容区域的宽度和高度。要知道,完全大小的元素,你还必须添加填充,边框和边距。
margin:10px 5px 15px 20px;-----------上 右 下 左
margin:10px 5px 15px;----------------上 右左 下
margin:10px 5px;---------------------上下  右左
margin:10px;    ---------------------上右下左


下面的例子中的元素的总宽度为300px:
width:250px;
padding:10px;
border:5px solid gray;
margin:10px;


(3)margin边界塌陷
margin collapse(边界塌陷或者说边界重叠)
    外边距的重叠只产生在普通流文档的上下外边距之间,这个看起来有点奇怪的规则,其实有其现实意义。设想,当我们上下排列一系列规则的块级元素(如段     落P)时,那么块元素之间因为外边距重叠的存在,段落之间就不会产生双倍的距离。又比如停车场
   1兄弟div:上面div的margin-bottom和下面div的margin-top会塌陷,也就是会取上下两者margin里最大值作为显示值
   2父子div :if  父级div中没有 border,padding,inline content,子级div的margin会一直向上找,直到找到某个标签包括border,padding,inline content中的其中一个,然后按此div 进行margin ;



练习: 300px*300px的盒子装着100px*100px的盒子,分别通过margin和padding设置将小盒子 移到大盒子的中间
<!DOCTYPE html>
<html lang="en" style="padding: 0px">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>

        body{
            margin: 0px;
        }

        .div1{
            background-color: aqua;
            width: 300px;
            height: 300px;
        }
        .div2{
            background-color: blueviolet;
            width: 100px;
            height: 100px;
            margin: 20px;

        }
    </style>
</head>
<body>

<div style="background-color: cadetblue;width: 300px;height: 300px"></div>

       <div class="div1">
           <div class="div2"></div>
           <div class="div2"></div>
       </div>

</body>
</html>

解决方法:(在.div1的class选择器中写)
1: border:1px solid transparent
2: padding:1px
3: over-flow:hidden; 


12.4.7 float属性

先来了解一下block元素和inline元素在文档流中的排列方式。
  block元素通常被现实为独立的一块,独占一行,多个block元素会各自新起一行,默认block元素宽度自动填满其父元素宽度。block元素可以设置width、height、margin、padding属性;
  inline元素不会独占一行,多个相邻的行内元素会排列在同一行里,直到一行排列不下,才会新换一行,其宽度随元素的内容而变化。inline元素设置width、height属性无效。inline元素的margin和padding属性。水平方向的padding-left, padding-right, margin-left, margin-right都产生边距效果;但竖直方向的padding-top, padding-bottom, margin-top, margin-bottom不会产生边距效果。
常见的块级元素有 div、form、table、p、pre、h1~h5、dl、ol、ul 等。
常见的内联元素有span、a、strong、em、label、input、select、textarea、img、br等

所谓的文档流,指的是元素排版布局过程中,元素会自动从左往右,从上往下的流式排列。
脱离文档流,也就是将元素从普通的布局排版中拿走,其他盒子在定位的时候,会当做脱离文档流的元素不存在而进行定位。
只有绝对定位absolute和浮动float才会脱离文档流。
   ---部分无视和完全无视的区别?需要注意的是,使用float脱离文档流时,其他盒子会无视这个元素,但其他盒子内的文本依然会为这个元素让出位置,环绕在周围(可以说是部分无视)。而对于使用absolute position脱离文档流的元素,其他盒子与其他盒子内的文本都会无视它。(可以说是完全无视)


(1)浮动

Float:浮动元素会判断上一个元素是否浮动,如果浮动,紧贴。
     否则,与上一个元素保持垂直

浮动的表现
    定义:浮动的框可以向左或向右移动,直到它的外边缘碰到包含框或另一个浮动框的边框为止。由于浮动框不在文档的普通流中,所以文档的普通流中的浮动框之后的块框表现得就像浮动框不存在一样。(注意这里是块框而不是内联元素;浮动框只对它后面的元素造成影响)
注意 当初float被设计的时候就是用来完成文本环绕的效果,所以文本不会被挡住,这是float的特性,即float是一种不彻底的脱离文档流方式。无论多么复杂的布局,其基本出发点均是:“如何在一行显示多个div元素”。








 

 



float覆盖时会覆盖div内容,但是不会覆盖文字标签


(2)去除浮动
去除浮动实例:

 

 


12.4.8 position(定位)

Position:relative
 1 参照物是元素之前文档流中的位置
 2 元素不是脱离文档流(之前的空间位置依然存在)

Position: absolute
 1 参照物是父类的定位属性,如果没有,找body的位置进行定位
 2 元素脱离了文档流(之前的空间位置不存在了)

Position:fixed
参照物是显示屏
 


(1)static
static 默认值,无定位,不能当作绝对定位的参照物,并且设置标签对象的left、top等值是不起作用的。

(2)position:relative 相对定位
relative相对定位。相对定位是相对于该元素在文档流中的原始位置,即以自己原始位置为参照物。有趣的是,即使设定了元素的相对定位以及偏移值,元素还占有着原来的位置,即占据文档流空间。对象遵循正常文档流,但将依据top,right,bottom,left等属性在正常文档流中偏移位置。而其层叠通过z-index属性定义。
注意:position:relative的一个主要用法:方便绝对定位元素找到参照物。

(3)position:absolute 绝对定位
定义:设置为绝对定位的元素框从文档流完全删除,并相对于最近的已定位祖先元素定位,如果元素没有已定位的祖先元素,那么它的位置相对于最初的包含块(即body元素)。元素原先在正常文档流中所占的空间会关闭,就好像该元素原来不存在一样。元素定位后生成一个块级框,而不论原来它在正常流中生成何种类型的框。
重点:如果父级设置了position属性,例如position:relative;,那么子元素就会以父级的左上角为原始点进行定位。这样能很好的解决自适应网站的标签偏离问题,即父级为自适应的,那我子元素就设置position:absolute;父元素设置position:relative;,然后Top、Right、Bottom、Left用百分比宽度表示。
另外,对象脱离正常文档流,使用top,right,bottom,left等属性进行绝对定位。而其层叠通过z-index属性定义。
总结:参照物用相对定位,子元素用绝对定位,并且保证相对定位参照物不会偏移即可。

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <style>
            *{
                margin:0;
            }
            .item1{
                width: 200px;
                height: 200px;
                background-color: green;
            }
            .item2{
                width: 200px;
                height: 200px;
                background: red;
                position: absolute;
                top:200px;
                left: 200px;
            }
            .item3{
                width:200px;
                height: 200px;
                background: gold;
                
            }
            .outer{
                border:1px solid black;
                position: relative;
            }
        </style>
    </head>
    <body>
        <div class="item1" ></div>
        <div class="outer">
            <div class="item2"></div>
            <div class="item3"></div>
        </div>
    </body>
</html>
输出结果为:
 


(4)position:fixed
 	fixed:对象脱离正常文档流,使用top,right,bottom,left等属性以窗口(屏幕)为参考点进行定位,当出现滚动条时,对象不会随着滚动。而其层叠通过z-index属性 定义。 注意点: 一个元素若设置了 position:absolute | fixed; 则该元素就不能设置float。这 是一个常识性的知识点,因为这是两个不同的流,一个是浮动流,另一个是“定位流”。但是 relative 却可以。因为它原本所占的空间仍然占据文档流。
    在理论上,被设置为fixed的元素会被定位于浏览器窗口的一个指定坐标,不论窗口是否滚动,它都会固定在这个位置。
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <style>
            .return_top{
                width:80px;
                height: 80px;
                background-color: yellow;
                color: black;
                text-align: center;
                line-height: 80px;
                position: fixed;
                bottom: 20px;
                right: 10px;
            }
        </style>
    </head>
    <body>
        <div class="return_top">
            return
        </div>
    </body>
</html>
输出结果为:
 



(5)仅使用margin属性布局绝对定位元素
此情况,margin-bottom 和margin-right的值不再对文档流中的元素产生影响,因为该元素已经脱离了文档流。另外,不管它的祖先元素有没有定位,都是以文档流中原来所在的位置上偏移参照物。  
  图9中,使用margin属性布局相对定位元素。
  层级关系为:
  <div——————————— position:relative;
  <div—————————-没有设置为定位元素,不是参照物
  <div———————-没有设置为定位元素,不是参照物
  <div box1
  <div box2 ——–position:absolute; margin-top:50px; margin-left:120px;
  <div box3
  效果图:
 


二十二 JavaScripts

1.1 JavaScript概述
1.1.1 JavaScript的历史

1992年Nombas开发出C-minus-minus(C--)的嵌入式脚本语言(最初绑定在CEnvi软件中).后将其改名ScriptEase.(客户端执行的语言)
Netscape(网景)接收Nombas的理念,(Brendan Eich)在其Netscape Navigator 2.0产品中开发出一套livescript的脚本语言.Sun和Netscape共同完成.后改名叫Javascript
微软随后模仿在其IE3.0的产品中搭载了一个JavaScript的克隆版叫Jscript.
为了统一三家,ECMA(欧洲计算机制造协会)定义了ECMA-262规范.国际标准化组织及国际电工委员会(ISO/IEC)也采纳 ECMAScript 作为标准(ISO/IEC-16262)。从此,Web 浏览器就开始努力(虽然有着不同的程度的成功和失败)将 ECMAScript 作为 JavaScript 实现的基础。EcmaScript是规范.


1.1.2 ECMAScript 

尽管 ECMAScript 是一个重要的标准,但它并不是 JavaScript 唯一的部分,当然,也不是唯一被标准化的部分。实际上,一个完整的 JavaScript 实现是由以下 3 个不同部分组成的:
核心(ECMAScript) 
文档对象模型(DOM) Document object model (整合js,css,html)
浏览器对象模型(BOM) Broswer object model(整合js和浏览器)
Javascript 在开发中绝大多数情况是基于对象的.也是面向对象的. 

 

简单地说,ECMAScript 描述了以下内容:
语法 
类型 
语句 
关键字 
保留字 
运算符 
对象 (封装 继承 多态) 基于对象的语言.使用对象.

1.1.3 JavaScript的引入方式

(1) 直接编写
    <script>
        alert(\'hello yuan\')
</script>

(2) 导入文件
    <script src="hello.js"></script> 



1.2 JavaScript的基础
1.2.1 变量

在 JavaScript 中,变量:
0 变量是弱类型的(很随便);
1 声明变量时不用声明变量类型. 全都使用var关键字;
var a;
2 一行可以声明多个变量.并且可以是不同类型.
var name="yuan", age=20, job="lecturer";
3  (了解) 声明变量时 可以不用var. 如果不用var 那么它是全局变量.
4 变量命名,首字符只能是字母,下划线,$美元符 三选一,且区分大小写,x与X是两个变量
5 变量还应遵守以下某条著名的命名规则:
Camel 标记法
首字母是小写的,接下来的字母都以大写字符开头。例如:
var myTestValue = 0, mySecondValue = "hi";
Pascal 标记法
首字母是大写的,接下来的字母都以大写字符开头。例如:
Var MyTestValue = 0, MySecondValue = "hi";
匈牙利类型标记法
在以 Pascal 标记法命名的变量前附加一个小写字母(或小写字母序列),说明该变量的类型。例如,i 表示整数,s 表示字符串,如下所示“
Var iMyTestValue = 0, sMySecondValue = "hi";

    function func1(){
        var a = 123;
        b=456
    }
    func1();
//    alert(a);
//    alert(b);
// 不推荐




1.2.2 基础规范

1 每行结束可以不加分号. 没有分号会以换行符作为每行的结束
a=1;b=2;
a=1 b=2;------错误
a=1
b=2
//推荐
a=1;
b=2;
{
 a=1;
 b=2;
    //推荐加tab
    a=1;
    b=2;
}

2 注释 支持多行注释和单行注释. /* */  //

3 使用{}来封装代码块


1.2.3 常量和标识符
常量 :直接在程序中出现的数据值

标识符:

由不以数字开头的字母、数字、下划线(_)、美元符号($)组成
常用于表示函数、变量等的名称
例如:_abc,$abc,abc,abc123是标识符,而1abc不是
JavaScript语言中代表特定含义的词称为保留字,不允许程序再定义为标识符

 


 


1.2.4 数据类型
 

 

(1)数字类型(Number)
简介
最基本的数据类型
不区分整型数值和浮点型数值
所有数字都采用64位浮点格式存储,相当于Java和C语言中的double格式
能表示的最大值是±1.7976931348623157 x 10308 
能表示的最小值是±5 x 10 -324 

  整数:
           在JavaScript中10进制的整数由数字的序列组成
           精确表达的范围是
-9007199254740992 (-253) 到 9007199254740992 (253)
           超出范围的整数,精确度将受影响
  浮点数:
           使用小数点记录数据
           例如:3.4,5.6
           使用指数记录数据
           例如:4.3e23 = 4.3 x 1023
  16进制和8进制数的表达
           16进制数据前面加上0x,八进制前面加0
           16进制数是由0-9,A-F等16个字符组成
           8进制数由0-7等8个数字组成
           16进制和8进制与2进制的换算

# 2进制: 1111 0011 1101 0100   <-----> 16进制:0xF3D4 <-----> 10进制:62420
# 2进制: 1 111 001 111 010 100 <-----> 8进制:0171724



(2)字符串(String)
简介
是由Unicode字符、数字、标点符号组成的序列
字符串常量首尾由单引号或双引号括起
JavaScript中没有字符类型
常用特殊字符在字符串中的表达
字符串中部分特殊字符必须加上右划线\
常用的转义字符 \n:换行  \\':单引号   \":双引号  \\:右划线

String数据类型的使用:
特殊字符的使用方法和效果
Unicode的插入方法

<script>
        var str="\u4f60\u597d\n欢迎来到\"JavaScript世界\"";
        alert(str);
</script>


(3)布尔型(Boolean)
简介
Boolean类型仅有两个值:true和false,也代表1和0,实际运算中true=1,false=0
布尔值也可以看作on/off、yes/no、1/0对应true/false
Boolean值主要用于JavaScript的控制语句,例如
    if (x==1){
    y=y+1;
}
else  {
    y=y-1;
    }
 

(4)Null & Undefined
Undefined 类型
    Undefined 类型只有一个值,即 undefined。当声明的变量未初始化时,该变量的默认值是 undefined。
当函数无明确返回值时,返回的也是值 "undefined";

Null 类型
    另一种只有一个值的类型是 Null,它只有一个专用值 null,即它的字面量。值 undefined 实际上是从值 null 派生来的,因此 ECMAScript 把它们定义为相等的。

尽管这两个值相等,但它们的含义不同。undefined 是声明了变量但未对其初始化时赋予该变量的值,null 则用于表示尚未存在的对象(在讨论 typeof 运算符时,简单地介绍过这一点)。如果函数或方法要返回的是对象,那么找不到该对象时,返回的通常是 null。

var person=new Person()
var person=null


(5)数据类型转换
JavaScript属于松散类型的程序语言
变量在声明的时候并不需要指定数据类型
变量只有在赋值的时候才会确定数据类型
表达式中包含不同类型数据则在计算过程中会强制进行类别转换

数字 + 字符串:数字转换为字符串
数字 + 布尔值:true转换为1,false转换为0
字符串 + 布尔值:布尔值转换为字符串true或false


(6)强制类型转换函数
函数parseInt:   强制转换成整数   
例如
parseInt("6.12")=6  ; parseInt(“12a")=12 ; parseInt(“a12")=NaN  ;parseInt(“1a2")=1

函数parseFloat: 强制转换成浮点数  
parseFloat("6.12")=6.12

函数eval:       将字符串强制转换为表达式并返回结果 
eval("1+1")=2 ; eval("1<2")=true



(7)类型查询函数(typeof)
 

ECMAScript 提供了 typeof 运算符来判断一个值是否在某种类型的范围内。可以用这种运算符判断一个值是否表示一种原始类型:如果它是原始类型,还可以判断它表示哪种原始类型。

函数typeof :查询数值当前类型
 (string / number / boolean / object )
例如typeof("test"+3)      "string"
例如typeof(null)          "object "
例如typeof(true+1)        "number"
例如typeof(true-false)    "number"

 
1.3 ECMAScript运算符

1.3.1算数运算符

加(+)、 减(-)、 乘(*) 、除(/) 、余数(% )  加、减、乘、除、余数和数学中的运算方法一样  例如:9/2=4.5,4*5=20,9%2=1
-除了可以表示减号还可以表示负号  例如:x=-y
+除了可以表示加法运算还可以用于字符串的连接  例如:"abc"+"def"="abcdef"


递增(++) 、递减(--):
假如x=2,那么x++表达式执行后的值为3,x--表达式执行后的值为1
i++相当于i=i+1,i--相当于i=i-1
递增和递减运算符可以放在变量前也可以放在变量后:--i
var i=1;
console.log(i++);
console.log(++i);
console.log(i--);
console.log(--i);

 


一元加减法:
var a=1;
    var b=1;
    a=-a;  //a=-1

    var c="10";
    alert(typeof (c));
    c=+c;    //类型转换
    alert(typeof (c));
//    -------------------
    var d="yuan";
    d=+d;
    alert(d);//NaN:属于Number类型的一个特殊值,当遇到将字符串转成数字无效时,就会得到一个NaN数据
    alert(typeof(d));//Number

    //NaN特点:
    
    var n=NaN;
    
    alert(n>3);
    alert(n<3);
    alert(n==3);
    alert(n==NaN);
    
    alert(n!=NaN);//NaN参与的所有的运算都是false,除了!=



1.3.2逻辑运算符

等于 ( == )  、不等于( != ) 、 大于( > ) 、 小于( < ) 
大于等于(>=) 、小于等于(<=)
与 (&&) 、或(||) 、非(!)
1 && 1 = 1  1 || 1 = 1
1 && 0 = 0  1 || 0 = 1
0 && 0 = 0  0 || 0 = 0
!0=1
!1=0


逻辑 AND 运算符(&&)
逻辑 AND 运算的运算数可以是任何类型的,不止是 Boolean 值。
如果某个运算数不是原始的 Boolean 型值,逻辑 AND 运算并不一定返回 Boolean 值:
如果某个运算数是 null,返回 null。 
如果某个运算数是 NaN,返回 NaN。 
如果某个运算数是 undefined,返回undefined。


逻辑 OR 运算符(||)
与逻辑 AND 运算符相似,如果某个运算数不是 Boolean 值,逻辑 OR 运算并不一定返回 Boolean 值


1.3.3 赋值运算符
赋值 = 
JavaScript中=代表赋值,两个等号==表示判断是否相等
例如,x=1表示给x赋值为1
if (x==1){...}程序表示当x与1相等时
if(x==“on”){…}程序表示当x与“on”相等时
 配合其他运算符形成的简化表达式
例如i+=1相当于i=i+1,x&=y相当于x=x&y

实例:
2 == “2”        
2 === “2”        
4 != “4”            
4 !== “4”        

var a = 2; var b = 4;
var c = a<b | --b>--a;
var c = a<b || --b>--a; 
var c = a<b &&--b>--a;
var c = a<b & --b>--a; 


1.3.4 等性运算符

执行类型转换的规则如下:

如果一个运算数是 Boolean 值,在检查相等性之前,把它转换成数字值。false 转换成 0,true 为 1。 
如果一个运算数是字符串,另一个是数字,在检查相等性之前,要尝试把字符串转换成数字。 
如果一个运算数是对象,另一个是字符串,在检查相等性之前,要尝试把对象转换成字符串。 
如果一个运算数是对象,另一个是数字,在检查相等性之前,要尝试把对象转换成数字。 
在比较时,该运算符还遵守下列规则:

值 null 和 undefined 相等。 
在检查相等性时,不能把 null 和 undefined 转换成其他值。 
如果某个运算数是 NaN,等号将返回 false,非等号将返回 true。 
如果两个运算数都是对象,那么比较的是它们的引用值。如果两个运算数指向同一对象,那么等号返回 true,否则两个运算数不等。 

 


1.3.5 关系运算符(重要)

var bResult = "Blue" < "alpha";
alert(bResult); 
//输出 true 
在上面的例子中,字符串 "Blue" 小于 "alpha",因为字母 B 的字符代码是 66,字母 a 的字符代码是 97。

比较数字和字符串:
另一种棘手的状况发生在比较两个字符串形式的数字时,比如:
var bResult = "25" < "3";
alert(bResult); //输出 "true"

上面这段代码比较的是字符串 "25" 和 "3"。两个运算数都是字符串,所以比较的是它们的字符代码("2" 的字符代码是 50,"3" 的字符代码是 51)。
不过,如果把某个运算数该为数字,那么结果就有趣了:
var bResult = "25" < 3;
alert(bResult); 
//输出 "false"


这里,字符串 "25" 将被转换成数字 25,然后与数字 3 进行比较,结果不出所料。
总结:
比较运算符两侧如果一个是数字类型,一个是其他类型,会将其类型转换成数字类型.
比较运算符两侧如果都是字符串类型,比较的是最高位的asc码,如果最高位相等,继续取第二位比较.


1.3.6 Boolean运算符(重要)

var temp=new Object();// false;[];0; null; undefined;object(new Object();)

    if(temp){
        console.log("yuan")
    }else {
        console.log("alex")
    }


1.3.7 全等号和非全等号
等号和非等号的同类运算符是全等号和非全等号。这两个运算符所做的与等号和非等号相同,只是它们在检查相等性前,不执行类型转换。


1.4 控制语句

1.4.1 if 控制语句
if-else基本格式
if (表达式){
语句1;
......
}else{
语句2;
.....
}
功能说明
如果表达式的值为true则执行语句1,
否则执行语句2
 



var x= (new Date()).getDay();
//获取今天的星期值,0为星期天
var y;
if ( (x==6) || (x==0) ) {
y="周末";
}else{
y="工作日";
}
alert(y);
//等价于
y="工作日";
if ( (x==6) || (x==0) ) {
y="周末";
}


if语句嵌套格式
if (表达式1) {
    语句1;
}else if (表达式2){
    语句2;
}else if (表达式3){
    语句3;
} else{
    语句4;
}



1.4.2 swith 选择控制语句
switch基本格式
switch (表达式) {
    case 值1:语句1;break;
    case 值2:语句2;break;
    case 值3:语句3;break;
    default:语句4;
}


实例:
switch(x){
case 1:y="星期一";    break;
case 2:y="星期二";    break;
case 3:y="星期三";    break;
case 4:y="星期四";    break;
case 5:y="星期五";    break;
case 6:y="星期六";    break;
case 7:y="星期日";    break;
default: y="未定义";
}

switch比else if结构更加简洁清晰,使程序可读性更强,效率更高。

首先要看一个问题,if 语句适用范围比较广,只要是 boolean 表达式都可以用 if 判断;而 switch 只能对基本类型进行数值比较。两者的可比性就仅限在两个基本类型比较的范围内。
说到基本类型的数值比较,那当然要有两个数。然后重点来了——
if 语句每一句都是独立的,看下面的语句:
if (a == 1) ...
else if (a == 2) ...
这样 a 要被读入寄存器两次,1 和 2 分别被读入寄存器一次。于是你是否发现其实 a 读两次是有点多余的,在你全部比较完之前只需要一次读入寄存器就行了,其余都是额外开销。但是 if 语句必须每次都把里面的两个数从内存拿出来读到寄存器,它不知道你其实比较的是同一个 a。
于是 switch case 就出来了,把上面的改成 switch case 版本:
switch (a) {
        case 0:
                break;
        case 1:
}            
总结:
1.switch用来根据一个整型值进行多路分支,并且编译器可以对多路分支进行优化
2.switch-case只将表达式计算一次,然后将表达式的值与每个case的值比较,进而选
  择执行哪一个case的语句块
3.if..else 的判断条件范围较广,每条语句基本上独立的,每次判断时都要条件加载
  一次。
所以在多路分支时用switch比if..else if .. else结构要效率高。


1.4.3 for循环控制语句
for循环基本格式
for (初始化;条件;增量){
    语句1;
    ...
}
功能说明
实现条件循环,当条件成立时,执行语句1,否则跳出循环体
 


for (var i=1;i<=7;i++){
    document.write("<H"+i+">hello</H "+i+"> ");
    document.write("<br>");
}
----------------------------------------------
    var arr=[1,"hello",true]//var dic={"1":"111"}
    for (var i in arr){
        console.log(i)
        console.log(arr[i])
    }


1.4.4 while循环控制语句

while循环基本格式
while (条件){
语句1;
...
}
功能说明
运行功能和for类似,当条件成立循环执行语句花括号{}内的语句,否则跳出循环

var i=1;
while (i<=7) {
    document.write("<H"+i+">hello</H "+i+"> ");
    document.write("<br>");
    i++;
}
//循环输出H1到H7的字体大小

<script language="JavaScript">
/* sayhello是定义的函数名,前面必须加上function和空格*/
function sayHello(){
    var hellostr;
    var myname=prompt("请问您贵姓?","苑"); 
    hellostr="您好,"+myname+\'先生,欢迎进入"探索之旅"!\';
    alert(hellostr); 
    document.write(hellostr);
}
 //这里是对前面定义的函数进行调用
sayHello();
</script>


1.4.5 异常处理

try {
    //这段代码从上往下运行,其中任何一个语句抛出异常该代码块就结束运行
}
catch (e) {
    // 如果try代码块中抛出了异常,catch代码块中的代码就会被执行。
    //e是一个局部变量,用来指向Error对象或者其他抛出的对象
}
finally {
     //无论try中代码是否有异常抛出(甚至是try代码块中有return语句),finally代码块中始终会被执行。
}

注:主动抛出异常 throw Error(\'xxxx\') 


1.5 ECMA对象

简介:
在JavaScript中除了null和undefined以外其他的数据类型都被定义成了对象,也可以用创建对象的方法定义变量,String、Math、Array、Date、RegExp都是JavaScript中重要的内置对象,在JavaScript程序大多数功能都是基于对象实现的。
<script language="javascript">
var aa=Number.MAX_VALUE; 
//利用数字对象获取可表示最大数
var bb=new String("hello JavaScript"); 
//创建字符串对象
var cc=new Date();
//创建日期对象
var dd=new Array("星期一","星期二","星期三","星期四"); 
//数组对象
</script>

 


1.5.1 String对象

(1)字符串对象创建
字符串创建(两种方式)
       ① 变量 = “字符串”
       ② 字串对象名称 = new String (字符串)
var str1="hello world";
var str1= new String("hello word");


(2)字符串对象的属性和函数

1.5.1.1获取字符串的长度

x.length

1.5.1.2转为小写

x.toLowerCase()

1.5.1.3转为大写

x.toUpperCase()  

1.5.1.4去除字符串两边空格 
    
x.trim()

1.5.1.5获取指定位置字符(字符串查询方法)
获取指定位置字符串,其中index为要获取的字符索引
x.charAt(index) 

1.5.1.6 查询字符串位置

x.indexOf(findstr,index)
x.lastIndexOf(findstr) 

1.5.1.7 match匹配
match返回匹配字符串的数组,如果没有匹配则返回null
x.match(regexp)

search返回匹配字符串的首字符位置索引
x.search(regexp)

var str1="welcome to the world of JS!";
var str2=str1.match("world");
var str3=str1.search("world");
alert(str2[0]);  // 结果为"world"
alert(str3);     // 结果为15


子字符串处理方法

start表示开始位置,length表示截取长度
x.substr(start, length)

start表示开始位置,end是结束位置
x.substring(start, end)

切片操作字符串
x.slice(start, end)

var str1="abcdefgh";
var str2=str1.slice(2,4);
var str3=str1.slice(4);
var str4=str1.slice(2,-1);
var str5=str1.slice(-3,-1);
alert(str2); //结果为"cd"
alert(str3); //结果为"efgh"
alert(str4); //结果为"cdefg"
alert(str5); //结果为"fg"

字符串替换
x.replace(findstr,tostr)

分割字符串
x.split();   

var str1="一,二,三,四,五,六,日"; 
var strArray=str1.split(",");
alert(strArray[1]);//结果为"二"

拼接字符串
x.concat(addstr)



1.5.2 Array对象
(1)数组创建
创建数组的三种方式
创建方式1:
var arrname = [元素0,元素1,….];          // var arr=[1,2,3];

创建方式2:
var arrname = new Array(元素0,元素1,….); // var test=new Array(100,"a",true);

创建方式3:
var arrname = new Array(长度); 
            //  初始化数组对象:
                var cnweek=new Array(7);
                    cnweek[0]="星期日";
                    cnweek[1]="星期一";
                    ...
                    cnweek[6]="星期六";


创建二维数组:
var cnweek=new Array(7);
for (var i=0;i<=6;i++){
    cnweek[i]=new Array(2);
}
cnweek[0][0]="星期日";
cnweek[0][1]="Sunday";
cnweek[1][0]="星期一";
cnweek[1][1]="Monday";
...
cnweek[6][0]="星期六";
cnweek[6][1]="Saturday";


(2)数组对象的属性和方法

join方法
x.join(bystr)       ----将数组元素拼接成字符串
var arr1=[1, 2, 3, 4, 5, 6, 7];
var str1=arr1.join("-");
alert(str1);  //结果为"1-2-3-4-5-6-7" 

concat方法
x.concat(value,...)    ---- 添加数组
                   var a = [1,2,3];
                  var b=a.concat(4,5) ;
                  alert(a.toString());  //返回结果为1,2,3            
                  alert(b.toString());  //返回结果为1,2,3,4,5


数组排序-reverse sort

//x.reverse()
//x.sort()

var arr1=[32, 12, 111, 444];
//var arr1=["a","d","f","c"];

arr1.reverse(); //颠倒数组元素
alert(arr1.toString());
//结果为444,111,12,32

arr1.sort();    //排序数组元素
alert(arr1.toString());
//结果为111,12,32,444

//------------------------------
arr=[1,5,2,100];

//arr.sort();
//alert(arr);
//如果就想按着数字比较呢?

function intSort(a,b){
    if (a>b){
        return 1;//-1
    }
    else if(a<b){
        return -1;//1
    }
    else {
        return 0
    }
}
arr.sort(intSort);
alert(arr);
function IntSort(a,b){
    return a-b;
}


数组切片操作
//x.slice(start, end)
//
//使用注解
//
//x代表数组对象
//start表示开始位置索引
//end是结束位置下一数组元素索引编号
//第一个数组元素索引为0
//start、end可为负数,-1代表最后一个数组元素
//end省略则相当于从start位置截取以后所有数组元素

var arr1=[\'a\',\'b\',\'c\',\'d\',\'e\',\'f\',\'g\',\'h\'];
var arr2=arr1.slice(2,4);
var arr3=arr1.slice(4);
var arr4=arr1.slice(2,-1);

alert(arr2.toString());
//结果为"c,d" 
alert(arr3.toString());
//结果为"e,f,g,h"
alert(arr4.toString());
//结果为"c,d,e,f,g"


删除子数组:
//x. splice(start, deleteCount, value, ...)
//使用注解
//x代表数组对象
//splice的主要用途是对数组指定位置进行删除和插入
//start表示开始位置索引
//deleteCount删除数组元素的个数
//value表示在删除位置插入的数组元素
//value参数可以省略           
var a = [1,2,3,4,5,6,7,8];
a.splice(1,2);
alert(a.toString());//a变为 [1,4,5,6,7,8]
a.splice(1,1);
alert(a.toString());//a变为[1,5,6,7,8]
a.splice(1,0,2,3);
alert(a.toString());//a变为[1,2,3,5,6,7,8]


数组的push和pop
//push pop这两个方法模拟的是一个栈操作
//x.push(value, ...)  压栈
//x.pop()          弹栈      
//使用注解
//x代表数组对象
//value可以为字符串、数字、数组等任何值
//push是将value值添加到数组x的结尾
//pop是将数组x的最后一个元素删除
var arr1=[1,2,3];
arr1.push(4,5);
alert(arr1);//结果为"1,2,3,4,5"
arr1.push([6,7]);
alert(arr1)//结果为"1,2,3,4,5,6,7"
arr1.pop();
alert(arr1);//结果为"1,2,3,4,5"


数组的shift和unshift:
//x.unshift(value,...)
//x.shift()
//使用注解
//x代表数组对象
//value可以为字符串、数字、数组等任何值
//unshift是将value值插入到数组x的开始
//shift是将数组x的第一个元素删除
var arr1=[1,2,3];
arr1.unshift(4,5);
alert(arr1);  //结果为"4,5,1,2,3"
arr1. unshift([6,7]);
alert(arr1);  //结果为"6,7,4,5,1,2,3"
arr1.shift();
alert(arr1);  //结果为"4,5,1,2,3"


总结js的数组特性:
 
//java中数组的特性,  规定是什么类型的数组,就只能装什么类型.只有一种类型.
//js中的数组特性1: js中的数组可以装任意类型,没有任何限制.
//js中的数组特性2: js中的数组,长度是随着下标变化的.用到多长就有多长.
var arr5 = [\'abc\',123,1.14,true,null,undefined,new String(\'1213\'),new Function(\'a\',\'b\',\'alert(a+b)\')];
/*  alert(arr5.length);//8
arr5[10] = "hahaha";
alert(arr5.length); //11
alert(arr5[9]);// undefined */


1.5.3 Date对象

(1)创建Date对象
//方法1:不指定参数
var nowd1=new Date();
alert(nowd1.toLocaleString( ));

//方法2:参数为日期字符串
var nowd2=new Date("2004/3/20 11:12");
alert(nowd2.toLocaleString( ));
var nowd3=new Date("04/03/20 11:12");
alert(nowd3.toLocaleString( ));

//方法3:参数为毫秒数
var nowd3=new Date(5000);
alert(nowd3.toLocaleString( ));
alert(nowd3.toUTCString());

//方法4:参数为年月日小时分钟秒毫秒
var nowd4=new Date(2004,2,20,11,12,0,300);
alert(nowd4.toLocaleString( ));//毫秒并不直接显示


(2)Date对象的方法-获取日期和时间
获取日期和时间
getDate()                 获取日
getDay ()                 获取星期
getMonth ()               获取月(0-11)
getFullYear ()            获取完整年份
getYear ()                获取年
getHours ()               获取小时
getMinutes ()             获取分钟
getSeconds ()             获取秒
getMilliseconds ()        获取毫秒
getTime ()                返回累计毫秒数(从1970/1/1午夜)


实例练习:
function getCurrentDate(){
        //1. 创建Date对象
        var date = new Date(); //没有填入任何参数那么就是当前时间
        //2. 获得当前年份
        var year = date.getFullYear();
        //3. 获得当前月份 js中月份是从0到11.
        var month = date.getMonth()+1;
        //4. 获得当前日
        var day = date.getDate();
        //5. 获得当前小时
        var hour = date.getHours();
        //6. 获得当前分钟
        var min = date.getMinutes();
        //7. 获得当前秒
        var sec = date.getSeconds();
        //8. 获得当前星期
        var week = date.getDay(); //没有getWeek
        // 2014年06月18日 15:40:30 星期三
        return year+"年"+changeNum(month)+"月"+day+"日 "+hour+":"+min+":"+sec+" "+parseWeek(week);
    }

alert(getCurrentDate());

//解决 自动补齐成两位数字的方法
    function changeNum(num){
    if(num < 10){
        return "0"+num;
    }else{
        return num;
    }

}
//将数字 0~6 转换成 星期日到星期六
    function parseWeek(week){
    var arr = ["星期日","星期一","星期二","星期三","星期四","星期五","星期六"];
    //             0      1      2      3 .............
    return arr[week];
}


(3)Date对象的方法-设置日期和时间
//设置日期和时间
//setDate(day_of_month)       设置日
//setMonth (month)                 设置月
//setFullYear (year)               设置年
//setHours (hour)         设置小时
//setMinutes (minute)     设置分钟
//setSeconds (second)     设置秒
//setMillliseconds (ms)       设置毫秒(0-999)
//setTime (allms)     设置累计毫秒(从1970/1/1午夜)
    
var x=new Date();
x.setFullYear (1997);    //设置年1997
x.setMonth(7);        //设置月7
x.setDate(1);        //设置日1
x.setHours(5);        //设置小时5
x.setMinutes(12);    //设置分钟12
x.setSeconds(54);    //设置秒54
x.setMilliseconds(230);        //设置毫秒230
document.write(x.toLocaleString( )+"<br>");
//返回1997年8月1日5点12分54秒

x.setTime(870409430000); //设置累计毫秒数
document.write(x.toLocaleString( )+"<br>");
//返回1997年8月1日12点23分50秒


(4)Date对象的方法—日期和时间的转换

日期和时间的转换:
getTimezoneOffset():8个时区×15度×4分/度=480;
返回本地时间与GMT的时间差,以分钟为单位
toUTCString()
返回国际标准时间字符串
toLocalString()
返回本地格式时间字符串
Date.parse(x)
返回累计毫秒数(从1970/1/1午夜到本地时间)
Date.UTC(x)
返回累计毫秒数(从1970/1/1午夜到国际时间)

1.5.4 Math对象
//该对象中的属性方法 和数学有关.
abs(x)    返回数的绝对值。
exp(x)    返回 e 的指数。
floor(x)对数进行下舍入。
log(x)    返回数的自然对数(底为e)。
max(x,y)    返回 x 和 y 中的最高值。
min(x,y)    返回 x 和 y 中的最低值。
pow(x,y)    返回 x 的 y 次幂。
random()    返回 0 ~ 1 之间的随机数。
round(x)    把数四舍五入为最接近的整数。
sin(x)    返回数的正弦。
sqrt(x)    返回数的平方根。
tan(x)    返回角的正切。
//方法练习:
        //alert(Math.random()); // 获得随机数 0~1 不包括1.
        //alert(Math.round(1.5)); // 四舍五入
        //练习:获取1-100的随机整数,包括1和100
             //var num=Math.random();
             //num=num*10;
             //num=Math.round(num);
             //alert(num)
        //============max  min=========================
        /* alert(Math.max(1,2));// 2
        alert(Math.min(1,2));// 1 */
        //-------------pow--------------------------------
        alert(Math.pow(2,4));// pow 计算参数1 的参数2 次方.

 

1.6 Function对象(重点)
1.6.1 函数定义

function 函数名 (参数){
<br>    函数体;
    return 返回值;
}
功能说明:
可以使用变量、常量或表达式作为函数调用的参数
函数由关键字function定义
函数名的定义规则与标识符一致,大小写是敏感的
返回值必须使用return
Function 类可以表示开发者定义的任何函数。

用 Function 类直接创建函数的语法如下:
var 函数名 = new Function("参数1","参数n","function_body");
虽然由于字符串的关系,第二种形式写起来有些困难,但有助于理解函数只不过是一种引用类型,它们的行为与用 Function 类明确创建的函数行为是相同的。

function func1(name){
    alert(\'hello\'+name);
    return 8
}
ret=func1("yuan");
 alert(ret);
var func2=new Function("name","alert(\"hello\"+name);")
func2("egon")

 



注意:js的函数加载执行与python不同,它是整体加载完才会执行,所以执行函数放在函数声明上面或下面都可以
<script>
    //f(); --->OK
    function f(){
        console.log("hello")
    }
    f() //----->OK
</script>

 

1.6.2 Function对象的属性
如前所述,函数属于引用类型,所以它们也有属性和方法。
比如,ECMAScript 定义的属性 length 声明了函数期望的参数个数。
alert(func1.length)


1.6.3 Function的调用
//只要函数名写对即可,参数怎么填都不报错.
function func1(a,b){

    alert(a+b);
}
    func1(1,2);  //3
    func1(1,2,3);//3
    func1(1);    //NaN
    func1();     //NaN


function a(a,b){
    alert(a+b);
}
var a=1;
var b=2;
a(a,b)
//报错:a is not function

1.6.4 函数的内置对象arguments


function add(a,b){
        console.log(a+b);//3
        console.log(arguments.length);//2
        console.log(arguments);//[1,2]
    }
add(1,2)

function nxAdd(){
        var result=0;
        for (var num in arguments){
            result+=arguments[num]
        }
        alert(result)
   }
nxAdd(1,2,3,4,5)

function f(a,b,c){
        if (arguments.length!=3){
            throw new Error("function f called with "+arguments.length+" arguments,but it just need 3 arguments")
        }
        else {
            alert("success!")
        }
    }
f(1,2,3,4,5)

不定长参数:
 


1.6.5 匿名函数

(1)匿名函数
    var func = function(arg){
        return "tony";
    }

(2)匿名函数的应用
    (function(){
        alert("tony");
    } )()

    (function(arg){
        console.log(arg);
    })(\'123\')



http://www.cnblogs.com/yuanchenqi/articles/5980312.html





 














二十三 MYSQL知识点
1.1 windows安装
1.1.1 mysqld(服务端启动不了的解决方案)
E:\Python\study\tools\Mysql\mysql-5.7.18-winx64\bin\mysqld --skip-grant-tables(必须使用mysql的绝对路径)
 

1.1.2 压缩包mysql的使用
放置任意目录

(1)初始化服务端  
服务端:E:\wupeiqi\mysql-5.7.16-winx64\mysql-5.7.16-winx64\bin\mysqld --initialize-insecure
启动服务端:直接E:\wupeiqi\mysql-5.7.16-winx64\mysql-5.7.16-winx64\bin\mysqld

(2)客户端登录   
E:\wupeiqi\mysql-5.7.16-winx64\mysql-5.7.16-winx64\bin\mysql –u root –p
初始密码为空

(3)为了方便起见:可以把mysql的安装路径添加到环境变量中

(4)windows服务:
	E:\wupeiqi\mysql-5.7.16-winx64\mysql-5.7.16-winx64\bin\mysqld --install
	net start MySQL
				
	E:\wupeiqi\mysql-5.7.16-winx64\mysql-5.7.16-winx64\bin\mysqld --remove
				
	net start MySQL
	net stop MySQL
	
1.2 mysql的使用
1.2.1 连接数据库后的
(1)创建用户:
	Create user “alxe” @”192.168.1.%” identified by \'123123\'
(2)授权	
	权限    人
	grant select insert update on 数据库.表 to ‘alxe’@’%’
	grant all privileges  on db1.t1 to \'alex\'@\'%\';
		All privileges(所有授权,除了授权的权限)
revoke all privileges on db1.t1 from \'alex\'@\'%\';
	revoke(解除授权)
(3)DBA管理员  管理用户授权

1.2.2 SQL语句规则(增删改查)
1.连接服务端后查看的指令:
(1)显示数据库
Show databases;
(2)显示表
Show tables
(3)进入数据库
use db1

2.操作文件夹(数据库):
(1)创建数据库
Create database db1;
Create database db1 default charset utf8;
(2)删除数据库
	Drop database db1;

3.表的操作
(1)创建表
Create table t1(id int, name char(10));
Create table t1(id int ,name char(10)) default charset=utf8;(指定数控行使用utf8编码写入)
Create table t1(id int auto_increment primary key, name char(10)) engine=innodb default charset=utf8;
	Auto_increment (表示该数据行能自增,一旦设置该数据行必须作为此表的主键)
	Engine=innodb   innodb 支持事物,原子性操作(断电转账不会有问题)
		  Mysiam 

后期添加主键的方法:alter table 表名 add primary key(字段名)  

(2)删除表
Drop table t1;
(3)清空表
Delete from 表名  (删除之后还会延续自增的属性)
Truncate form 表名 (删除之后从1开始自增)

	
4.Sql语句表内容的操作
(1)	查看
Select * from db1.t1;
Select id,name,age from 表名
(2)	删除(清空表)
Delete from 表名  (删除之后还会延续自增的属性)
Truncate form 表名 (删除之后从1开始自增)
(3)	增加
Insert into t1(id,name) values(1,”alex”);
(4)	修改
Update t1 set name=”alex”;(把name数据行的所有值全部改成alex)		
Update t1 set name=”alex” where name=”egon”
	2. SQL语句数据行操作补充
			create table tb12(
				id int auto_increment primary key,
				name varchar(32),
				age int
			)engine=innodb default charset=utf8;
	
		增
			insert into tb11(name,age) values(\'alex\',12);
			
			insert into tb11(name,age) values(\'alex\',12),(\'root\',18);
			
			insert into tb12(name,age) select name,age from tb11;
		删
			delete from tb12;
			delete from tb12 where id !=2 
			delete from tb12 where id =2 
			delete from tb12 where id > 2 
			delete from tb12 where id >=2 
			delete from tb12 where id >=2 or name=\'alex\'
		
		改
			update tb12 set name=\'alex\' where id>12 and name=\'xx\'
			update tb12 set name=\'alex\',age=19 where id>12 and name=\'xx\'
		查
			
			select * from tb12;
			
			select id,name from tb12;
			
			select id,name from tb12 where id > 10 or name =\'xxx\';
			
			select id,name as cname from tb12 where id > 10 or name =\'xxx\';
			
			select name,age,11 from tb12;
			
			其他:
				select * from tb12 where id != 1
				select * from tb12 where id in (1,5,12);
				select * from tb12 where id not in (1,5,12);
				select * from tb12 where id in (select id from tb11)
				select * from tb12 where id between 5 and 12;
	
(5)通配符:
				
				select * from tb12 where name like "a%"
				select * from tb12 where name like "a_"
	
			
(6)分页:
					select * from tb12 limit 10;
					
					select * from tb12 limit 0,10;
					select * from tb12 limit 10,10;
					select * from tb12 limit 20,10;
					
					select * from tb12 limit 10 offset 20;
		
		
					# page = input(\'请输入要查看的页码\')
					# page = int(page)
					# (page-1) * 10
					# select * from tb12 limit 0,10; 1
					# select * from tb12 limit 10,10;2
				
				
(7)排序:
					select * from tb12 order by id desc; 大到小
					select * from tb12 order by id asc;  小到大
					
					取后10条数据
					select * from tb12 order by id desc limit 10;
(8)分组:
				
					select count(id),max(id),part_id from userinfo5 group by part_id;
					
					count()
					max()
					min()
					sum()
					avg()  平均值
					
					**** 如果对于聚合函数结果进行二次筛选时?必须使用having ****
					select count(id),part_id from userinfo5 group by part_id having count(id) > 1;
					
					select count(id),part_id from userinfo5 where id > 0 group by part_id having count(id) > 1;
			
					
(9)连表操作:
				
					select * from userinfo5,department5
					
					select * from userinfo5,department5 where userinfo5.part_id = department5.id
					

					select * from userinfo5 left join department5 on userinfo5.part_id = department5.id
					select * from department5 left join userinfo5 on userinfo5.part_id = department5.id
					# userinfo5左边全部显示
					
					
					# select * from userinfo5 right join department5 on userinfo5.part_id = department5.id
					# department5右边全部显示
				
				
				
					select * from userinfo5 innder join department5 on userinfo5.part_id = department5.id
# 将出现null时一行隐藏
														
					select * from 
						department5 
					left join userinfo5 on userinfo5.part_id = department5.id
					left join userinfo6 on userinfo5.part_id = department5.id
				
				
select 
score.sid,
student.sid 
from 
score

left join student on score.student_id = student.sid
left join course on score.course_id = course.cid
left join class on student.class_id = class.cid
left join teacher on course.teacher_id=teacher.tid
select count(id) from userinfo5;    查看表有多少行

1.3 mysql 数据类型
1.3.1 int 数字类型
	bit[(M)]
            二进制位(101001),m表示二进制位的长度(1-64),默认m=1

        tinyint[(m)] [unsigned] [zerofill]

            小整数,数据类型用于保存一些范围的整数数值范围:
            有符号:
                -128 ~ 127.						
            无符号:
                0 ~ 255

            特别的: MySQL中无布尔值,使用tinyint(1)构造。

        int[(m)][unsigned][zerofill]

            整数,数据类型用于保存一些范围的整数数值范围:
                有符号:
                    -2147483648 ~ 2147483647
                无符号:
                    0 ~ 4294967295

            特别的:整数类型中的m仅用于显示,对存储范围无限制。例如: int(5),当插入数据2时,select 时数据显示为: 00002

        bigint[(m)][unsigned][zerofill]
            大整数,数据类型用于保存一些范围的整数数值范围:
                有符号:
                    -9223372036854775808 ~ 9223372036854775807
                无符号:
                    0  ~  18446744073709551615

        decimal[(m[,d])] [unsigned] [zerofill]
            准确的小数值,m是数字总个数(负号不算),d是小数点后个数。 m最大值为65,d最大值为30。

            特别的:对于精确数值计算时需要用此类型
                   decaimal能够存储精确值的原因在于其内部按照字符串存储。

        FLOAT[(M,D)] [UNSIGNED] [ZEROFILL]
            单精度浮点数(非准确小数值),m是数字总个数,d是小数点后个数。
                无符号:
                    -3.402823466E+38 to -1.175494351E-38,
                    0
                    1.175494351E-38 to 3.402823466E+38
                有符号:
                    0
                    1.175494351E-38 to 3.402823466E+38

            **** 数值越大,越不准确 ****

        DOUBLE[(M,D)] [UNSIGNED] [ZEROFILL]
            双精度浮点数(非准确小数值),m是数字总个数,d是小数点后个数。

                无符号:
                    -1.7976931348623157E+308 to -2.2250738585072014E-308
                    0
                    2.2250738585072014E-308 to 1.7976931348623157E+308
                有符号:
                    0
                    2.2250738585072014E-308 to 1.7976931348623157E+308
            **** 数值越大,越不准确 ****
 
1.3.2 char字符类型
(1)char(10)
	会固定数据行必须全部使用10个字符来存储数据,占内存,但是读取速度快
	Top:优化mysql的时候,尽量把定义成char的数据行放表的前面。
(2)varchar(10)
	会根据存储数据的长度来存储,不占内存,但是读取速度慢
(3)text
	当固定的长度大于255个字符的时候,使用text
	text数据类型用于保存变长的大字符串,可以组多到65535 (2**16 − 1)个字符

1.3.3 时间类型
DATE
            YYYY-MM-DD(1000-01-01/9999-12-31)

        TIME
            HH:MM:SS(\'-838:59:59\'/\'838:59:59\')

        YEAR
            YYYY(1901/2155)

        DATETIME

            YYYY-MM-DD HH:MM:SS(1000-01-01 00:00:00/9999-12-31 23:59:59    Y)

        TIMESTAMP

            YYYYMMDD HHMMSS(1970-01-01 00:00:00/2037 年某时)

1.3.4 enum枚举类型
        enum
           枚举类型,
     An ENUM column can have a maximum of 65,535 distinct elements. (The practical limit is less than 3000.)
           示例:
                CREATE TABLE shirts (
                    name VARCHAR(40),
                    size ENUM(\'x-small\', \'small\', \'medium\', \'large\', \'x-large\')
                );
                INSERT INTO shirts (name, size) VALUES (\'dress shirt\',\'large\'), (\'t-shirt\',\'medium\'),(\'polo shirt\',\'small\');
1.3.5 set集合类型
   set
            集合类型
            A SET column can have a maximum of 64 distinct members.
            示例:
                CREATE TABLE myset (col SET(\'a\', \'b\', \'c\', \'d\'));
                INSERT INTO myset (col) VALUES (\'a,d\'), (\'d,a\'), (\'a,d,a\'), (\'a,d,d\'), (\'d,a,d\');


1.4 外键
1.4.1 外键定义
把一张表里面的主键作为另外一张表XX字段行里面的值,但是有依赖关系,有点索引的味道。
		create table userinfo(
			uid bigint auto_increment primary key,
			name varchar(32),
			department_id int,
			xx_id int,
			constraint fk_user_depar foreign key ("department_id",) references department(\'id\'),
			constraint fk_xx_ff foreign key ("xx_id",) references XX(\'id\')
		)engine=innodb default charset=utf8;
		
		create table department(
			id bigint auto_increment primary key,
			title char(15)
		)engine=innodb default charset=utf8;

补充:外键 ?
					CREATE TABLE t5 (
					  nid int(11) NOT NULL AUTO_INCREMENT,
					  pid int(11) not NULL,
					  num int(11),
					  primary key(nid,pid)         --设置两个主键
					) ENGINE=InnoDB DEFAULT CHARSET=utf8;



					create table t6(
						id int auto_increment primary key,
						name char(10),
						id1 int,
						id2 int,
						CONSTRAINT fk_t5_t6 foreign key (id1,id2) REFERENCES t1(nid,pid)
					)engine=innodb default charset=utf8;

1.5 自增 auto_increment
(1)查看表的创建语句	
	Show create table tb1;
Create table tb1(
Tid int not null auto_increment primary key,
Name char(10),
)engine=innodb auto_increment=3 default charset=utf8;

从100开始自增的配置
aflter table tb1 auto_increment=100;
 
1.6 自增步长
MySQL: 自增步长
			基于会话级别:
				show session variables like \'auto_inc%\';	查看全局变量
                set session auto_increment_increment=2; 	设置会话步长
				# set session auto_increment_offset=10;   设置自增数从10开始
			基于全局级别:
				show global variables like \'auto_inc%\';	    查看全局变量
                set global auto_increment_increment=2; 	    设置会话步长
				# set global auto_increment_offset=10;
				
				
		SqlServer:自增步长:
			基础表级别:
				CREATE TABLE `t5` (
				  `nid` int(11) NOT NULL AUTO_INCREMENT,
				  `pid` int(11) NOT NULL,
				  `num` int(11) DEFAULT NULL,
				  PRIMARY KEY (`nid`,`pid`)
				) ENGINE=InnoDB AUTO_INCREMENT=4, 步长=2 DEFAULT CHARSET=utf8
				
				CREATE TABLE `t6` (
				  `nid` int(11) NOT NULL AUTO_INCREMENT,
				  `pid` int(11) NOT NULL,
				  `num` int(11) DEFAULT NULL,
				  PRIMARY KEY (`nid`,`pid`)
				) ENGINE=InnoDB AUTO_INCREMENT=4, 步长=20 DEFAULT CHARSET=utf8

1.7唯一索引
Create unique index 唯一索引名 on tb1(name);后天创建
创建表的时候创建:
		create table t1(
			id int ....,
			num int,
			xx int,
			unique 唯一索引名称 (列名,列名),
			constraint ....
		)
		# 
		1   1   1
		2   1   2
		PS: 
			唯一:
				约束不能重复(可以为空)
				PS: 主键不能重复(不能为空)
			加速查找
1.8 组合索引
创建表的时候:
create table t1(
			id int ....,
			num int,
			xx int,
			index 唯一索引名称 (列名,列名),
			constraint ....
		)

后天创建:create index 联合索引名称 on tb1(name,age)(使用最左前缀匹配)



	1. 外键的变种
		
		a. 用户表和部门表
		
			用户:
				1 alex     1
				2 root	   1
				3 egon	   2
				4 laoyao   3
				
			部门:
				1 服务
				2 保安
				3 公关
			===》 一对多
		b. 用户表和博客表
			用户表:
				1 alex    
				2 root	   
				3 egon	   
				4 laoyao   
			博客表:
								  FK() + 唯一
				1   /yuanchenqi/   4
				2    /alex3714/    1
				3    /asdfasdf/    3
				4    /ffffffff/    2
				
			===> 一对一
			
				create table userinfo1(
					id int auto_increment primary key,
					name char(10),
					gender char(10),
					email varchar(64)
				)engine=innodb default charset=utf8;

				create table admin(
					id int not null auto_increment primary key,
					username varchar(64) not null,
					password VARCHAR(64) not null,
					user_id int not null,
					unique uq_u1 (user_id),
					CONSTRAINT fk_admin_u1 FOREIGN key (user_id) REFERENCES userinfo1(id)
				)engine=innodb default charset=utf8;

			
			
			
		c. 用户表(百合网) 相亲记录表
		
			示例1:
				用户表
				相亲表
				
			示例2:
				用户表
				主机表
				用户主机关系表
			===》多对多
	
				create table userinfo2(
					id int auto_increment primary key,
					name char(10),
					gender char(10),
					email varchar(64)
				)engine=innodb default charset=utf8;

				create table host(
					id int auto_increment primary key,
					hostname char(64)
				)engine=innodb default charset=utf8;


				create table user2host(
					id int auto_increment primary key,
					userid int not null,
					hostid int not null,
					unique uq_user_host (userid,hostid),
					CONSTRAINT fk_u2h_user FOREIGN key (userid) REFERENCES userinfo2(id),
					CONSTRAINT fk_u2h_host FOREIGN key (hostid) REFERENCES host(id)
				)engine=innodb default charset=utf8;





1.8 Pymysql
1.8.1 安装pymysql模块
# #1.创建链接      打开柜子
# conn=pymysql.connent(host="locallhost",port=3306,username="root",db="management")
# #2.创建游标      用手
# cursor=conn.cursor()
# #3.执行SQL,并且返回收影响行
# ret=cursor.execute("select * ---------")
# #4.提交,不然无法保存新建或者修改的数据
# conn.commit
# #5.关闭游标
# cursor.close()
# #6.关闭链接
# conn.close()
#
#
# # 获取最新自增ID
# new_id = cursor.lastrowid
# # 获取第一行数据
# row_1 = cursor.fetchone()
# # 获取前n行数据
# row_2 = cursor.fetchmany(3)
# # 获取所有数据
# row_3 = cursor.fetchall()


1.9 视图
1.9.1 创建视图
	某个查询语句查出来的临时表,给这张临时表起一个别名
	创建:
Create view 视图名称 as aql语句;
	修改:
		Alter view 视图名称 as aql语句;
	删除:
		Drop view视图名称;

使用方法:select * from 视图名称


1.10 触发器
在执行某一个sql语句之前或者之后做一个操作(装饰器)

例如:用户注册,用户表里多了一条数据,日志表里面存一下信息(往日志表里面在插入一条数据)
1.10.1 创建一个触发器:
(只要往tb1表里插入数据就会引发插入student表的sql语句)
delimiter  //   (把终止符;改成//)
create trigger 触发器名  before insert on tb1 for each row
Begin 
	Insert into student(sname) value(“egon”)
end
delimiter ;(必须要改回来)

1.10.2关键词
new 新插入的数据(insert)
old  (delete的时候会有)
update 的时候既有new也有old


1.11 函数
1.11.1 内置函数
(1)使用方法
	Select + 函数名()
		Select curdate();   查询当前时间
		Select char_length(“egon”) 查看字符串的长度
		Select concat(“alex”,”is”,”sb”) 拼接字符串
(2)时间格式化
	自定义时间的格式
Date_format(“2019-1-1”,”%y-%m”)

1.11.2自定义函数
Delimiter //
Create function foo(
I1 int
I2 int
return int
)		
Begin
	declare num int default 0;              ( 声明变量num=0 )
	set num=l1+l2                        ( 变量赋值 )
	return num ;
end //
select foo(1,100);
1.12 存储过程
1.12.1定义
保存在mysql上的一个别名====》一堆sql语句
别名()(代替了很多的sql语句)
用于替代程序员写sql语句

Delimiter //
	Create procedure p1()
	begin
		select * from student;
insert into student(sname) values(“egon”)
	end
delimiter ;		

终端执行存储过程的方法:call p1()        执行存储过程(同时操作这两条sql语句)		
1.12.2 pymysql 调用存储过程的方法
不需要写
Sql=”select *-----------------------”
cursor.execute(sql)
换成
Cursor.callproc(“p1”)
					
1.12.3 传参数的存储过程(in)
Delimiter //
Create procedere p2(
in n1 int,
in n2 int
)
Begin
	Select * from student where sid>n1;
End //
Delimiter;

In 表示要传入
Out 
Mysql  的调用方法:call p2(12,2);
Pymysql 的调用方式:cursor.callproc(“p2”,(12,2));

1.12.4 传参数的存储过程(out)
存储过程是没有return返回值的 可以是用out方法来实现
Delimiter //
Create procedure p3(
In n1 int,
Out n2 int
)
Begin
	Set n2=123123
	Select *from student where sid>n1;
End //
Delimiter ;

Mysql执行p3 的存储过程
Set @v1=0
Call p3(12,2)
Select @v1     可以拿到@V1=123123

Pymysql执行p3的存储过程
Cursor.callprot(“p3”,(12,2))   拿到的是搜索集

Cursor.excute(“select @_p3_0,@_p3_1”)  能拿到n2 out的值

1.12.5 事务
delimiter \\
create PROCEDURE p1(
    OUT p_return_code tinyint
)
BEGIN 
  DECLARE exit handler for sqlexception 
  BEGIN 
    -- ERROR 
    set p_return_code = 1; 
    rollback; 
  END; 
 
  DECLARE exit handler for sqlwarning 
  BEGIN 
    -- WARNING 
    set p_return_code = 2; 
    rollback; 
  END; 
 
  START TRANSACTION; 
    DELETE from tb1;
    insert into tb2(name)values(\'seven\');
  COMMIT; 
 
  -- SUCCESS 
  set p_return_code = 0; 
 
  END\\
delimiter ;

1.12.6 游标循环
1.12.7 动态执行SQL (防sql注入)


delimiter//
create procedure p7(
in tp1 varchar(255),
in arg int
)
begin
预检测某个东西sql语句的合法性
Sql=格式化tp1+arg
执行sql语句
Set @xc=arg;
Prepare prod from “select * from tb where id>?”
Execute prod using @xc;    只能是sesson中的变量
Deallocate prepare prod;

End //
Delimiter;

Call p7(“select * from tb where id>?”,9)

1.13 索引
1.13.1 作用
(1)约束
(2)快速查找

1.13.2 普通索引(加速查找)
(1)创建索引:
Create index ix_name on tb1(name)       #给tb1表中的name创建索引,后期添加
Drop index ix_name on tb1(name)        #删除索引,没有了加速查找
创建表的时候:
create table t1(
			id int ....,
			num int,
			xx int,
			index 唯一索引名称 (列名,列名),
			constraint ....
		)

Create index 索引名 on tb1(title(16))表示使用字段title前16个字符做索引
1.13.3 hash索引
(1)会把字段中的每一个数据转换成一个hash值存储在一张表中,因为转换成hash值时候跟数据表中的顺序不一样,比如:
Name
Alex1          hash  1235
Alex2          hash  7545
Alex3          hash  8956
查询单个数据的时候速度是特别特别快的
但是,想要查询一段就会很慢。
1.13.4 btree索引
(1)把字段里得每一个数据换成一个值,放在表中
二叉树数据结构



1.13.5 覆盖索引
(1)定义
直接到索引的特殊文件中直接去就能获取到的数据
1.13.6 索引合并
(1)定义
把多个单列索引合并着使用
组合索引的效率高于索引合并
1.13.7不能命中索引的语句
(1)假如name已经创建了索引
Select * from tb1 where name like “%alex%”  (不能加速查找)
加函数不能加速 
Select * from tb1 where (有索引)id=999 or (没索引)email=”ph@qq.com” (or不能加速找)
特别的:当or条件中有未建立索引的列才失效,以下会走索引
		select * from tb1 where nid = 1 or name = \'seven\';
		select * from tb1 where nid = 1 or name = \'seven@live.com\' and email = \'alex\'
									
- 类型不一致
		如果列是字符串类型,传入条件是必须用引号引起来,不然...
		select * from tb1 where email = 999;
			- !=
		select * from tb1 where email != \'alex\'
				
		特别的:如果是主键,则还是会走索引
		select * from tb1 where nid != 123
			- >
		select * from tb1 where email > \'alex\'
		特别的:如果是主键或索引是整数类型,则还是会走索引
		select * from tb1 where nid > 123
		select * from tb1 where num > 123
					
					
			- order by
				select name from tb1 order by email desc;
				
				当根据索引排序时候,选择的映射如果不是索引,则不走索引
				特别的:如果对主键排序,则还是走索引:
					select * from tb1 order by nid desc;
			 
			- 组合索引最左前缀
				如果组合索引为:(name,email)
				name and email       -- 使用索引
				name                 -- 使用索引
				email                -- 不使用索引
1.13.8 执行计划 explain
(1)预估执行sql语句的运行时间
(2)explain select * from tb1;
(3)根据上述执行代码查看type的类型
All      全表扫描
Ref      走了索引


id
        查询顺序标识
            如:mysql> explain select * from (select nid,name from tb1 where nid < 10) as B;
            +----+-------------+------------+-------+---------------+---------+---------+------+------+-------------+
            | id | select_type | table      | type  | possible_keys | key     | key_len | ref  | rows | Extra       |
            +----+-------------+------------+-------+---------------+---------+---------+------+------+-------------+
            |  1 | PRIMARY     | <derived2> | ALL   | NULL          | NULL    | NULL    | NULL |    9 | NULL        |
            |  2 | DERIVED     | tb1        | range | PRIMARY       | PRIMARY | 8       | NULL |    9 | Using where |
            +----+-------------+------------+-------+---------------+---------+---------+------+------+-------------+
        特别的:如果使用union连接气值可能为null


    select_type
        查询类型
            SIMPLE          简单查询
            PRIMARY         最外层查询
            SUBQUERY        映射为子查询
            DERIVED         子查询
            UNION           联合
            UNION RESULT    使用联合的结果
            ...
    table
        正在访问的表名


    type
        查询时的访问方式,性能:all < index < range < index_merge < ref_or_null < ref < eq_ref < system/const
            ALL             全表扫描,对于数据表从头到尾找一遍
                            select * from tb1;
                            特别的:如果有limit限制,则找到之后就不在继续向下扫描
                                   select * from tb1 where email = \'seven@live.com\'
                                   select * from tb1 where email = \'seven@live.com\' limit 1;
                                   虽然上述两个语句都会进行全表扫描,第二句使用了limit,则找到一个后就不再继续扫描。

            INDEX           全索引扫描,对索引从头到尾找一遍
                            select nid from tb1;

            RANGE          对索引列进行范围查找
                            select *  from tb1 where name < \'alex\';
                            PS:
                                between and
                                in
                                >   >=  <   <=  操作
                                注意:!= 和 > 符号


            INDEX_MERGE     合并索引,使用多个单列索引搜索
                            select *  from tb1 where name = \'alex\' or nid in (11,22,33);

            REF             根据索引查找一个或多个值
                            select *  from tb1 where name = \'seven\';

            EQ_REF          连接时使用primary key 或 unique类型
                            select tb2.nid,tb1.name from tb2 left join tb1 on tb2.nid = tb1.nid;



            CONST           常量
                            表最多有一个匹配行,因为仅有一行,在这行的列值可被优化器剩余部分认为是常数,const表很快,因为它们只读取一次。
                            select nid from tb1 where nid = 2 ;

            SYSTEM          系统
                            表仅有一行(=系统表)。这是const联接类型的一个特例。
                            select * from (select nid from tb1 where nid = 1) as A;
    possible_keys
        可能使用的索引

    key
        真实使用的

    key_len
        MySQL中使用索引字节长度

    rows
        mysql估计为了找到所需的行而要读取的行数 ------ 只是预估值

    extra
        该列包含MySQL解决查询的详细信息
        “Using index”
            此值表示mysql将使用覆盖索引,以避免访问表。不要把覆盖索引和index访问类型弄混了。
        “Using where”
            这意味着mysql服务器将在存储引擎检索行后再进行过滤,许多where条件里涉及索引中的列,当(并且如果)它读取索引时,就能被存储引擎检验,因此不是所有带where子句的查询都会显示“Using where”。有时“Using where”的出现就是一个暗示:查询可受益于不同的索引。
        “Using temporary”
            这意味着mysql在对查询结果排序时会使用一个临时表。
        “Using filesort”
            这意味着mysql会对结果使用一个外部索引排序,而不是按索引次序从表里读取行。mysql有两种文件排序算法,这两种排序方式都可以在内存或者磁盘上完成,explain不会告诉你mysql将使用哪一种文件排序,也不会告诉你排序会在内存里还是磁盘上完成。
        “Range checked for each record(index map: N)”
            这个意味着没有好用的索引,新的索引将在联接的每一行上重新估算,N是显示在possible_keys列中索引的位图,并且是冗余的。

详细

1.14	limit 分页

1.14.1 分页
Select * from tb1 limit 0,10;
Select * from tb1 limit 10,10;
Select * from tb1 limit 20,10;
随着取得值越大,效率会越来越低,速度越来越慢
1.14.2解决方案
a.到达一定页数不让用户看,只显示前200页
b.下一页:记录当前页能看到的最大id,找大于id的后10个
  上一页:记录当前页能看到的最小id。找小于id的前10个(必须要ord by desc)








1.15 DBA工作
1.15.1配置MySQL自动记录慢日志(内存)
slow_query_log = OFF                            是否开启慢日志记录
long_query_time = 2                              时间限制,超过此时间,则记录
slow_query_log_file = /usr/slow.log        日志文件
log_queries_not_using_indexes = OFF     为使用索引的搜索是否记录
注:查看当前配置信息:
       show variables like \'%query%\'
     修改当前配置:
    set global 变量名 = 值
1.15.1 修改配置文件(mysql启动的时候执行)


1.16 ORM框架
1.16.1 定义
SQLAlchemy 是python语言下的orm框架,把表看出一个类,一行数据看成一个对象,通过对象转换成sql,然后把sql传达给diaiect,通过diaievt与dbapi交流使用相对应的api来执行并产生结果。
Api数据有多种,数据库不一样api就不一样
 

1.16.2 创建表
DB first:已经有表,然后生成类的代码块,先有的表手动创建库,表,自动生成类
Code first: 先写上类,然后自动创建表,手动创建库,代码创建表

SQLAlchemy 属于code first
(1)	创建表的前提必须手动创建库
(2)	Base=declarative_base()

#创建单表
class Users(Base):
    __tablename__="users"
    id=Column(Integer,primary_key=True)
    name=Column(String(32))
    email=Column(String(16))

#sqlalchemy会解析“mysql。。。。”然后连接远程
engine = create_engine("mysql+pymysql://root:@127.0.0.1:3306/sfour?charset=utf8",max_overflow=5)
#create_all()表示会找到代码页里所有的类(继承Base)
Base.metadata.create_all(engine)
(3)sqlalchemy不能执行sql,只是传达给了diaiect

删除表  Base.metadata.drop_all(engine)
1.16.3 联合唯一
__table_args__=(
    #联合唯一
    UniqueConstraint("id","name",name="uix_id_name"),
    #索引组合
    Index("ix_n_ex","name","extra"),
)

1.16.4外键
class UserType(Base):
    __tablename__ = "usertype"
    id = Column(Integer, primary_key=True)
    title = Column(String(32))

#创建单表
class Users(Base):
    __tablename__="users"
    id=Column(Integer,primary_key=True)
    name=Column(String(32))
    email=Column(String(16))
    usertype_id=Column(Integer,ForeignKey("usertype.id"))
1.16.5类型
导入的时候需要定义:
from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, Index
Integer
Char
枚举类型

1.16.6 操作数据行(增删改查)
engine=create_engine()  创建链接之后必须跟上
Session=sessionmaker(engine=engine)
Session=Usertype(title=”普通用户”)
(1)增:
#操作数据行
#先连接
#engine=create_engine("mysql+pymysql://root:@127.0.0.1:3306/sfour?charset=utf8",max_overflow=5)

#去连接里面拿一个连接过来对数据库进行操作
Session=sessionmaker(bind=engine)
session=Session()

# #增加,这两步转换成sql语句
# obj1=UserType(title="普通用户")
# session.add(obj1)

#多行添加
objs=[
UserType(title="普通用户1"),
UserType(title="普通用户2"),
UserType(title="普通用户3"),
UserType(title="普通用户4"),
UserType(title="普通用户5"),
]
session.add_all(objs)

#提交
session.commit()
session.close()

(2)删:(必须先查在删)
#查
#engine=create_engine("mysql+pymysql://root:@127.0.0.1:3306/sfour?charset=utf8",max_overflow=5)
#去连接里面拿一个连接过来对数据库进行操作
Session=sessionmaker(bind=engine)
session=Session()

# #查
# user_type_list=session.query(UserType).all()
# for row in user_type_list:
#     print(row.id,row.title)

#先查在删
user_type_list=session.query(UserType.id,UserType.title).filter(UserType.id>2).delete()
for row in user_type_list:
    print(row.id,row.title)

#提交
session.commit()
session.close()


(3)改:
#改
#engine=create_engine("mysql+pymysql://root:@127.0.0.1:3306/sfour?charset=utf8",max_overflow=5)
#去连接里面拿一个连接过来对数据库进行操作
Session=sessionmaker(bind=engine)
session=Session()

#整列替换
user_type_list=session.query(UserType.id,UserType.title).filter(UserType.id>2).update({"title":"超级用户"})

#普通用户x ,如果参数是一样的int型就可以直接相加
user_type_list=session.query(UserType.id,UserType.title).filter(UserType.id>2).update({UserType.title+"x"},synachronize_session=False)


#提交
session.commit()
session.close()



(4)查:
#查
#engine=create_engine("mysql+pymysql://root:@127.0.0.1:3306/sfour?charset=utf8",max_overflow=5)
#去连接里面拿一个连接过来对数据库进行操作
Session=sessionmaker(bind=engine)
session=Session()

# #查
# user_type_list=session.query(UserType).all()
# for row in user_type_list:
#     print(row.id,row.title)

#过滤查
user_type_list=session.query(UserType.id,UserType.title).filter(UserType.id>2)
for row in user_type_list:
    print(row.id,row.title)

#提交
session.commit()
session.close()


1.16.7	条件
ret = session.query(Users).filter_by(name=\'alex\').all()
ret = session.query(Users).filter(Users.id > 1, Users.name == \'eric\').all()
ret = session.query(Users).filter(Users.id.between(1, 3), Users.name == \'eric\').all()
ret = session.query(Users).filter(Users.id.in_([1,3,4])).all()
ret = session.query(Users).filter(~Users.id.in_([1,3,4])).all()
ret = session.query(Users).filter(Users.id.in_(session.query(Users.id).filter_by(name=\'eric\'))).all()
from sqlalchemy import and_, or_
ret = session.query(Users).filter(and_(Users.id > 3, Users.name == \'eric\')).all()
ret = session.query(Users).filter(or_(Users.id < 2, Users.name == \'eric\')).all()
ret = session.query(Users).filter(
    or_(
        Users.id < 2,
        and_(Users.name == \'eric\', Users.id > 3),
        Users.extra != ""
    )).all()

1.16.8通配符
ret = session.query(Users).filter(Users.name.like(\'e%\')).all()
ret = session.query(Users).filter(~Users.name.like(\'e%\')).all()

1.16.9限制
ret = session.query(Users)[1:2]

1.16.10排序
ret = session.query(Users).order_by(Users.name.desc()).all()
ret = session.query(Users).order_by(Users.name.desc(), Users.id.asc()).all()

1.16.11分组
from sqlalchemy.sql import func

ret = session.query(Users).group_by(Users.extra).all()
ret = session.query(
    func.max(Users.id),
    func.sum(Users.id),
    func.min(Users.id)).group_by(Users.name).all()

ret = session.query(
    func.max(Users.id),
    func.sum(Users.id),
    func.min(Users.id)).group_by(Users.name).having(func.min(Users.id) >2).all()
1.16.12连表
ret = session.query(Users, Favor).filter(Users.id == Favor.nid).all()

ret = session.query(Person).join(Favor).all()

ret = session.query(Person).join(Favor, isouter=True).all()

1.16.13组合
q1 = session.query(Users.name).filter(Users.id > 2)
q2 = session.query(Favor.caption).filter(Favor.nid < 2)
ret = q1.union(q2).all()

q1 = session.query(Users.name).filter(Users.id > 2)
q2 = session.query(Favor.caption).filter(Favor.nid < 2)
ret = q1.union_all(q2).all()

1.16.14临时表

1.16.15
1.16.16
1.16.17简述orm 框架原理
(1)目的为了让程序员不在写原生的sql语句
(2)让程序员以类,对象的形式结合orm里定义的方法来操作数据库,
(3)把程序员写的类跟对象转换成sql传给delait
(4)delait看程序员写的是要连接什么数据库,传给dbapi,然后进行连接

二十四 web框架
1.1HTTP协议
http:是无状态的,是短连接(无状态是指下次连接,服务端不认识客户端)
Tcp:不断开
浏览器是socket客户端,网站是socket的服务端
1.1.1	web程序的解析
(1)域名通过dns的解析,就能拿到ip地址,默认端口都是80
(2)客户端发送内容,服务端返回结果就断开


1.1.2 自己定义框架(url,函数)路由系统
(1)客户端(请求头+请求体)
GET / HTTP/1.1
Host:127.0.0.1:8080
Connection:keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110Safari/537.36
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: zh-CN,zh;q=0.8


\r\n\r\n\'(空两行以后就是请求)

(2)服务端(响应头+响应体)
响应体:一般都是html
(3)访问的url发生变化 locallhost:8080/XXXX/    可以在服务端进行判断
GET / HTTP/1.1
Host:127.0.0.1:8080
Connection:keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110Safari/537.36
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: zh-CN,zh;q=0.8




import socket
server=socket.socket()
server.bind(("127.0.0.1",8080))
server.listen(5)
while True:
    conn,add=server.accept()
    #获取用户传来的数据
    data=conn.recv(1024)
    print(data)
    # data=str(data,encoding="utf8")
    data=data.decode("utf-8")
    #以\r\n\r\n 分割成请求头跟请求体
    header,body=data.split("\r\n\r\n")
    
    #把请求头以\r\n 分割成一行一行的数据
    header_list=header.split("\r\n")
    
    #把第一行以空格分割,得到传至方式,url,协议版本
    method,url,protocal=header_list[0].split(" ")

    print("ok",method,url,protocal)

    if url=="/xxxx":
        conn.send(b"HTTP 1.1 200 ok\r\n\r\n")
        conn.send(b"123123123123")
    else:
        conn.send(b"HTTP 1.1 200 ok\r\n\r\n")
        conn.send(b"404 error")

    conn.close()

(3)访问静态页面
#! /user/bin/env python
# -*- coding:utf-8 -*-
import socket

#读文件发送
def f2(reguest):
    f=open("articl.html","rb")
    readdb=f.read()
    f.close()
    return readdb


url_list=[
    ("/xxxx",f1),
    ("/oooo",f2),
    ("/aaaa",f3),
]



def run():
    server=socket.socket()
    server.bind(("127.0.0.1",8080))
    server.listen(5)
    while True:
        conn,add=server.accept()
        #获取用户传来的数据
        data=conn.recv(1024)
        print(data)
        # data=str(data,encoding="utf8")
        data=data.decode("utf-8")
        #以\r\n\r\n 分割成请求头跟请求体
        header,body=data.split("\r\n\r\n")

        #把请求头以\r\n 分割成一行一行的数据
        header_list=header.split("\r\n")

        #把第一行以空格分割,得到传至方式,url,协议版本
        method,url,protocal=header_list[0].split(" ")
        conn.send(b"HTTP 1.1 200 ok\r\n\r\n")
        print("ok",method,url,protocal)
        fun=None
        for item in url_list:
            if item[0]==url:
                fun=item[1]
                break
        if fun:
            resonse=fun(data)
        else:
            resonse=b"404"


        # if url=="/xxxx":
        #     conn.send(b"HTTP 1.1 200 ok\r\n\r\n")
        #     conn.send(b"123123123123")
        # else:
        #     conn.send(b"HTTP 1.1 200 ok\r\n\r\n")
        #     conn.send(b"404 error")
        conn.send(resonse)
        conn.close()
#如果执行当前页面,就会执行下面的代码
if __name__ == \'__main__\':
    run()
(4)从数据库访问动态页面
#! /user/bin/env python
# -*- coding:utf-8 -*-
import socket
import pymysql
#读数据库信息发送
def f3(reguest):
    f=open("articl2.html","r",encoding="utf8")
    readdb=f.read()

    conn=pymysql.connect(host="127.0.0.1" ,port=3306,user="root",passwd="",db="userinfo",charset="utf8")
    cursor=conn.cursor()
    sql="select cid,cname,teacher_id from course"
    cursor.execute(sql)
    db_list=cursor.fetchall()
    print("===================",db_list)
    cursor.close()
    conn.close()

    content=[]
    for row in db_list:
         tr="<tr><th>%s</th><th>%s</th><th>%s</th></tr>"%(row[0],row[1],row[2])
        content.append(tr)
        scontent="".join(content)

        return_db=readdb.replace("@@swapper@@",scontent)
        print("====================================",return_db)

    return return_db.encode("utf8")


url_list=[
    ("/xxxx",f1),
    ("/oooo",f2),
    ("/aaaa",f3),
]

def run():
    server=socket.socket()
    server.bind(("127.0.0.1",8080))
    server.listen(5)
    while True:
        conn,add=server.accept()
        #获取用户传来的数据
        data=conn.recv(1024)
        print(data)
        # data=str(data,encoding="utf8")
        data=data.decode("utf-8")
        #以\r\n\r\n 分割成请求头跟请求体
        header,body=data.split("\r\n\r\n")

        #把请求头以\r\n 分割成一行一行的数据
        header_list=header.split("\r\n")

        #把第一行以空格分割,得到传至方式,url,协议版本
        method,url,protocal=header_list[0].split(" ")
        conn.send(b"HTTP 1.1 200 ok\r\n\r\n")
        print("ok",method,url,protocal)
        fun=None
        for item in url_list:
            if item[0]==url:
                fun=item[1]
                break
        if fun:
            resonse=fun(data)
        else:
            resonse=b"404"

        conn.send(resonse)
        conn.close()

#如果执行当前页面,就会执行下面的代码
if __name__ == \'__main__\':
    run()

1.1.3 jinja2引擎渲染模板
(1)不在需要人工替换数据
把从数据库取出来的数据变成(
							{“cid”:“1”},
							{“cname”:“alex”}
)
cursor=conn.cursor(cursor=pymysql.cursor.DictCursor)
     把前段html代码@@swapper@@修改成
{% for row in xxxxx %}                    xxxxxx表示从数据库中取出来的数据
<tr>                                    查出来的数据是一个元祖
	<td>{{ row.cid}}</td>                  需要把游标改成
			<td>{{ row.cname}}</td>               
			<td>{{ row.teacher_id}}</td>
		</tr>
		{% endfor %}

服务端应该写成:
	基于上面的代码f3()
from jiaja2 import Template
template=Template(“读取模板后的数据data”)
data=template.rander(xxxxx=”数据库中查询来的数据”)

得到模板渲染后替换的数据直接return


二十五 web框架的种类
1.1 http:无状态,短连接
1.2 介绍socket
	浏览器作为socket客户端
	网站作为socket服务端
1.3 自己写网站
	A   socket服务端
	B   根据url的不同返回不同的内容
		使用路由系统
			URL函数
	C   字符串返回给客户端
		模板引擎渲染:
			Html充当模板(特殊字符){}
			自己创造任意数据
		字符串

1.4 web框架种类
- A  B  C                             Tornado
-[第三方A]   B   C                     django用的是python本地自带的wsgiref
-[第三方A]   B  [第三方C]             flask,

分类:
-Django框架(重量级)(web里面有很多的功能)
-其他(轻量级)


二十六 django 框架
1.1 安装django 模块,创建django工程
pip3 install django
1.1.1创建一个django项目
添加到环境变量  django_admin  (python下面的script文件夹中)
Django-admin startproject 项目名
1.1.2让django的服务端run起来
Django服务的默认端口是8000
Python manage.py runserver 项目名 127.0.0.1:8000
   
 
Settings.py   Django的配置文件    
url.py       路由系统      (url,函数)
Wsgi  叫web服务网管接口,是用来调用 就是socket
1.2 pycharm中创建django项目
1.2.1 manage.py
	对当前django程序进行所有操作可以基于 python manage.py runserver
1.2.2 路由系统在django中的操作
	(1)在url.py中操作
#返回内容,里边内容是什么,客户端就看到什么
from django.shortcuts import HttpResponse


def foo(request):
    """
    处理用户请求,并且返回内容
    :param request: 用户请求的相关信息(对象)
    :return: 
    """

    return HttpResponse("123")

urlpatterns = [
    url(r\'^login/\', foo),
]


1.2.3 HttpResponse(“” ) 
给客户端返回一个字符串,html标签也可以渲染
	里面写什么,客户端就显示什么,只能是字符串

1.2.4 render() 模板引擎渲染
可以return rendel(request,“login.html”)   
会自动到template文件夹下面找模板 login.html
#返回内容,里边内容是什么,客户端就看到什么
from django.shortcuts import HttpResponse,render


def foo(request):
    """
    处理用户请求,并且返回内容
    :param request: 用户请求的相关信息(对象)
    :return: 
    """

    #return HttpResponse("123")
    #自动到templates下面寻找login.html,读取内容并返回给客户端
    return  render(request,"login.html")

urlpatterns = [
    url(r\'^login/\', foo),
]


1.2.5 静态文件的路径配置(css样式)

#使用时html中的前缀时什么?
STATIC_URL = \'/static/\'
STATICFILES_DIRS=(
    os.path.join(BASE_DIR,"sta"),         #必须加逗号
)


1.2.6模板配置路径
TEMPLATES = [
    {
        \'BACKEND\': \'django.template.backends.django.DjangoTemplates\',
        \'DIRS\': [os.path.join(BASE_DIR, \'templates\')]
        ,
        \'APP_DIRS\': True,
        \'OPTIONS\': {
            \'context_processors\': [
                \'django.template.context_processors.debug\',
                \'django.template.context_processors.request\',
                \'django.contrib.auth.context_processors.auth\',
                \'django.contrib.messages.context_processors.messages\',
            ],
        },
    },
]

1.2.7 额外的配置
注释这个
MIDDLEWARE = [
    \'django.middleware.security.SecurityMiddleware\',
    \'django.contrib.sessions.middleware.SessionMiddleware\',
    \'django.middleware.common.CommonMiddleware\',
    #\'django.middleware.csrf.CsrfViewMiddleware\',
    \'django.contrib.auth.middleware.AuthenticationMiddleware\',
    \'django.contrib.messages.middleware.MessageMiddleware\',
    \'django.middleware.clickjacking.XFrameOptionsMiddleware\',
]

1.2.8 request  请求的所有内容(对象)
request.method  请求的方法
request.POST    post的请求体,会是一个字典的形式,最好用get(“key”)来取值
request.GET    get的请求体,会是一个字典的形式,最好用get(“key”)来取值

1.2.9 redirect(“url”) 重定向
(1)登录成功
	跳转到其他的url连接  return redirect(“http://www.baidu.com”)
(2)登录失败
	return render(request,”/login”) 登录失败重新渲染这个页面
(3)用户登录失败,在input后面直接提示的方法
	在模板中加上{{msg}}
	在(2)中
	return render(request,”/login”,{“msg”:”用户输入错误”})
	

1.3django中的增删改查
1.3.1增
def add_class(request):

    if request.method=="GET":
        return render(request,"add_class.html")
    else:
        v = request.POST.get("title")
        conn = pymysql.connect(host="127.0.0.1", port=3306, user="root", passwd="", db="django_db", charset="utf8")
        cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
        cursor.execute("insert into classname(title) values(%s)", v)
        conn.commit()
        cursor.close()
        conn.close()
        return redirect("/classes/")

1.3.2 删
def del_class(request):
    nid=request.GET.get("nid")
    print("nid",nid)
    conn=pymysql.connect(host="127.0.0.1",port=3306,user="root",passwd="",db="django_db",charset="utf8")
    cursor=conn.cursor()
    print("ok")

    cursor.execute("delete from classname where cid=%s",[nid,])
    print("ok")
    conn.commit()
    cursor.close()
    conn.close()

    return redirect("/classes/")

1.3.3 改
def edit_class(request):
    if request.method=="GET":
        nid = request.GET.get("nid")
        title = request.GET.get("title")
        obj={"id":nid,"title":title}
        return render(request,"edit_class.html",{\'obj\':obj})
    else:
        id=request.GET.get("id")
        print("nid",id)
        title=request.POST.get("title")
        print("title",title)
        conn=pymysql.connect(host="127.0.0.1",port=3306,user="root",passwd="",db="django_db",charset="utf8")
        cursor=conn.cursor(cursor=pymysql.cursors.DictCursor)
        cursor.execute("update classname set title=%s where cid=%s",[title,id])
        conn.commit()
        cursor.close()
        conn.close()
        return redirect("/classes/")

1.3.4 查
def classes(request):
    conn=pymysql.connect(host="127.0.0.1",port=3306,user="root",passwd="",db="django_db",charset="utf8")
    cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
    # cursor=conn.cursor()
    sql="select cid,title from classname"
    cursor.execute(sql)
    class_list=cursor.fetchall()
    cursor.close()
    conn.close()
    print("----------------------",class_list)
    return render(request,"classes.html",{\'class_list\':class_list})

1.4 应用软件知识
1.4.1 django创建一个应用软件
Python manage.py startapp 应用名称
1.4.2 应用软件文件信息
 
Admin.py (django内置的一个图形化操作数据库)
Apps.py()
Models.py(与数据库相关的内容)
Test.py()
Views.py(存放视图函数)

1.4.3 应用软件的注册
找到django项目的配置文件,在installed_apps中添加
INSTALLED_APPS = [
    \'django.contrib.admin\',
    \'django.contrib.auth\',
    \'django.contrib.contenttypes\',
    \'django.contrib.sessions\',
    \'django.contrib.messages\',
    \'django.contrib.staticfiles\',
    \'program01\',
]
1.5 路由系统
1.5.1 普通的url与视图函数
urlpatterns = [
    url(r\'^admin/\', admin.site.urls),
    url(r\'^index/\', views.index),
]
1.5.2 正则表达式的 url与视图函数
url(r\'^index/\w+/\', views.index),
1.5.3 使用正则表达式url
url(r\'^index/\w+/\w+\', views.index),
Python端想要获取到匹配的值时需要在视图函数中添加参数
Def foo(request,a1,a2)
	Print(a1,a2)
1.5.4 url的分发
Django项目下面的urls.py  url(^”app01/”,include(“app01.urls”))
应用软件下面的urls.py  url(^”index/”,view.index),
1.5.5 url的别名操作
From django.urls import reverse
url(^”index.html$”,views.index,name=”zhuye”)
reverse(“zhuye”)

1.6orm操作
1.6.1 orm利用pymysql第三方工具连接数据库
	Django 默认连接的数据库是sqllite   
	Django默认连接mysql的方式是mysqldb
1.6.2 连接mysql的配置
	先把默认连接的sqllite改成连接mysql
(1)	在django项目下,修改settings
(2)	# DATABASES = {
#     \'default\': {
#         \'ENGINE\': \'django.db.backends.sqlite3\',
#         \'NAME\': os.path.join(BASE_DIR, \'db.sqlite3\'),
#     }
# }

DATABASES = {
    \'default\': {
        \'ENGINE\': \'django.db.backends.mysql\',
        \'NAME\':\'db2\',
        \'USER\': \'root\',
        \'PASSWORD\': \'\',
        \'HOST\': \'localhost\',
        \'PORT\': 3306,
                }
         }
(3)	django不能创建数据库,只能先手动创建数据库
(4)	修改django连接mysql使用的第三方工具为pymysql
在django项目下的——init—
import pymysql
pymysql.install_as_MySQLdb()
1.6.3 注册app应用软件
	Django setting.py下面添加
	INSTALLED_APPS = [
    \'django.contrib.admin\',
    \'django.contrib.auth\',
    \'django.contrib.contenttypes\',
    \'django.contrib.sessions\',
    \'django.contrib.messages\',
    \'django.contrib.staticfiles\',
    \'program01\'
]

1.6.4 orm操作表
	(1)创建表
		在应用软件的models.py下创建表跟数据行
		类表示创建表
			类里面可以创建数据行
	Class UserInfo(models.Model)
		Nid = models.BigAutoField(primary_key=True)
		Username=models.CharField(max_leng=32)

1.6. 5创建数据库表的命令
	在Django数据库中如果我们不写自增id那一列,Django会自动帮我们写的,只不过名称时id,并且还是主键,也是int类型
	通过这个命令可以修改已经创建好表的名称,还可以增加字段
 	 Python manage.py makemigrations
	 Python manage.py migrate

1.6.6 创建字段命令里的参数	
	Primary_key           主键 
Max_length=32        最大长度
	Default=1             默认列等于1
	Null=True             允许为空

1.6.7 创键外键关系表
	Class  UserGrop(models.Model):
		“””
			部门
“””
		Title=models.Charfield(max_length=32) 

Class UserInfo(models.Model)
“””
员工
“””
		Nid = models.BigAutoField(primary_key=True)
		Username=models.CharField(max_leng=32)
外键表  Ug=models.ForeignKey(“UserGroup”,null=True)  

Ps:执行创建命令后,数据库会自动在外键的字段名称后面加—id    Ug_id

1.6.8 数据库表的操作 (全部写在views.py)
先引入
From app01 import models
(1)	增
models.UserGroup.objests.create(title=”销售部”)
(2)	查
Group_list=models.UserGrop.objects.all()       #QuerySet
Models.UserGrop.objects.all().first()           #obj
拿到的是一个对象
For row in group_list:
	  Print(row.id,row.title)
	    
		All() 拿所有的信息
		Filter(id=1) 过滤
				id__gt=1  (id大于1)
id__gte=1(大于等于1)
       			id__lt=1	 (id 小于1)	
id__gte=1(小于等于1)

(3)删
Group_list=models.UserGrop.objects.filter(id=1).delete()
(4)改
Group_list=models.UserGrop.objects.filter(id=1).update(title=”公关部”)

 
1.6.9通过外键取值
(1)连表的正向取值操作
Userinfo表中有 id  username  ut_id(外键到UserType)
UserType表中的字段有 id title
	A.user_list=models.Userinfo.objects.all()          取到的是queryset
		For row in User_list:
			Print(row.id,row.username,row.ut.title)
	B.user_list=models.Userinfo.objects.all().values(“id”,”username”,”ut__title”)   字典型
C.user_list=models.Userinfo.objects.all().values_list(“id”,”username”,”ut__title”)   元祖型
(2)连表的反向取值操作 
!!!在UserType表中       (普通用户对应两个人)
A.obj=models.UserType.object.all().first()
print(“obj.id”,”obj.title”,”obj.userinfo_set.all()”)
	For row in obj.userinfo_set.all():
		Print(“row.id”,”row.username”)
B.字典型
models.UserType.object.all().values(“id”,”title”,”userinfo”) 
    models.UserType.object.all().values(“id”,”title”,”userinfo__username”)
C.元祖型
models.UserType.object.all().values_list(“id”,”title”,”userinfo”) 
    models.UserType.object.all().values_list(“id”,”title”,”userinfo__username”)


ps:反向取值的时候可以添加一个参数related_name
外键字段名  ut=models.ForeignKey(“UserType”,related_name=”xxxxx”)
那么在反向取值的时候就可以不使用Userinfo_set,直接使用xxxxx
		

1.6.10django的内置分页
(1) python端
from django.core.paginator import Paginator,Page

def django_limte(request):
    """
    分页
    :param request: 
    :return: 
    """
    # models.UserType.objects.create(title="普通用户")
    # models.UserType.objects.create(title="2b用户")
    # models.UserType.objects.create(title="4b用户")


    # 创建300条数据
    for i in range(300):
        name="root"+str(i)
        #models.UserInfo.objects.create(username=name,ut_id=18)


    userinfo_list=models.UserInfo.objects.all()

    #一页显示10行数据
    paginator=Paginator(userinfo_list,10)
    # per_page:每页显示条目数量
    # count:  数据总行数
    # num_pages:总页数
    # page_range:总页数的索引范围,如:(1,10),(1,200)
    # page:page对象

    #当前显示第几页
    current_page=request.GET.get("page")
    posts=paginator.page(current_page)
    #has_next        是否有下一页
    #next_page_number   下一页页码
    #has_previous    是否有上一页
    #previous_page_number  上一页页码
    #object_list  分页后的数据列表
    #number   当前页
    #paginator   paginaton对象


    return render(request,"django_limte.html",{"posts":posts})

(2)html端
<body>
<ur>
    {% for row in posts.object_list %}
        <li>{{ row.username }}</li>
    {% endfor %}
</ur>
<div>
     {% if posts.has_previous %}
        <a href="/program01/django_limte/?page={{ posts.previous_page_number }}">上一页</a>
    {% endif %}

{% for num in posts.paginator.page_range %}
    <a href="/program01/django_limte/?page={{ num }}">{{ num }}</a>
{% endfor %}

    {% if posts.has_next %}
        <a href="/program01/django_limte/?page={{ posts.next_page_number }}">下一页</a>
    {% endif %}

</div>
</body>


1.6.11orm其他操作

(1)排序
models.userInfo.objects.all().order_by(“id”)   #从小到大
models.userInfo.objects.all().order_by(“-id”)  #从大到小
(2)分组
From djamgo.db.models import Count,Sum,Max,Min
#filter在聚合函数后面表示having,在聚合函数之前表示where
Models.userInfo.objects.values(“ut_id”).annotate(xxx=Count(“id”)).filter(xxx__gt=2)
(3)
Models.UserInfo.object.filter(id__lte=1)   #小于等于
Models.UserInfo.object.filter(id__gte=1)   #大于等于	  
Models.UserInfo.object.filter(id__range=[1,10])   #大于等于	
Models.UserInfo.object.filter(name__startswith=””)  #以xxx开头的	
Models.UserInfo.object.filter(name__contains=”xxxx”)  #包含xxx
Models.UserInfo.object.exclude(id=1)  #除了id=1以外
(4)F
from django.db.models import F 
Models.UserInfo.objects.all().update(age=F(“age”)+1) 
Update UserInfo set age=age+1
(5)Q解决查询问题的
Condition={
“id”:1,
“name”:”小王”
}
Models.UserInfo.objects.filter(**condition) 
应用一:	
Model.UserInfo.objects.filter(Q(id=1) | Q(id=2))  或者
Model.UserInfo.objects.filter(Q(id=1) & Q(id=2))  与

应用二:
#三个q1用OR来连接
q1=Q()
q1.connector=”OR”
q1.children.append((“id”,1))
q1.children.append((“id”,10))
q1.children.append((“id”,20))


#
#三个q2用OR来连接
q2=Q()
q2.connector=”OR”
q2.children.append((“sid”,1))
q2.children.append((“sid”,10))
q2.children.append((“sid”,20))


Con=Q()
Con.add(q1,”AND”)
Con.add(q2,”AND”)
(id=1 or id=10 or id=20)and(sid=1 or sid=10 or sid=20)


(6)ectra()额外的
Models.userInfo.objects.extra(self.select=None,where=None.params=None,tables=None,order_by=None,select_params=None)

a.映射
Select 此处 from 表
Select
Select_params=None

b.条件
where=None
params=None
select * from 表 where 此处

c.表
tables
select * from 表 此处

c.排序
order_by=None
select * from 表 order by 此处
 


应用一:
Ectra(select={
	“n”:”select count(1) from UsetType where id>=%s”
},select_params=[1,])

应用二:
Models.UserInfo.object.extra(
Tables=[“UserType”],
Where=[“id”=%s,”name”=%s],     ,之间以and连接
Params=[1,alex],
)

(7)写源生的sql语句
From django.db import connection,connections

Cursor=connection.cursor()        默认连接default数据
Cursor=connections[“数据库名’].cursor
Cursor.execute(“select * from userInfo where id=%s”,[1])
row=cursor.fechall()

(8)简单的操作
distinct()去重                mysql中不要加参数
reverse()                    前面必须要有order_by
all().only(“id”,”name”)         取到的还是对象 
all().defer(“name”)            除了name字段的其他全取
using(“db3”)                 指定去db3数据库取数据


none()
get(id=1)                    找不到会抛异常

objs=[
	models.UserInfo(username=”root1”),
models.UserInfo(username=”root1”),
]
bulk_create(objs,10)           10代表一次最多提交10个对象,批量创建数据
in_bulk([1,2,3])               主键的值在1,2,3列表中

1.16.12跟性能有关的两个方法
(1)连表性能差
#查询主动做连表
Select_related(外键字段名称)  连表进行查询 
(2)prefetch_related()   不做连表查询,做单表的多次查询
prefetch_related(“ut”)
a.	select * from userinfo 
b.	selset * from usertype where id in[2,4]

1.6.12利用django自动生成多对多的连表 ManyToManyFiels
(1)有两个表,一个男生表,一个女生表
	Class Boy(models.Model):
		name=models.CharField(max_length=32)
		#django自带生成多对多的关系表
		m=models.ManyToManyFiels(“Girl”)
		自定义多对多表跟自定义表的结合
		m=models.ManyToManyFiels(“Girl”,through=”love”,through_fields=(“b”,”g”))
		这种结合的方式不支持add(),remove(),set()方法,有Obj.m.all(),clean()方法


	Class Girl(models.Model):
		name=models.CharField(max_length=32)


#手动创建多对多的关系表
	class love(models.Model):
		b=models.ForeignKey(“Boy”)
g=models.ForeignKey(“Girl”)
#联合唯一索引
class Meta:
	unique_together=[
		(“b”,”a”),
]

(2)django自带创建多对多表后的操作
Obj=models.Boy.objects.filter(name=”小王”).first()
Obj.m.add(3)        #会在第三张表中添加一行   g_id=3
Obj.m.add(2,4)       #同时增加2,4
Obj.m.add(*[1,])    
Obj.m.remove(*[1,])
Obj.m.set([1,])       #重置
Obj.m.all()           #拿到的是Girl的对象
Obj.m.clean()        #把跟obj有关的女生都删了

(3)反向拿跟某个女生有过约会的男生
Obj=models.Girl.objects.filter(name=“大大”)
Boy_queryset=Obj.boy_set.all()
1.6.13xss攻击  跨站脚本攻击
xss 攻击:是通过用户的信任来进行攻击,比如在评论里写一堆HTML标签,成功提交后会保存数据库中或者本地文件中,
当别的用户在点开查看的时候,之前用户填写的评论也会通过字符串发送到浏览器,如果在html页面上设置【|safe】那么你就成功的被注释了
他可以获取你的相关信息,如果你在html页面中设置了safe 在HTML也是可及进行关键词判断,防止被注释!(Django本身就进行了XSS攻击防范的)

(1)在模板中安全显示
	在变量的后面添加  |safe
(2)在python端
(3)From django.utils.safestring import mark_safe
(4)如果要显示的内容是由网友添加的,类似提交评论,切记一定不要轻易|safe,如果|safe了,必须做判断,判断网友提交的数据中有没有script关键词

1.6.14csrf  跨站请求伪造之from表单提交
个人理解:crsf是防止跨站伪装请求,
用户如果访问服务端,服务端会发送一端字符串给客户端浏览器,
如果在次有请求时服务端会检测客户端浏览器上有没有 服务端发送的随机字符串,
如果有就可以进行访问,如果没有就不能进行访问

(1)以POST请求的形式过来的时候
a.如果django 项目中的setting
\'django.middleware.csrf.CsrfViewMiddleware\'   没有被注释
以POST形式过来的时候,服务端会自动发送一段特殊的随机字符串到客户端,当客户端在进行form.ajax提交数据的时候,服务端会验证是否还有随机字符串
解决方案:在form表单中添加
{% csrf_token%}
<form action="/login.html">
    {% csrf_token %}
    
    <input type="text" name="user">
    <input type="submit" value="提交">
</form>
(2)全站禁用
#\'django.middleware.csrf.CsrfViewMiddleware\'
(3)局部禁用
在视图函数的上方加一个装饰器(全局能用)
@csrf_exemot
Def login(request):
(4)局部使用
在视图函数的上方加一个装饰器(全局禁用)
@csrf_protect
Def login(request)

(5)cbv中的相关使用
只能在类的上面添加一个装饰器
From django.utils.decorators import method_decorator

@ method_decorator(csrf_protect,name=”dispatch”)     表示给;类里面的所有方法添加
Class Login(View):

	Def dispatch(self,request)
		pass
	
	Def get(self,request)
		pass

	Def post(self,request)
		pass
1.6.15 csrf 跨站请求伪造之ajax请求
(1)方式一(携带POST请求后网站获取到的csrf_token随机字符串去提交处理)
<form action="/login.html">
    {% csrf_token %}
    <input type="text" id=”user” name="user">
    <input type="submit" value="ajax提交" onclick=”setAjax()”>
</form>
<script src="/static/jquery-3.2.1.js"></script>
<script>
    function setAjax(){
    var csrf=$(\'input[name="csrfmiddlewaretoken"]\').val();
    var user=$("#user");
    $.ajax({
        url:"/login.html",
        type:"POST",
        data:{"user":user,"csrfmiddlewaretoken":csrf},
        dataType:"JSON",
        success:function (arg) {
            controls.log(arg)
        }
    })
    }
</script>


(2)方式二 (写完{% csrf_token%}会有一个隐藏的cookie字符串跟csrf_token得到的字符串不一样,放在请求头中传送)
取本地的cookie方法:
		A.document.cookie      得到一个字符串
		B.下载一个插件,叫jquary-cookie,放到静态文件夹下面
		  $.cookie(csrftoken)    得到cookie的值
			Ps:这个cookie值必须放到请求头中
script>
    function setAjax(){
    var token=$(“cookie”);
    var user=$("#user");
    $.ajax({
        url:"/login.html",
        type:"POST",
	headers:{“X-CSRFToken”:token}
        data:{"user":user },
        dataType:"JSON",
        success:function (arg) {
            controls.log(arg)
        }
    })
    }
</script>
1.7 CBV跟FBV的区别
1.7.1 CBV 是指路由系统中,url对应的是一个类
(1)Urls.py中:	
From django.views import View
url(r’^index.html$’,views.Login.as_view())
(2)views.py中
	From django.views import View
	Class Login(View):

		def get(self,request):
			print(“login get”)
			可以进行返回 return

		def post(self,request):
			print(“login post“)
			可以进行返回 return

PS:客户端在请求服务端的时候,服务端是根据反射来进行web框架都是基于反射来实现请求的方式是get,post,put等
1.7.2 FBV是指路由系统中,url对应的是一个函数
(1)url.py中
	url(r’^index.html$’,views.index)
(2)views.py中
		def index(request)

1.8 form表单,跟ajax提交的区别
1.8.1 form表单的提交方式只能有两种
	(1)get
	(2)post
Ps:提交默认刷新,不会保存上次输入内容
1.8.2ajax提交的方式有
Ps:默认不会刷新,默认保存上次输入内容
	(1)get   查
(2)post  创建
(3)put   更新
(4)delete  删除
点击按钮会自动触发
<a onclick=”AjaxSend()”></a>
Function AjaxSend(){
	$.ajax({
		url:”/index.html/”,
		type:”POST”,
		data:{     },
		dataType:”JSON”,
		success:function(arg){

}
})
}
1.9django数据类型	
1.9.1在数据库中默认使用字符串存储,但是在django.admin自带的管理 
EmailField()
IPAddressField()
URLField()
FilePathField()
FileField()
CommaseparatedintegerField()

1.9.2时间类的
Ctime=models.DateTimeField()
1.9.3 数字类的
IntegerField()
FloatField()
DecimalField()
1.9.4枚举
Sex_list={
	(1,”男”),
	(2,”女”),
}
Sex=models.IntegerField(choices=sex_list )
1.9.5字段参数
http://www.cnblogs.com/amiee-785563694/articles/7083668.html
null=True,
							default=\'1111\',   默认的内容
							db_index=True,  索引
							unique=True     唯一索引
							
							class Meta:
								# unique_together = (
								#     (\'email\',\'ctime\'),
								# )
								# index_together = (
								#     (\'email\',\'ctime\'),
								# )
1.10session知识点
1.10.1session原理
个人Session理解说明:
Session就是在存再服务端的数据,每次有人来访问网页Session会检测用户是否带Session发送的随机字符串,
如果有随机字符串Session就自己会查看自己以字典形式保存在内存的数据,字典键就是Session生成的随机字符串,字典的值就是用户的信息。
整体总结:
Session是依赖于Cookie的一种会话保持,Session会向第一次成功访问的用户发送一个随机的字符串,以便以后用户登录可以准确的定位到用户是谁,用户的相关信息。
需要注意一点,每个用户之间都是独立的字典,不会出现用户信息混乱状况,使用Session的好处,用户查看不到敏感信息

Cookie是什么?
			保存在客户端浏览器上的键值对
Session是什么?
			保存在服务端的数据(本质是键值对)
			{
			“asdfasdfasdfasdfasdf":{\'id\':1,\'name\':\'于浩\',email=\'xxxx\'}
				asdffffsdfdfdfdfd":{\'id\':2,\'name\':\'啥子\',email=\'xxxx\'}
				sdfsdf":{\'id\':2,\'name\':\'啥子\',email=\'xxxx\'}
				fdfd":{\'id\':2,\'name\':\'啥子\',email=\'xxxx\'}
				dfddfdfd":{\'id\':2,\'name\':\'啥子\',email=\'xxxx\'}
				dfdfd":{\'id\':2,\'name\':\'啥子\',email=\'xxxx\'}
				dfdfd":{\'id\':2,\'name\':\'啥子\',email=\'xxxx\'}
				dfdfdf":{\'id\':2,\'name\':\'啥子\',email=\'xxxx\'}
			}
			应用:依赖cookie
			作用:保持会话(Web网站)
			好处:敏感信息不会直接给客户端
		
		梳理:
			1. 保存在服务端的数据(本质是键值对)
			2. 配置文件中:
				- 存储位置
				- 超时时间、每次刷新更新时间
			3. 
				request.session
					- 增伤
					- 获取随机字符串
					- 主动设置超时时间 ***
1.10.2session的应用
Post提交请求时:服务端向客户端发送随机字符串作为cookie的值传送给客户端,服务端本身把随机字符串作为key,保存相应的数据
Request.session[“username”]=username
验证是否有访问权限,没有cookie就返回登录页面
Request.session.get(“username”)
1.10.3session的功能
Request.session.session_key       获取随机字符串
Request.session.delete(“session_key”)  
# 用户session的随机字符串
        request.session.session_key
 
       # 将所有Session失效日期小于当前日期的数据删除
        request.session.clear_expired()
 
       # 检查 用户session的随机字符串 在数据库中是否
        request.session.exists("session_key")
 
       # 删除当前用户的所有Session数据
        request.session.delete("session_key")
a. 配置 settings.py
 
    SESSION_ENGINE = \'django.contrib.sessions.backends.cache\'  # 引擎
    SESSION_CACHE_ALIAS = \'default\'                            # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置
 
 
    SESSION_COOKIE_NAME = "sessionid"                        # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串
    SESSION_COOKIE_PATH = "/"                                # Session的cookie保存的路径
    SESSION_COOKIE_DOMAIN = None                              # Session的cookie保存的域名
    SESSION_COOKIE_SECURE = False                             # 是否Https传输cookie
    SESSION_COOKIE_HTTPONLY = True                            # 是否Session的cookie只支持http传输
    SESSION_COOKIE_AGE = 1209600                              # Session的cookie失效日期(2周)
    SESSION_EXPIRE_AT_BROWSER_CLOSE = False                   # 是否关闭浏览器使得Session过期
SESSION_SAVE_EVERY_REQUEST = False                        # 是否每次请求都保存Session,默认修改之后才保存,30分钟以后不会超时
1.10.4session的保存方式
A.数据库
SESSION_ENGINE = \'django.contrib.sessions.backends.cache\'  # 引擎
B文件
  SESSION_ENGINE = \'django.contrib.sessions.backends.file\'    # 引擎
    SESSION_FILE_PATH = None                                    # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir()                        # 如:/var/folders/d3/j9tj0gz93dg06bmwxmhh6_xm0000gn/T
C缓存
 SESSION_ENGINE = \'django.contrib.sessions.backends.cache\'  # 引擎
 SESSION_CACHE_ALIAS = \'default\'                            # 使用的缓存别名(默认内存缓存,也可以是memcache),此处

1.11 模板
1.11.1母版
母板就是把大家共有的内容封装起来,直接使用
A.在母版中:把想要改变的html内容用
{% block xx  %}{% endblock %}
B.在想要导入母版的模板中
{% extends "layout.html" %}

{% block xx %}
自己编写的模板内容
{% endblock %}

Ps:一般的在模板中添加三个站位
(1){% block css %}{% endblock %}
(2){% block xx %}{% endblock %}
(3){% block js %}{% endblock %}

1.11.2模板添加函数,不需要添加括号就能运行

1.11.3自定义simple_filter
a、在app中创建templatetags模块
b、创建任意 .py 文件,如:xx.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15	#!/usr/bin/env python
#coding:utf-8
from django import template
from django.utils.safestring import mark_safe
   
register = template.Library()
   
@register.simple_tag
def my_simple_time(v1,v2,v3):
    return  v1 + v2 + v3
   
@register.simple_tag
def my_input(id,arg):
    result = "<input type=\'text\' id=\'%s\' class=\'%s\' />" %(id,arg,)
    return mark_safe(result)
c、在使用自定义simple_tag的html文件中导入之前创建的 xx.py 文件名
1	{% load xx %}
d、使用simple_tag
1
2	{% my_simple_time 1 2 3%}
{% my_input \'id_username\' \'hide\'%}
e、在settings中配置当前app,不然django无法找到自定义的simple_tag  
1
2
3
4
5
6
7
8
9	INSTALLED_APPS = (
    \'django.contrib.admin\',
    \'django.contrib.auth\',
    \'django.contrib.contenttypes\',
    \'django.contrib.sessions\',
    \'django.contrib.messages\',
    \'django.contrib.staticfiles\',
    \'app01\',
)
1.11.4小组件的导入
在需要被导入的地方
{%include “组件.html”}
 
1.12中间件
1.12.1一个url的请求生命周期
A.先经过中间件检测   中间件其实是一个类
B.匹配url
C.执行url对应的视图函数
D.返回数据经中间件
E.显示在客户端游览器
1.12.2编写中间件及使用
A.创建中间件
 
在process_response函数中必须要加入return response
B.注册中间件
 
C.process_view
D.process_exception
E.process_template_response#对视图函数的返回值必须有render方法,这个函数才会被调用



1.13Form表单组件校验
From组件主要用于验证,验证流程是每一个字段都先进行正则匹配,然后检测有没有clean_字段函数,此函数建议取自己字段的名称进行数据库内容检测,最后有一个clean函数可以进行整体检测
(在字段中的正则中还可以通过validates进行多重正则(自定义正则))
还可以直接使用自定义的正则字段(RegxFiled)
1.13.1定义规则
A.用户提交数据进行校验
B.保留上次输入的内容
from django.forms import Form
form django.forms import fields   fields里面有一堆正则表达式

class xxx(From):
	xx=fields.CharField(required=True,max_lenght,error_message={“required”:”用户名不能为空”})
校验的方法
Obj=xxx(request.POST)
Obj.is_valid()    
Obj.claeaned_data      正确的信息
Obj.errors             错误的信息

1.13.2用户提交数据进行验证
a.class Login(From):
	username=fileds.CharField()
b.obj=xxx(request.POST)#用户提交的数据
用obj.is_valid()校验,成功返回True,失败返回False
PS:校验的方式:html标签中的name值必须等于form类中字段的名称

校验成功:    create(**obj.cleaned_data)    #字典类型,添加到数据库
校验失败:    把obj对象的错误信息传到前段
				{{ obj.errors.username.0 }}
(c)is_valid()的校验原理
def login(request):
    if request.method=="GET":
        return render(request,"login.html")
    else:
        obj=Userinfo(request.POST)
        """
        #类中字段名字必须跟form提交过来的name属性值一样的原因
        1.Userinfo实例化的时候
        self.fields={
            "name":正则表达式,
            “passwd”:正则表达式,
        }
        2.循环self.fields
        for k,v in self.fields
            K:name passwd
            v:正则表达式对象
            input_value=request.POST.get(k)
        
        """
        if obj.is_valid():
            print(obj.cleaned_data)
            redirect(request,\'http://www.baidu.com\')
        else:
            print(obj.errors)
            return render(request,"login.html",{"obj":obj})
1.13.3form表单的请求   不保留输入内容

Form表单请求 自动刷新页面,但是不能保留上次输入的值
Html端
<form id="f1" method="post" action="/login/">
    {% csrf_token %}
    username:<input type="text" name="name">{{ obj.errors.name.0 }}
    password:<input type="text" name="passwd">{{ obj.errors.passwd.0 }}
    <input type="submit" value="提交">



Views.py端
class Userinfo(Form):
    name=fields.CharField(max_length=16,min_length=6)
    passwd=fields.IntegerField(max_value=120,min_value=0)

def login(request):
    if request.method=="GET":
        return render(request,"login.html")
    else:
        obj=Userinfo(request.POST)
        """
        #类中字段名字必须跟form提交过来的name属性值一样的原因
        1.Userinfo实例化的时候
        self.fields={
            "name":正则表达式,
            “passwd”:正则表达式,
        }
        2.循环self.fields
        for k,v in self.fields
            K:name passwd
            v:正则表达式对象
            input_value=request.POST.get(k)
        
        """
        if obj.is_valid():
            print(obj.cleaned_data)
            redirect(request,\'http://www.baidu.com\')
        else:
            print(obj.errors)
            return render(request,"login.html",{"obj":obj})


1.13.4 Ajax请求提交  默认不刷新保留输入内容
Html端
<form id="f1" method="post" action="/login/">
    {% csrf_token %}
    username:<input type="text" name="name">
    password:<input type="text" name="passwd">
    <input type="submit" value="提交">
    <a onclick="submitForm();">ajax提交</a>
</form>
<script src="/static/jquery-3.2.1.js"></script>
<script>
    function submitForm() {
        $.ajax({
            url:"/ajaxlogin/",
            type:"POST",
            //$("input[name=\'mobile\']").val()
            data:$(\'#f1\').serialize(),   //把form中的数据全部打包成  name=xxxxx&passwd=xxxx
            dataType:"JSON",
            success:function (arg) {
                console.log(arg);
                if (arg.static){

                }else {
                    //循环错误信息
                    $.each(arg.message,function (index,val) {
                        console.log(index,val);
                        var tag=document.createElement("span");
                        tag.innerHTML=val[0];
                        $(\'#f1\').find("input[name=\'"+user+"\']").after(tag)
                    })
                }
            }
        })

    }
</script>
Views.py端
def ajaxlogin(request):
    ret={"static":True,"messageg":None}
    obj=Userinfo(request.POST)
    if obj.is_valid():
        print(obj.cleaned_data)
    else:
        import json

        ret["static"]=False
        ret["message"]=obj.errors
        v = json.dumps(ret)
        return HttpResponse(v)

1.13.5保留上次输入的内容
(1)生成html标签(浏览器带有验证,去除游览器验证的参数,在form表单中加 novalidate入)
第一次get请求的时候没有值,返回一个标签
Obj=Text()
Obj.t1   ->  <input  type=”text” name=”t1” >
第二次POST请求的时候,带了用户输入的内容
Obj.Text(request.POST)
Obj.t1   ->  <input  type=”text” name=”t1” value=有值>

实例:
Html
<form action="/regiter/" method="post">
    {% csrf_token %}
    <p>{{ obj.username }} {{ obj.errors.username.0 }}</p>
    <p>{{ obj.password }} {{ obj.errors.password.0 }}</p>
    <p>{{ obj.email }} {{ obj.errors.email.0 }}</p>
    <p>{{ obj.phone }} {{ obj.errors.phone.0 }}</p>
    <input type="submit" value="提交">
</form>
Views.py
class Regiter(Form):
    username=fields.CharField(max_length=10)
    password=fields.CharField(min_length=3)
    email=fields.EmailField()
    phone=fields.RegexField("139\d+")



def regiter(request):
    if request.method=="GET":
        obj=Regiter()
        return render(request,"regiter.html",{"obj":obj})
    else:
        obj=Regiter(request.POST)
        if obj.is_valid():
            return HttpResponse("ok")
        else:
            return render(request,"regiter.html",{"obj":obj})



1.13.6内置定制的正则以及自定义的正则
http://www.cnblogs.com/amiee-785563694/articles/7119910.html
class Text(Form):
    #字符串类型
    t1=fields.CharField(
        required=True,
        max_length=8,
        min_length=2,
        error_messages={
            "required":"不能为空",
            "max_length":"不能超过8",
            "min_length":"不能少于2",
        }
    )
    #数字类型
    t2=fields.IntegerField(
        max_value="1000",    #最大值为1000
        min_value="0",       #最小值为0
        error_messages={
            "invalid":"格式错误不能填写字符串",

        }
    )
    #邮箱类型
    t3=fields.EmailField(
        error_messages={
            "invalid":"格式错误,只能是邮箱类型",
        }
    )

    t4=fields.URLField()            #有效的url
    t5=fields.SlugField()             #字母数字下划线,没有*
    t6=fields.GenericIPAddressField()  #IP
    t7=fields.DateField()              #
    t8=fields.DateTimeField()          #
    t9=fields.RegexField("139\d+")     #自己写正则

1.13.7常用的参数
    #常用的参数类
    t10=fields.CharField(
       required=True,
        label=\'阿西吧\',         #html    obj.t10.label
        initial="666",         #在input框中显示默认值
        help_text="可以做提示",   #html    obj.t10.label
        widget=None,           #自动生成html标签,默认生成input框   html  obj.t10
        disabled=False,
        label_suffix=":"   #
    )


def text(request):
    if request.method=="GET":
        obj=Text()
        return render(request,"text.html",{"obj":obj})

1.13.8widget的插件   
Widget=None      #不写默认生成input框

From django.froms import widgets
Widget=widgets.Textarea      生成文本框
Widget=widgets.Select        生成下拉框

单选CheckboxInput
Fields.CharField(
Widget=widgets.CheckboxInput             生成checkbox
)

多选
Fields.MultipleChoiceField(
	Choices=[(1,“篮球”),(2,“足球”)],             
	Widget=widgets.CheckboxSelectMultiple
)

Fields. ChoiceField(
	Choices=[(1,“篮球”),(2,“足球”)],     生成redio
	Widget=widgets.RedioSelectMultiple
)
1.13.9form表单生成标签中的常见问题
(1)从后台生成input标签,并且在input框中,显示默认值(编辑的时候)
def edit_class(request,nid):
    if request.method=="GET":
        nid=nid
        row_class=models.Classes.objects.filter(id=nid).first()
        #get请求的时候传输默认值,initial可以让页面初始值不进行校验
        obj=Classes(initial={"title":row_class.title})
        print(id)
        return render(request,"edit_class.html"
(2)单选从后台生成select标签,并且让select标签中有默认值(添加的时候)
     在类中写,在生成标签的同时就能把数据带过去
class Student(Form):
    name=fields.CharField(max_length=10,min_length=3)
    email=fields.EmailField()
    age=fields.IntegerField(min_value=18)
    cls_id=fields.CharField(
        #Select中要放[(),(),()]的格式
        widget=widgets.Select(choices=models.Classes.objects.values_list("id","title")),
    )
(3)单选生成标签并且带着从数据库中取到的数据作为默认值(编辑的时候)
     改的是obj=Student(必须是字典格式,data的时候会校验传过去的数据,initial不会校验第一次)
v=models.Student.objects.filter(id=nid).values("name","email","age","cls_id").first()
obj=Student(initial=v)
(4)从后台生成标签,并且添加一个属性值
widget=widgets.Select(choices=models.Classes.objects.values_list("id","title"),attrs={"class":"xxx"}),
)
(5)下拉框多选
class Teacher(Form):
    tname=fields.CharField(max_length=16)

    #单选使用这个
    # c2t=fields.IntegerField(
    #     widget=widgets.Select(choices=models.Classes.objects.values_list("id","title"),attrs={"multiple":"multiple"})
    # )

    # #单选也可以使用这个   使用SelectMultiple,choicse必须提出来定义
    # c2t=fields.CharField(
    #     choices=models.Classes.objects.values_list("id", "title"),
    #     widget=widgets.Select()

    #多选使用SelectMultiple不带括号, choicse必须提出来定义
    c2t=fields.MultipleChoiceField(
        choices=models.Classes.objects.values_list("id", "title"),
        widget=widgets.SelectMultiple
    )
    
    
    #解决form动态无法获取数据的bug
    def __init__(self,*args,**kwargs):
        super(Teacher,self).__init__(*args,**kwargs)
        self.fields["c2t"].widget.choices=models.Classes.objects.values_list("id", "title")

def teacher(request):
    if request.method=="GET":
        teacher_list=models.Teacher.objects.all()
        for row in teacher_list:
            print(row.c2t.all())

        return render(request,"teacher.html",{"teacher_list":teacher_list})


def add_teacher(request):
    if request.method=="GET":
        obj=Teacher()
        return render(request,"add_teacher.html",{"obj":obj})
    else:
        obj=Teacher(request.POST)
        if obj.is_valid():
            print(obj.cleaned_data)
            class_list=obj.cleaned_data.pop("c2t")
            print(class_list)
            row=models.Teacher.objects.create(**obj.cleaned_data)
            row.c2t.add(*class_list)
            return redirect("/teacher/")
        else:
            print(obj.errors)
            return render(request,"add_teacher.html",{"obj":obj})



def edit_teacher(request,nid):
    nid=nid
    if request.method=="GET":
        row=models.Teacher.objects.filter(id=nid).first()
        class_ids=row.c2t.all().values_list("id")
        print(class_ids)
        id_list=list(zip(*class_ids))[0] if list(zip(*class_ids)) else []

        obj=Teacher(initial={"tname":row.tname,"c2t":id_list})
        return render(request,"edit_teacher.html",{"obj":obj,"nid":nid})
    else:
        obj=Teacher(request.POST)
        if obj.is_valid():
            print(obj.cleaned_data)
            class_ids=obj.cleaned_data.pop("c2t")
            row=models.Teacher.objects.filter(id=nid)
            row.update(**obj.cleaned_data)
            row.first().c2t.set(class_ids)
            return redirect("/teacher/")
        return render(request,"edit_teacher.html",{"obj":obj,"nid":nid})

1.13.10 Form之解决数据无法动态显示的bug
(1)方法一
 

 
1.14MVC跟MTV
1.14.1 MVC
Model(数据库操作)  view(html模板)  contraller(控制器,视图函数)
1.14.2 MTV
Model(数据库操作)  templates(html模板)  views(控制器,视图函数)

Ps:django是典型的mtv模型框架


1.15django里的socket    wsgi 
1.15.1wsgi web服务网管应用接口
--wsgiref+django django默认使用
--uwsfi+django 正式环境中使用
  
from wsgiref.simple_server import make_server
 
 
def RunServer(environ, start_response):
    start_response(\'200 OK\', [(\'Content-Type\', \'text/html\')])
    return [bytes(\'<h1>Hello, web!</h1>\', encoding=\'utf-8\'), ]
 
 
if __name__ == \'__main__\':
    httpd = make_server(\'\', 8000, RunServer)
    print("Serving HTTP on port 8000...")
    httpd.serve_forever()
1.16Ajax请求
不刷新页面向后台提交请求有俩种方式(原生Ajax 和伪Ajax)
如果进行原生 Ajax post请求要注意设置请求头,不然post没有数据,如果设置了请求头post就会起body里把数据解析出来
Ajax(异步的js和xml)
Ajax【偷偷的向后台发送请求】
不让用JQuery 就用原生的 Ajax

1.16.1原生ajax,XMLHttpRequest
 
 
New XMLHttpRequest【这是一个类   使用原生的时候需要从这个类创建对象】
	对象.open(sting(类型) method(提交方式) string(类型) url (地址)boolen() asyns(异步就是回调 默认是异步,不让异步就把页面挺住了))
	对象.send( 发送请求)
	对象.setRequestHeader(string (类型)header(请求头)string(类型) value(值 ))  设置请求头   如果是post请求要记得设置请求头
 
	对象.string getALLResponseHeaders() 获取请求头
	对象.onreadystatechange   回调函数(只要数据发生过变化就执行)
		JQuery Ajax  JQuery没有Ajax   JQuery Ajax是基于原生的Ajax  
	Xml 没有太多的特殊要求 只是用<>包起来  就是能拿到名字
			执行状态共有好几种
							
1.16.2jQuery Ajax 内部基于原生Ajax
 
 
1.16.3伪Ajax,非XMLHttpRequest
使用的是(form和iframe标签通过关联达到一个伪Ajax)
 

 
1.16.4 Ajax 上传文件
原生Ajax上传文件
 
 

JQuery Ajax 文件上传
 
 

伪Ajax 文件上传
 
 
1.17跨域Ajax,JSONP
Josnp只能用GET请求,如果是post请求内部也会转换成GET请求
由于浏览器的同于策略 Ajax跨域发送请求时,再回来时浏览器拒绝接受
访问自己域名的url  没有问题    访问其他域名的url 就会被阻止
浏览器:
	禁止Ajax跨域发送请求时,再回来时浏览器拒绝接受(请求发过去并执行了)
	允许 带有src的标签
浏览器对Ajax有同源策略 
Josnp 这项技术就是转空子
	把数据拼接成script放在html
	<script src=”http://ww.baidu.com”></script>
如果进行跨域访问,一种俩种方式,一种是自己动态创建script标签
Jsonp是一种技术方式 不是仅python有jsonp

{#这个是原始的发送方式,使用的Ajax 因为浏览器对Ajax有同源策略,Ajax发请求发生发送到后台并进行执行,只是在返回的时候浏览器拒绝接受#}
 
1.17.1 JOSNP方式跨域
通过创建一个script标签成功的绕过浏览器的同源策略
 

1.17.1.1采用josnp方式自动动态生成标签  
这是自己动态创建标签,通过src访问对方的url成功的绕过浏览器的同源策略
 


 

1.1.7.1.2使用JQuery的josnp
 

1.17.2 Cors 方法
Cors 方法也是一种解决同源策略的一种方法(就是向浏览器告知,这个域名的访问请求不进行同源策略的拦截)

普通简单cors方式
 
注意点就是要给浏览器返回响应头,告诉浏览器这个API不执行同源策略
 

复杂的cors请求,以DELETE方式请求
 
Cors复杂的请求方式,请求过来需要进行一个预检,预检成功后再把允许什么域名什么请求方式访问发送给浏览器,浏览器在安装这个方式进行请求
 


1.18 随机验证码

 

把随机验证码封装,以后使用只需要调用就好
1.18.1 生成一个画板
画上随机生成的字母和颜色
from PIL import Image,ImageDraw,ImageFont,ImageFilter
import random
#导入生成随机字符的模块
def rd_check_code(width=120,height=30,char_length=5,font_file=\'kumo.ttf\',font_size=28):
    # 定义一个生成验证码的函数,宽120,高30是验证码的背景图大小,char_langth是在这个背景图上能写几个字符,file是设置显示在背景的字体样式,font_size是字体大小
    code=[]
    # 定义一个空列表
    img=Image.new(mode=\'RGB\',size=(width,height),color=(255,255,255))
    #设置一个img背景图颜色是使用RGB,背景图的大小是120,30 颜色是用RGB的255,255,255
    draw=ImageDraw.Draw(img,mode=\'RGB\')
    #创建一个画板,画板背景图是img,颜色采用RGB
    def rndChar():
        \'\'\'
        生成随机字母
        :return: 
        \'\'\'
        return chr(random.randint(65,90))
        #返回一个转成字符的字母(65,90之间在ascii码表是26个字母的大写)
    def rndColor():
        \'\'\'
        生成随机颜色
        :return: 
        \'\'\'
        return (random.randint(0,255),random.randint(0,255),random.randint(0,255))
        #返回一个组数字(由在ascii码表0,255对应的内容生成三个数字拼接)

1.18.2 生成文字
font=ImageFont.truetype(font_file,font_size)
# 创建一生成个文本的(font_file是文本字体的样式,font_size是文本字体的大小)
for i in range(char_length):
    \'\'\'
    生成字符(文字)
    \'\'\'
    # 循环在这个文本上最大能写几个字符总数
    char=rndChar()
    # 获取一个随机的字符
    code.append(char)
    # 追加到code列表中
    h=random.randint(0,4)
    # 从0之4之间生成随机的数字
    draw.text([i * width / char_length , h], char,font=font,fill=rndColor())
    #写文本(坐标1是每次循环的i数值乘以宽度的值在除以最大显示数值,h是从0之4生成的随机数字,组成---->0,h ---->24,h---->48,h---->120,h)
    #设置font=font是默认自己选择的字体类型,fill是填充的颜色是执行随机颜色函数获取的随机颜色组


1.18.3 生成干扰点和圈
for i in range(5):
    \'\'\'
    生成干扰点
    \'\'\'
    draw.point([random.randint(0,width),random.randint(0,height)],fill=rndColor())
    #画点坐标1是0和,120之间随机生成的数值,坐标2是0和30随机生成的数值,填充的颜色是执行随机颜色函数获取的随机颜色组

for i in range(5):
    \'\'\'
     生成干扰圆圈
    \'\'\'
    draw.point([random.randint(0,width),random.randint(0,height)],fill=rndColor())
    #画点坐标1是0和,120之间随机生成的数值,坐标2是0和30随机生成的数值,填充的颜色是执行随机颜色函数获取的随机颜色组
    x=random.randint(0,width)
    # x=0到120之间随机生成的数字
    y=random.randint(0,height)
    # y=0到30之间随机生成的数字
    draw.arc((x,y,x+4,y+4),0,90,fill=rndColor())
    #设置圆存在的坐标和圆的坐标,设置角度的起始位置,设置角度的结束位置,填充的颜色是执行随机颜色函数获取的随机颜色组


1.18.4 生成干扰线并返回
for i in range(5):
    \'\'\'
    设置干扰的线
    \'\'\'
    x1=random.randint(0,width)
    # x1=从0到120之间生成随机的数字
    y1=random.randint(0,height)
    # y1=从0到30之间生成随机的数字
    x2 = random.randint(0, width)
    # x2 = 从0到120之间生成随机的数字
    y2 = random.randint(0, height)
    # y2=从0到30之间生成随机的数字
    draw.line((x1,y1,x2,y2),fill=rndColor())
    #设置画线的起始坐标和结束坐标,填充的颜色是执行随机颜色函数获取的随机颜色组
img=img.filter(ImageFilter.EDGE_ENHANCE_MORE)
# 设置滤镜,让验证码的色差更加明显
return img,\'\'.join(code)
#返回img整个画板,和拼接的随机字符(字符串类型)


1.19 注册头像上传预览
1.19.1 执行流程控制方案
《HTML》	
function bindAvatar() {
    if (window.URL.createObjectURL){
        bindAvatar2()
    }else if (window.FileReader){
        bindAvatar3()
    }else{
        bindAvatar1()
    }
}

执行流程控制方案



1.19.2 Ajax上传预览

function bindAvatar1() {
    $(\'#imgSelect\').change(function () {
        var obj = $(this)[0].files[0];
        document.getElementById(\'ifr\').onload=Idndfes;
        $(\'#fs\').submit()
        })
}
function Idndfes() {
    var content =document.getElementById(\'ifr\').contentWindow.document.body.innerText;
     tag=\'/\'+content;
     $(\'#previewImg\').attr(\'src\',tag)

}


1.19.3 本地上传预览

function bindAvatar2() {
    $(\'#imgSelect\').change(function () {
        var obj = $(this)[0].files[0];
        var v=window.URL.createObjectURL(obj);
        $(\'#previewImg\').attr(\'src\',v);
        $(\'#previewImg\').load(function () {
            window.URL.revokeObjectURL(v)
        })
    })
}


本地上传预览
function bindAvatar3() {
    $(\'#imgSelect\').change(function () {
        var obj = $(this)[0].files[0];
        var reader = new FileReader();
        reader.onload = function () {
            $(\'#previewImg\').attr(\'src\',this.result)
        };
        reader.readAsDataURL(obj);
    })
}


1.20 KindEditor插件
1.20.1 插件种类
CKEditor,TinyEditor( 博客园使用这个插件),UEEditor,KindEditor(汽车之家使用这个插件)这插件使用比较简单,而且官网还有中文解释
1. 安装: pip3 install beautifulsoup4
2. 下载: https://files.cnblogs.com/files/wupeiqi/kindeditor_a5.zip 
参考:http://www.cnblogs.com/wupeiqi/articles/6307554.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14	├── asp                          asp示例
├── asp.net                    asp.net示例
├── attached                  空文件夹,放置关联文件attached
├── examples                 HTML示例
├── jsp                          java示例
├── kindeditor-all-min.js 全部JS(压缩)
├── kindeditor-all.js        全部JS(未压缩)
├── kindeditor-min.js      仅KindEditor JS(压缩)
├── kindeditor.js            仅KindEditor JS(未压缩)
├── lang                        支持语言
├── license.txt               License
├── php                        PHP示例
├── plugins                    KindEditor内部使用的插件
└── themes                   KindEditor主题

 
1.20.2 Beautifulsoup4

插件知识点:
【find 查找第一个,
find_all查找所有的,
attrs 查看标签属性,del attrs【xx】删除属性
name标签名,
clear,删除标签的内容,
decompose 这个是删除整个标签,
decode 对象转换成字符串
encode对象转换成字节
】
通过这插件封装了一个防止xss攻击的过滤
from bs4 import BeautifulSoup
def xss(content):
    # 通过 BeautifulSoup4 这个模块把整个html和html等级关系  转换成对象和对象关系
    soup=BeautifulSoup(content,\'html.parser\')
    # 创建一个变量,参数是contenthtml字符串,html.parser是python内置的解析器,BeautifulSoup会根据这个解析器把html解析成一个一个的对象
    # tag=soup.find(name=\'img\')
    # 找个这里边的第一个img标签,name不是标签属性,而是标签的名字
    # tag.find(name=\'p\')
    # 这是在找到第一个img标签下继续查找里边的第一个p标签( 只有对象才能进行点的方式)
    # v=tag.find(name=p,attrs={\'id\':\'i2\'})
    # 这是设置查找标签的id是i2的标签(找到标签是p的,并且id是i2的标签)
    # print(tag)
    # b=soup.find_all(name=\'p\')
    # 这是查询所有的p标签,得到是一个列表类型
    ss=soup.find_all()
    # 这样就是查找所有的标签,查找顺序是找到某个标签的所有子标签后再进行查找下一个
    # valid_tag=[\'p\',\'div\',\'img\']
    # 设置一个白名单
    valid_tags={
        \'p\':[\'class\',\'id\'],
        \'img\':[\'src\'],
        \'div\':[\'class\']
    }

    # 这是设置一个白名单并且设置这些标签都能有那些属性
    for tag in ss:
        if tag.name not in valid_tags:
            tag. clear ()
    # clear 是删除标签内容,(decompose()这方法是删除整个标签)
        for k in list(tag.attrs.keys()):
            # attrs是拿到这个标签是什么属性,拿到都是字典格式,强型准换成列表进行循环,所以循环每个标签属性的键
            if k not in valid_tags[tag.name]:
                # 拿到每个标签的键在判断是不是不在白名单中,valid_tags[tag.name]是通过每次循环tag.name当做键去查看valid_tags这个字典对应的值
                del tag.attrs[k]
    #通过循环的标签得到属性,属性字典格式,之前循环过属性字典的键,通过属性字典的键找到相应的值,最后删除
    content_str=soup.decode()
    # soup本身是一个对象,如果想要拿他们的内容就要用decode方法转换成字符串
    # 爬虫也是用这个方式
    return content_str

1.21  多级评论
1.21.1 字符串格式化
定义一个F字符串格式化的函数
String.prototype.Format = function (arg) {
    var temp = this.replace(/\{\w+\}/,function (k,kk) {
        return arg[kk];

    });
    return temp;
};

字符串格式化调用方式

1.21.2多级评论在前端页面进行递归
class JsonCustomEncoder(json.JSONEncoder):
    def default(self, field):
        if isinstance(field, datetime):
            return field.strftime(\'%Y-%m-%d %H:%M:%S\');
        elif isinstance(field, date):
            return field.strftime(\'%Y-%m-%d\');
        else:
            return json.JSONEncoder.default(self, field);



def commcnts(request,nid):
    response={\'status\':True,\'data\':None,\'mag\':None}
    print(\'is nid...\',nid)
    try:
        comment_list=models.Comment.objects.filter(article_id=nid).values(
        "nid", "reply_id", "user__nid",
        "user__nickname", "create_time", "content",
        "reply__user__nickname",
        )
        # 连表查询内容
        comment_list_dict = {}
        # 定一个空字典
        for item in comment_list:
            #循环连表获取到的内容
            item[\'child\'] = []
            # 给循环的每一个字典添加一个空列表
            comment_list_dict[item[\'nid\']] = item
            #给新创建的字典添加一对数据,键是循环的字典里的每一个id 值是循环的每一个字典

        result = []
        # 定一个空列表
        for item in comment_list:
            # 循环连表获取到的内容
            pid = item[\'reply_id\']
            # 拿到每个字典reply_id键对应的值赋值给变量
            if pid:  # 如果pid为True
                comment_list_dict[pid][\'child\'].append(item)
            #刚刚判断回复的id有值,就把回复有值字典添加comment_lsit_dict字典的child键对应的列表中
            else:  # 为None
                result.append(item)  # result结果增加没有孩子的字典
        response[\'data\'] = result
        # 重写response字典data的值,值更换为刚刚得到的新的字典
        print(\'is result\',result)
    except Exception as e:
        # 捕捉异常
        response[\'status\'] = False
        # 如果有异常就重写response字典的status键对应的值,值更改为False
        response[\'msg\'] = str(e)
        #如果有异常就重写response字典的msg键对应的值,值更改为捕捉到的异常信息     
    return HttpResponse(json.dumps(response, cls=JsonCustomEncoder))
# 返回一个字符串返回的有response的内容和一个类和对象




{#==================================定一个函数添加评论楼的信息============================#}
    function commentTree(comment_list) {
{#定义一个函数,这个函数的参数 comment_list 是在被调用时( var comment = commentTree(arg.data);)arg.data传值过来的#}
        var comment_str = "<div class=comment>";
{#定一个自变量,内容是字符串#}
        $.each(comment_list, function (k,row) {
{#循环一下 comment_list 列表 分别拿到索引和参数,函数中只写一个的时候获取到的是索引值#}
{#            var temp = "<div class=\'content\'>" + row.content + "</div>";#}
            console.log(comment_list);
            var temp = "<div class=\'content\'>{user}-{content}--{time}</div>";
{#定一个变量,内容是字符串但是有机个字符占位符#}
            var temp1 = temp.Format({ user:row.user__nickname, content:row.content, time: row.create_time});
{#定一个一个变量接受刚刚定义的变量格式化的内容#}
            comment_str += temp1;
{#每循环一次都把得到的内容添加到 comment_str 变量中#}
{#            comment_str += temp;#}
            if (row.child.length > 0) {
{#通过row.child 每次循环的字典中的孩子长度判断孩子存不存在#}
                comment_str += commentTree(row.child)
{#孩子存在的就进行递归的查找和添加#}
            }
        });

        comment_str += \'</dic>\';
{#在循环完以后在给变量设置一个div的结束标签#}
        return comment_str
{#最后把得到字符串变量返回#}
    }


1.21.3多级评论在后端页面进行递归

comment_list = models.Comment.objects.filter(article_id=user_id).values(
    "nid", "reply_id", "user__nid",
    "user__nickname", "create_time", "content",
    "reply__user__nickname"
)
print(\'is comment_list\',comment_list)

comment_list_dict = {}
for item in comment_list:
    item[\'child\'] = []
    comment_list_dict[item[\'nid\']] = item

print(comment_list_dict)
result = []
for item in comment_list:
    pid = item[\'reply_id\']
    if pid:  # 如果pid为True
        comment_list_dict[pid][\'child\'].append(item)
    else:  # 为None
        result.append(item)  # result结果增加item

from check.comment import comment_tree
comment_str = comment_tree(result)
caregory_list, tag_lsit, time_list, article_list, fen, guanzhu, users_list = fiflers(request, blog)
print(\'title,user_id\',title,user_id)
xx=models.ArticleDetail.objects.filter(id=user_id)
print(xx)

return render(request,\'xiangxi.html\',{
    \'xx\':xx,
    \'caregory_list\': caregory_list,
    \'tag_lsit\': tag_lsit,
    \'time_list\': time_list,
    \'article_list\': article_list,
    \'fen\': fen,
    \'guanzhu\': guanzhu,
    \'users_list\': users_list,
    \'blog\':blog,
    \'user_id\':user_id,
    "comment_str":comment_str
})

1.21.4 前端补充
1.调用对象方法时,通过调用类的prototype中的方法(prototype其实就是我们的原型),基于prototype可以进行扩展

2.在前端可以写正则表达式(/正则/)在正则中加g是拿全部,不加就是拿一个

3.一个字符串可以replace 进行替换

在前端页面通过一条命令就可以更改已经写好的页面内容

在DOM里要对标签进行事件绑定,需要循环这些对象然后进行事件绑定,
在jquery 里获取很多标签不需要进行循环绑定事件

 
1.21.5前端正则
v1=\' i am {name},age is{age}

v2= v1.replace(/\{(\w+)\)}/g,function(k){})   k代表正则表达式中匹配到的值
对v1字符串进行替换,在前端使用正则//放在俩个斜杠里边 正则用{}包起来,注意在前端使用正则使用大括号需要进行转义\{\},再正则中如果有g存在就表示全局。

在正则中加括号就是进行分组获取的是正则里边的内容比如:v1=\' i am {name},age is{age} 获取的内容就是name 和age ,如果不进行分组得到的内容就是{name}和{age}

v2= v1.replace(/\{(\w+)\)}/g,function(k,kk){return 11})
这个有几个正则分组在function中就有几个接收值,在中括号中return什么值就 把正则的替换成什么   
1.22权限管理
1.22.1 权限和动态生成菜单(版本一)
Role basic access control 

1.	用户登录先在数据库中获取用户和密码来判断用户是否存在,
2.	用户存在后在从数据库中获取当前用户的所有权限(权限就是用户访问的url)以及权限对应的操作方法,和所有的菜单信息全部写到当前用户的session中
3.	把所有的权限写入session中用于判断当前用不是否拥有这个权限,把所有的菜单写入session中是用于生成动态多级菜单(当用户的权限url特别多的时候要生成动态菜单)

4.	通过用户访问的页面,我们可以通过request.path_info获取当前的url在从当期用户的session中查看当前登录的用户有没有这个权限,权限有了以后我们要获取通过url携带的md操作这个url权限有没有响应的操作方法,如果有md相应的操作我们在在views逻辑中编写相应操作的代码,例如:md=get
5.	因为我们自定义的数据库中get是查看功能,那我们就返回相应查看的操作,在显示的页面中还显示着当前用户都具有哪些操作,就可以通过reques.permission_code_list中内容判断显示哪些操作按钮,数据多的时候就把权限挂靠在菜单中,然后再前端递归的显示菜单以及当前用户的权限url。 
6.	在我们rbac包中封装了中间件,service,simple_tag,以及那7张关系表 还封装了初始化操作,初始化就是把我们获取到的数据更改成我们想要的数据类型在写入到session中

7.	当一个用户访经过数据库验证和中间件的验证到达视图函数是我们可以通过当前用户的session中保存的判断当前是以什么操作进行访问的,我们在在视图函数中做出相应的操作

8.	我们又对其做了相应的判断是否展开这个菜单和是否显示这些菜单,最后又添加js操作当点击这个菜单是展开当前点击的菜单,显示当前菜单的子菜单或者权限url


1.22.2 权限和动态生成菜单(版本二)

权限:
登录成功:角色1 所带的功能,url

基本原理:
一、角色多管理(基于权限)
a:登录(login)
  1.将用户信息存储放在session里(检验是否登录,到时候直接去session里面获取响应的值)
  2.将权限及菜单信息放置session里(检验是否有权限访问)
    权限里面本质放置的是(url(如 \'/order.html\')和action方法[get,post,edit,del])

b:访问网站其他功能:
   一 需要获取当前的url 获取url  request.path_info
       url会带着\'/order.html?md=get\' 匹配
   一 当获取到的url需要去自己session里面进行权限匹配 看有无权限访问 如果匹配不成功则没有权限访问
       当url匹配成功了之后接着会匹配action[get,post,edit,del]
 	
       request.permission_code=\'EDIT\' 当前使用的权限
       request.permission_code_list=[get,post,edit,del] 所有方法的权限
       这两个值会传给request 
   一 会将这两个参数放置中间件中,在由中间件返回给视图函数,在视图函数中判断这两个值该怎么写业务逻辑

c:到达视图函数
    request在获取 request.permission_code=\'EDIT\' 在视图函数里面判断你是以什么方式来权限访问,做业务逻辑编写
                           request.permission_code_list=[get,post,edit,del]  一般会用在前端生成按钮 判断你所在的权限在不在这些里面方法里面,然后根据不同的方式生成不同的按钮如 添加 删除 编辑 查看详细 

d:模板 request.permission_code_list=[get,post,edit,del] 显示相应的按钮

e:创建动态菜单【多级菜单】
     一 去session里面获取菜单信息 (用于生产动态多级菜单)
          在登录的时候已经把权限和菜单都放在session里面
          这样不需要平凡的去数据库里面获取值
         当前用户权限
         及所有菜单
     一 需要把权限挂靠在菜单上
     一 多级菜单之间 需要把菜单与菜单挂靠在一起(菜单父子的处理 把高级别的菜单显示出来,还在菜单放在里面)
     一 递归生成菜单展示在页面上 如果要显示的好看 要辅助css 和 js (一般推荐使用simple_tag)前端要想使用直接在模板上调用


二、使用:
封装rbac app项目
1.导入rbac
   models 里面有七张关系表 (用户表,角色表,用户角色表 权限表,菜单表,操作表,权限操作菜单表)
   中间件
   service 用于初始化操作
   simple_tag(多级菜单使用)
   css
2.settings里面注册rbac
3.用户登录
   导入from rbac.service import initial_permission #导入初始值权限
   初始化权限信息:initail_permission(request,obj.user_id)
4.settings配置中间件
  rbac.middleware.rbac.RbacMiddleware
5.生成动态菜单 前端导入{%rbac_menu request%} 获取到菜单名称

其他配置:在settings里面配置
#Session中保存权限信息的Key
RBAC_PERMISSION_SESSION_KEY = "rbac_permission_session_key"

#Http请求中传入的参数,根据其获取GET、POST、EDIT等检测用户是否具有相应权限
# 例如:
#       http://www.example.com?md=get   表示获取
#       http://www.example.com?md=post  表示添加
#       http://www.example.com?md=delete  表示删除

RBAC_QUERY_KEY = "md"  方式
RBAC_DEFAULT_QUERY_VALUE="look"  默认值为look 查看

#无权访问时,页面提示信息
RBAC_PERMISSION_MSG = "无权限访问"   页面到时候显示无限期访问


#Session中保存菜单和权限信息的Key
RBAC_MENU_PERMISSION_SESSION_KEY = "rbac_menu_permission_session_key"
RBAC_MENU_KEY = "rbac_menu_key"
RBAC_MENU_PERMISSION_KEY = "rbac_menu_permission_key"

#菜单主题
RBAC_THEME = "default"
1.23报障系统
1.23.1 报障系统以及处理报障
设置登录页面,用户登录后session中写入菜单,权限以及操的方法,
from django.shortcuts import render,HttpResponse,redirect
from web import models
from django.db.models import Count
from rbac.service import initial_permission #导入初始值权限
from django.db.models import Q
import datetime
import json

def login(request):
    if request.method == "GET":
        return render(request,\'login.html\')
    else:
        u=request.POST.get("username")
        p=request.POST.get("password")
        obj=models.UserInfo.objects.filter(user__username=u,user__password=p).first()
        # 从数据库中匹配用户密码
        if obj: #登录成功 获取当前用户权限和菜单 去配置文件中获取key,写入session中
            request.session["user_info"]={\'username\':u,\'password\':p,\'nickname\':obj.nickname,\'nid\':obj.id}
            initial_permission(request,obj.user_id)
        #通过用户id 和request进行初始化权限
                                                    #初始化权限,获取当前用户权限并添加到session中
                                                     #将当前用户权限信息转换为以下格式,并将其添                                                        Session中
            return redirect(\'/index.html\')
        #如果用户和密码存在数据库中就跳转到主页
        else:
            return redirect(\'/login.html\')
        #否则就跳转到登录页面


def index(request):
    if not request.session.get(\'user_info\'):
        # 如果session中没有值
        return render(request,\'login.html\')
        #就跳转到登录页面
    return render(request,\'index.html\')
    #session中有值,就跳转到主页


def trouble(request):
    if request.permission_code == \'LOOK\':
        # 判断当前用户的权限是LOOK
        trouble_list=models.Order.objects.filter(create_user_id=request.session[\'user_info\'][\'nid\'])
        # 通过session中的id查看当前用户的的信息
        return render(request,\'trouble.html\',{\'trouble_list\':trouble_list})
        #把得到当前用户详细信息返回给html页面渲染
    elif request.permission_code == \'DEL\':
        # 判断当前用户的权限是DEL
        nid=request.GET.get(\'nid\')
        # 获取通过 get请求携带的id
        models.Order.objects.filter(create_user_id=request.session[\'user_info\'][\'nid\'],id=nid).delete()
        # 获取当前的用户id和创建订单的用户id在进行删除(为了防止,通过id删除别的用户的报障内容)
        return redirect(\'/trouble.html\')
        #重定向到/trouble.html url
    elif request.permission_code == \'POST\':
        # 判断当前用户的权限是POST
        if request.method == \'GET\':
            # 如果是以GET方式访问
            return render(request,\'trouble_add.html\')
            #就返回一个html页面进行渲染
        else:
            # 不是以GET请求来的请求
            title=request.POST.get(\'title\')
            # 获取标题
            content=request.POST.get(\'content\')
            # 获取内容
            models.Order.objects.create(title=title,detail=content,create_user_id=request.session[\'user_info\'][\'nid\'])
            # 增加一条新的数据,标题等于刚获取到,内容也前端页面获取到的,发布报障用户id等于session中携带的id
            return redirect(\'/trouble.html\')
            #重定向到 /trouble.html url
    elif request.permission_code == \'EDIT\':
        # 判断当前用户的权限是EDIT
        if request.method == \'GET\':
            # 如果是以GET请求执行下边代码
            order_list=models.Order.objects.filter(create_user_id=request.session[\'user_info\'][\'nid\'])
            # 通过当前登录的用户查询相关的报障信息
            return render(request,\'trouble_edit.html\',{\'order_list\':order_list})
            #把查询到报障信息返回到前端页面
        else:
            title=request.POST.get(\'title\')
            # 通过请求体中获取数据
            detail=request.POST.get(\'detail\')
            # 通过请求体中获取数据
            models.Order.objects.filter(create_user_id=request.session[\'user_info\'][\'nid\']).update(title=title,detail=detail)
            # 找到发布报障人的id和当前的登录人的id相同的报障单进行更新修改
            return redirect(\'/trouble.html\')
            #修改完成后重定向到   /trouble.html  url
    elif request.permission_code == \'DETAIL\':
        # 判断通过中间件重写的方法是不是等于DETAIL
        ordet_list=models.Order.objects.filter(create_user_id=request.session[\'user_info\'][\'nid\'])
        #通过当前登录的用户查询相关的报障信息
        return render(request,\'trouble_detail.html\',{\'ordet_list\':ordet_list})
        # 把查询到报障信息返回到前端页面

def trouble_kill(request):
    nid = request.session[\'user_info\'][\'nid\']
    # 获取当前登录用户的id
    if request.permission_code == \'LOOK\':
        # 判断通过中间件重新赋值的操作是不是查看
        if request.method == \'GET\':
            # 判断是不是get请求
            ordet_list = models.Order.objects.filter(Q(status=1)|Q(processor=nid))
            # 获取报障单是未完成,以及和自己处理过的报障内容
            return render(request, \'trouble_kill.html\',{\'ordet_list\': ordet_list, })
            # 把获取的未完成和已完成的列表返回给前端页面
    elif request.permission_code == \'EDIT\':
        # 判断通过中间件重新赋值的操作是不是编辑
        if request.method == \'GET\':
            # 判断是不是get请求
            ordr_id=request.GET.get(\'nid\')
            # 获取报障单的id
            if models.Order.objects.filter(id=ordr_id,processor_id=nid,status=2):
                #通过报障的id和当前登录的用户和故障处理人查看,如果为真就表示抢单成功,但是未处理
                obj = models.Order.objects.filter(id=ordr_id).first()
                #获取报障单是提交时携带的id的内容
                return render(request,\'trouble_kill_edit.html\',{\'obj\':obj})
                #获取到报障单的相关信息,返回给前端页面
            v = models.Order.objects.filter(id=ordr_id,status=1).update(processor_id=nid,status=2)
            #通过报障单的id,同时进行抢单,抢到单后就执行更新数据(在更新后v会得到一个数据,数据是受影响的行数)
            if not v:
            #如果v不为True,就是代表报障单没抢到
                return HttpResponse(\'小伙子,手速太慢了,回家多练练吧!!!\')
            # 在前前端显示提示
            else:
                obj = models.Order.objects.filter(id=ordr_id).first()
                #获取当前报障单id相同的相关信息,表示抢到报障单
                return render(request,\'trouble_kill_edit.html\',{\'obj\':obj})
                #获取报障单的相关信息返回给前端
        else:
            # 否则
            solution=request.POST.get(\'solution\')
            # 通过请求体中获取数据
            order_id=request.GET.get(\'nid\')
            # 通过请求头中获取数据
            models.Order.objects.filter(id=order_id,processor_id=nid).update(status=3,solution=solution,ptime=datetime.datetime.now())
            # 通过查询报障单id号,和当前的用户的id获取的内容更新内容,状态标识符改成3,处理意见内容,时间是date当前时间
            return redirect(\'/trouble-kill.html\')
            #更新完数据后就跳转带 /trouble-kill.html  url

处理报障中有个抢单功能
用户权限等级划分
用户功能分配
1.23.2 UUid
UUID是一种可以生成不重复的字符串的一种方式,使用方式
uuid.uuid4()

主要生成原理是,根据主板和当前时间以及一些硬件信息,不重复的字符串
1.23.3 数据库取时间(分组)
一. 数据库取时间,按年月分组
time_list = models.Article.objects.filter(blog=blog).extra(
        select={"c": "DATE_FORMAT(create_time,\'%%Y-%%m\')"}).values("c").annotate(ct=Count("nid"))
二. 前端显示

{{ row.ctime|date:"Y-m-d H:i:s"  }}

1.24前端页面视图
1.24.1 后端饼图&折线图(视图)
def report(request):
    if request.permission_code == \'LOOK\':
        #判断通过中间件重写的值是不是等于LOOK
        if request.method == \'GET\':
            #判断是不是以get请求
            return render(request,\'report.html\')
            #到前端显示内容
        else:
            result=models.Order.objects.filter(status=3).values_list(\'processor__nickname\').annotate(C=Count(1))
            # 查询报障单的状态码等于3的,通过处理人的名称进行去重分组,(注意报障人姓名要设置成唯一的不然数据会出现混乱)
            ymd_list = models.Order.objects.filter(status=3).extra(
                select={\'ymd\': "strftime(\'%%s\',strftime(\'%%Y-%%m-%%d\',ptime))"}).values(
                \'processor_id\',
                \'processor__nickname\',
                \'ymd\').annotate(ct=Count(\'id\'))
            # 查询报障单的状态码等于3的,在通过额外的查询把数据库的时间格式化成年月日并进行去重和组合后又格式化成时间戳秒显示的格式,在以解决报障的人的id和名称进行分组,并统计总数
            ymd_dict={}
            # 创建一个新字典
            for row in ymd_list:
                # 循环刚刚以时间组合的列表
                key = row[\'processor_id\']
                # 获取到每次循环的内容赋值到key变量中
                if key in ymd_dict:
                    # 判断如果变量 key的值在字典ymd_dict中
                    ymd_dict[key][\'data\'].append([float(row[\'ymd\'])*1000,row[\'ct\']])
                    # 通过key找到值,因为是字典嵌套字典又通过[\'data\']找到列表把数据追加到列表中
                else:
                    ymd_dict[key] = {\'name\': row[\'processor__nickname\'],\'data\': [[float(row[\'ymd\'])*1000, row[\'ct\']], ]}
                    # 判断如果字典中没有变量key就创建一个字典,键就是key变量,值是一个字典,
            response={
                # 定一个字典,字典中存放俩组数据
                \'pie\':list(result),
                # 饼图对应的数据
                \'zhexian\':list(ymd_dict.values())
                #折线对应的数据是字典,只把字典的值以列表的形式存放
            }
            return HttpResponse(json.dumps(response))
            #把刚创建的字典通过json序列化成字符串返回

1.24.1 前端饼图&折线图(视图)
{% extends \'layout.html\' %}
{#继承母板#}
{% block content %}
{#重写母板内容#}
    <div id="container" style="min-width: 300px;height: 300px"></div>
{#这个是饼图的显示大小#}
    <div id="container2" style="min-width: 500px;height: 500px"></div>
{#这个是折线的显示大小#}
{% endblock %}

{% block js %}
{#重写母板的js#}
    <script src="https://img.hcharts.cn/highcharts/highcharts.js"></script>
    <script src="https://img.hcharts.cn/highcharts/modules/exporting.js"></script>
    <script src="https://img.hcharts.cn/highcharts-plugins/highcharts-zh_CN.js"></script>
    {#使用在线插件#}
    <script>
        $(function () {
        {#页面加载时执行这个函数#}
            Highcharts.setOptions({
            {##}
                global: {
                    useUTC: false
                }
            });
            $.ajax({
            {#通过ajax向后台获取数据#}
                url: \'/report.html\',
                {#后台url#}
                type: "POST",
                {#提交方式#}
                data: {\'csrfmiddlewaretoken\': \'{{ csrf_token }}\'},
                {#通过data把 csrf_token携带上#}
                dataType: \'JSON\',
                {#以json格式转换数据#}
                success: function (arg) {
                {#设置回调函数,获取后台返回的值#}
                    console.log(arg);
                    {#打印后台返回的值#}
                    $(\'#container\').highcharts({
                    {#找到标签设置图例的属性和参数#}
                        chart: {
                            plotBackgroundColor: null,
                            plotBorderWidth: null,
                            plotShadow: false
                        },
                        title: {
                            text: \'运维人员处理报障占比\'
                        {#饼图的标题#}
                        },
                        tooltip: {
                            headerFormat: \'{series.name}<br>\',
                            pointFormat: \'{point.name}: <b>{point.percentage:.1f}%</b>\'
                        },
                        plotOptions: {
                            pie: {
                                allowPointSelect: true,
                                cursor: \'pointer\',
                                dataLabels: {
                                    enabled: true,
                                    format: \'<b>{point.name}</b>: {point.percentage:.1f} %\',
                                    style: {
                                        color: (Highcharts.theme && Highcharts.theme.contrastTextColor) || \'black\'
                                    }
                                }
                            }
                        },
                        series: [{
                            type: \'pie\',
                            name: \'运维人员处理报障占比\',
                            data: arg.pie
                        }]
                    });

                    Highcharts.chart(\'container2\', {
                        title: {
                            text: \'每日处理订单详细\',
                            x: -20 //center
                        },
                        subtitle: {
                            text: \'...\',
                            x: -20
                        },
                        legend: {
                            layout: \'horizontal\',

                            align: \'center\',
                            verticalAlign: \'bottom\',
                            borderWidth: 1
                        },
                        xAxis:{
                            type:\'datetime\',
                            labels:{
                                formatter:function(){
                                    return Highcharts.dateFormat("%Y-%m-%d",this.value);
                                    //return this.value;
                                }
                            },
{#                            minTickInterval:24#}
                        },
                        series: arg.zhexian
                    });
                }
            });



        })
    </script>

{% endblock %}

1.25资产采集
【公司对运维的远景】
(1)自动装机(服务器)
(2)配置管理(服务器)
(3)监控系统(这个机器的整个状态)
(4)远程连接(服务器)堡垒机 所有的客户端命令都先到堡垒机进行记录(记录当前用户记录用户发出的指令)在发送给服务器(有记录可以查看)
上述操作都是对某台服务器的操作
公司如果使用Excel表格就会造成信息不准确,给别人查看的时候只能是截图
把Excel替换成资产自动 采集并汇报入库
服务端与客户端连接方式共分为四种分别是一下内容
1.25.1 Agent
B=Subprocess.getoutput(‘ipconfig) 是将命令执行并且获取字符串赋值给B
使用Agent的前提条件是客户端(服务器)特别多的时候使用这种方法。这个方法的弊端就是服务器比较占资源,性能会变低。

 


Agent每一个客户端执行的 
import subprocess
import requests
url="http://127.0.0.1:8000/asset.html"
# 设置一个url,也就是api的地址
value1= subprocess.getoutput(\'ipconfig\')
# 通过subprocess.getoutput获取括号内的命令执行后转成的字符串
value2= subprocess.getoutput(\'dir\')
# 通过subprocess.getoutput获取括号内的命令执行后转成的字符串
response=requests.post(url,data={\'k1\':value1,\'k2\':value2})
# 通过requests模块以post请求携带字典内容发送到url中,在api端我们设置了返回值,所以当前有一个接收的值
print(response.text)
# 打印接收参数的内容
  
API
from django.shortcuts import render,HttpResponse

# Create your views here.


def asset(request):
    if request.method==\'POST\':
        print(request.POST.get(\'a\'))
        print(1)
        return HttpResponse(\'收到了\')
    else:
        return HttpResponse(\'没有收到\')

1.25.2 SSH类(Pramiko)
1.通过中控机远程连接多个客户端,远程登录到服务器在执行命令查看服务器的信息,在中控机部署一份python脚本,通过中控机远程连接服务器,执行命令,获取结果

ssh中控主机的代码
import paramiko
ssh=paramiko.SSHClient()
# 允许连接不在know_hosts文件中的主机
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 连接服务器
ssh.connect(hostname=\'192.168.227.146\',port=22,username=\'root\',password=\'123456\')

stdin,stdout,stderr = ssh.exec_command(\'ifconfig\')
# 执行命令
result = stdout.read()
# 获取命令结果

ssh.close()
# 关闭连接

print(result)
url="http://127.0.0.1:8000/asset.html"
# 设置一个url,也就是api的地址
response=requests.post(url,data={\'k1\':\'value1\',\'k2\':\'value2\'})
# 通过requests模块以post请求携带字典内容发送到url中,在api端我们设置了返回值,所以当前有一个接收的值
print(response.text)
# 打印接收参数的内容



1.25.3 saltstack(Python开发)安装与配置
CentOS7  安装
yum install -y  https://repo.saltstack.com/yum/redhat/salt-repo-latest-2.el7.noarch.rpm 
在线yum源连接  


yum install salt-master -y
通过yum安装服务端


yum install salt-minion -y
通过yum安装客户端



CentOS6 安装
 https://repo.saltstack.com/yum/redhat/salt-repo-latest-2.el6.noarch.rpm 

  

yum install salt-master -y

yum install salt-minion -y 

在服务端的编辑器中修改(vim /etc/salt/master)
找到master 192.168.227.144    这个地址是服务端本机的地址

安装完成并配置完文件后启动客户端
service salt- master start


在客户端的编辑器中修改(vim /etc/salt/minion)
找到master 192.168.227.144   这个地址是服务端的地址
或
    master:
        - 10.211.55.4
        - 10.211.55.5
    random_master: True

id: c2.salt.com                    # 客户端在salt-master中显示的唯一ID

安装完成并配置完文件后启动服务端

service salt-minion start


需要注意:在服务端和客户端都要把防火墙关闭,不然就不能接受到访问

saltstack(Python开发)授权

salt-key -L                    # 查看已授权和未授权的slave
salt-key -a  salve_id      # 接受指定id的salve
salt-key -r  salve_id      # 拒绝指定id的salve
salt-key -d  salve_id      # 删除指定id的salve

在客户端和服务端的配置文件都没有问题后,能够通讯后就查看授权
 

1.25.4 puppet(ruby)
1.老公司一般使用这种方式
puppet 原理每个服务器会定时回向api发送自己机器相关数据(定时默认为30分钟)然后通过api向数据库提交数据


1.26资产管理采集方式
1.AGENT
2.SSH
3.saltstack
 


代码的使用要遵循高内聚低耦合标准

Bin可执行文件
Config配置文件【配置文件的名称要大写】
Lib 公共的模块
Src 业务逻辑代码

我们要把用用户自定义的配置文件和内置配置文件通过一个功能全部可以获取

我们在导入包的时候就执行了包里__int__方法,这个方法就获取所有的插件,然后把插件的内容进行采集


导入字符模块
import importlib
# 导入这个模块用于通过字符串获取模块
m = importlib.import_module(settings_module)
# 根据字符串导入模块【settings_module】是一个模块,然后赋值给变量m,m是一个对象


获取当前执行主脚本的方法:
1.26.1 python __file__ 与argv[0]
__file__ 是用来获得模块所在的路径的,这可能得到的是一个相对路径


1.	为什么开发CMDB?
EXCCEL维护资产信息 ,资产变更时难以保证正确性

信息交换不方便

就要开发一个自动采集资产工具,目标:自动汇报,保存变更记录

最终目标:实现运维自动化

2.	CMDB架构是什么模式的?
-	 资产采集(资产采集)
-	API (接收数据保存入库,对外提供数据接口)
-	后台管理

3.	你负责做什么?
【一般是2,3人共同开发 
自己对那个部分熟就说那个】

资产采集,三种方案
-agent
-paramiko
-saltstack
提高项目的扩展性,参开Django:配置文件,中间件(反射),
错误堆栈信息

4.	项目周期2-3月

5.	有没有遇到难题(坑)?
Linux:不太熟
唯一标识:大问题(大坑)



1.26.2资产管理(唯一标识)
在做任何事之前要有标准化 
物理机:SN号是物理机的主板号也就是唯一标识(虚拟机不需要采集信息)

标准化:
-主机名不能重复,主机名是唯一标识
	-流程
		资产录入,机房,机柜,机柜位置,主机名
装机时,需要将服务信息录入CMDB
资产采集 主机名
虚拟机:
A.	装系统初始化软件(CMDB),运行CMDB
-通过命令获取主机名
-写入本地指定文件
	B.将资产信息发送到API

B.	获取资产信息
-本地文件主机名 !=  命令获取的主机名(按照文件中的主机信息)
-本地文件主机名 ==  命令获取的主机名
最终流程:
1.	标准化:主机名不重复,流程标准化(装机同时,主机名在cmdb中设置)
2.	服务器资产采集(Agent)
a)	第一次:文件不存在或者内容为空
b)	采集资产【主机名写入文件】
c)	第N次 采集资产,主机名文件中获取
SSH/SALT:
	中控机:获取未采集主机名列表【服务器名称信息】在返回给中控机

1.26.3进程池或线池
Python2 :
	线程池 无
	进程池 有
Python3
	线程池 有
	进程池 有

		代码:
			import time
			from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor

			def task(i):
				time.sleep(1)
				print(i)

			p = ThreadPoolExecutor(10)
			创建线程池,表示最多开10个线程
			for row in range(100):
				p.submit(task,row)参数1是任务名称,参数2是执行任务的参数

			p = ProcessPoolExecutor 集合(10)
			创建进程程池,表示最多开10个线程
			for row in range(100):
				p.submit(task,row)参数1是任务名称,参数2是执行任务的参数



1.26.4 三层架构
数据库访问层
	User:增,删,改
业务处理层:
	用户
	授权
UI  UI展示层



1.27 加装了装饰器的API验证
1.API 的验证一共分为三关
	关卡一是访问请求来了以后先验证数据中携带的时间和服务端的时间如果时长差别很大就不能通过,	
	关卡二是验证客户端的密钥和客户端当前时间组合的md5,然后再在服务端获取到客户端携带的时间和保存在服务端的密钥组合成的md5 进行匹配
	关卡三是在额定的时间里创建一个字典,判断如果在规定的时间内字典中还存在就表示已经有访问请求来过了
def auth(func):
    def wrapper(*args,**kwargs):
        request= args[0]
        api_key_record={}
        # print(\'is request.META....\',request.META)
        # api_key_record 是存放来访问的信息,key是md5后的密文,值是超时时间
        client_md5_time_key=request.META.get(\'HTTP_OPENKEY\')
        # 从请求中获取加密的内容
        client_md5_key,client_ctime = client_md5_time_key.split(\'|\')
        # 在获取的加密串中通过管道符进行分割
        client_ctime = float(client_ctime)
        # 将字符串类型转换成浮点型
        server_time = time.time()
        # 获取服务端的当前时间

        # ===========第一关===========
        print(\'sdfsdf\')
        if server_time - client_ctime > 10:
        # 如果服务端的时间减去客户端的时间结果等于10就表示,客户端的时间已超时
            return HttpResponse(\'【第一关】 小伙子,别唬我,时间太长了!\')
            # 返回提示信息

        # ===========第二关===========
        print(\'is 2\')
        temp = "%s|%s"%(settings.AUTH_KEY,client_ctime)
        # 字符串格式化 拼接
        m = hashlib.md5()
        # 创建一个哈西对象
        m.update(bytes(temp,encoding=\'utf-8\'))
        #
        server_md5_key = m.hexdigest()
        print(\'is 2.5\')
        if server_md5_key != client_md5_key:
            print(\'wefwf\')
        # 如果服务端转换成的加密字符串和客户端转换成的加密字符串不一样
            return HttpResponse(\'【第二关】小子,你不是修改时间了!\')
            # 返回提示信息
        for k,v in list(api_key_record.keys()):
            v = api_key_record[k]
            if server_time >v:
                del api_key_record[k]

        # ===========第三关===========
        print(\'is 3\')
        if client_md5_time_key in api_key_record:

            # 如果加密的字符串在列表中
            return HttpResponse(\'【第三关】有人已经来过了。。。。\')
            # 就表示已经访问过了
        else:
            api_key_record[client_md5_time_key] = float(client_ctime)+10
        print(1121)
        # if server_md5_key != client_md5_key:
        #     return HttpResponse(\'认证失败、、、、\')
        res = func(*args,**kwargs)
        return res
    return wrapper



1.28 数据加密
数据加密需要注意,
	在加密之前要对加密的内容进行json转换,转换成字符串
	然后在加密时对加密的内容转化成的字节长度要求必须是16个字节或者16的倍数
from Crypto.Cipher import AES
import json
def encrypt(message):
    \'\'\'
    因为这个数据加密有一个需要【加密的内容必须是16个字节或者16个字节的倍数】
    :param message: 
    :return: 
    \'\'\'
    key = b\'dafsdfewsddfafds\'
    cipher = AES.new(key,AES.MODE_CBC,key)
    ba_data = bytearray(str(message),encoding=\'utf-8\')
    # 把传进来的数据通过字节数组转化成字节,注意要加密的一定是字符串类型
    v1 = len(ba_data)
    # 获取转换成字节的长度
    v2 = v1 % 16
    # 求v1 和16的余数
    if v2 == 0:
        # 如果他们的余数等于0
        v3 =16
        # 就给v3 进行赋值操作
    else:
        # 不等于 0
        v3 = 16 -v2
        # v3 就等于16 减去他们的余数
        for i in range(v3):
            # 循环v3
            ba_data.append(v3)
            # 给字节不够16为进行补充
        final_data = bytes(ba_data)
        # 把字节数据转换成字节在赋值给final_data变量
        msg = cipher.encrypt(final_data)
        # 把变量final_data加密然后赋值给msg
        return msg
        # 返回加密后的内容


def decrypt(msg):
    key= b\'dafsdfewsddfafds\'
    # 设定一个加密的密钥
    cipher = AES.new(key,AES.MODE_CBC,key)
    result = cipher.decrypt(msg)
    # 把加密的内容进行解密,解密的内容赋值给result变量中
    data = result[0:-result[-1]]
    # 通过索引获取最后一位的内容,然后在切片获取0到最后一位的数值内容,把加密的内容还原
    return str(data,encoding=\'utf-8\')
    # 把字节转换成字符串进行返回
    # return json.dumps(data)


1.29 后台显示


1.30 GIT
简单总结:
Vss:不用
  Svn:集中式版本控制系统:服务端有所有版本;客户端只有一个版本
GIT 分布式版本控制系统(软件)
设计到版本更新就会使用

常用命令:
1
2
3
4
5
6
7
8
9
10
11	cd                                                  - 进入程序目录
git init                                            - 创建一个空的Git仓库或重新初始化一个现有的
git config --global user.name "John Doe"            - 设置用户名
git config --global user.email johndoe@example.com  - 设置电子邮件地址
git status                                          - 查看状态
git add [file]                                      - 将文件从《工作区》提交到《暂存区》
git commit -m "提交信息"                            - 将文件从《暂存区》提交到《版本库 》
git rm --cached <file>                                - 将文件从《暂存区》或《版本库》删除文件
工作区:ls                                          - 查看工作区
暂存区:git ls-files -s                             - 查看暂存区和版本库
版本库:git ls-tree HEAD                            - 查看版本库<code class="python comments"><br></code>


1.版本控制
	blogv1
	...
	blogv100
	
简单总结:
	vss 不用
	svn 集中式版本控制系统,服务端有所有版本,客户端只用一个版本
	
	共同点: 
		服务端(保存代码的仓库)
		客户端(个人用户7777)
	
	git 分布式版本控制系统
	
原始方法:
	a.创业初期:系统上线

	b.开发新功能

	c.紧急修复bug
		从线上拷贝一份,修复bug,然后上线
		
git方式:
	(无服务)
	进入程序目录
	git init   初始化
	生成一个.get文件
	git status  查看状态
	
	git add  文件名
	
	git commit -m \'提交信息\'
	  
	必须在有.gitt文件目录里进行git操作 
	
	
回滚:
	git reset -- soft mix hard
	git reset -- hard 版本号
	
	git reflog
	整个周期的状态
	git log 
	
	git reflog
	git reset 日志号
	git checkout 
	
	git stash
	git stash list
	git stash pop
	
	
	git branch
	git branch dev 
	
	git checkout --文件名
	git checkout dev
	
	
	git merge bug  分支合并
	(无冲突)
	
	有冲突: 手动解决
		解决完成后需要重新提交
	
	git branch -d bug
	
	
	
	https://github.com/baoyuanguo/wwwww.git

	
	命令总结:
		初始化  
			- git init【创建了一个.git文件,也就是版本库】
		添加到暂存区 
			-git add 【.所有的意思,add后边设置文件名】
		添加到分支中 
			-git commit -m \'[描述信息]\'
		
		查看状态
			- git status
			
		查看分支
			- git ls-tree head 
		
		查看版本库中的所有内容
			- git ls-files -s 
		
			
		配置用户信息:
			git config -- local user.name \'名称\'
			git config -- local user。email\'邮箱\'
		
		回滚:
		【当不使用这个版本的软件时使用这个功能】
		git reset --hard 版本号
		
		git reset --【默认mix】版本号
		git checkout 文件名
		
		开发文件中出现紧急bug处理方案一:
		
			git stash 【将当秦已经做过的修改保存到一个特殊的地方,保存完后代码就恢复成干净的状态】
			进行bug修复
			git add .
			git commit -m \'修复bug\'
			修复完成bug后进行提交
			
			然后再通特殊的地方把保存得获取到
			git stash pop 获取
			
			如果bug出现的地点是我们开发的文件,在然后再从特殊的地方获取保存的内容,会出现冲突现象
			如果出现冲突现象需要我们手动解决
			从新执行一遍添加操作
		
		开发文件中出现紧急bug处理方案二:
			分支:
			在master上保留线上版本
			在进行开发时,创建一个新的分支线
			然后在新创建的分支线上进行开发,开发完成后在进行合并
			需要注意:在创建分支时需要知道当前的分支时什么,因为在创建时就是拷贝了当前的分支
			
			查看当前的分支:
				- git branch
				
			创建分支
				- git branch dev
			
			切换分支
				git checkout dev
				[回退到源文件是在checkout后面设置--文件名]
			合并
				首先要切换到上线的分支中,然后在进行合并
				git checkout master
				git merge dev
			
			处理bug也一样
			当有bug时在上线的分支上创建一个bug分支
			然后在bug分支上处理bug
			处理完bug后切换到上线的分支,然后再进行合并,数据合并完成后删除bug分支
			
			删除分支
			git branch -d bug
			
1.	Git init 创建一个库
2.	Git add . 添加所有的文件到暂存区
3.	Git commit –m ‘ 112’ 从暂存区提交到分支区
4.	Git status 查看当前的状态
5.	Git git config -- local user.user ‘用户名’
6.	Git git config -- local user. email\'邮箱\'
7.	Git remote add origin 远程仓库连接
8.	Git push origin master 把本地文件推送到远程仓库


Github 普通操作的流程
1、注册github账号
2、在Github上创建一个库
3、在本地文件中先进行初始化【git init】
4、把本地文件添加到暂存区【git add .】
5、把暂存区的文件提交的分支【git commit -m \'备注\'】 
6、创建用户名和邮件
7、把网上的仓库的链接设置一个别名【git remote add origin 远程连接】
8、向云仓库推文件【git push origin master】
9、下载从云仓库克隆【git clone 远程仓库的连接】
10、下载后创建分支【git branch 分支名】
11、切换分支【git checkout 分支名】
12、编写代码
13、更新最新的内容【git add .】
14、把暂存区的提交到分支【git commit -m ‘备注’】
15、把内容推上云仓库中【git push origin 新创建的分支】
 
1.31 rabbitmq
rabbitmq  消息队列
	解耦
	异步【解决的就是排队问题,同步解决不了的问题】
	
	异步:
		优点:解决排队问题
		缺点:不能保证任务被及时执行
		应用场景:去哪儿网 12306
	同步:
		优点:保证任务被及时的执行
		缺点:不能解决排队问题,浪费时间
	
	大并发:
		web 网站使用nginx  10000-20000
		
			早些年间使用 apache 只能承受1000-2000 同时访问
		pv = page visit  页面访问量 上亿的访问量是大网站 
		10 server web cluster 集群 才能承受大并发
	
		uv = user visit 
		
		qps =每秒查询率QPS是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。

队列的作用:
	1、存储消息。数据
	2、保证消息顺序
	3、保证数据的交付
		
	Python 中的队列只是在同一个进程中,只要涉及到跨进程就不能使用了【可以同一个进行里使用多线程】

	为什么用reabbitmg instead of Python queue 是因为 Python queue 不能跨进程

	写程序的要最大程度的降低耦合度
	队列天生就解决了耦合问题
	
生产者消费者模型 :
	分布式

	
【使用时先运行生产端,在运行消费端】	
	
确保消息被消费完毕

	1.生产者发消息时,加参数
	properties=pika.BasicProperties(delibery_mode=2)设置数据长久保存
	
	2.消费者端,消息处理完毕时,发送确认包
		ch.basic_ack(deliery_tag=methood.delibery_tag)
		
		channel.basic_consume(callback)取到消息后,调用callback函数
		queues =‘task1’
		no_ack=‘True)消息处理后,不想reabbit-server确认消息已消费完毕
		durable =True, 保证队列持久化
		
广播和订阅方式都要在线才能接受到队列中的内容	
exchange type    exchange表示消息的过滤
 	fanout =广播 routing_key = \'\' 为空
	direct =组播 routing_key = 
	topic = 规则播 【条件筛选】
	
新浪微博就是采用的广播方式,这种方式就是很高效。	
	
	
RPC
RPC-CLIENT
1.声明一个队列,作为reply_to返回 消息结果的队列
2、发消息到队列,消息里带一个唯一标示符uuid,reply_to
3、监听reply_to的队列,直到有结果
	
	
主机管理
crm

RabbitMQ 消息队列

1、队列持久化
	在之前的列子中,我们所用的队列都是临时的队列,当服务重启后之前创建的队列就都没有了。
	队列 的持久化是咋定义队列时的第二个参数决定的 durable等于True是创建一个持久队列

2、消息持久化
	在队列持久化后如果设置了消息持久化,再重启服务的时候队列还存在消息也就存在

3、队列持久化和消息持久化合并
	如果持久化标志设置为True 则代表是一个持久的队列,那么在服务重启后,也会存在。因为服务会把持久化的queue存放在硬盘上,当服务重启的时候,会重新申明之前被持久化的queue,队列是可以被持久化,但里面的消息是否为持久化那还要看消息的持久化设置。也就是说,如果重启之前的那个queue里面还有没有发出去的消息的话,重启之后那队列里面的消息设置的持久化就还会保留,如果没有进行持久化在服务重启后就不会存在
	

消息队列的执行流程
1、一对一通过消息队列传输
	生产者:
	1.1 创建阻塞连接,如果是远程要进行创建用户,并且设置权限
	1.2 创建一个连接通道
	1.3 声明一个队列,队列可以设置成持久化
	1.4 设置一检测声明的对列中的内容【可以设置接受完消息后向消费者返回信息】如果有内容就通过队列进行发送
	1.5 断开连接
	
	消费者:
	1.1 创建阻塞连接,如果是远程要进行创建用户,并且设置权限
	1.2 创建一个连接通道
	1.3 声明一个队列,队列可以设置成持久化
	1.4 设置一检测声明的对列中的内容【可以设置接受完消息后向生产者返回信息】如果有内容就从列中获取内容
	1.5 断开连接
	
	【在生产者和消费者都进行声明队列是为了防止,消费者先执行找不到队列报错】
	
2、分布式
	生产者:
	2.1 创建阻塞连接,如果是远程要进行创建用户,并且设置权限
	2.2 创建一个连接通道
	2.3 声明一个队列,队列可以设置成持久化
	2.4 设置一检测声明的对列【可以设置接受完消息后向消费者返回信息】消息内容设置成持久化
	2.5 断开连接
	
	消费者:
	2.1 创建阻塞连接,如果是远程要进行创建用户,并且设置权限
	2.2 创建一个连接通道
	2.3 声明一个队列,队列可以设置成持久化
	2.4 设置一检测声明的对列【可以设置接受完消息后向生产者返回信息】消息内容设置成持久化
	2.5 在设定的接受方式中返回标识符,因为是设置的持久消息,如果不设置返回消息,队列中就一直存在
	2.6 断开连接
	
	【在生产者和消费者都进行声明队列是为了防止,消费者先执行找不到队列报错】
	
	
3、广播
	生产者:
	3.1 创建阻塞连接,如果是远程要进行创建用户,并且设置权限
	3.2 创建一个连接通道
	3.3 设置过滤的名称
	3.4 设置一检测声明的对列【可以设置接受完消息后向消费者返回信息】消息内容设置成持久化
	3.5 断开连接
	
	消费者:
	3.1 创建阻塞连接,如果是远程要进行创建用户,并且设置权限
	3.2 创建一个连接通道
	3.3 声明一个队列,绑定在过滤器上
	3.4 设置一检测声明的对列【可以设置接受完消息后向消费者返回信息】消息内容设置成持久化,执行回调函数
	3.5 断开连接
	
	
	
4、组播
	生产者:
	4.1 创建阻塞连接,如果是远程要进行创建用户,并且设置权限
	4.2 创建一个连接通道
	4.3 设置过滤的名称,类型是direct组播【channel.exchange_declare(exchange=\'direct_logs\',type=\'direct\'】声明一个名为direct_logs类型为direct的Exchange
	4.4 设置一检测声明的对列【可以设置接受完消息后向消费者返回信息】消息内容设置成持久化
	4.5 routing 设置发送给那个路由
	4.6 断开连接
	
	消费者:
	4.1 创建阻塞连接,如果是远程要进行创建用户,并且设置权限
	4.2 创建一个连接通道
	4.3 循环获取组的队列,绑定在过滤器上
	4.4 设置检测对列【可以设置接受完消息后向消费者返回信息】消息内容设置成持久化,然后执行回调函数
	4.5 断开连接
	
5、规播
	生产者:
	4.1 创建阻塞连接,如果是远程要进行创建用户,并且设置权限
	4.2 创建一个连接通道
	4.3 设置过滤的名称,类型是topic规则播【channel.exchange_declare(exchange=\'direct_logs\',type=\'topic\'】声明一个名为direct_logs类型为direct的Exchange
	4.4 设置一检测声明的对列【可以设置接受完消息后向消费者返回信息】消息内容设置成持久化
	4.5 routing 设置发送给那个路由
	4.6 断开连接
	
	消费者:
	4.1 创建阻塞连接,如果是远程要进行创建用户,并且设置权限
	4.2 创建一个连接通道
	4.3 循环获取组的队列,绑定在过滤器上
	4.4 设置检测对列【可以设置接受完消息后向消费者返回信息】消息内容设置成持久化,然后执行回调函数
	4.5 断开连接
	
	
1.32 堡垒机
启动文件
import sys,os
import django
from Audit.backend import user_interavtive

if __name__ == \'__main__\':
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "LuffyAudit.settings")
    # 设置系统环境变量
    django.setup()
    # 手动注册Django所有的APP
    obj=user_interavtive.UserSehll(sys.argv) #传输脚本文件
    obj.start()

执行文件
import getpass
from django.contrib.auth import authenticate
# Django自动进行验证模块

class UserSehll(object):
    \'\'\'
    用户登录堡垒机后的shell
    \'\'\'

    def __init__(self,sys_argv):
        self.sys_avgv=sys_argv
        # 接受脚本文件
        self.user = None

    def auth(self):
        count =0
        while count < 3:
            username = input(\'username:\').strip()
            password = getpass.getpass(\'password:\').strip()
            user = authenticate(username=username,password=password)
            # 使用Django的认证功能如果认证成功就返回一个对象,认证失败就返回None

            if not user:
                # 如果不为空
                print(\'Inavlid username or password!\')
                # 打印错误提示
                count+=1
                # 次数加一
            else:
                # 否则
                self.user = user
                # 初始化变量
                return True
                # 返回True

    def start(self):
        \'\'\'
        启动交互程序
        :return: 
        \'\'\'
        if self.auth():
            # 判断auth函数返回的值是不是True
            print(\'host list ....\',self.user.account.host_user_binds.all())
            # 如果是true就打印当前用户管理的机器
            while True:
                # 循环
                host_groups =self.user.account.host_groups.all()
                # 查看当前用户管理的主机组
                for index,group in enumerate(host_groups):
                    # 循环获取当前用户管理的主机组中的信息
                    print("%s.\t %s[%s]"%(index,group,group.host_user_binds.count()))
                    # 打印拼接的字符串,拼接的内容是索引值,循环的内容,循环得到的主机绑定的用户
                print("%s. \t 未分组机器【%s】"%(len(host_groups),self.user.account.host_user_binds.count()))
                # 打印拼接的字符串,拼接的内容是host_groups的长度当索引值,没分组的机器总数
                choice =input("select group >:").strip()
                # 等待用户输入
                if choice.isdigit():
                    # 判断用户输入的是不是数字
                    choice = int(choice)
                    # 把输入的内容转换成整形
                    if choice > 0 and choice <= len(host_groups):
                        # 判断输入的是不是大于0 并且输入的内容小于 host_groups 的长度
                        selectad_group = host_groups[choice]
                        # 获取到数据索引的内容
                        host_bind_list =selectad_group.host_user_bind.all()
                        # 获取 host_groups 绑定的用户信息
                    elif choice == len(host_groups):
                        # 判断如果选择的数等于 host_groups长度
                        # selectad_group =self.user.account.host_user_binds.all()
                        host_bind_list = self.user.account.host_user_binds.all()
                        # 获取当前用户的绑定的主机
                    if host_bind_list:
                        # 判断如果不为空
                        while True:
                            # 循环
                            for index,host in enumerate(host_bind_list):
                                # 循环host_bind_lsit里的内容,并且设置索引值
                                print("%s.\t %s" %(index,host))
                                # 打印拼接的字符串
                            choice2 =input("select host>:").strip()
                            # 保存用户输入

                            if choice2.isdigit():
                                # 判断是不是数字
                                choice2 = int(choice2)
                                # 如果是数字在把它转成整形
                                if choice2 >0 and choice2 < len(host_bind_list):
                                    # 判断用户输入的数字是不是大于零,并且用户选择的小于循环的内容的长度
                                    selectad_host =host_bind_list[choice2]
                                    # 获取每个内容里的选择的内容
                                    print("selected host",selectad_host)
                                    # 打印用户选择的内容
                            elif choice2 =="b":
                                # 否则判断用户输入的是不是等于b
                                break
                                # 如果是b就进行后退


因为是在外部调用Django的内容出现错误
 



1.33 算法:	

 



 
一个计算过程,解决的方法
	在开发的人眼里,计算过程就是计算机就算的过程

	递归就是算法的核心思想

		递归的俩个特点:
			1、调用本身
			2、结束条件

	时间复杂度
		o(1)
		o(n)
		o(n**2)
		o(logn)

		循环减半的过程--> o(logn)
		几次循环就是n的几次方的复杂度
	空间复杂度
		变量==o(1)
		列表==o(n)
		二维列表==o(n2)
		.....

	列表查找:
		for循环查找,从列表的第一个元素开始,顺序进行搜索,直到找到为止

	切换就是把要切的内容拷贝到新的内存中,切片是非常耗时的
	不能再递归的函数上加装装饰器
	

1.33.1二分查找:
算法案例:
import time
def times(func):
	def timess(*args,**kwargs):
		t1 = time.time()
		res =func(*args,**kwargs)
		t2 =time.time()
		print(\'run time is\',t1-t2)
		return res
	return timess

@times
def bin_search(data_set,value):
	low=0
	high = len(data_set) - 1

	while low <= high:
		mid =(low+high) //2
		if data_set[mid] ==value:
			print(data_set[mid])
			return mid
		elif data_set[mid] > value:
			high = mid -1
		else:
			low = mid +1
@times
def linear_search(data_set,value):
	for i in range(data_set):
		if i == int(value):
			print(i)
			return i
	return


bin_search(range(100000),15946)
linear_search(100000,15946)

运行结果:
15946
run time is 0.0
15946
run time is -0.005001544952392578
[Finished in 0.7s]


1.33.2冒泡排序:


1.33.3选择排序:


1.33.4插入排序:




1.34爬虫:

例子1:

import requests
from bs4 import BeautifulSoup

response = requests.get(\'http://www.autohome.com.cn/news/\')
# 爬取的url,通过requests发送get请求
response.encoding =\'gbk\'
# 把获取的字符串转换成gbk编码
soup =BeautifulSoup(response.text,\'html.parser\')
# 把获取的字符串传进去,然后通过html.parser解析成html标签,然后创建一个对象
tag =soup.find(id=\'auto-channel-lazyload-article\')
# 通过创建的对象查找一个id是auto-channel-lazyload-article的标签,探后生成一个标签对象
h3 = tag.find(name=\'h3\')
# 通过上边创建的标签对象继续查找h3的标签
print(h3)
# 打印h3标签
print(h3.text)
# 打印h3标签的内容


例子2:
import requests
from bs4 import BeautifulSoup
\'\'\'
有的网站是通过post访问的时候给浏览器写入cookie,有的网站策略是第一次访问的时候就给浏览器写入一个cookie但是没有进行授权,
在进行post请求的时候需要携带浏览器写入的cookie,检测没有问题以后就对这个cookie进行授权。
\'\'\'
r1 = requests.get(\'http://github.com/login\')
# 通过一个get请求访问页面
s1 = BeautifulSoup(r1.text,\'html.parser\')
# 把获取到了字符串通过BeautifulSoup类子,再以html.parser进行解析
token = s1.find(name=\'input\',attrs={\'name\':\'authenticity_token\'}).get(\'value\')
# 把获取到的标签对象进行查找标签是input的标签的属性名称是authenticity_token,获取属性的名字值[获取token]
r1_cookie_dict = r1.cookies.get_dict()
# 获取cookie,【.get_dict是将对象转换成字典的类型】
r2 =requests.post(
    # 发送第二次请求,使用post
    \'https://github.com/session\',
    # 第二次请求的url
    data={
        # data是要进行的数据
        "utf8": "✓",
        "authenticity_token": token,
        # 携带token
        "login": "932023756@qq.com",
        # 携带账号 [这样就模拟了手动输入账号密码]
        "password": "guobaoyuan123",
        # 携带密码
        "commit": "Sign in"
    },
    cookies=r1_cookie_dict
# 携带第一次访问的cookie
)

r2_cookie_dict =r2.cookies.get_dict()
# 获取进行授权的cookie
cookie_dict={}
# 创建一个新的字典
cookie_dict.update(r1_cookie_dict)
# 把第一次获取的cookie和新创建的字典进行更新
cookie_dict.update(r2_cookie_dict)
# 把第二次获取的cookie和新创建的字典进行更新
r3 = requests.get(
    # 通过get请求,查看当前用户的信息
    url=\'https://github.com/settings/emails\',
    # 进行的访问的url
    cookies=cookie_dict
# 携带着最新的cookie
)

print(r3.text)
# 打印访问请求返回的内容



import requests
from bs4 import BeautifulSoup

response = requests.get(\'http://www.autohome.com.cn/news/\')
response.encoding =\'gbk\'
# soup =BeautifulSoup(response.text,\'html.parser\')
soup =BeautifulSoup(response.content,\'html.parser\')
tag =soup.find(id=\'auto-channel-lazyload-article\')
h3 = tag.find(name=\'h3\')
print(h3)





import requests
from bs4 import BeautifulSoup

r1 = requests.get(\'http://github.com/login\')
s1 = BeautifulSoup(r1.text,\'html.parser\')
token = s1.find(name=\'input\',attrs={\'name\':\'authenticity_token\'}).get(\'value\')
r1_cookie_dict = r1.cookies.get_dict()

r2 =requests.post(
    \'https://github.com/session\',
    data={
        "utf8": "✓",
        "authenticity_token": token, 
        "login": "932023756@qq.com",
        "password": "guobaoyuan123",
        "commit": "Sign in"
    },
    cookies=r1_cookie_dict
)

r2_cookie_dict =r2.cookies.get_dict()
cookie_dict={}
cookie_dict.update(r1_cookie_dict)
cookie_dict.update(r2_cookie_dict)

r3 = requests.get(
    url=\'https://github.com/settings/emails\',
    cookies=cookie_dict
)

print(r3.text)






import requests
1.调用关系
requests.get()
requests.post()
requests.put()
requests.delete()
requests.requestI(\'post\')

2.常用参数
url:\'xxx\'
params={\'k1\':\'v1\',\'nid\':888}
这个相当于get请求时,url后边?k1=v1&nid=888
cookies = {}
headers = {}
请求头

requests.post(
	url:\'xxx\',
	params={\'k1\'=\'v1\',k2=\'k2\'},
	cookies={},
	headers={},
	data={},
	json={},
	)

data == headers={\'content-type\':\'application/x-www-from-urlencided\'}
json == headers={\'content-type\':\'application/json\'}


from requests.auth import HTTPBasicAuth,HTTPDigestAuth

一般路由器都是使用HTTPBasicAuth 进行加密
HTTPBasicAuth 	是一种加密的验证规则
HTTPDigestAuth  是比BasicAuth加密更加多的一种规则


ret = request.get(\'http://192.168.1.1\',auth=HTTPBasicAuth(\'zhanghao\',\'mima\'))
ret = request.get(\'http://192.168.1.1\',auth=HTTPDigestAuth(\'zhanghao\',\'mima\'))

ret=requests.get(\'http://192.168.1.1\',
	auth=HTTPBasicAuth(\'admin\',\'admin\'))
	ret.encoding=\'gbk\',
	print(ret.text)
	

timeout  在使用的时候,填写一个参数是页面放问后等待反馈的时间
timeout	 在使用的时候,填写俩个参数第一个参数是访问这个网页请求的超时时间,第二个参数是服务器返回数据给浏览器的超时时间

allow_redirects 表示是否进行重定向,如果可以重定向,拿到的数据就是被重定向的,如果不进行重定向拿到的数据就是第一个站点


response = requests.get(\'http://www.abc.com\',allow_redirects=True)
print(response.text)这是进行重定向的时候显示的是重定向的内容
print(response.text) 这是不进行重定向的,显示的内容是www.adc.com

response = requests.get(\'url\',stream=True)



with open(文件) as f:
	f.write(xx.text) 内容
	for line in response.iter_content():
		pass

response.close()

from contextlib import closing
这样就是关闭上下文

with closing(requests.get(\'http://bin.org/get\',stream =True)) as r:
	for i in r.iter_content():
		print(i)


requests.get(\'http://http.org/get\',stream=True,cert =\'xxx.pem\')
stream等于True 是需要证书
cert是证书的文件

session是一个容器,容器存放请求头,请求体什么的


xlmtl 和html.parser 都是标签的解析器,第一种效率较高


1.35Redis面试题
 http://blog.csdn.net/guchuanyun111/article/category/6335900   
(1)什么是redis?
 
Redis 是一个基于内存的高性能key-value数据库。 (有空再补充,有理解错误或不足欢迎指正)

(2)Reids的特点
Redis本质上是一个Key-Value类型的内存数据库,很像memcached,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存。因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。
Redis的出色之处不仅仅是性能,Redis最大的魅力是支持保存多种数据结构,此外单个value的最大限制是1GB,不像 memcached只能保存1MB的数据,因此Redis可以用来实现很多有用的功能,比方说用他的List来做FIFO双向链表,实现一个轻量级的高性 能消息队列服务,用他的Set可以做高性能的tag系统等等。另外Redis也可以对存入的Key-Value设置expire时间,因此也可以被当作一 个功能加强版的memcached来用。
Redis的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。
 
(3)Redis支持的数据类型
Redis通过Key-Value的单值不同类型来区分, 以下是支持的类型:
Strings
Lists
Sets 求交集、并集
Sorted Set 
hashes

(4)为什么redis需要把所有数据放到内存中?
Redis为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以redis具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘I/O速度为严重影响redis的性能。在内存越来越便宜的今天,redis将会越来越受欢迎。
如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。

 
(5)Redis是单进程单线程的
redis利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销
 
(6)虚拟内存
当你的key很小而value很大时,使用VM的效果会比较好.因为这样节约的内存比较大.
当你的key不小时,可以考虑使用一些非常方法将很大的key变成很大的value,比如你可以考虑将key,value组合成一个新的value.
vm-max-threads这个参数,可以设置访问swap文件的线程数,设置最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的.可能会造成比较长时间的延迟,但是对数据完整性有很好的保证.
自己测试的时候发现用虚拟内存性能也不错。如果数据量很大,可以考虑分布式或者其他数据库
 
(7)分布式
redis支持主从的模式。原则:Master会将数据同步到slave,而slave不会将数据同步到master。Slave启动时会连接master来同步数据。
这是一个典型的分布式读写分离模型。我们可以利用master来插入数据,slave提供检索服务。这样可以有效减少单个机器的并发访问数量
(8)读写分离模型
通过增加Slave DB的数量,读的性能可以线性增长。为了避免Master DB的单点故障,集群一般都会采用两台Master DB做双机热备,所以整个集群的读和写的可用性都非常高。
读写分离架构的缺陷在于,不管是Master还是Slave,每个节点都必须保存完整的数据,如果在数据量很大的情况下,集群的扩展能力还是受限于单个节点的存储能力,而且对于Write-intensive类型的应用,读写分离架构并不适合。

                                                                 
(9)数据分片模型
为了解决读写分离模型的缺陷,可以将数据分片模型应用进来。
可以将每个节点看成都是独立的master,然后通过业务实现数据分片。
结合上面两种模型,可以将每个master设计成由一个master和多个slave组成的模型。
 (10)Redis的回收策略
 
•	volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
•	volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
•	volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
•	allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
•	allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
•	no-enviction(驱逐):禁止驱逐数据
•	1. 使用Redis有哪些好处?
(1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
(2) 支持丰富数据类型,支持string,list,set,sorted set,hash
(3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
(4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除

2. redis相比memcached有哪些优势?
(1) memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型
(2) redis的速度比memcached快很多
(3) redis可以持久化其数据

3. redis常见性能问题和解决方案:
(1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
(2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
(3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
(4) 尽量避免在压力很大的主库上增加从库
(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3...
这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。


 
4. MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据
 相关知识:redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。redis 提供 6种数据淘汰策略:
voltile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据
 
5. Memcache与Redis的区别都有哪些?
1)、存储方式
Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。
Redis有部份存在硬盘上,这样能保证数据的持久性。
2)、数据支持类型
Memcache对数据类型支持相对简单。
Redis有复杂的数据类型。
3)、使用底层模型不同
它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。
Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
4),value大小
redis最大可以达到1GB,而memcache只有1MB


6. Redis 常见的性能问题都有哪些?如何解决?
 
1).Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照。
 
2).Master AOF持久化,如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。
 
3).Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。
4). Redis主从复制的性能问题,为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内


7, redis 最适合的场景
 
Redis最适合所有数据in-momory的场景,虽然Redis也提供持久化功能,但实际更多的是一个disk-backed的功能,跟传统意义上的持久化有比较大的差别,那么可能大家就会有疑问,似乎Redis更像一个加强版的Memcached,那么何时使用Memcached,何时使用Redis呢?
       如果简单地比较Redis与Memcached的区别,大多数都会得到以下观点:
     1 、Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
     2 、Redis支持数据的备份,即master-slave模式的数据备份。
     3 、Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。
(1)、会话缓存(Session Cache)
最常用的一种使用Redis的情景是会话缓存(session cache)。用Redis缓存会话比其他存储(如Memcached)的优势在于:Redis提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗?
幸运的是,随着 Redis 这些年的改进,很容易找到怎么恰当的使用Redis来缓存会话的文档。甚至广为人知的商业平台Magento也提供Redis的插件。
(2)、全页缓存(FPC)
除基本的会话token之外,Redis还提供很简便的FPC平台。回到一致性问题,即使重启了Redis实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似PHP本地FPC。
再次以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。
此外,对WordPress的用户来说,Pantheon有一个非常好的插件  wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。
(3)、队列
Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得Redis能作为一个很好的消息队列平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如Python)对 list 的 push/pop 操作。
如果你快速的在Google中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目的就是利用Redis创建非常好的后端工具,以满足各种队列需求。例如,Celery有一个后台就是使用Redis作为broker,你可以从这里去查看。
(4),排行榜/计数器
Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis只是正好提供了这两种数据结构。所以,我们要从排序集合中获取到排名最靠前的10个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可:
当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行:
ZRANGE user_scores 0 10 WITHSCORES
Agora Games就是一个很好的例子,用Ruby实现的,它的排行榜就是使用Redis来存储数据的,你可以在这里看到。
(5)、发布/订阅
最后(但肯定不是最不重要的)是Redis的发布/订阅功能。发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用Redis的发布/订阅功能来建立聊天系统!(不,这是真的,你可以去核实)。
Redis提供的所有特性中,我感觉这个是喜欢的人最少的一个,虽然它为用户提供如果此多功能。

  

分类:

技术点:

相关文章: