first commit
913
01.基础语法/01.python基础.md
Normal file
@@ -0,0 +1,913 @@
|
||||
# Python基础
|
||||
|
||||
## 安装python
|
||||
|
||||
略
|
||||
|
||||
## 运行python代码
|
||||
|
||||
在硬盘创建文件 t1.py,并且使用 PyCharm 打开,输入以下代码
|
||||
|
||||
```python
|
||||
print('Hello World!')
|
||||
```
|
||||
|
||||
然后右键运行
|
||||
|
||||

|
||||
|
||||
运行结果
|
||||
|
||||
```
|
||||
C:\Users\Aaron\AppData\Local\Programs\Python\Python35\python.exe C:/Users/Aaron/Desktop/py/t1.py
|
||||
Hello World!
|
||||
|
||||
Process finished with exit code 0
|
||||
```
|
||||
|
||||
## 注释
|
||||
|
||||
当行注释:# 被注释内容
|
||||
|
||||
多行注释:''' 被注释内容 ''',或者 """ 被注释内容 """
|
||||
|
||||
## 变量
|
||||
|
||||
变量是什么? 变量:把程序运行的中间结果临时的存在内存里,以便后续的代码调用。
|
||||
|
||||
## 声明变量
|
||||
|
||||
```python
|
||||
a = "Hello World!"
|
||||
print(a)
|
||||
```
|
||||
|
||||
## 变量定义的规则
|
||||
|
||||
- 变量名只能是 字母、数字或下划线的任意组合
|
||||
- 变量名的第一个字符不能是数字
|
||||
- 以下关键字不能声明为变量名
|
||||
- ['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']
|
||||
- 变量的定义要具有可描述性。
|
||||
|
||||
## 变量的赋值
|
||||
|
||||
```python
|
||||
a = "变量1"
|
||||
b = "变量2"
|
||||
```
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
```python
|
||||
a = "变量1"
|
||||
b = a
|
||||
```
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
## 常量
|
||||
|
||||
常量即指不变的量,如 pi 3.141592653..., 或在程序运行过程中不会改变的量。
|
||||
|
||||
## 程序交互
|
||||
|
||||
```python
|
||||
name = input("请输入姓名:")
|
||||
print(name)
|
||||
```
|
||||
|
||||
执行脚本就会发现,程序会等待你输入姓名后再往下继续走。
|
||||
|
||||
```python
|
||||
请输入姓名:Aaron
|
||||
Aaron
|
||||
```
|
||||
|
||||
## 基础数据类型
|
||||
|
||||
### 整数型(int)
|
||||
|
||||
在 32 位机器上,整数的位数为 32 位,取值范围为 -2**31~2**31-1,即 -2147483648~2147483647
|
||||
|
||||
在 64 位系统上,整数的位数为 64 位,取值范围为 -2**63~2**63-1,即 -9223372036854775808~9223372036854775807
|
||||
|
||||
注意:在 Python3 里不再有 long 类型了,全都是 int
|
||||
|
||||
```python
|
||||
a = 2**64
|
||||
print(type(a)) #type()是查看数据类型的方法
|
||||
|
||||
b = 2**60
|
||||
print(type(b))
|
||||
```
|
||||
|
||||
运行结果
|
||||
|
||||
```python
|
||||
<class 'int'>
|
||||
<class 'int'>
|
||||
```
|
||||
|
||||
## 字符串类型(str)
|
||||
|
||||
在 Python 中, 加了引号的字符都被认为是字符串!
|
||||
|
||||
```python
|
||||
a = "Eagle's Lab"
|
||||
b = '''
|
||||
欢迎大家来到英格科技!
|
||||
今天我们学习python!'''
|
||||
|
||||
print(a,b)
|
||||
```
|
||||
|
||||
运行结果
|
||||
|
||||
```python
|
||||
Eagle's Lab
|
||||
欢迎大家来到英格科技!
|
||||
今天我们学习python!
|
||||
```
|
||||
|
||||
字符串拼接
|
||||
|
||||
```python
|
||||
a = 'eagle '
|
||||
b = 'welcome '
|
||||
print(b + a,'*' * 3,a * 3)
|
||||
```
|
||||
|
||||
运行结果
|
||||
|
||||
```python
|
||||
welcome eagle *** eagle eagle eagle
|
||||
```
|
||||
|
||||
## 布尔值(True, False)
|
||||
|
||||
布尔类型很简单,就两个值 ,一个 True(真),一个 False(假), 主要用记逻辑判断
|
||||
|
||||
```python
|
||||
a = 3
|
||||
b = 5
|
||||
print(a < b, a > b , a != b)
|
||||
```
|
||||
|
||||
运行结果
|
||||
|
||||
```
|
||||
True False True
|
||||
```
|
||||
|
||||
## 格式化输出
|
||||
|
||||
```python
|
||||
name = input("姓名:")
|
||||
age = input("年龄:")
|
||||
job = input("工作:")
|
||||
info = '''
|
||||
----------- info of %s -----------
|
||||
姓名:%s
|
||||
年龄:%s
|
||||
工作:%s
|
||||
''' % (name,name,age,job)
|
||||
print(info)
|
||||
```
|
||||
|
||||
运行结果
|
||||
|
||||
```
|
||||
姓名:Aaron
|
||||
年龄:18
|
||||
工作:Teacher
|
||||
----------- info of Aaron -----------
|
||||
姓名:Aaron
|
||||
年龄:18
|
||||
工作:Teacher
|
||||
```
|
||||
|
||||
## 基本运算符
|
||||
|
||||
算数运算
|
||||
|
||||
| 运算符 | 描述 | 实例 |
|
||||
| ------ | -------------- | ----------------- |
|
||||
| + | 加 | a+b 输出结果30 |
|
||||
| - | 减 | a-b 输出结果-10 |
|
||||
| * | 乘 | a*b 输出结果200 |
|
||||
| / | 除 | b/a 输出结果2 |
|
||||
| % | 取模 得到余数 | b%a 输出结果0 |
|
||||
| ** | 幂 | a**2 输出结果100 |
|
||||
| // | 取整 | 9//2 输出结果4 |
|
||||
|
||||
比较运算
|
||||
|
||||
| 运算符 | 描述 | 实例 |
|
||||
| ------ | -------- | ----------------- |
|
||||
| == | 等于 | a == b 返回False |
|
||||
| != | 不等于 | a != b 返回True |
|
||||
| <> | 不等于 | a <> b 返回True |
|
||||
| > | 大于 | a > b 返回False |
|
||||
| < | 小于 | a < b 返回True |
|
||||
| >= | 大于等于 | a >= b 返回False |
|
||||
| <= | 小于等于 | a <= b 返回True |
|
||||
|
||||
赋值运算
|
||||
|
||||
| 运算符 | 描述 | 实例 |
|
||||
| ------ | ---------------- | --------- |
|
||||
| = | 赋值运算符 | c = a + b |
|
||||
| += | 加法赋值运算符 | c += a |
|
||||
| -= | 加法赋值运算符 | c -= a |
|
||||
| *= | 乘法赋值运算符 | c *= a |
|
||||
| /= | 除法赋值运算符 | c /= a |
|
||||
| %= | 取模赋值运算符 | c %= a |
|
||||
| **= | 幂赋值运算符 | c **= a |
|
||||
| //= | 取整除赋值运算符 | c //= a |
|
||||
|
||||
逻辑运算
|
||||
|
||||
| 运算符 | 描述 | 实例 |
|
||||
| ------ | ---- | ------------ |
|
||||
| and | 与 | a and b |
|
||||
| or | 或 | a or b |
|
||||
| not | 非 | not(a and b) |
|
||||
|
||||
在没有 () 的情况下 not 优先级高于 and,and 优先级高于 or,即优先级关系为()>not>and>or,同一优先级从左往右计算。
|
||||
|
||||
x or y , x 为真,值就是 x,x 为假,值是 y; x and y, x 为真,值是 y,x 为假,值是 x。
|
||||
|
||||
成员运算
|
||||
|
||||
| 运算符 | 描述 | 实例 |
|
||||
| ------ | ------------------------------------------------- | ---------- |
|
||||
| in | 如果在指定的序列中找到值返回True,否则返回False | x in y |
|
||||
| not in | 如果在指定的序列中没有找到返回True,否则返回False | x not in y |
|
||||
|
||||
```python
|
||||
print('a' in 'abcd')
|
||||
print('y' not in 'xyzasd')
|
||||
```
|
||||
|
||||
运行结果
|
||||
|
||||
```
|
||||
True
|
||||
False
|
||||
```
|
||||
|
||||
**Python 运算符优先级**
|
||||
|
||||
以下表格列出了从最高到最低优先级的所有运算符:
|
||||
|
||||
| 运算符 | 描述 |
|
||||
| ------------------------ | ------------------------------------------------------ |
|
||||
| ** | 指数 (最高优先级) |
|
||||
| ~ + - | 按位翻转, 一元加号和减号 (最后两个的方法名为 +@ 和 -@) |
|
||||
| * / % // | 乘,除,取模和取整除 |
|
||||
| + - | 加法减法 |
|
||||
| >> << | 右移,左移运算符 |
|
||||
| & | 位 'AND' |
|
||||
| ^ | 位运算符 |
|
||||
| <= < > >= | 比较运算符 |
|
||||
| <> == != | 等于运算符 |
|
||||
| = %= /= //= -= += *= **= | 赋值运算符 |
|
||||
| is is not | 身份运算符 |
|
||||
| in not in | 成员运算符 |
|
||||
| not and or | 逻辑运算符 |
|
||||
|
||||
## 流程控制之 --if
|
||||
|
||||
单分支
|
||||
|
||||
```python
|
||||
if 条件:
|
||||
满足条件后要执行的代码
|
||||
```
|
||||
|
||||
双分支
|
||||
|
||||
```python
|
||||
"""
|
||||
if 条件:
|
||||
满足条件执行代码
|
||||
else:
|
||||
if条件不满足就走这段
|
||||
"""
|
||||
age = 48
|
||||
|
||||
if age > 50 :
|
||||
print("尚能饭否")
|
||||
else:
|
||||
print("廉颇老矣")
|
||||
```
|
||||
|
||||
运行结果
|
||||
|
||||
```
|
||||
廉颇老矣
|
||||
```
|
||||
|
||||
if...else ... 可以有多个分支条件
|
||||
|
||||
```python
|
||||
if 条件:
|
||||
满足条件执行代码
|
||||
elif 条件:
|
||||
上面的条件不满足就走这个
|
||||
elif 条件:
|
||||
上面的条件不满足就走这个
|
||||
elif 条件:
|
||||
上面的条件不满足就走这个
|
||||
else:
|
||||
上面所有的条件不满足就走这段
|
||||
```
|
||||
|
||||
## 流程控制之 --while
|
||||
|
||||
基本循环
|
||||
|
||||
```python
|
||||
while 条件:
|
||||
循环体
|
||||
```
|
||||
|
||||
如果条件为真,那么循环体则执行
|
||||
|
||||
如果条件为假,那么循环体不执行
|
||||
|
||||
循环中止语句
|
||||
|
||||
break 用于完全结束一个循环,跳出循环体执行循环后面的语句
|
||||
|
||||
continue 和 break 有点类似,区别在于 continue 只是终止本次循环,接着还执行后面的循环,break 则完全终止循环
|
||||
|
||||
```python
|
||||
print('猜数字游戏开始')
|
||||
num = 54
|
||||
while True:
|
||||
guess = int(input("您猜数字是什么?(输入0-100的数字)"))
|
||||
if guess < num:
|
||||
print("您猜小了")
|
||||
continue
|
||||
elif guess > num:
|
||||
print("您猜大了")
|
||||
continue
|
||||
break
|
||||
|
||||
print("您猜对了!")
|
||||
```
|
||||
|
||||
运行结果
|
||||
|
||||
```
|
||||
猜数字游戏开始
|
||||
您猜数字是什么?(输入0-100的数字)50
|
||||
您猜小了
|
||||
您猜数字是什么?(输入0-100的数字)60
|
||||
您猜大了
|
||||
您猜数字是什么?(输入0-100的数字)54
|
||||
您猜对了!
|
||||
```
|
||||
|
||||
while ... else ..
|
||||
|
||||
while 后面的 else 作用是指,当 while 循环正常执行完,中间没有被 break 中止的话,就会执行 else 后面的语句
|
||||
|
||||
## 基础数据类型
|
||||
|
||||
### 数字 (int)
|
||||
|
||||
```python
|
||||
v = 11
|
||||
data = v.bit_length() #当十进制用二进制表示时,最少使用的位数
|
||||
print(data)
|
||||
```
|
||||
|
||||
运行结果
|
||||
|
||||
```
|
||||
4
|
||||
```
|
||||
|
||||
### 布尔值 ()bool
|
||||
|
||||
布尔值就两种:True,False。就是反应条件的正确与否。
|
||||
|
||||
真 1 True。
|
||||
|
||||
假 0 False。
|
||||
|
||||
### 字符串 str
|
||||
|
||||
#### 字符串的索引与切片
|
||||
|
||||
```python
|
||||
a = 'ABCDEFGHIJK'
|
||||
print(a[0])
|
||||
print(a[3])
|
||||
print(a[5])
|
||||
print(a[7])
|
||||
|
||||
a = 'ABCDEFGHIJK'
|
||||
print(a[0:3])
|
||||
print(a[2:5])
|
||||
print(a[0:]) #默认到最后
|
||||
print(a[0:-1]) # -1 是列表中最后一个元素的索引,但是要满足顾头不顾腚的原则,所以取不到K元素
|
||||
print(a[0:5:2]) #加步长
|
||||
print(a[5:0:-2]) #反向加步长
|
||||
```
|
||||
|
||||
运行结果
|
||||
|
||||
```
|
||||
A
|
||||
D
|
||||
F
|
||||
H
|
||||
ABC
|
||||
CDE
|
||||
ABCDEFGHIJK
|
||||
ABCDEFGHIJ
|
||||
ACE
|
||||
FDB
|
||||
```
|
||||
|
||||
#### 字符串常用方法
|
||||
|
||||
```python
|
||||
words = "beautiful is better than ugly."
|
||||
print(words.capitalize()) #首字母大写
|
||||
print(words.swapcase()) #大小写翻转
|
||||
print(words.title()) #每个单词的首字母大写
|
||||
|
||||
|
||||
|
||||
## 内容居中,总长度,空白处填充
|
||||
a = "test"
|
||||
ret = a.center(20,"*")
|
||||
print(ret)
|
||||
|
||||
|
||||
## 数字符串中的元素出现的个数
|
||||
ret = words.count("e",0,30)
|
||||
print(ret)
|
||||
a = "aisdjioadoiqwd12313assdj"
|
||||
|
||||
|
||||
|
||||
## startswith 判断是否以...开头
|
||||
## endswith 判断是否以...结尾
|
||||
print(a.startswith("a"))
|
||||
print(a.endswith("j"))
|
||||
print(a.startswith('sdj',2,5))
|
||||
print(a.endswith('ado',7,10))
|
||||
|
||||
|
||||
## 寻找字符串中的元素是否存在
|
||||
print(a.find('sdj',1,10)) # 返回的找到的元素的索引,如果找不到返回-1
|
||||
print(a.index('sdj',1,10)) # 返回的找到的元素的索引,找不到报错。
|
||||
|
||||
|
||||
## split 以什么分割,最终形成一个列表此列表不含有这个分割的元素。
|
||||
ret = words.split(' ')
|
||||
print(ret)
|
||||
ret = words.rsplit(' ',2)
|
||||
print(ret)
|
||||
|
||||
|
||||
## format的三种玩法 格式化输出
|
||||
print('{} {} {}'.format('aaron',18,'teacher'))
|
||||
print('{1} {0} {1}'.format('aaron',18,'teacher'))
|
||||
print('{name} {age} {job}'.format(job='teacher',name='aaron',age=18))
|
||||
|
||||
|
||||
## strip
|
||||
a = '****asdasdasd********'
|
||||
print(a.strip('*'))
|
||||
print(a.lstrip('*'))
|
||||
print(a.rstrip('*'))
|
||||
|
||||
|
||||
## replace
|
||||
print(words.replace('e','a',2))
|
||||
print(words.isalnum()) #字符串由字母或数字组成
|
||||
print(words.isalpha()) #字符串只由字母组成
|
||||
print(words.isdigit()) #字符串只由数字组成
|
||||
```
|
||||
|
||||
### 元祖 tuple
|
||||
|
||||
元组被称为只读列表,即数据可以被查询,但不能被修改。
|
||||
|
||||
### 列表 list
|
||||
|
||||
列表相比于字符串,不仅可以储存不同的数据类型,而且可以储存大量数据,32 位 python 的限制是 536870912 个元素,64 位 python 的限制是 1152921504606846975 个元素。而且列表是有序的,有索引值,可切片,方便取值。
|
||||
|
||||
#### 增
|
||||
|
||||
```python
|
||||
li = [1,'a',2,'d',4]
|
||||
li.insert(0,22) # 按照索引去增加
|
||||
print(li)
|
||||
li.append('ddd') # 增加到最后
|
||||
print(li)
|
||||
li.extend(['q,a,w']) # 迭代的去增
|
||||
print(li)
|
||||
li.extend(['q,a,w','das']) # 迭代的去增
|
||||
print(li)
|
||||
```
|
||||
|
||||
运行结果
|
||||
|
||||
```
|
||||
[22, 1, 'a', 2, 'd', 4]
|
||||
[22, 1, 'a', 2, 'd', 4, 'ddd']
|
||||
[22, 1, 'a', 2, 'd', 4, 'ddd', 'q,a,w']
|
||||
[22, 1, 'a', 2, 'd', 4, 'ddd', 'q,a,w', 'q,a,w', 'das']
|
||||
```
|
||||
|
||||
#### 删
|
||||
|
||||
```python
|
||||
li = [1,'a',2,'d',4,5,'f']
|
||||
a = li.pop(1) # 按照位置去删除,有返回值
|
||||
print(a)
|
||||
del li[1:3] # 按照位置去删除,也可切片删除没有返回值。
|
||||
print(li)
|
||||
li.remove('f')
|
||||
print(li)
|
||||
li.clear()
|
||||
print(li)
|
||||
```
|
||||
|
||||
运行结果
|
||||
|
||||
```
|
||||
a
|
||||
[1, 4, 5, 'f']
|
||||
[1, 4, 5]
|
||||
[]
|
||||
```
|
||||
|
||||
#### 改
|
||||
|
||||
```python
|
||||
li = [1,'a',2,'d',4,5,'f']
|
||||
li[1] = 'aaa'
|
||||
print(li)
|
||||
li[2:3] = [3,'e']
|
||||
print (li)
|
||||
```
|
||||
|
||||
运行结果
|
||||
|
||||
```
|
||||
[1, 'aaa', 2, 'd', 4, 5, 'f']
|
||||
[1, 'aaa', 3, 'e', 'd', 4, 5, 'f']
|
||||
```
|
||||
|
||||
#### 查
|
||||
|
||||
切片去查,或者循环去查。
|
||||
|
||||
#### 其他操作
|
||||
|
||||
```python
|
||||
li = [1,2,4,5,4,2,4]
|
||||
print (li.count(4)) # 统计某个元素在列表中出现的次数
|
||||
print (li.index(2)) # 用于从列表中找出某个值第一个匹配项的索引位置
|
||||
li.sort() # 用于在原位置对列表进行排序
|
||||
print (li)
|
||||
li.reverse() # 将列表中的元素反向存放
|
||||
print (li)
|
||||
```
|
||||
|
||||
运行结果
|
||||
|
||||
```
|
||||
3
|
||||
1
|
||||
[1, 2, 2, 4, 4, 4, 5]
|
||||
[5, 4, 4, 4, 2, 2, 1]
|
||||
```
|
||||
|
||||
### 字典dict
|
||||
|
||||
字典是python中唯一的映射类型,采用键值对(key-value)的形式存储数据。python对key进行哈希函数运算,根据计算的结果决定value的存储地址,所以字典是无序存储的,且key必须是可哈希的。可哈希表示key必须是不可变类型,如:数字、字符串、元组。
|
||||
|
||||
#### 增
|
||||
|
||||
```python
|
||||
dic = {"age":18, "name":"aaron"}
|
||||
|
||||
dic['li'] = ["a","b","c"]
|
||||
print(dic)
|
||||
|
||||
dic.setdefault('k','v')
|
||||
## 在字典中添加键值对,如果只有键那对应的值是none,但是如果原字典中存在设置的键值对,则他不会更改或者覆盖。
|
||||
print(dic)
|
||||
|
||||
dic.setdefault('k','v1')
|
||||
print(dic)
|
||||
```
|
||||
|
||||
#### 删
|
||||
|
||||
```python
|
||||
dic = {"age":18, "name":"aaron"}
|
||||
|
||||
dic_pop = dic.pop('age')
|
||||
## pop根据key删除键值对,并返回对应的值,如果没有key则返回默认返回值
|
||||
print(dic_pop)
|
||||
|
||||
dic_pop = dic.pop('sex','查无此项')
|
||||
print(dic_pop)
|
||||
|
||||
dic['age'] = 18
|
||||
print(dic)
|
||||
|
||||
del dic['name']
|
||||
print(dic)
|
||||
|
||||
dic['name'] = 'demo'
|
||||
dic_pop = dic.popitem()
|
||||
## 随机删除字典中的某个键值对,将删除的键值对以元祖的形式返回
|
||||
print(dic_pop)
|
||||
|
||||
dic_clear = dic.clear()
|
||||
## 清空字典
|
||||
print(dic,dic_clear)
|
||||
```
|
||||
|
||||
#### 改
|
||||
|
||||
```python
|
||||
dic = {"age":18, "name":"aaron", 'sex':'male'}
|
||||
dic2 = {"age":30, "name":'demo'}
|
||||
|
||||
dic2.update(dic)
|
||||
## 将dic所有的键值对覆盖添加(相同的覆盖,没有的添加)到dic2中
|
||||
print(dic2)
|
||||
|
||||
dic2['age'] = 30
|
||||
print(dic2)
|
||||
```
|
||||
|
||||
#### 查
|
||||
|
||||
```python
|
||||
dic = {"age":18, "name":"aaron", 'sex':'male'}
|
||||
|
||||
value = dic['name']
|
||||
## 没有会报错
|
||||
print(value)
|
||||
|
||||
value = dic.get('abc','查无此项')
|
||||
print(value)
|
||||
```
|
||||
|
||||
#### 其他操作
|
||||
|
||||
```python
|
||||
dic = {"age":18, "name":"aaron", 'sex':'male'}
|
||||
|
||||
for i in dic.items():
|
||||
# 将键和值作为元祖列出
|
||||
print(i)
|
||||
|
||||
for key,value in dic.items():
|
||||
print(key,value)
|
||||
|
||||
for i in dic:
|
||||
# 只是迭代键
|
||||
print(i)
|
||||
|
||||
keys = dic.keys()
|
||||
print(keys,type(keys))
|
||||
|
||||
value = dic.values()
|
||||
print(value,type(value))
|
||||
```
|
||||
|
||||
### 集合set
|
||||
|
||||
集合是无序的,不重复的数据集合,它里面的元素是可哈希的(不可变类型),但是集合本身是不可哈希(所以集合做不了字典的键)的。以下是集合最重要的两点:
|
||||
|
||||
- 去重,把一个列表变成集合,就自动去重了。
|
||||
- 关系测试,测试两组数据之前的交集、差集、并集等关系。
|
||||
|
||||
#### 创建集合
|
||||
|
||||
```python
|
||||
set1 = set({1,2,'barry'})
|
||||
set2 = {1,2,'barry'}
|
||||
|
||||
print(set1,set2)
|
||||
```
|
||||
|
||||
#### 集合的增
|
||||
|
||||
```python
|
||||
set1 = {'abc','def',123,'asdas'}
|
||||
|
||||
set1.add('qwer')
|
||||
print(set1)
|
||||
|
||||
set1.update('A')
|
||||
#update:迭代着增加
|
||||
print(set1)
|
||||
|
||||
set1.update('哈哈哈')
|
||||
print(set1)
|
||||
|
||||
set1.update([1,2,3])
|
||||
print(set1)
|
||||
```
|
||||
|
||||
#### 集合的删
|
||||
|
||||
```python
|
||||
set1 = {'abc','def',123,'asdas'}
|
||||
|
||||
set1.remove('abc')
|
||||
print(set1)
|
||||
|
||||
set1.pop()
|
||||
## 随机删除一个数
|
||||
print(set1)
|
||||
|
||||
set1.clear()
|
||||
## 清空合集
|
||||
print(set1)
|
||||
|
||||
del set1
|
||||
## 删除合集
|
||||
print(set1)
|
||||
```
|
||||
|
||||
#### 集合的其他操作
|
||||
|
||||
###### 交集(& 或者 intersection)
|
||||
|
||||
```python
|
||||
set1 = {1,2,3,4,5}
|
||||
set2 = {3,4,5,6,7}
|
||||
|
||||
print(set1 & set2)
|
||||
|
||||
print(set1.intersection(set2))
|
||||
|
||||
## 列出两个集合中共同拥有的项
|
||||
```
|
||||
|
||||
##### 并集(|或者 union)
|
||||
|
||||
```python
|
||||
set1 = {1,2,3,4,5}
|
||||
set2 = {3,4,5,6,7}
|
||||
|
||||
print(set1 | set2)
|
||||
|
||||
print(set2.union(set1))
|
||||
|
||||
## 列出两个集合中所有的项
|
||||
```
|
||||
|
||||
##### 差集(- 或者 difference)
|
||||
|
||||
```python
|
||||
set1 = {1,2,3,4,5}
|
||||
set2 = {3,4,5,6,7}
|
||||
|
||||
print(set1 - set2)
|
||||
|
||||
print(set1.difference(set2))
|
||||
|
||||
## 在set1中删除set2中有的项
|
||||
```
|
||||
|
||||
##### 反交集 (^ 或者 symmetric_difference)
|
||||
|
||||
```python
|
||||
set1 = {1,2,3,4,5}
|
||||
set2 = {3,4,5,6,7}
|
||||
|
||||
print(set1 ^ set2)
|
||||
|
||||
print(set1.symmetric_difference(set2))
|
||||
|
||||
## 显示set1和set2不共存的项
|
||||
```
|
||||
|
||||
##### 子集与超集
|
||||
|
||||
```python
|
||||
set1 = {1,2,3}
|
||||
set2 = {1,2,3,4,5,6}
|
||||
|
||||
print(set1 < set2)
|
||||
print(set1.issubset(set2)) # 这两个相同,都是说明set1是set2子集。
|
||||
|
||||
print(set2 > set1)
|
||||
print(set2.issuperset(set1)) # 这两个相同,都是说明set2是set1超集
|
||||
```
|
||||
|
||||
##### frozenset不可变集合,让集合变成不可变类型
|
||||
|
||||
```python
|
||||
set1 = {1,2,3,4,5,6}
|
||||
|
||||
s = frozenset(set1)
|
||||
print(s,type(s))
|
||||
|
||||
s.add(7) # 不可以修改,会报错
|
||||
```
|
||||
|
||||
## 基础数据类型的总结
|
||||
|
||||
### 按存储空间的占用分(从低到高)
|
||||
|
||||
1. 数字
|
||||
2. 字符串
|
||||
3. 集合:无序,即无序存索引相关信息
|
||||
4. 元组:有序,需要存索引相关信息,不可变
|
||||
5. 列表:有序,需要存索引相关信息,可变,需要处理数据的增删改
|
||||
6. 字典:无序,需要存key与value映射的相关信息,可变,需要处理数据的增删改
|
||||
|
||||
### 按存值个数区分
|
||||
|
||||
| 标量/原子类型 | 数字,字符串 |
|
||||
| -------------- | ---------------- |
|
||||
| 容器类型 | 列表,元组,字典 |
|
||||
|
||||
### 按可变不可变区分
|
||||
|
||||
| 可变 | 列表,字典 |
|
||||
| ------ | -------------------------- |
|
||||
| 不可变 | 数字,字符串,元组,布尔值 |
|
||||
|
||||
### 按访问顺序区分
|
||||
|
||||
| 直接访问 | 数字 |
|
||||
| --------------------- | ------------------ |
|
||||
| 顺序访问(序列类型) | 字符串,列表,元组 |
|
||||
| key值访问(映射类型) | 字典 |
|
||||
|
||||
## 其他(for,enumerate,range)
|
||||
|
||||
for循环:用户按照顺序循环可迭代对象的内容。
|
||||
|
||||
```python
|
||||
s = '先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。'
|
||||
|
||||
for i in s:
|
||||
print(i)
|
||||
|
||||
li = ['甲','乙','丙','丁']
|
||||
for i in li:
|
||||
print(i)
|
||||
|
||||
dic = {'a':1,'b':2,'c':3}
|
||||
for k,v in dic.items():
|
||||
print(k,v)
|
||||
```
|
||||
|
||||
enumerate:枚举,对于一个可迭代的(iterable)/可遍历的对象(如列表、字符串),enumerate将其组成一个索引序列,利用它可以同时获得索引和值。
|
||||
|
||||
```python
|
||||
li = ['甲','乙','丙','丁']
|
||||
for i in li:
|
||||
print(i)
|
||||
|
||||
for i in enumerate(li):
|
||||
print(i)
|
||||
|
||||
for index,value in enumerate(li):
|
||||
print(index,value)
|
||||
|
||||
for index,value in enumerate(li,100): #从哪个数字开始索引
|
||||
print(index,value)
|
||||
```
|
||||
|
||||
range:指定范围,生成指定数字。
|
||||
|
||||
```python
|
||||
for i in range(1,10):
|
||||
print(i)
|
||||
|
||||
for i in range(1,10,2): # 步长
|
||||
print(i)
|
||||
|
||||
for i in range(10,1,-2): # 反向步长
|
||||
print(i)
|
||||
```
|
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 7.2 KiB |
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 47 KiB |
After Width: | Height: | Size: 53 KiB |
After Width: | Height: | Size: 45 KiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 15 KiB |
321
01.基础语法/02.python文件操作.md
Normal file
@@ -0,0 +1,321 @@
|
||||
# Python文件操作
|
||||
|
||||
## 文件操作基本流程
|
||||
|
||||
|
||||
|
||||
```python
|
||||
#1. 打开文件,得到文件句柄并赋值给一个变量
|
||||
f=open('a.txt','r',encoding='utf-8') #默认打开模式就为r
|
||||
|
||||
#2. 通过句柄对文件进行操作
|
||||
data=f.read()
|
||||
|
||||
#3. 关闭文件
|
||||
f.close()
|
||||
```
|
||||
|
||||
|
||||
|
||||
打开一个文件包含两部分资源:操作系统级打开的文件+应用程序的变量。在操作完毕一个文件时,必须把与该文件的这两部分资源一个不落地回收,回收方法为:
|
||||
|
||||
|
||||
|
||||
```python
|
||||
f.close()
|
||||
# 回收操作系统级打开的文件
|
||||
def f
|
||||
# 回收应用程序级的变量
|
||||
```
|
||||
|
||||
|
||||
|
||||
在操作完毕文件后,一定要记住f.close(),推荐操作方式:使用with关键字来帮我们管理上下文
|
||||
|
||||
|
||||
|
||||
```python
|
||||
with open('a.txt','r') as read_f,open('b.txt','w') as write_f:
|
||||
data=read_f.read()
|
||||
write_f.write(data)
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 文件编码
|
||||
|
||||
|
||||
|
||||
f=open(...)是由操作系统打开文件,那么如果我们没有为open指定编码,那么打开文件的默认编码很明显是操作系统说了算了,操作系统会用自己的默认编码去打开文件,在windows下是gbk,在linux下是utf-8。
|
||||
|
||||
|
||||
|
||||
```python
|
||||
f=open('a.txt','r',encoding='utf-8')
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 文件的打开模式
|
||||
|
||||
|
||||
|
||||
### 打开文件的模式
|
||||
|
||||
|
||||
|
||||
| r | 只读模式【默认模式,文件必须存在,不存在则抛出异常】 |
|
||||
| ---- | ------------------------------------------------------ |
|
||||
| w | 只写模式【不可读;不存在则创建;存在则清空内容】 |
|
||||
| a | 只追加写模式【不可读;不存在则创建;存在则只追加内容】 |
|
||||
|
||||
|
||||
|
||||
对于非文本文件,我们只能使用b模式,"b"表示以字节的方式操作(而所有文件也都是以字节的形式存储的,使用这种模式无需考虑文本文件的字符编码、图片文件的jgp格式、视频文件的avi格式)
|
||||
|
||||
|
||||
|
||||
rb
|
||||
|
||||
wb
|
||||
|
||||
ab
|
||||
|
||||
|
||||
|
||||
注:以b方式打开时,读取到的内容是字节类型,写入时也需要提供字节类型,不能指定编码
|
||||
|
||||
|
||||
|
||||
### ‘+’模式(就是增加了一个功能)
|
||||
|
||||
|
||||
|
||||
| r+ | 读写【可读,可写】 |
|
||||
| ---- | ------------------ |
|
||||
| w+ | 写读【可写,可读】 |
|
||||
| a+ | 写读【可写,可读】 |
|
||||
|
||||
| 模式 | 可做操作 | 若文件不存在 | 是否覆盖 |
|
||||
| ---- | -------- | ------------ | ---------- |
|
||||
| r | 只能读 | 报错 | - |
|
||||
| r+ | 可读可写 | 报错 | 是 |
|
||||
| w | 只能写 | 创建 | 是 |
|
||||
| w+ | 可读可写 | 创建 | 是 |
|
||||
| a | 只能写 | 创建 | 否,追加写 |
|
||||
| a+ | 可读可写 | 创建 | 否,追加写 |
|
||||
|
||||
### 以bytes类型操作的读写,写读,写读模式
|
||||
|
||||
|
||||
|
||||
| r+b | 读写【可读,可写】 |
|
||||
| ---- | ------------------ |
|
||||
| w+b | 写读【可写,可读】 |
|
||||
| a+b | 写读【可写,可读】 |
|
||||
|
||||
|
||||
|
||||
## 文件操作方法
|
||||
|
||||
|
||||
|
||||
### 常用操作方法
|
||||
|
||||
|
||||
|
||||
read(3):
|
||||
|
||||
|
||||
|
||||
1. 文件打开方式为文本模式时,代表读取3个字符
|
||||
2. 文件打开方式为b模式时,代表读取3个字节
|
||||
|
||||
|
||||
|
||||
其余的文件内光标移动都是以字节为单位的如:seek,tell,truncate
|
||||
|
||||
|
||||
|
||||
注意:
|
||||
|
||||
|
||||
|
||||
1. seek有三种移动方式0,1,2,其中1和2必须在b模式下进行,但无论哪种模式,都是以bytes为单位移动的
|
||||
2. truncate是截断文件,所以文件的打开方式必须可写,但是不能用w或w+等方式打开,因为那样直接清空文件了,所以truncate要在r+或a或a+等模式下测试效果。
|
||||
|
||||
|
||||
|
||||
### 所有的操作方法
|
||||
|
||||
|
||||
|
||||
```python
|
||||
def close(self, *args, **kwargs): # real signature unknown
|
||||
关闭文件
|
||||
pass
|
||||
|
||||
def fileno(self, *args, **kwargs): # real signature unknown
|
||||
文件描述符
|
||||
pass
|
||||
|
||||
def flush(self, *args, **kwargs): # real signature unknown
|
||||
刷新文件内部缓冲区
|
||||
pass
|
||||
|
||||
def isatty(self, *args, **kwargs): # real signature unknown
|
||||
判断文件是否是同意tty设备
|
||||
pass
|
||||
|
||||
def read(self, *args, **kwargs): # real signature unknown
|
||||
读取指定字节数据
|
||||
pass
|
||||
|
||||
def readable(self, *args, **kwargs): # real signature unknown
|
||||
是否可读
|
||||
pass
|
||||
|
||||
def readline(self, *args, **kwargs): # real signature unknown
|
||||
仅读取一行数据
|
||||
pass
|
||||
|
||||
def seek(self, *args, **kwargs): # real signature unknown
|
||||
指定文件中指针位置
|
||||
pass
|
||||
|
||||
def seekable(self, *args, **kwargs): # real signature unknown
|
||||
指针是否可操作
|
||||
pass
|
||||
|
||||
def tell(self, *args, **kwargs): # real signature unknown
|
||||
获取指针位置
|
||||
pass
|
||||
|
||||
def truncate(self, *args, **kwargs): # real signature unknown
|
||||
截断数据,仅保留指定之前数据
|
||||
pass
|
||||
|
||||
def writable(self, *args, **kwargs): # real signature unknown
|
||||
是否可写
|
||||
pass
|
||||
|
||||
def write(self, *args, **kwargs): # real signature unknown
|
||||
写内容
|
||||
pass
|
||||
|
||||
def __getstate__(self, *args, **kwargs): # real signature unknown
|
||||
pass
|
||||
|
||||
def __init__(self, *args, **kwargs): # real signature unknown
|
||||
pass
|
||||
|
||||
@staticmethod # known case of __new__
|
||||
def __new__(*args, **kwargs): # real signature unknown
|
||||
""" Create and return a new object. See help(type) for accurate signature. """
|
||||
pass
|
||||
|
||||
def __next__(self, *args, **kwargs): # real signature unknown
|
||||
""" Implement next(self). """
|
||||
pass
|
||||
|
||||
def __repr__(self, *args, **kwargs): # real signature unknown
|
||||
""" Return repr(self). """
|
||||
pass
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 文件的修改
|
||||
|
||||
|
||||
|
||||
文件的数据是存放于硬盘上的,因而只存在覆盖、不存在修改这么一说,我们平时看到的修改文件,都是模拟出来的效果,具体的说有两种实现方式:
|
||||
|
||||
|
||||
|
||||
方式一:将硬盘存放的该文件的内容全部加载到内存,在内存中是可以修改的,修改完毕后,再由内存覆盖到硬盘(word,vim,nodpad++等编辑器)
|
||||
|
||||
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
with open('a.txt') as read_f,open('a.txt.new','w') as write_f:
|
||||
data = read_f.read()
|
||||
data = data.replace('Hello','nihao')
|
||||
|
||||
write_f.write(data)
|
||||
|
||||
os.remove('a.txt')
|
||||
os.rename('a.txt.new','a.txt')
|
||||
```
|
||||
|
||||
|
||||
|
||||
方式二:将硬盘存放的该文件的内容一行一行地读入内存,修改完毕就写入新文件,最后用新文件覆盖源文件
|
||||
|
||||
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
with open('a.txt') as read_f,open('a.txt.new','w') as write_f:
|
||||
for line in read_f:
|
||||
line = line.replace('nihao','Hello')
|
||||
write_f.write(line)
|
||||
|
||||
os.remove('a.txt')
|
||||
os.rename('a.txt.new','a.txt')
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 小测验
|
||||
|
||||
|
||||
|
||||
文件a.txt内容:每一行内容分别为商品名字,价钱,个数。
|
||||
|
||||
|
||||
|
||||
apple 10 3
|
||||
|
||||
|
||||
|
||||
tesla 100000 1
|
||||
|
||||
|
||||
|
||||
mac 3000 2
|
||||
|
||||
|
||||
|
||||
lenovo 30000 3
|
||||
|
||||
|
||||
|
||||
chicken 10 3
|
||||
|
||||
|
||||
|
||||
通过代码,将其构建成这种数据类型:[{'name':'apple','price':10,'amount':3},{'name':'tesla','price':1000000,'amount':1}......] 并计算出总价钱。
|
||||
|
||||
|
||||
|
||||
```python
|
||||
list = []
|
||||
with open('a.txt','r',encoding='utf-8') as file:
|
||||
for line in file:
|
||||
list2 = line.strip().split()
|
||||
if list2:
|
||||
dic = {'name':list2[0],'price':list2[1],'amount':list2[2]}
|
||||
list.append(dic)
|
||||
continue
|
||||
|
||||
print(list)
|
||||
price = 0
|
||||
for i in list:
|
||||
price += int(i['price']) * int(i['amount'])
|
||||
|
||||
print(price)
|
||||
```
|
410
01.基础语法/03.python认识函数.md
Normal file
@@ -0,0 +1,410 @@
|
||||
# 认识函数
|
||||
|
||||
## 什么是函数
|
||||
|
||||
函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。
|
||||
|
||||
## 函数的定义与调用
|
||||
|
||||
```python
|
||||
def my_len():
|
||||
s = 'hello world'
|
||||
length = 0
|
||||
for i in s:
|
||||
length = length + 1
|
||||
print(length)
|
||||
|
||||
my_len()
|
||||
```
|
||||
|
||||
```python
|
||||
定义:def 关键词开头,空格之后接函数名称和圆括号(),最后还有一个":"。
|
||||
|
||||
def 是固定的,不能变,他就是定义函数的关键字。
|
||||
|
||||
空格 为了将def关键字和函数名分开,必须空(四声),当然你可以空2格、3格或者你想空多少都行,但正常人还是空1格。
|
||||
|
||||
函数名:函数名只能包含字符串、下划线和数字且不能以数字开头。虽然函数名可以随便起,但我们给函数起名字还是要尽量简短,并能表达函数功能
|
||||
|
||||
括号:是必须加的,先别问为啥要有括号,总之加上括号就对了!
|
||||
|
||||
注释:每一个函数都应该对功能和参数进行相应的说明,应该写在函数下面第一行。以增强代码的可读性。
|
||||
|
||||
调用:就是 函数名() 要记得加上括号。
|
||||
```
|
||||
|
||||
## 函数的返回值
|
||||
|
||||
```python
|
||||
def my_len():
|
||||
s = 'hello world'
|
||||
length = 0
|
||||
for i in s:
|
||||
length = length + 1
|
||||
return length
|
||||
|
||||
str_len = my_len()
|
||||
print(str_len)
|
||||
```
|
||||
|
||||
**return关键字的作用**
|
||||
|
||||
- return 是一个关键字,这个词翻译过来就是“返回”,所以我们管写在return后面的值叫“返回值”。
|
||||
- 不写return的情况下,会默认返回一个None
|
||||
- 一旦遇到return,结束整个函数。
|
||||
- 返回的多个值会被组织成元组被返回,也可以用多个值来接收
|
||||
|
||||
```python
|
||||
def ret_demo():
|
||||
return 1,2,'a',['hello','world']
|
||||
|
||||
ret = ret_demo()
|
||||
print(ret)
|
||||
```
|
||||
|
||||
## 函数的参数
|
||||
|
||||
带参数的函数
|
||||
|
||||
```python
|
||||
def my_len(s):
|
||||
length = 0
|
||||
for i in s:
|
||||
length += 1
|
||||
return length
|
||||
|
||||
ret = my_len('hello world!')
|
||||
print(ret)
|
||||
```
|
||||
|
||||
实际的要交给函数的内容,简称实参。
|
||||
|
||||
在定义函数的时候它只是一个形式,表示这里有一个参数,简称形参。
|
||||
|
||||
1. 按照位置传值:位置参数
|
||||
|
||||
```python
|
||||
def maxnumber(x,y):
|
||||
the_max = x if x > y else y
|
||||
return the_max
|
||||
|
||||
ret = maxnumber(10,20)
|
||||
print(ret)
|
||||
```
|
||||
|
||||
1. 按照关键字传值:关键字参数。
|
||||
|
||||
```python
|
||||
def maxnumber(x,y):
|
||||
the_max = x if x > y else y
|
||||
return the_max
|
||||
|
||||
ret = maxnumber(y = 10,x = 20)
|
||||
print(ret)
|
||||
```
|
||||
|
||||
1. 位置、关键字形式混着用:混合传参。
|
||||
|
||||
```python
|
||||
def maxnumber(x,y):
|
||||
the_max = x if x > y else y
|
||||
return the_max
|
||||
|
||||
ret = maxnumber(10,y = 20)
|
||||
print(ret)
|
||||
```
|
||||
|
||||
位置参数必须在关键字参数的前面
|
||||
|
||||
对于一个形参只能赋值一次
|
||||
|
||||
1. 默认参数。
|
||||
|
||||
```python
|
||||
def stu_info(name,age = 18):
|
||||
print(name,age)
|
||||
|
||||
stu_info('aaron')
|
||||
stu_info('song',50)
|
||||
```
|
||||
|
||||
1. 默认参数是一个可变数据类型
|
||||
|
||||
```python
|
||||
def demo(a,l = []):
|
||||
l.append(a)
|
||||
print(l)
|
||||
|
||||
demo('abc')
|
||||
demo('123')
|
||||
```
|
||||
|
||||
1. 动态参数
|
||||
|
||||
```python
|
||||
def demo(*args,**kwargs):
|
||||
print(args,type(args))
|
||||
print(kwargs,type(kwargs))
|
||||
|
||||
demo('aaron',1,3,[1,3,2,2],{'a':123,'b':321},country='china',b=1)
|
||||
|
||||
##动态参数,也叫不定长传参,就是你需要传给函数的参数很多,不定个数,那这种情况下,你就用*args,**kwargs接收,args是元祖形式,接收除去键值对以外的所有参数,kwargs接收的只是键值对的参数,并保存在字典中。
|
||||
```
|
||||
|
||||
## 命名空间和作用域
|
||||
|
||||
代码在运行伊始,创建的存储“变量名与值的关系”的空间叫做全局命名空间;
|
||||
|
||||
在函数的运行中开辟的临时的空间叫做局部命名空间。
|
||||
|
||||
命名空间一共分为三种:
|
||||
|
||||
- 全局命名空间
|
||||
- 局部命名空间
|
||||
- 内置命名空间
|
||||
|
||||
取值顺序:
|
||||
|
||||
- 在局部调用:局部命名空间->全局命名空间->内置命名空间
|
||||
- 在全局调用:全局命名空间->内置命名空间
|
||||
|
||||
作用域
|
||||
|
||||
- 全局作用域:包含内置名称空间、全局名称空间,在整个文件的任意位置都能被引用、全局有效
|
||||
- 局部作用域:局部名称空间,只能在局部范围内生效
|
||||
|
||||
### globals和locals方法
|
||||
|
||||
```python
|
||||
print(globals())
|
||||
print(locals())
|
||||
|
||||
def func():
|
||||
a = 12
|
||||
b = 20
|
||||
print(globals())
|
||||
print(locals())
|
||||
|
||||
func()
|
||||
```
|
||||
|
||||
**global**
|
||||
|
||||
1. 声明一个全局变量。
|
||||
2. 在局部作用域想要对全局作用域的全局变量进行修改时,需要用到 global(限于字符串,数字)。
|
||||
|
||||
```python
|
||||
def func():
|
||||
global a
|
||||
a = 3
|
||||
|
||||
func()
|
||||
print(a)
|
||||
|
||||
count = 1
|
||||
def search():
|
||||
global count
|
||||
count = 2
|
||||
|
||||
search()
|
||||
print(count)
|
||||
```
|
||||
|
||||
对可变数据类型(list,dict,set)可以直接引用不用通过global
|
||||
|
||||
```python
|
||||
li = [1,2,3]
|
||||
dic = {'name':'aaron'}
|
||||
|
||||
def change():
|
||||
li.append(4)
|
||||
dic['age'] = 18
|
||||
print(dic)
|
||||
print(li)
|
||||
|
||||
change()
|
||||
print(dic)
|
||||
print(li)
|
||||
```
|
||||
|
||||
**nonlocal**
|
||||
|
||||
1. 不能修改全局变量。
|
||||
2. 在局部作用域中,对父级作用域(或者更外层作用域非全局作用域)的变量进行引用和修改,并且引用的哪层,从那层及以下此变量全部发生改变。
|
||||
|
||||
```python
|
||||
def add_b():
|
||||
b = 1
|
||||
def do_global():
|
||||
b = 10
|
||||
print(b)
|
||||
def dd_nolocal():
|
||||
nonlocal b # 应用了上一层的变量b
|
||||
b = b + 20
|
||||
print(b) # 发生了改变
|
||||
dd_nolocal() # 调用函数,导致do_global的命名空间b也改变了
|
||||
print(b)
|
||||
do_global()
|
||||
print(b)
|
||||
add_b() # 最上面一层没有变化
|
||||
```
|
||||
|
||||
## 函数的嵌套和作用域链
|
||||
|
||||
```python
|
||||
def mymax(x,y):
|
||||
m = x if x > y else y
|
||||
return m
|
||||
|
||||
def maxx(a,b,c,d):
|
||||
res1 = mymax(a,b)
|
||||
res2 = mymax(res1,c)
|
||||
res3 = mymax(res2,d)
|
||||
return res3
|
||||
|
||||
ret = maxx(23,453,12,-13)
|
||||
print(ret)
|
||||
```
|
||||
|
||||
```python
|
||||
def f1():
|
||||
print("in f1")
|
||||
def f2():
|
||||
print("in f2")
|
||||
f2()
|
||||
|
||||
f1()
|
||||
```
|
||||
|
||||
## 函数名的本质
|
||||
|
||||
函数名本质上就是函数的内存地址
|
||||
|
||||
1. 可以被引用
|
||||
|
||||
```python
|
||||
def func():
|
||||
print('in func')
|
||||
|
||||
f = func
|
||||
|
||||
print(f)
|
||||
f()
|
||||
```
|
||||
|
||||
1. 可以被当作容器类型的元素
|
||||
|
||||
```python
|
||||
def f1():
|
||||
print('f1')
|
||||
|
||||
def f2():
|
||||
print('f2')
|
||||
|
||||
def f3():
|
||||
print('f3')
|
||||
|
||||
l = [f1,f2,f3]
|
||||
d = {'f1':f1,'f2':f2,'f3':f3}
|
||||
|
||||
#调用
|
||||
l[0]()
|
||||
d['f2']()
|
||||
```
|
||||
|
||||
1. 可以当作函数的参数和返回值
|
||||
|
||||
```python
|
||||
def f1():
|
||||
print('f1')
|
||||
|
||||
def func(argv):
|
||||
argv()
|
||||
return argv
|
||||
|
||||
f = func(f1)
|
||||
f()
|
||||
```
|
||||
|
||||
## 闭包
|
||||
|
||||
```python
|
||||
def func():
|
||||
name = 'aaron'
|
||||
def inner():
|
||||
print(name)
|
||||
return inner
|
||||
|
||||
f = func()
|
||||
f()
|
||||
```
|
||||
|
||||
内部函数包含对外部作用域而非全剧作用域变量的引用,该内部函数称为闭包函数
|
||||
|
||||
判断闭包函数的方法**closure**
|
||||
|
||||
```python
|
||||
def func():
|
||||
name = 'aaron'
|
||||
def inner():
|
||||
print(name)
|
||||
print(inner.__closure__)
|
||||
return inner
|
||||
|
||||
f = func()
|
||||
f()
|
||||
# 最后运行的结果里面有cell就是闭包
|
||||
|
||||
name = 'aaron'
|
||||
def func():
|
||||
def inner():
|
||||
print(name)
|
||||
print(inner.__closure__)
|
||||
return inner
|
||||
|
||||
f = func()
|
||||
f()
|
||||
# 输出结果为None,说明不是闭包
|
||||
```
|
||||
|
||||
```python
|
||||
def wrapper():
|
||||
money = 1000
|
||||
def func():
|
||||
name = 'apple'
|
||||
def inner():
|
||||
print(name,money)
|
||||
return inner
|
||||
return func
|
||||
|
||||
f = wrapper()
|
||||
i = f()
|
||||
i()
|
||||
```
|
||||
|
||||
```python
|
||||
def func(a,b):
|
||||
def inner(x):
|
||||
return a*x + b
|
||||
return inner
|
||||
|
||||
func1 = func(4,5)
|
||||
func2 = func(7,8)
|
||||
print(func1(5),func2(6))
|
||||
```
|
||||
|
||||
```python
|
||||
from urllib.request import urlopen
|
||||
def func():
|
||||
content = urlopen('http://myip.ipip.net').read()
|
||||
def get_content():
|
||||
return content
|
||||
return get_content
|
||||
|
||||
code = func()
|
||||
content = code()
|
||||
print(content.decode('utf-8'))
|
||||
|
||||
content2 = code()
|
||||
print(content2.decode('utf-8'))
|
||||
```
|
175
01.基础语法/04.装饰器.md
Normal file
@@ -0,0 +1,175 @@
|
||||
# 装饰器
|
||||
|
||||
## 什么是装饰器
|
||||
|
||||
让其他函数在不需要做任何代码变动的前提下,增加额外的功能,装饰器的返回值也是一个函数对象。
|
||||
|
||||
装饰器的应用场景:比如插入日志,性能测试,事务处理,缓存等等场景。
|
||||
|
||||
## 装饰器的形成过程
|
||||
|
||||
如果我想测试某个函数的执行时间
|
||||
|
||||
```python
|
||||
import time
|
||||
def func1():
|
||||
print('in func1')
|
||||
def timer(func):
|
||||
def inner():
|
||||
start = time.time()
|
||||
func()
|
||||
print(time.time() - start)
|
||||
return inner
|
||||
func1 = timer(func1)
|
||||
func1()
|
||||
```
|
||||
|
||||
但是如果有多个函数,我都想让你测试他们的执行时间,你每次是不是都得func1 = timer(func1)?这样还是有点麻烦,因为这些函数的函数名可能是不相同,有func1,func2,graph,等等,所以更简单的方法,python给你提供了,那就是语法糖。
|
||||
|
||||
```python
|
||||
import time
|
||||
def timer(func):
|
||||
def inner():
|
||||
start = time.time()
|
||||
func()
|
||||
print(time.time() - start)
|
||||
return inner
|
||||
@timer
|
||||
def func1():
|
||||
time.sleep(1)
|
||||
print('in func1')
|
||||
func1()
|
||||
```
|
||||
|
||||
装饰一个带参数的函数
|
||||
|
||||
```python
|
||||
import time
|
||||
def timer(func):
|
||||
def inner(a):
|
||||
start = time.time()
|
||||
func(a)
|
||||
print(time.time() - start)
|
||||
return inner
|
||||
@timer
|
||||
def func1(a):
|
||||
time.sleep(1)
|
||||
print(a)
|
||||
func1('hello world')
|
||||
```
|
||||
|
||||
装饰一个带各种参数的函数
|
||||
|
||||
```python
|
||||
import time
|
||||
def timer(func):
|
||||
def inner(*args,**kwargs):
|
||||
start = time.time()
|
||||
func(args,kwargs)
|
||||
print(time.time() - start)
|
||||
return inner
|
||||
@timer
|
||||
def func1(*args,**kwargs):
|
||||
print(args,kwargs)
|
||||
func1('hello world','abc',123,432)
|
||||
```
|
||||
|
||||
查看函数的相关信息,在加上装饰器后就失效了
|
||||
|
||||
```python
|
||||
def index():
|
||||
'''这是一条注释信息'''
|
||||
print('from index')
|
||||
print(index.__doc__) # 查看函数注释
|
||||
print(index.__name__) # 查看函数名称
|
||||
```
|
||||
|
||||
导入wraps装饰器
|
||||
|
||||
```python
|
||||
from functools import wraps
|
||||
def deco(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args,**kwargs):
|
||||
return func(*args,**kwargs)
|
||||
return wrapper
|
||||
@deco
|
||||
def index():
|
||||
'''这是一条注释信息'''
|
||||
print('from index')
|
||||
print(index.__doc__) # 查看函数注释
|
||||
print(index.__name__) # 查看函数名称
|
||||
```
|
||||
|
||||
## 开放封闭原则
|
||||
|
||||
一句话,软件实体应该是可扩展但是不可修改的。
|
||||
|
||||
* 对于扩展是开放的
|
||||
* 对于修改是封闭的
|
||||
|
||||
装饰器完美的遵循了这个开放封闭原则
|
||||
|
||||
## 装饰器的主要功能和固定结构
|
||||
|
||||
```python
|
||||
def timer(func):
|
||||
def inner(*args,**kwargs):
|
||||
'''执行函数之前要做的'''
|
||||
re = func(*args,**kwargs)
|
||||
'''执行函数之后要做的'''
|
||||
return re
|
||||
return inner
|
||||
# 下面是加上wraps的固定结构
|
||||
from functools import wraps
|
||||
def timer(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args,**kwargs)
|
||||
return func(*args,**kwargs)
|
||||
return wrapper
|
||||
```
|
||||
|
||||
## 带参数的装饰器
|
||||
|
||||
加上一个outer函数,可以携带一个flag的值,然后控制装饰器是否生效
|
||||
|
||||
```python
|
||||
def outer(flag):
|
||||
def timer(func):
|
||||
def inner(*args,**kwargs):
|
||||
if flag:
|
||||
print('函数开始执行')
|
||||
re = func(*args,**kwargs)
|
||||
if flag:
|
||||
print('函数执行完毕')
|
||||
return re
|
||||
return inner
|
||||
return timer
|
||||
@outer(True)
|
||||
def func():
|
||||
print('test')
|
||||
func()
|
||||
```
|
||||
|
||||
## 多个装饰器装饰一个函数
|
||||
|
||||
```python
|
||||
def wrapper1(func):
|
||||
def inner():
|
||||
print('第一个装饰器,在程序运行之前')
|
||||
func()
|
||||
print('第一个装饰器,在程序运行之后')
|
||||
return inner
|
||||
def wrapper2(func):
|
||||
def inner():
|
||||
print('第二个装饰器,在程序运行之前')
|
||||
func()
|
||||
print('第二个装饰器,在程序运行之后')
|
||||
return inner
|
||||
@wrapper1
|
||||
@wrapper2
|
||||
def f():
|
||||
print('Hello')
|
||||
f()
|
||||
```
|
||||
|
177
01.基础语法/05.迭代器与生成器.md
Normal file
@@ -0,0 +1,177 @@
|
||||
# 迭代器与生成器
|
||||
|
||||
## 迭代器
|
||||
|
||||
字符串、列表、元组、字典、集合都可以被for循环,说明他们都是可迭代的
|
||||
|
||||
```python
|
||||
from collections import Iterable
|
||||
l = [1, 2, 3, 4]
|
||||
t = (1, 2, 3, 4)
|
||||
d = {1: 2, 3: 4}
|
||||
s = {1, 2, 3, 4}
|
||||
print(isinstance(l, Iterable))
|
||||
print(isinstance(t, Iterable))
|
||||
print(isinstance(d, Iterable))
|
||||
print(isinstance(s, Iterable))
|
||||
```
|
||||
|
||||
### 可迭代协议
|
||||
|
||||
可以被迭代要满足的要求就叫做可迭代协议。可迭代协议的定义非常简单,就是内部实现了iter方法。
|
||||
|
||||
```python
|
||||
l = [1, 2, 3, 4]
|
||||
t = (1, 2, 3, 4)
|
||||
d = {1: 2, 3: 4}
|
||||
s = {1, 2, 3, 4}
|
||||
print(dir(l))
|
||||
print(dir(t))
|
||||
print(dir(d))
|
||||
print(dir(s))
|
||||
```
|
||||
|
||||
可迭代的:内部必须含有一个iter方法。
|
||||
|
||||
### 迭代器
|
||||
|
||||
```python
|
||||
l = [1, 2, 3, 4]
|
||||
l_iter = l.__iter__()
|
||||
item = l_iter.__next__()
|
||||
print(item)
|
||||
item = l_iter.__next__()
|
||||
print(item)
|
||||
item = l_iter.__next__()
|
||||
print(item)
|
||||
item = l_iter.__next__()
|
||||
print(item)
|
||||
item = l_iter.__next__()
|
||||
print(item)
|
||||
```
|
||||
|
||||
迭代器遵循迭代器协议:必须拥有iter方法和next方法。
|
||||
for循环,能遍历一个可迭代对象,他的内部到底进行了什么?
|
||||
|
||||
将可迭代对象转化成迭代器。(可迭代对象.iter())
|
||||
|
||||
内部使用next方法,一个一个取值。
|
||||
|
||||
加了异常处理功能,取值到底后自动停止。
|
||||
|
||||
```python
|
||||
l = [1, 2, 3, 4]
|
||||
l_iter = l.__iter__()
|
||||
while True:
|
||||
try:
|
||||
item = l_iter.__next__()
|
||||
print(item)
|
||||
except StopIteration:
|
||||
break
|
||||
```
|
||||
|
||||
### 为什么要有for循环
|
||||
|
||||
for循环就是基于迭代器协议提供了一个统一的可以遍历所有对象的方法,即在遍历之前,先调用对象的iter方法将其转换成一个迭代器,然后使用迭代器协议去实现循环访问,这样所有的对象就都可以通过for循环来遍历了
|
||||
|
||||
最重要的一点,转化成迭代器,在循环时,同一时刻在内存中只出现一条数据,极大限度的节省了内存
|
||||
|
||||
## 生成器
|
||||
|
||||
### 初识生成器
|
||||
|
||||
**Python中提供的生成器**
|
||||
|
||||
1. 生成器函数:常规函数定义,但是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行
|
||||
2. 生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表
|
||||
|
||||
**生成器Generator**
|
||||
|
||||
* 本质:迭代器(所以自带了iter方法和next方法,不需要我们去实现)
|
||||
* 特点:惰性运算,开发者自定义
|
||||
|
||||
### 生成器函数
|
||||
|
||||
一个包含yield关键字的函数就是一个生成器函数。yield可以为我们从函数中返回值,但是yield又不同于return,return的执行意味着程序的结束,调用生成器函数不会得到返回的具体的值,而是得到一个可迭代的对象。每一次获取这个可迭代对象的值,就能推动函数的执行,获取新的返回值。直到函数执行结束。
|
||||
|
||||
```python
|
||||
import time
|
||||
def genrator_func1():
|
||||
a = 1
|
||||
print('将a赋值')
|
||||
yield a
|
||||
b = 2
|
||||
print('将b赋值')
|
||||
yield b
|
||||
g1 = genrator_func1()
|
||||
print(g1,next(g1))
|
||||
print(next(g1))
|
||||
```
|
||||
|
||||
生成器不会一下子在内存中生成太多数据
|
||||
比如我想卖包子,让包子工厂开始加工10000个包子,但是如果一下子全部生产好,没地方放,而且容易坏。
|
||||
|
||||
那么可以让包子工厂在我需要的时候再生产
|
||||
|
||||
```python
|
||||
def produce():
|
||||
'''生产包子'''
|
||||
for i in range(10000):
|
||||
yield '生产了第%s个包子'%i
|
||||
produce_g = produce()
|
||||
print(produce_g.__next__())
|
||||
print(produce_g.__next__())
|
||||
print(produce_g.__next__())
|
||||
|
||||
# 需要一批包子
|
||||
num = 0
|
||||
for i in produce_g:
|
||||
print(i)
|
||||
num += 1
|
||||
if num == 5:
|
||||
break
|
||||
```
|
||||
|
||||
### send
|
||||
|
||||
send 获取下一个值的效果和next基本一致
|
||||
|
||||
只是在获取下一个值的时候,给上一yield的位置传递一个数据
|
||||
|
||||
使用send的注意事项
|
||||
|
||||
* 第一次使用生成器的时候 是用next获取下一个值
|
||||
* 最后一个yield不能接受外部的值
|
||||
|
||||
```python
|
||||
def generator():
|
||||
print(123)
|
||||
content = yield 1
|
||||
print('=========',content)
|
||||
print(456)
|
||||
yield 2
|
||||
g = generator()
|
||||
ret = g.__next__()
|
||||
print('***',ret)
|
||||
ret = g.send('hello')
|
||||
print('***',ret)
|
||||
```
|
||||
|
||||
## 列表推导式和生成器表达式
|
||||
|
||||
```python
|
||||
l = [i for i in range(10)]
|
||||
print(l)
|
||||
l1 = ['项目%s'%i for i in range(10)]
|
||||
print(l1)
|
||||
```
|
||||
|
||||
1. 把列表解析的[]换成()得到的就是生成器表达式
|
||||
2. 列表解析与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存
|
||||
3. Python不但使用迭代器协议,让for循环变得更加通用。大部分内置函数,也是使用迭代器协议访问对象的。例如, sum函数是Python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议,所以,我们可以直接这样计算一系列值的和:
|
||||
|
||||
```python
|
||||
ret = sum(x for x in range(101))
|
||||
print(ret)
|
||||
```
|
||||
|
73
01.基础语法/06.推导式.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# 推导式
|
||||
|
||||
## 推导式详细格式
|
||||
|
||||
```plain
|
||||
variable = [out_exp_res for out_exp in input_list if out_exp == 2]
|
||||
out_exp_res: 列表生成元素表达式,可以是有返回值的函数。
|
||||
for out_exp in input_list: 迭代input_list将out_exp传入out_exp_res表达式中。
|
||||
if out_exp == 2: 根据条件过滤哪些值可以。
|
||||
```
|
||||
|
||||
## 列表推导式
|
||||
|
||||
30以内所有能被3整除的数
|
||||
|
||||
```python
|
||||
multiples = [i for i in range(30) if i % 3 is 0]
|
||||
print(multiples)
|
||||
```
|
||||
|
||||
30以内所有能被3整除的数的平方
|
||||
|
||||
```python
|
||||
def squared(x):
|
||||
return x*x
|
||||
multiples = [squared(i) for i in range(30) if i % 3 is 0]
|
||||
print(multiples)
|
||||
```
|
||||
|
||||
找到嵌套列表中名字含有两个及以上‘a’的所有名字
|
||||
|
||||
```python
|
||||
fruits = [['peach','Lemon','Pear','avocado','cantaloupe','Banana','Grape'],
|
||||
['raisins','plum','apricot','nectarine','orange','papaya']]
|
||||
print([name for lst in fruits for name in lst if name.count('a') >= 2])
|
||||
```
|
||||
|
||||
## 字典推导式
|
||||
|
||||
将一个字典的key和value对调
|
||||
|
||||
```python
|
||||
dic1 = {'a':1,'b':2}
|
||||
dic2 = {dic1[k]: k for k in dic1}
|
||||
print(dic2)
|
||||
```
|
||||
|
||||
合并大小写对应的value值,将k统一成小写
|
||||
|
||||
```python
|
||||
dic1 = {'a':1,'b':2,'A':4,'Y':9}
|
||||
dic2 = {k.lower():dic1.get(k.lower(),0) + dic1.get(k.upper(),0) for k in dic1.keys()}
|
||||
print(dic2)
|
||||
```
|
||||
|
||||
## 集合推导式
|
||||
|
||||
计算列表中每个值的平方,自带去重功能
|
||||
|
||||
```python
|
||||
l = [1,2,3,4,1,-1,-2,3]
|
||||
squared = {x**2 for x in l}
|
||||
print(squared)
|
||||
```
|
||||
|
||||
## 练习题
|
||||
|
||||
1. 过滤掉长度小于3的字符串列表,并将剩下的转换成大写字母
|
||||
2. 求(x,y)其中x是0-5之间的偶数,y是0-5之间的奇数组成的元祖列表
|
||||
3. 将1000以内的素数放入一个列表中
|
||||
|
||||
|
||||
|
535
01.基础语法/07.内置函数与匿名函数.md
Normal file
@@ -0,0 +1,535 @@
|
||||
# 内置函数与匿名函数
|
||||
|
||||
## 内置函数
|
||||
|
||||
截止到python版本3.6.2,现在python一共为我们提供了**68个内置函数。**
|
||||
|
||||
| 内置函数 | | | | |
|
||||
| :------------ | :---------- | :----------- | :--------- | :------------- |
|
||||
| abs() | dict() | help() | min() | setattr() |
|
||||
| all() | dir() | hex() | next() | slice() |
|
||||
| any() | divmod() | id() | object() | sorted() |
|
||||
| ascii() | enumerate() | input() | oct() | staticmethod() |
|
||||
| bin() | eval() | int() | open() | str() |
|
||||
| bool() | exec() | isinstance() | ord() | sum() |
|
||||
| bytearray() | filter() | issubclass() | pow() | super() |
|
||||
| bytes() | float() | iter() | print() | tuple() |
|
||||
| callable() | format() | len() | property() | type() |
|
||||
| chr() | frozenset() | list() | range() | vars() |
|
||||
| classmethod() | getattr() | locals() | repr() | zip() |
|
||||
| compile() | globals() | map() | reversed() | __import__() |
|
||||
| complex() | hasattr() | max() | round() | |
|
||||
| delattr() | hash() | memoryview() | set() | |
|
||||
|
||||
### 作用域相关
|
||||
|
||||
* locals :函数会以字典的类型返回当前位置的全部局部变量。
|
||||
* globals:函数以字典的类型返回全部全局变量。
|
||||
|
||||
```python
|
||||
a = 1
|
||||
b = 2
|
||||
print(locals())
|
||||
print(globals())
|
||||
# 这两个一样,因为是在全局执行的
|
||||
def func(argv):
|
||||
c = 2
|
||||
print(locals())
|
||||
print(globals())
|
||||
func(3)
|
||||
```
|
||||
|
||||
### 其他相关
|
||||
|
||||
#### 字符串类型代码的执行 eval,exec,complie
|
||||
|
||||
* eval:执行字符串类型的代码,并返回最终结果。
|
||||
|
||||
```python
|
||||
ret = eval('2 + 2')
|
||||
print(ret)
|
||||
n = 20
|
||||
ret = eval('n + 23')
|
||||
print(ret)
|
||||
eval('print("Hello world")')
|
||||
```
|
||||
|
||||
* exec:执行字符串类型的代码。
|
||||
|
||||
```python
|
||||
s = '''
|
||||
for i in range(5):
|
||||
print(i)
|
||||
'''
|
||||
exec(s)
|
||||
```
|
||||
|
||||
compile:将字符串类型的代码编译。代码对象能够通过exec语句来执行或者eval()进行求值。
|
||||
|
||||
1. 参数source:字符串。即需要动态执行的代码段。
|
||||
2. 参数 filename:代码文件名称,如果不是从文件读取代码则传递一些可辨认的值。当传入了source参数时,filename参数传入空字符即可。
|
||||
3. 参数model:指定编译代码的种类,可以指定为 ‘exec’,’eval’,’single’。当source中包含流程语句时,model应指定为‘exec’;当source中只包含一个简单的求值表达式,model应指定为‘eval’;当source中包含了交互式命令语句,model应指定为'single'。
|
||||
|
||||
```python
|
||||
# 流程语句使用exec
|
||||
code1 = 'for i in range(5): print(i)'
|
||||
compile1 = compile(code1,'','exec')
|
||||
exec(compile1)
|
||||
# 简单求值表达式用eval
|
||||
code2 = '1 + 2 + 3'
|
||||
compile2 = compile(code2,'','eval')
|
||||
eval(compile2)
|
||||
# 交互语句用single
|
||||
code3 = 'name = input("please input you name: ")'
|
||||
compile3 = compile(code3,'','single')
|
||||
exec(compile3)
|
||||
print(name)
|
||||
```
|
||||
|
||||
有返回值的字符串形式的代码用eval,没有返回值的字符串形式的代码用exec,一般不用compile。
|
||||
|
||||
#### 输入输出相关 input,print
|
||||
|
||||
* input:函数接受一个标准输入数据,返回为 string 类型。
|
||||
* print:打印输出。
|
||||
|
||||
```python
|
||||
''' 源码分析
|
||||
def print(self, *args, sep=' ', end='\n', file=None): # known special case of print
|
||||
"""
|
||||
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
|
||||
file: 默认是输出到屏幕,如果设置为文件句柄,输出到文件
|
||||
sep: 打印多个值之间的分隔符,默认为空格
|
||||
end: 每一次打印的结尾,默认为换行符
|
||||
flush: 立即把内容输出到流文件,不作缓存
|
||||
"""
|
||||
'''
|
||||
print(11,22,33,sep='*')
|
||||
print(11,22,33,end='')
|
||||
print(44,55)
|
||||
with open('log','w',encoding='utf-8') as f:
|
||||
print('写入文件',file=f,flush=True)
|
||||
```
|
||||
|
||||
#### 内存相关 hash id
|
||||
|
||||
* hash:获取一个对象(可哈希对象:int,str,Bool,tuple)的哈希值。
|
||||
|
||||
```python
|
||||
print(hash(12322))
|
||||
print(hash('123'))
|
||||
print(hash('arg'))
|
||||
print(hash('aaron'))
|
||||
print(hash(True))
|
||||
print(hash(False))
|
||||
print(hash((1,2,3)))
|
||||
```
|
||||
|
||||
* id:用于获取对象的内存地址。
|
||||
|
||||
```python
|
||||
print(id('abc'))
|
||||
print(id('123'))
|
||||
```
|
||||
|
||||
#### 文件操作相关
|
||||
|
||||
* open:函数用于打开一个文件,创建一个 file 对象,相关的方法才可以调用它进行读写。
|
||||
|
||||
#### 模块相关`__import__`
|
||||
|
||||
* `__import__`:函数用于动态加载类和函数 。
|
||||
|
||||
#### 帮助
|
||||
|
||||
* help:函数用于查看函数或模块用途的详细说明。
|
||||
|
||||
```python
|
||||
print(help(print))
|
||||
```
|
||||
|
||||
#### 调用相关
|
||||
|
||||
* callable:函数用于检查一个对象是否是可调用的。如果返回True,object仍然可能调用失败;但如果返回- False,调用对象ojbect绝对不会成功。
|
||||
|
||||
```python
|
||||
print(callable(0))
|
||||
print(callable('hello'))
|
||||
def demo1(a, b):
|
||||
return a + b
|
||||
print(callable(demo1))
|
||||
class Demo2:
|
||||
def test1(self):
|
||||
return 0
|
||||
print(callable(Demo2))
|
||||
a = Demo2()
|
||||
print(callable(a))
|
||||
# 没有实现 __call__, 返回 False
|
||||
```
|
||||
|
||||
#### 查看内置属性
|
||||
|
||||
* dir:函数不带参数时,返回当前范围内的变量、方法和定义的类型列表;带参数时,返回参数的属性、方法列表。如果参数包含方法`__dir__()`,该方法将被调用。如果参数不包含`__dir__()`,该方法将最大限度地收集参数信息。
|
||||
|
||||
```python
|
||||
print(dir()) # 获得当前模块的属性列表
|
||||
print(dir([ ])) # 查看列表的方法
|
||||
```
|
||||
|
||||
### 迭代器生成器相关
|
||||
|
||||
* range:函数可创建一个整数对象,一般用在 for 循环中。
|
||||
* next:内部实际使用了`__next__`方法,返回迭代器的下一个项目。
|
||||
|
||||
```python
|
||||
# 首先获得Iterator对象:
|
||||
it = iter([1,2,3,4,5,6])
|
||||
# 循环
|
||||
while True:
|
||||
try:
|
||||
# 获得下一个值
|
||||
x = next(it)
|
||||
print(x)
|
||||
except StopIteration: # 遇到StopIteration就退出循环
|
||||
break
|
||||
```
|
||||
|
||||
* iter:函数用来生成迭代器(讲一个可迭代对象,生成迭代器)。
|
||||
|
||||
```python
|
||||
from collections import Iterable
|
||||
from collections import Iterator
|
||||
l = [1,2,3,4] # 可迭代对象,但不是迭代器
|
||||
print(isinstance(l,Iterable))
|
||||
print(isinstance(l,Iterator))
|
||||
l1 = iter(l) # 从一个可迭代对象生成迭代器
|
||||
print(isinstance(l1,Iterable))
|
||||
print(isinstance(l1,Iterator))
|
||||
```
|
||||
|
||||
### 基础数据类型相关
|
||||
|
||||
#### 数字相关(14个)
|
||||
|
||||
数据类型(4个):
|
||||
|
||||
* bool :用于将给定参数转换为布尔类型,如果没有参数,返回 False。
|
||||
* int:函数用于将一个字符串或数字转换为整型。
|
||||
|
||||
```python
|
||||
print(int())
|
||||
print(int('12'))
|
||||
print(int(3.6))
|
||||
print(int('0100',base=2)) # 将2进制的 0100 转化成十进制。结果为 4
|
||||
```
|
||||
|
||||
* float:函数用于将整数和字符串转换成浮点数。
|
||||
* complex:函数用于创建一个值为 real + imag * j 的复数或者转化一个字符串或数为复数。如果第一个参数为字符串,则不需要指定第二个参数。。
|
||||
|
||||
```python
|
||||
print(complex(1,2))
|
||||
print(complex(1))
|
||||
print(complex("1"))
|
||||
print(complex("1+2j"))
|
||||
```
|
||||
|
||||
进制转换(3个):
|
||||
|
||||
* bin:将十进制转换成二进制并返回。
|
||||
* oct:将十进制转化成八进制字符串并返回。
|
||||
* hex:将十进制转化成十六进制字符串并返回。
|
||||
|
||||
```python
|
||||
print(bin(10),type(bin(10)))
|
||||
print(oct(10),type(oct(10)))
|
||||
print(hex(10),type(hex(10)))
|
||||
```
|
||||
|
||||
数学运算(7):
|
||||
|
||||
* abs:函数返回数字的绝对值。
|
||||
* divmod:计算除数与被除数的结果,返回一个包含商和余数的元组(a // b, a % b)。
|
||||
* round:保留浮点数的小数位数,默认保留整数。 -pow:函数是计算x的y次方,如果z在存在,则再对结果进行取模,其结果等效于pow(x,y) %z)
|
||||
|
||||
```python
|
||||
print(abs(-5)) # 5
|
||||
print(divmod(7,2)) # (3, 1)
|
||||
print(round(7/3,2)) # 2.33
|
||||
print(round(7/3)) # 2
|
||||
print(round(3.32567,3)) # 3.326
|
||||
print(pow(2,3)) # 8
|
||||
print(pow(2,3,3)) # 2
|
||||
```
|
||||
|
||||
* sum:对可迭代对象进行求和计算(可设置初始值)。
|
||||
* min:返回可迭代对象的最小值(可加key,key为函数名,通过函数的规则,返回最小值)。
|
||||
* max:返回可迭代对象的最大值(可加key,key为函数名,通过函数的规则,返回最大值)。
|
||||
|
||||
```python
|
||||
print(sum([1,2,3]))
|
||||
print(sum([1,2,3],100))
|
||||
print(min([1,2,3]))
|
||||
ret = min([1,2,3,-10],key=abs)
|
||||
print(ret)
|
||||
dic = {'a':3,'b':2,'c':1}
|
||||
print(min(dic,key=lambda x:dic[x]))
|
||||
# x为dic的key,lambda的返回值(即dic的值进行比较)返回最小的值对应的键
|
||||
print(max([1,2,3]))
|
||||
ret = max([1,2,3,-10],key=abs)
|
||||
print(ret)
|
||||
dic = {'a':3,'b':2,'c':1}
|
||||
print(max(dic,key=lambda x:dic[x]))
|
||||
```
|
||||
|
||||
#### 数据结构相关(24个)
|
||||
|
||||
列表和元祖(2个)
|
||||
|
||||
* list:将一个可迭代对象转化成列表(如果是字典,默认将key作为列表的元素)。
|
||||
* tuple:将一个可迭代对象转化成元祖(如果是字典,默认将key作为元祖的元素)。
|
||||
|
||||
```python
|
||||
l = list((1,2,3))
|
||||
print(l)
|
||||
l = list({1,2,3})
|
||||
print(l)
|
||||
l = list({'k1':1,'k2':2})
|
||||
print(l)
|
||||
tu = tuple((1,2,3))
|
||||
print(tu)
|
||||
tu = tuple([1,2,3])
|
||||
print(tu)
|
||||
tu = tuple({'k1':1,'k2':2})
|
||||
print(tu)
|
||||
```
|
||||
|
||||
相关内置函数(2个)
|
||||
|
||||
* reversed:将一个序列翻转,并返回此翻转序列的迭代器。
|
||||
* slice:构造一个切片对象,用于列表的切片。
|
||||
|
||||
```python
|
||||
ite = reversed(['a',2,4,'f',12,6])
|
||||
for i in ite:
|
||||
print(i)
|
||||
l = ['a','b','c','d','e','f','g']
|
||||
sli = slice(3)
|
||||
print(l[sli])
|
||||
sli = slice(0,7,2)
|
||||
print(l[sli])
|
||||
```
|
||||
|
||||
字符串相关(9)
|
||||
|
||||
* str:将数据转化成字符串。
|
||||
* format:与具体数据相关,用于计算各种小数,精算等。
|
||||
|
||||
```python
|
||||
# 字符串可以提供的参数,指定对齐方式,<是左对齐, >是右对齐,^是居中对齐
|
||||
print(format('test','<20'))
|
||||
print(format('test','>20'))
|
||||
print(format('test','^20'))
|
||||
# 整形数值可以提供的参数有 'b' 'c' 'd' 'o' 'x' 'X' 'n' None
|
||||
print(format(192,'b')) # 转换为二进制
|
||||
print(format(97,'c')) # 转换unicode成字符
|
||||
print(format(11,'d')) # 转换成10进制
|
||||
print(format(11,'o')) # 转换为8进制
|
||||
print(format(11,'x')) # 转换为16进制,小写字母表示
|
||||
print(format(11,'X')) # 转换为16进制,大写字母表示
|
||||
print(format(11,'n')) # 和d一样
|
||||
print(format(11)) # 和d一样
|
||||
# 浮点数可以提供的参数有 'e' 'E' 'f' 'F' 'g' 'G' 'n' '%' None
|
||||
print(format(314159265,'e')) # 科学计数法,默认保留6位小数
|
||||
print(format(314159265,'0.2e')) # 科学计数法,保留2位小数
|
||||
print(format(314159265,'0.2E')) # 科学计数法,保留2位小数,大写E
|
||||
print(format(3.14159265,'f')) # 小数点计数法,默认保留6位小数
|
||||
print(format(3.14159265,'0.10f')) # 小数点计数法,保留10位小数
|
||||
print(format(3.14e+10000,'F')) # 小数点计数法,无穷大转换成大小字母
|
||||
# g的格式化比较特殊,假设p为格式中指定的保留小数位数,先尝试采用科学计数法格式化,得到幂指数exp,如果-4<=exp<p,则采用小数计数法,并保留p-1-exp位小数,否则按小数计数法计数,并按p-1保留小数位数
|
||||
print(format(0.00003141566,'.1g'))
|
||||
# p=1,exp=-5 ==》 -4<=exp<p不成立,按科学计数法计数,保留0位小数点
|
||||
print(format(0.00003141566,'.2g'))
|
||||
# p=2,exp=-5 ==》 -4<=exp<p不成立,按科学计数法计数,保留1位小数点
|
||||
print(format(3.1415926777,'.1g'))
|
||||
# p=1,exp=0 ==》 -4<=exp<p成立,按小数计数法计数,保留0位小数点
|
||||
print(format(3.1415926777,'.2g'))
|
||||
# p=2,exp=0 ==》 -4<=exp<p成立,按小数计数法计数,保留1位小数点
|
||||
print(format(3141.5926777,'.2g'))
|
||||
# p=2,exp=3 ==》 -4<=exp<p不成立,按科学计数法计数,保留1位小数点
|
||||
print(format(0.00003141566,'.1n')) # 和g相同
|
||||
print(format(0.00003141566)) # 和g相同
|
||||
```
|
||||
|
||||
* bytes:用于不同编码之间的转化。
|
||||
|
||||
```python
|
||||
s = '你好'
|
||||
bs = s.encode('utf-8')
|
||||
print(bs)
|
||||
s1 = bs.decode('utf-8')
|
||||
print(s1)
|
||||
bs = bytes(s,encoding='utf-8')
|
||||
print(bs)
|
||||
b = '你好'.encode('gbk')
|
||||
b1 = b.decode('gbk')
|
||||
print(b1.encode('utf-8'))
|
||||
```
|
||||
|
||||
* bytearry:返回一个新字节数组。这个数组里的元素是可变的,并且每个元素的值范围: 0 <= x < 256。
|
||||
|
||||
```python
|
||||
ret = bytearray('aaron',encoding='utf-8')
|
||||
print(id(ret))
|
||||
print(ret)
|
||||
print(ret[0])
|
||||
ret[0] = 65
|
||||
print(ret)
|
||||
print(id(ret))
|
||||
```
|
||||
|
||||
* memoryview: 内存查看对象,是指对支持缓冲区协议的数据进行包装,在不需要复制对象基础上允许Python代码访问。
|
||||
|
||||
```python
|
||||
ret = memoryview(bytes('你好',encoding='utf-8'))
|
||||
print(len(ret))
|
||||
print(ret)
|
||||
print(bytes(ret[:3]).decode('utf-8'))
|
||||
print(bytes(ret[3:]).decode('utf-8'))
|
||||
```
|
||||
|
||||
* ord:输入字符找该字符编码的位置
|
||||
* chr:输入位置数字找出其对应的字符
|
||||
* ascii:是ascii码中的返回该值,不是就返回/u…
|
||||
|
||||
```python
|
||||
# ord 输入字符找该字符编码的位置
|
||||
print(ord('a'))
|
||||
print(ord('中'))
|
||||
# chr 输入位置数字找出其对应的字符
|
||||
print(chr(97))
|
||||
print(chr(20013))
|
||||
# 是ascii码中的返回该值,不是就返回/u...
|
||||
print(ascii('a'))
|
||||
print(ascii('中'))
|
||||
```
|
||||
|
||||
* repr:返回一个对象的string形式
|
||||
|
||||
```python
|
||||
name = 'aaron'
|
||||
print('Hello %r'%name)
|
||||
str1 = '{"name":"aaron"}'
|
||||
print(repr(str1))
|
||||
print(str1)
|
||||
```
|
||||
|
||||
数据集合(3个)
|
||||
|
||||
* dict:创建一个字典。
|
||||
* set:创建一个集合。
|
||||
* frozenset:返回一个冻结的集合,冻结后集合不能再添加或删除任何元素。
|
||||
|
||||
相关内置函数(8个)
|
||||
|
||||
* len:返回一个对象中元素的个数。
|
||||
* sorted:对所有可迭代的对象进行排序操作。
|
||||
|
||||
```python
|
||||
l = [('a',1),('c',3),('d',4),('b',2)]
|
||||
print(sorted(l,key=lambda x:x[1]))
|
||||
print(sorted(l,key=lambda x:x[1],reverse=True)) # 降序
|
||||
```
|
||||
|
||||
* enumerate: 用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。
|
||||
|
||||
```python
|
||||
print(enumerate([1,2,3]))
|
||||
for i in enumerate([1,2,3]):
|
||||
print(i)
|
||||
for i in enumerate([1,2,3],100):
|
||||
print(i)
|
||||
```
|
||||
|
||||
* all:可迭代对象中,全都是True才是True
|
||||
* any:可迭代对象中,有一个True 就是True
|
||||
|
||||
```python
|
||||
print(all([1,2,True,0]))
|
||||
print(any([1,'',0]))
|
||||
```
|
||||
|
||||
* zip:函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同。
|
||||
|
||||
```python
|
||||
l1 = [1,2,3,]
|
||||
l2 = ['a','b','c',5]
|
||||
l3 = ('*','**',(1,2,3))
|
||||
for i in zip(l1,l2,l3):
|
||||
print(i)
|
||||
```
|
||||
|
||||
filter:用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表。
|
||||
|
||||
```python
|
||||
def func(x): return x%2 == 0
|
||||
ret = filter(func,[1,2,3,4,5,6,7,8,9,10])
|
||||
print(ret)
|
||||
for i in ret:
|
||||
print(i)
|
||||
```
|
||||
|
||||
* map:会根据提供的函数对指定序列做映射。Python 3.x 返回迭代器
|
||||
|
||||
```python
|
||||
def square(x):
|
||||
return x**2
|
||||
ret1 = map(square,[1,2,3,4,5,6,7,8])
|
||||
ret2 = map(lambda x:x ** 2,[1,2,3,4,5,6,7,8])
|
||||
ret3 = map(lambda x,y : x+y,[1,2,3,4,5,6,7,8],[8,7,6,5,4,3,2,1])
|
||||
for i in ret1:
|
||||
print(i,end=' ')
|
||||
print('')
|
||||
for i in ret2:
|
||||
print(i,end=' ')
|
||||
print('')
|
||||
for i in ret3:
|
||||
print(i,end=' ')
|
||||
```
|
||||
|
||||
## 匿名函数
|
||||
|
||||
匿名函数:为了解决那些功能很简单的需求而设计的一句话函数。
|
||||
|
||||
```python
|
||||
# 这段代码
|
||||
def calc(n):
|
||||
return n ** n
|
||||
|
||||
print(calc(10))
|
||||
# 换成匿名函数
|
||||
calc = lambda n: n ** n
|
||||
print(calc(10))
|
||||
```
|
||||
|
||||
>匿名函数格式的说明
|
||||
|
||||
**函数名 = lambda 参数 :返回值**
|
||||
|
||||
1. 参数可以有多个,用逗号隔开
|
||||
2. 匿名函数不管逻辑多复杂,只能写一行,且逻辑执行结束后的内容就是返回值
|
||||
3. 返回值和正常的函数一样可以是任意数据类型
|
||||
|
||||
```python
|
||||
l=[3,2,100,999,213,1111,31121,333]
|
||||
print(max(l))
|
||||
dic={'k1':10,'k2':100,'k3':30}
|
||||
|
||||
print(max(dic))
|
||||
print(dic[max(dic,key=lambda k:dic[k])])
|
||||
res = map(lambda x:x**2,[1,5,7,4,8])
|
||||
for i in res:
|
||||
print(i)
|
||||
res = filter(lambda x:x>10,[5,8,11,9,15])
|
||||
for i in res:
|
||||
print(i)
|
||||
```
|
139
01.基础语法/08.递归函数.md
Normal file
367
01.基础语法/09.模块和包.md
Normal file
@@ -0,0 +1,367 @@
|
||||
# 模块和包
|
||||
|
||||
## 什么是模块
|
||||
|
||||
1. 使用python编写的代码(.py文件)
|
||||
2. 已被编译为共享库或DLL的C或C++扩展
|
||||
3. 包好一组模块的包
|
||||
4. 使用C编写并链接到python解释器的内置模块
|
||||
|
||||
## 为何要使用模块
|
||||
|
||||
实现代码和功能的复用
|
||||
|
||||
### import 自定义模块my_module.py
|
||||
|
||||
文件名my_module.py,模块名my_module
|
||||
|
||||
```python
|
||||
# my_module.py
|
||||
|
||||
print('from the my_module.py')
|
||||
|
||||
money = 100
|
||||
|
||||
def read1():
|
||||
print('my_module->read1->money',money)
|
||||
|
||||
def read2():
|
||||
print('my_module->read2 calling read1')
|
||||
read1()
|
||||
|
||||
def change():
|
||||
global money
|
||||
money=0
|
||||
```
|
||||
|
||||
模块可以包含可执行的语句和函数的定义,这些语句的目的是初始化模块,它们只在模块名第一次遇到导入import语句时才执行(import语句是可以在程序中的任意位置使用的,且针对同一个模块很import多次,为了防止你重复导入。
|
||||
python的优化手段是:第一次导入后就将模块名加载到内存了,后续的import语句仅是对已经加载大内存中的模块对象增加了一次引用,不会重新执行模块内的语句)
|
||||
|
||||
```python
|
||||
import my_module
|
||||
import my_module
|
||||
import my_module
|
||||
import my_module
|
||||
|
||||
import sys
|
||||
print(sys.modules)
|
||||
# sys.modules是一个字典,内部包含模块名与模块对象的映射,该字典决定了导入模块时是否需要重新导入。
|
||||
```
|
||||
|
||||
每个模块都是一个独立的名称空间,定义在这个模块中的函数,把这个模块的名称空间当做全局名称空间,这样我们在编写自己的模块时,就不用担心我们定义在自己模块中全局变量会在被导入时,与使用者的全局变量冲突
|
||||
|
||||
```python
|
||||
import my_module
|
||||
money=10
|
||||
print(my_module.money)
|
||||
```
|
||||
|
||||
```python
|
||||
import my_module
|
||||
def read1():
|
||||
print('=========')
|
||||
|
||||
my_module.read1()
|
||||
```
|
||||
|
||||
```python
|
||||
import my_module
|
||||
|
||||
money = 1
|
||||
my_module.change()
|
||||
print(money)
|
||||
print(my_module.money)
|
||||
```
|
||||
|
||||
总结:首次导入模块my_module时会做三件事:
|
||||
|
||||
1. 为源文件(my_module模块)创建新的名称空间,在my_module中定义的函数和方法若是使用到了global时访问的就是这个名称空间。
|
||||
|
||||
2. 在新创 建的命名空间中执行模块中包含的代码
|
||||
|
||||
3. 创建名字my_module来引用该命名空间
|
||||
|
||||
#### 为模块名起别名,相当于m1=1;m2=m1
|
||||
|
||||
```python
|
||||
import my_module as mm
|
||||
|
||||
print(mm.money)
|
||||
```
|
||||
|
||||
> 示范用法:
|
||||
|
||||
有两中sql模块mysql和oracle,根据用户的输入,选择不同的sql功能
|
||||
|
||||
```python
|
||||
# mysql.py
|
||||
def sqlparse():
|
||||
print('from mysql sqlparse')
|
||||
|
||||
# oracle
|
||||
def sqlparse():
|
||||
print('from oracle sqlparse')
|
||||
|
||||
# test.py
|
||||
db_type=input('>>: ')
|
||||
if db_type == 'mysql':
|
||||
import mysql as db
|
||||
elif db_type == 'oracle':
|
||||
import oracle as db
|
||||
|
||||
db.sqlparse()
|
||||
```
|
||||
|
||||
#### 在一行导入多个模块
|
||||
|
||||
```python
|
||||
import sys, os, re
|
||||
```
|
||||
|
||||
### from ... import ...
|
||||
|
||||
对比import my_module,会将源文件的名称空间'my_module'带到当前名称空间中,使用时必须是my_module.名字的方式
|
||||
|
||||
而from 语句相当于import,也会创建新的名称空间,但是将my_module中的名字直接导入到当前的名称空间中,在当前名称空间中,直接使用名字就可以了
|
||||
|
||||
```python
|
||||
from my_module import read1,read2
|
||||
money = 1000
|
||||
read1()
|
||||
# 导入的函数read1,执行时仍然回到my_module.py中寻找全局变量money
|
||||
```
|
||||
|
||||
```python
|
||||
from my_module import read1,read2
|
||||
money = 1000
|
||||
def read1():
|
||||
print('*'*10)
|
||||
|
||||
read2()
|
||||
# 导入的函数read2,执行时需要调用read1(),仍然回到my_module.py中找read1()
|
||||
```
|
||||
|
||||
```python
|
||||
from my_module import read1,read2
|
||||
money = 1000
|
||||
def read1():
|
||||
print('*'*10)
|
||||
|
||||
read1()
|
||||
# 导入的函数read1,被当前位置定义的read1覆盖掉了
|
||||
```
|
||||
|
||||
```python
|
||||
from my_module import read1 as read
|
||||
|
||||
read()
|
||||
# 也支持as
|
||||
```
|
||||
|
||||
from my_module import * 把my_module中所有的不是以下划线(_)开头的名字都导入到当前位置
|
||||
大部分情况下我们的python程序不应该使用这种导入方式,因为*你不知道你导入什么名字,很有可能会覆盖掉你之前已经定义的名字。而且可读性极其的差,在交互式环境中导入时没有问题。
|
||||
|
||||
在my_module.py中新增一行
|
||||
|
||||
```python
|
||||
.....
|
||||
__all__ = ['money','read1']
|
||||
# 这样在另外一个文件中用from my_module import *就这能导入列表中规定的两个名字
|
||||
|
||||
# test.py
|
||||
from my_module import *
|
||||
|
||||
print(money)
|
||||
read1()
|
||||
read2()
|
||||
```
|
||||
|
||||
<font color=red>注意:如果my_module.py中的名字前加_,即_money,则from my_module import *,则_money不能被导入</font>
|
||||
|
||||
- 编写好的一个python文件可以有两种用途:
|
||||
|
||||
1. 脚本,一个文件就是整个程序,用来被执行
|
||||
2. 模块,文件中存放着一堆功能,用来被导入使用
|
||||
|
||||
|
||||
- python为我们内置了全局变量`__name__`,
|
||||
|
||||
1. 当文件被当做脚本执行时:`__name__ 等于'__main__'`
|
||||
2. 当文件被当做模块导入时:`__name__等于模块名`
|
||||
|
||||
- 作用:用来控制.py文件在不同的应用场景下执行不同的逻辑(或者是在模块文件中测试代码)
|
||||
|
||||
1. `if __name__ == '__main__':`
|
||||
|
||||
```python
|
||||
def fib(n):
|
||||
a, b = 0, 1
|
||||
while b < n:
|
||||
print(b, end=',')
|
||||
a, b = b, a+b
|
||||
print()
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(__name__)
|
||||
num = input('num :')
|
||||
fib(int(num))
|
||||
```
|
||||
|
||||
## 模块的搜索路径
|
||||
|
||||
模块的查找顺序是:内存中已经加载的模块->自建模块->sys.path路径中包含的模块
|
||||
|
||||
1. 在第一次导入某个模块时(比如my_module),会先检查该模块是否已经被加载到内存中(当前执行文件的名称空间对应的内存),如果有则直接引用
|
||||
ps:python解释器在启动时会自动加载一些模块到内存中,可以使用sys.modules查看
|
||||
2. 如果没有,解释器则会查找同名的内建模块
|
||||
3. 如果还没有找到就从sys.path给出的目录列表中依次寻找my_module.py文件。
|
||||
|
||||
<font color=red>注意:自定义的模块名不应该与系统内置模块重名</font>
|
||||
|
||||
## 编译python文件
|
||||
|
||||
为了提高加载模块的速度,python解释器会在`__pycache__`目录中下缓存每个模块编译后的版本,格式为:module.version.pyc。通常会包含python的版本号。例如,在CPython3.3版本下,my_module.py模块会被缓存成`__pycache__/my_module.cpython-33.pyc`。这种命名规范保证了编译后的结果多版本共存。
|
||||
|
||||
|
||||
## 包
|
||||
|
||||
包就是一个包含有`__init__.py`文件的文件夹,所以其实我们创建包的目的就是为了用文件夹将文件/模块组织起来
|
||||
|
||||
需要强调的是:
|
||||
|
||||
1. 在python3中,即使包下没有`__init__.py`文件,import 包仍然不会报错,而在python2中,包下一定要有该文件,否则import 包报错
|
||||
|
||||
2. 创建包的目的不是为了运行,而是被导入使用,记住,包只是模块的一种形式而已,包的本质就是一种模块
|
||||
|
||||
### 为何要使用包
|
||||
|
||||
**包的本质就是一个文件夹,那么文件夹唯一的功能就是将文件组织起**来
|
||||
随着功能越写越多,我们无法将所以功能都放到一个文件中,于是我们使用模块去组织功能,而随着模块越来越多,我们就需要用文件夹将模块文件组织起来,以此来提高程序的结构性和可维护性
|
||||
|
||||
### 注意事项
|
||||
|
||||
1. 关于包相关的导入语句也分为`import`和`from ... import ...`两种,但是无论哪种,无论在什么位置,在导入时都必须遵循一个原则:凡是在导入时带点的,点的左边都必须是一个包,否则非法。可以带有一连串的点,如`item.subitem.subsubitem`,但都必须遵循这个原则。但对于导入后,在使用时就没有这种限制了,点的左边可以是包,模块,函数,类(它们都可以用点的方式调用自己的属性)。
|
||||
|
||||
2. import导入文件时,产生名称空间中的名字来源于文件,import 包,产生的名称空间的名字同样来源于文件,即包下的`__init__.py`,导入包本质就是在导入该文件
|
||||
|
||||
3. 包A和包B下有同名模块也不会冲突,如A.a与B.a来自俩个命名空间
|
||||
|
||||
### 包的使用
|
||||
|
||||
示例文件
|
||||
|
||||
```python
|
||||
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
|
||||
```
|
||||
|
||||
文件内容
|
||||
|
||||
```python
|
||||
#文件内容
|
||||
|
||||
#policy.py
|
||||
def get():
|
||||
print('from policy.py')
|
||||
|
||||
##versions.py
|
||||
def create_resource(conf):
|
||||
print('from version.py: ',conf)
|
||||
|
||||
#manage.py
|
||||
def main():
|
||||
print('from manage.py')
|
||||
|
||||
#models.py
|
||||
def register_models(engine):
|
||||
print('from models.py: ',engine)
|
||||
|
||||
```
|
||||
|
||||
#### 使用import导入包
|
||||
|
||||
```python
|
||||
import glance.db.models
|
||||
# 在导入glance的时候会执行glance下的__init__.py中的代码
|
||||
|
||||
glance.db.models.register_models('mysql')
|
||||
```
|
||||
|
||||
单独导入包名称时不会导入包中所有包含的所有子模块
|
||||
|
||||
```python
|
||||
import glance
|
||||
glance.cmd.manage.main()
|
||||
```
|
||||
|
||||
解决方法
|
||||
|
||||
```python
|
||||
# glance/__init__.py
|
||||
from . import cmd
|
||||
|
||||
# glance/cmd/__init__.py
|
||||
from . import manage
|
||||
```
|
||||
|
||||
#### 使用from (具体的路径) import (具体的模块)
|
||||
|
||||
需要注意的是from后import导入的模块,必须是明确的一个不能带点,否则会有语法错误,如:`from a import b.c`是错误语法
|
||||
|
||||
```python
|
||||
from glance.db import models
|
||||
from glance.db.models import register_models
|
||||
|
||||
models.register_models('mysql')
|
||||
register_models('mysql')
|
||||
```
|
||||
|
||||
`from glance.api import *`
|
||||
想从包api中导入所有,实际上该语句只会导入包api下`__init__.py`文件中定义的名字,我们可以在这个文件中定义`__all__`
|
||||
|
||||
```python
|
||||
x = 10
|
||||
|
||||
def func():
|
||||
print('from api.__init.py')
|
||||
|
||||
__all__=['x','func','policy']
|
||||
```
|
||||
|
||||
```python
|
||||
from glance.api import *
|
||||
|
||||
func()
|
||||
print(x)
|
||||
policy.get()
|
||||
```
|
||||
|
||||
## 绝对导入和相对导入
|
||||
|
||||
- 绝对导入:以glance作为起始
|
||||
- 相对导入:用.或者..的方式最为起始(只能在一个包中使用,不能用于不同目录内)
|
||||
|
||||
绝对导入: 以执行文件的sys.path为起始点开始导入,称之为绝对导入
|
||||
|
||||
1. 优点: 执行文件与被导入的模块中都可以使用
|
||||
2. 缺点: 所有导入都是以sys.path为起始点,导入麻烦
|
||||
|
||||
相对导入: 参照当前所在文件的文件夹为起始开始查找,称之为相对导入
|
||||
|
||||
1. 符号: .代表当前所在文件的文件加,..代表上一级文件夹,...代表上一级的上一级文件夹
|
||||
2. 优点: 导入更加简单
|
||||
3. 缺点: 只能在导入包中的模块时才能使用
|
||||
注意:
|
||||
|
||||
- 相对导入只能用于包内部模块之间的相互导入,导入者与被导入者都必须存在于一个包内
|
||||
- 试图在顶级包之外使用相对导入是错误的,言外之意,必须在顶级包内使用相对导入,每增加一个.代表跳到上一级文件夹,而上一级不应该超出顶级包
|
1377
01.基础语法/10.常用模块.md
Normal file
BIN
01.基础语法/10.常用模块/3856406007.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
01.基础语法/10.常用模块/5NnyAJyLO6F1x0aY.png!thumbnail
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
01.基础语法/10.常用模块/5sL2I6iz3J2Qn00r.png!thumbnail
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
01.基础语法/10.常用模块/838049513.png
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
01.基础语法/10.常用模块/987936105.png
Normal file
After Width: | Height: | Size: 82 KiB |
BIN
01.基础语法/10.常用模块/VA9mmGsaAk8HNGKh.png!thumbnail
Normal file
After Width: | Height: | Size: 77 KiB |
331
01.基础语法/12.异常处理.md
Normal file
@@ -0,0 +1,331 @@
|
||||
# 异常处理
|
||||
|
||||
## 异常和错误
|
||||
|
||||
### 程序中难免出现错误,而错误分成两种
|
||||
|
||||
1. 语法错误(这种错误,根本过不了python解释器的语法检测,必须在程序执行前就改正)
|
||||
|
||||
```python
|
||||
#语法错误示范一
|
||||
if
|
||||
|
||||
#语法错误示范二
|
||||
def test:
|
||||
pass
|
||||
|
||||
#语法错误示范三
|
||||
print(haha
|
||||
```
|
||||
|
||||
### 逻辑错误
|
||||
|
||||
```python
|
||||
#用户输入不完整(比如输入为空)或者输入非法(输入不是数字)
|
||||
num=input(">>: ")
|
||||
res1 = int(num)
|
||||
|
||||
#无法完成计算
|
||||
res1=1/0
|
||||
res2=1+'str'
|
||||
```
|
||||
|
||||
### 异常
|
||||
|
||||
异常就是程序运行时发生错误的信号
|
||||
异常之后的代码就不执行
|
||||
|
||||

|
||||
|
||||
### 异常种类
|
||||
|
||||
在python中不同的异常可以用不同的类型(python中统一了类与类型,类型即类)去标识,不同的类对象标识不同的异常,一个异常标识一种错误
|
||||
|
||||
```python
|
||||
# 触发IndexError
|
||||
l=['eagle','aa']
|
||||
l[3]
|
||||
# 触发KeyError
|
||||
dic={'name':'eagle'}
|
||||
dic['age']
|
||||
|
||||
#触发ValueError
|
||||
s='hello'
|
||||
int(s)
|
||||
```
|
||||
|
||||
常见异常
|
||||
|
||||
| AttributeError | 试图访问一个对象没有的属性,比如foo.x,但是foo没有属性x |
|
||||
| :---------------- | :----------------------------------------------------------- |
|
||||
| IOError | 输入/输出异常;基本上是无法打开文件 |
|
||||
| ImportError | 无法引入模块或包;基本上是路径问题或名称错误 |
|
||||
| IndentationError | 语法错误(的子类) ;代码没有正确对齐 |
|
||||
| IndexError | 下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5] |
|
||||
| KeyError | 试图访问字典里不存在的键 |
|
||||
| KeyboardInterrupt | Ctrl+C被按下 |
|
||||
| NameError | 使用一个还未被赋予对象的变量 |
|
||||
| SyntaxError | Python代码非法,代码不能编译(个人认为这是语法错误,写错了) |
|
||||
| TypeError | 传入对象类型与要求的不符合 |
|
||||
| UnboundLocalError | 试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量,导致你以为正在访问它 |
|
||||
| ValueError | 传入一个调用者不期望的值,即使值的类型是正确的 |
|
||||
|
||||
其他错误
|
||||
|
||||
```python
|
||||
ArithmeticError
|
||||
AssertionError
|
||||
AttributeError
|
||||
BaseException
|
||||
BufferError
|
||||
BytesWarning
|
||||
DeprecationWarning
|
||||
EnvironmentError
|
||||
EOFError
|
||||
Exception
|
||||
FloatingPointError
|
||||
FutureWarning
|
||||
GeneratorExit
|
||||
ImportError
|
||||
ImportWarning
|
||||
IndentationError
|
||||
IndexError
|
||||
IOError
|
||||
KeyboardInterrupt
|
||||
KeyError
|
||||
LookupError
|
||||
MemoryError
|
||||
NameError
|
||||
NotImplementedError
|
||||
OSError
|
||||
OverflowError
|
||||
PendingDeprecationWarning
|
||||
ReferenceError
|
||||
RuntimeError
|
||||
RuntimeWarning
|
||||
StandardError
|
||||
StopIteration
|
||||
SyntaxError
|
||||
SyntaxWarning
|
||||
SystemError
|
||||
SystemExit
|
||||
TabError
|
||||
TypeError
|
||||
UnboundLocalError
|
||||
UnicodeDecodeError
|
||||
UnicodeEncodeError
|
||||
UnicodeError
|
||||
UnicodeTranslateError
|
||||
UnicodeWarning
|
||||
UserWarning
|
||||
ValueError
|
||||
Warning
|
||||
ZeroDivisionError
|
||||
```
|
||||
|
||||
## 异常处理
|
||||
|
||||
- python解释器检测到错误,触发异常(也允许程序员自己触发异常)
|
||||
- 程序员编写特定的代码,专门用来捕捉这个异常(这段代码与程序逻辑无关,与异常处理有关)
|
||||
- 如果捕捉成功则进入另外一个处理分支,执行你为其定制的逻辑,使程序不会崩溃,这就是异常处理
|
||||
**首先须知,异常是由程序的错误引起的,语法上的错误跟异常处理无关,必须在程序运行前就修正**
|
||||
|
||||
```python
|
||||
num1=input('>>: ') #输入一个字符串试试
|
||||
if num1.isdigit():
|
||||
int(num1) #我们的正统程序放到了这里,其余的都属于异常处理范畴
|
||||
elif num1.isspace():
|
||||
print('输入的是空格,就执行我这里的逻辑')
|
||||
elif len(num1) == 0:
|
||||
print('输入的是空,就执行我这里的逻辑')
|
||||
else:
|
||||
print('其他情情况,执行我这里的逻辑')
|
||||
|
||||
'''
|
||||
问题一:
|
||||
使用if的方式我们只为第一段代码加上了异常处理,但这些if,跟你的代码逻辑并无关系,这样你的代码会因为可读性差而不容易被看懂
|
||||
|
||||
问题二:
|
||||
这只是我们代码中的一个小逻辑,如果类似的逻辑多,那么每一次都需要判断这些内容,就会倒置我们的代码特别冗长。
|
||||
'''
|
||||
```
|
||||
|
||||
总结:
|
||||
|
||||
1. if判断式的异常处理只能针对某一段代码,对于不同的代码段的相同类型的错误你需要写重复的if来进行处理。
|
||||
|
||||
2. 在你的程序中频繁的写与程序本身无关,与异常处理有关的if,会使得你的代码可读性极其的差
|
||||
|
||||
3. if是可以解决异常的,只是存在1,2的问题,所以,千万不要妄下定论if不能用来异常处理。
|
||||
|
||||
```python
|
||||
def test():
|
||||
print('test.runing')
|
||||
|
||||
choice_dic = {
|
||||
'1':test
|
||||
}
|
||||
|
||||
while True:
|
||||
choice = (input('>>: ').strip())
|
||||
if not choice or choice not in choice_dic:continue
|
||||
choice_dic[choice]()
|
||||
```
|
||||
|
||||
**python:为每一种异常定制了一个类型,然后提供了一种特定的语法结构用来进行异常处理**
|
||||
|
||||
### 基本语法
|
||||
|
||||
```python
|
||||
try:
|
||||
被检测的代码块
|
||||
except 异常类型:
|
||||
try中一旦检测到异常,就执行这个位置的逻辑
|
||||
```
|
||||
|
||||
将文件的每一行变成一个迭代器,然后读出来
|
||||
|
||||
```python
|
||||
f = open('a.txt')
|
||||
|
||||
g = (line.strip() for line in f)
|
||||
for line in g:
|
||||
print(line)
|
||||
else:
|
||||
f.close()
|
||||
```
|
||||
|
||||
但是如果超出了迭代器的范围就会出现`StopIteration`错误
|
||||
|
||||
使用异常处理
|
||||
|
||||
```python
|
||||
try:
|
||||
f = open('a.txt')
|
||||
|
||||
g = (line.strip() for line in f)
|
||||
print(next(g))
|
||||
print(next(g))
|
||||
print(next(g))
|
||||
print(next(g))
|
||||
print(next(g))
|
||||
print(next(g))
|
||||
print(next(g))
|
||||
print(next(g))
|
||||
print(next(g))
|
||||
print(next(g))
|
||||
except StopIteration:
|
||||
f.close()
|
||||
print('读取出错')
|
||||
```
|
||||
|
||||
### 异常类只能用来处理指定的异常情况
|
||||
|
||||
```python
|
||||
s1 = 'hello'
|
||||
try:
|
||||
int(s1)
|
||||
except IndexError as e:
|
||||
print e
|
||||
```
|
||||
|
||||
### 多分支
|
||||
|
||||
主要是用来针对不同的错误情况进行错误处理
|
||||
|
||||
```python
|
||||
s1 = 'hello'
|
||||
try:
|
||||
int(s1)
|
||||
except IndexError as e:
|
||||
print(e)
|
||||
except KeyError as e:
|
||||
print(e)
|
||||
except ValueError as e:
|
||||
print(e)
|
||||
```
|
||||
|
||||
### 万能异常:Exception
|
||||
|
||||
```python
|
||||
s1 = 'hello'
|
||||
try:
|
||||
int(s1)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
```
|
||||
|
||||
多分支加万能异常
|
||||
|
||||
```python
|
||||
s1 = 'hello'
|
||||
try:
|
||||
int(s1)
|
||||
except IndexError as e:
|
||||
print(e)
|
||||
except KeyError as e:
|
||||
print(e)
|
||||
except ValueError as e:
|
||||
print(e)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
```
|
||||
|
||||
## 其他异常情况
|
||||
|
||||
```python
|
||||
s1 = '10'
|
||||
try:
|
||||
int(s1)
|
||||
except IndexError as e:
|
||||
print(e)
|
||||
except KeyError as e:
|
||||
print(e)
|
||||
except ValueError as e:
|
||||
print(e)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
else:
|
||||
print('try内代码块没有异常则执行我')
|
||||
finally:
|
||||
print('无论异常与否,都会执行该模块,通常是进行清理工作')
|
||||
```
|
||||
|
||||
### 主动触发异常
|
||||
|
||||
```python
|
||||
try:
|
||||
raise TypeError('类型错误')
|
||||
except Exception as e:
|
||||
print(e)
|
||||
```
|
||||
|
||||
### 自定义异常
|
||||
|
||||
```python
|
||||
class EvaException(BaseException):
|
||||
def __init__(self,msg):
|
||||
self.msg=msg
|
||||
def __str__(self):
|
||||
return self.msg
|
||||
|
||||
try:
|
||||
raise EvaException('类型错误')
|
||||
except EvaException as e:
|
||||
print(e)
|
||||
```
|
||||
|
||||
### 断言
|
||||
|
||||
表达式位True时,程序继续运行,表达式为False时程序终止运行,并报AssertionError错误
|
||||
|
||||
```python
|
||||
assert 1 == 1
|
||||
assert 1 == 2
|
||||
```
|
||||
|
||||
### try..except的方式比较if的方式的好处
|
||||
|
||||
1. 把错误处理和真正的工作分开来
|
||||
2. 代码更易组织,更清晰,复杂的工作任务更容易实现
|
||||
3. 毫无疑问,更安全了,不至于由于一些小的疏忽而使程序意外崩溃了
|
BIN
01.基础语法/12.异常处理/1480155108.png
Normal file
After Width: | Height: | Size: 135 KiB |
BIN
01.基础语法/12.异常处理/YG9kCGCs3sF9bTJI.png!thumbnail
Normal file
After Width: | Height: | Size: 203 KiB |
618
01.基础语法/13.垃圾回收机制.md
Normal file
310
02.面向对象/01.初识面向对象.md
Normal file
@@ -0,0 +1,310 @@
|
||||
# 初识面向对象
|
||||
|
||||
## 面向对象
|
||||
|
||||
### 面向过程编程vs函数式编程
|
||||
|
||||
```python
|
||||
# 面向过程编程
|
||||
s1 = 'fjdsklafsjda'
|
||||
count = 0
|
||||
for i in s1:
|
||||
count += 1
|
||||
|
||||
|
||||
l1 = [1,2,3,4]
|
||||
count = 0
|
||||
for i in l1:
|
||||
count += 1
|
||||
```
|
||||
|
||||
```python
|
||||
# 函数式编程
|
||||
def func(s):
|
||||
count = 0
|
||||
for i in s:
|
||||
count += 1
|
||||
return count
|
||||
func('fdsafdsa')
|
||||
func([1,2,3,4])
|
||||
```
|
||||
|
||||
通过对比可知:函数编程较之面向过程编程最明显的两个特点:
|
||||
|
||||
1. 减少代码的重用性。
|
||||
|
||||
2. 增强代码的可读性。
|
||||
|
||||
### 函数式编程vs面向对象编程
|
||||
|
||||
```python
|
||||
# 函数式编程
|
||||
|
||||
# auth 认证相关
|
||||
def login():
|
||||
pass
|
||||
|
||||
def regisgter():
|
||||
pass
|
||||
|
||||
# account 账户相关
|
||||
def func1():
|
||||
pass
|
||||
|
||||
def func2():
|
||||
pass
|
||||
|
||||
|
||||
# 购物车相关
|
||||
def shopping(username,money):
|
||||
pass
|
||||
def check_paidgoods(username,money):
|
||||
pass
|
||||
def check_unpaidgoods(username,money):
|
||||
pass
|
||||
def save(username,money):
|
||||
pass
|
||||
```
|
||||
|
||||
```python
|
||||
class LoginHandler:
|
||||
def login(self):
|
||||
pass
|
||||
|
||||
def regisgter(self):
|
||||
pass
|
||||
|
||||
class Account:
|
||||
def func1(self):
|
||||
pass
|
||||
|
||||
def func2(self):
|
||||
pass
|
||||
|
||||
class ShoppingCar:
|
||||
def shopping(username,money):
|
||||
pass
|
||||
def check_paidgoods(username,money):
|
||||
pass
|
||||
def check_unpaidgoods(username,money):
|
||||
pass
|
||||
def save(username,money):
|
||||
pass
|
||||
```
|
||||
|
||||
1. 面向对象编程:是一类相似功能函数的集合,使你的代码更清晰化,更合理化。
|
||||
2. 面向对象,要拥有上帝的视角看问题,类其实就是一个公共模板(厂房),对象就从具体的模板实例化出来。
|
||||
|
||||
### 类的结构
|
||||
|
||||
```python
|
||||
class Human:
|
||||
"""
|
||||
此类主要是构建人类
|
||||
"""
|
||||
mind = '有思想' # 第一部分:静态属性 属性 静态变量 静态字段
|
||||
dic = {}
|
||||
l1 = []
|
||||
def work(self): # 第二部分:方法 函数 动态属性
|
||||
print('人类会工作')
|
||||
```
|
||||
|
||||
class 是关键字与def用法相同,定义一个类。
|
||||
Human是此类的类名,类名使用驼峰(CamelCase)命名风格,首字母大写,私有类可用一个下划线开头。
|
||||
类的结构从大方向来说就分为两部分:
|
||||
|
||||
- 静态变量
|
||||
- 动态方法
|
||||
|
||||
## 从类名的角度研究类
|
||||
|
||||
### 类名操作静态属性
|
||||
|
||||
**查看类中的所有内容:类名.__dict__方式。**
|
||||
|
||||
```python
|
||||
class Human:
|
||||
mind = '有思想'
|
||||
dic = {}
|
||||
l1 = []
|
||||
def work(self):
|
||||
print('会工作')
|
||||
|
||||
print(Human.__dict__)
|
||||
print(Human.__dict__['mind'])
|
||||
Human.__dict__['mind'] = '高智慧'
|
||||
print(Human.__dict__)
|
||||
# 通过这种方式只能查询,不能增删改
|
||||
```
|
||||
|
||||
**万能的点.**
|
||||
|
||||
```python
|
||||
class Human:
|
||||
mind = '有思想'
|
||||
dic = {}
|
||||
l1 = []
|
||||
def work(self):
|
||||
print('会工作')
|
||||
|
||||
print(Human.mind)
|
||||
Human.mind = '高智慧'
|
||||
print(Human.mind)
|
||||
|
||||
del Human.mind
|
||||
Human.walk = '用脚走'
|
||||
print(Human.walk)
|
||||
# 通过万能的点 可以增删改查类中的单个属性
|
||||
```
|
||||
|
||||
**对以上两种做一个总结:如果想查询类中的所有内容,通过 第一种__dict__方法,如果只是操作单个属性则用万能的点的方式。**
|
||||
|
||||
### 类名操作动态方法
|
||||
|
||||
```python
|
||||
class Human:
|
||||
mind = '有思想'
|
||||
dic = {}
|
||||
l1 = []
|
||||
def work(self):
|
||||
print(self,'会工作')
|
||||
|
||||
Human.work('chensong')
|
||||
Human.__dict__['work']('chensong')
|
||||
```
|
||||
|
||||
## 从对象的角度研究类
|
||||
|
||||
### 对象
|
||||
|
||||
对象是从类中出来的,只要是**类名加上()**,这就是一个实例化过程,这个就会实例化一个对象。
|
||||
|
||||
```python
|
||||
class Human:
|
||||
mind = '有思想'
|
||||
dic = {}
|
||||
l1 = []
|
||||
def work(self):
|
||||
print(self,'会工作')
|
||||
|
||||
obj = Human()
|
||||
# 只要实例化对象,它会自动执行__init__方法
|
||||
print(obj)
|
||||
```
|
||||
|
||||
其实实例化一个对象总共发生了三件事:
|
||||
|
||||
1. 在内存中开辟了一个对象空间。
|
||||
|
||||
2. 自动执行类中的__init__方法,并将这个对象空间(内存地址)传给了__init__方法的第一个位置参数self。
|
||||
|
||||
3. 在__init__ 方法中通过self给对象空间添加属性。
|
||||
|
||||
```python
|
||||
class Human:
|
||||
mind = '有思想'
|
||||
work = '用两只腿走'
|
||||
def __init__(self,name,sex,age,hobby):
|
||||
self.n = name
|
||||
self.s = sex
|
||||
self.a = age
|
||||
self.h = hobby
|
||||
|
||||
obj = Human('chensong','男','18','男')
|
||||
```
|
||||
|
||||
### 对象操作对象空间属性
|
||||
|
||||
**对象查询对象中所有属性。 `对象.__dict__`**
|
||||
|
||||
```python
|
||||
class Human:
|
||||
|
||||
mind = '有思想'
|
||||
language = '实用语言'
|
||||
def __init__(self,name,sex,age,hobby):
|
||||
# self 和 obj 指向的是同一个内存地址同一个空间,下面就是通过self给这个对象空间封装四个属性。
|
||||
self.n = name
|
||||
self.s = sex
|
||||
self.a = age
|
||||
self.h = hobby
|
||||
|
||||
obj = Human('chensong','男',18,'男')
|
||||
print(obj.__dict__)
|
||||
```
|
||||
|
||||
**对象操作对象中的单个属性。 万能的点.**
|
||||
|
||||
```python
|
||||
class Human:
|
||||
|
||||
mind = '有思想'
|
||||
language = '实用语言'
|
||||
def __init__(self,name,sex,age,hobby):
|
||||
# self 和 obj 指向的是同一个内存地址同一个空间,下面就是通过self给这个对象空间封装四个属性。
|
||||
self.n = name
|
||||
self.s = sex
|
||||
self.a = age
|
||||
self.h = hobby
|
||||
|
||||
obj = Human('chensong','男',18,'男')
|
||||
obj.job = 'IT'
|
||||
del obj.n
|
||||
obj.s = '女'
|
||||
print(obj.s)
|
||||
print(obj.__dict__)
|
||||
```
|
||||
|
||||
### 对象查看类中的属性
|
||||
|
||||
```python
|
||||
class Human:
|
||||
|
||||
mind = '有思想'
|
||||
language = '实用语言'
|
||||
def __init__(self,name,sex,age,hobby):
|
||||
# self 和 obj 指向的是同一个内存地址同一个空间,下面就是通过self给这个对象空间封装四个属性。
|
||||
self.n = name
|
||||
self.s = sex
|
||||
self.a = age
|
||||
self.h = hobby
|
||||
|
||||
obj = Human('chensong','男',18,'男')
|
||||
print(obj.mind)
|
||||
print(obj.language)
|
||||
obj.a = 666
|
||||
print(obj.a)
|
||||
```
|
||||
|
||||
### 对象操作类中的方法
|
||||
|
||||
```python
|
||||
class Human:
|
||||
|
||||
mind = '有思想'
|
||||
language = '实用语言'
|
||||
def __init__(self,name,sex,age,hobby):
|
||||
# self 和 obj 指向的是同一个内存地址同一个空间,下面就是通过self给这个对象空间封装四个属性。
|
||||
self.n = name
|
||||
self.s = sex
|
||||
self.a = age
|
||||
self.h = hobby
|
||||
|
||||
def work(self):
|
||||
print(self)
|
||||
print(self.n,'会工作')
|
||||
|
||||
def tools(self):
|
||||
print(self.n,'会使用工具')
|
||||
|
||||
obj = Human('chensong','男',18,'男')
|
||||
obj.work()
|
||||
obj.tools()
|
||||
```
|
||||
|
||||
**类中的方法一般都是通过对象执行的(出去类方法,静态方法外),并且对象执行这些方法都会自动将对象空间传给方法中的第一个参数self.**
|
||||
|
||||
*self其实就是类中方法(函数)的第一个位置参数,只不过解释器会自动将调用这个函数的对象传给self。所以咱们把类中的方法的第一个参数约定俗成设置成self, 代表这个就是对象。*
|
||||
|
||||
**一个类可以实例化多个对象**
|
||||
|
314
02.面向对象/02.类空间与类之间的关系.md
Normal file
@@ -0,0 +1,314 @@
|
||||
# 类空间与类之间的关系
|
||||
|
||||
## 类的空间问题
|
||||
|
||||
### 添加对象属性
|
||||
|
||||
```python
|
||||
class A:
|
||||
def __init__(self,name):
|
||||
self.name = name
|
||||
|
||||
def func(self,sex):
|
||||
self.sex = sex
|
||||
```
|
||||
|
||||
在类外部添加
|
||||
|
||||
```python
|
||||
class A:
|
||||
def __init__(self,name):
|
||||
self.name = name
|
||||
|
||||
def func(self,sex):
|
||||
self.sex = sexa
|
||||
|
||||
obj = A('chensong')
|
||||
obj.age = 18
|
||||
print(obj.__dict__)
|
||||
```
|
||||
|
||||
类的内部添加
|
||||
|
||||
```python
|
||||
class A:
|
||||
def __init__(self,name):
|
||||
self.name = name
|
||||
|
||||
def func(self,sex):
|
||||
self.sex = sex
|
||||
|
||||
obj = A('chensong')
|
||||
obj.func('男')
|
||||
print(obj.__dict__)
|
||||
```
|
||||
|
||||
**总结:对象的属性不仅可以在__init__里面添加,还可以在类的其他方法或者类的外面添加。**
|
||||
|
||||
### 添加类的静态属性
|
||||
|
||||
```python
|
||||
class A:
|
||||
def __init__(self,name):
|
||||
self.name = name
|
||||
|
||||
def func(self,sex):
|
||||
self.sex = sex
|
||||
|
||||
def func1(self):
|
||||
A.bbb = self
|
||||
|
||||
A.aaa = 'test' # 类的外部添加
|
||||
print(A.__dict__)
|
||||
|
||||
A.func1('123') # 类的内部添加
|
||||
print(A.__dict__)
|
||||
```
|
||||
|
||||
**总结:类的属性不仅可以在类内部添加,还可以在类的外部添加**
|
||||
|
||||
## 对象如何找到类的属性
|
||||
|
||||
对象空间
|
||||
|
||||
1. 产生这个对象空间,并有一个类对象指针
|
||||
2. 执行`__init__`方法,给对象封装属性
|
||||
|
||||
对象查找属性的顺序:先从对象空间找 ------> 类空间找 ------> 父类空间找 ------->.....
|
||||
|
||||
类名查找属性的顺序:先从本类空间找 -------> 父类空间找--------> ........
|
||||
|
||||
上面的顺序都是单向不可逆,类名不可能找到对象的属性。
|
||||
|
||||
## 类与类之间的关系
|
||||
|
||||
类与类中存在以下关系:
|
||||
|
||||
1. 依赖关系
|
||||
2. 关联关系
|
||||
3. 组合关系
|
||||
4. 聚合关系
|
||||
5. 实现关系
|
||||
6. 继承关系(类的三大特性之一:继承。)
|
||||
|
||||
### 依赖关系
|
||||
|
||||
例:将大象装进冰箱,需要两个类, ⼀个是⼤象类, ⼀个是冰箱类
|
||||
|
||||
```python
|
||||
class Elphant:
|
||||
def __init__(self,name):
|
||||
self.name = name
|
||||
|
||||
def open(self):
|
||||
'''
|
||||
开门
|
||||
'''
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
'''
|
||||
关门
|
||||
'''
|
||||
pass
|
||||
|
||||
class Refrigerator:
|
||||
def open_door(self):
|
||||
print('冰箱门打开了')
|
||||
|
||||
def open_door(self):
|
||||
print('冰箱门关上了')
|
||||
```
|
||||
|
||||
将大象类和冰箱类进行依赖
|
||||
|
||||
```python
|
||||
class Elphant:
|
||||
def __init__(self,name):
|
||||
self.name = name
|
||||
|
||||
def open(self,obj1):
|
||||
'''
|
||||
开门
|
||||
'''
|
||||
print(self.name,'要开门了')
|
||||
obj1.open_door()
|
||||
|
||||
def close(self):
|
||||
'''
|
||||
关门
|
||||
'''
|
||||
pass
|
||||
|
||||
class Refrigerator:
|
||||
def open_door(self):
|
||||
print('冰箱门打开了')
|
||||
|
||||
def close_door(self):
|
||||
print('冰箱门关上了')
|
||||
|
||||
elphant1 = Elphant('大象')
|
||||
haier = Refrigerator()
|
||||
elphant1.open(haier)
|
||||
```
|
||||
|
||||
### 关联,聚合,组合关系
|
||||
|
||||
其实这三个在代码上写法是⼀样的. 但是, 从含义上是不⼀样的.
|
||||
|
||||
1. 关联关系. 两种事物必须是互相关联的. 但是在某些特殊情况下是可以更改和更换的.
|
||||
|
||||
2. 聚合关系. 属于关联关系中的⼀种特例. 侧重点是xxx和xxx聚合成xxx. 各⾃有各⾃的声明周期. 比如电脑. 电脑⾥有CPU, 硬盘, 内存等等. 电脑挂了. CPU还是好的. 还是完整的个体
|
||||
|
||||
3. 组合关系. 属于关联关系中的⼀种特例. 写法上差不多. 组合关系比聚合还要紧密. 比如⼈的⼤脑, ⼼脏, 各个器官. 这些器官组合成⼀个⼈. 这时. ⼈如果挂了. 其他的东⻄也跟着挂了
|
||||
|
||||
**关联关系**
|
||||
|
||||
```python
|
||||
class Boy:
|
||||
def __init__(self,name,girlFirend=None):
|
||||
self.name = name
|
||||
self.girlFriend = girlFirend
|
||||
|
||||
def have_a_dinner(self):
|
||||
if self.girlFriend:
|
||||
print('%s 和 %s 一起晚饭'%(self.name,self.girlFriend.name))
|
||||
else:
|
||||
print('单身狗,吃什么饭')
|
||||
|
||||
class Girl:
|
||||
def __init__(self,name):
|
||||
self.name = name
|
||||
|
||||
b = Boy('日天')
|
||||
b.have_a_dinner()
|
||||
|
||||
b.girlFriend = Girl('如花')
|
||||
b.have_a_dinner()
|
||||
|
||||
gg = Girl('花花')
|
||||
bb = Boy('songsong',gg)
|
||||
bb.have_a_dinner()
|
||||
```
|
||||
|
||||
注意. 此时Boy和Girl两个类之间就是关联关系. 两个类的对象紧密联系着. 其中⼀个没有了. 另⼀个就孤单的不得了. 关联关系, 其实就是 我需要你. 你也属于我
|
||||
|
||||
学校和老师之间的关系
|
||||
|
||||
```python
|
||||
class School:
|
||||
|
||||
def __init__(self,name,address):
|
||||
self.name = name
|
||||
self.address = address
|
||||
|
||||
|
||||
class Teacher:
|
||||
|
||||
def __init__(self,name,school):
|
||||
self.name = name
|
||||
self.school = school
|
||||
|
||||
s1 = School('北京校区','北京')
|
||||
s2 = School('上海校区','上海')
|
||||
s3 = School('深圳校区','深圳')
|
||||
|
||||
t1 = Teacher('T1',s1)
|
||||
t2 = Teacher('T2',s2)
|
||||
t3 = Teacher('T3',s3)
|
||||
|
||||
print(t1.school.name)
|
||||
print(t2.school.name)
|
||||
print(t3.school.name)
|
||||
```
|
||||
|
||||
但是学校也是依赖于老师的,所以老师学校应该互相依赖。
|
||||
|
||||
```python
|
||||
class School:
|
||||
|
||||
def __init__(self,name,address):
|
||||
self.name = name
|
||||
self.address = address
|
||||
self.teacher_list = []
|
||||
def append_teacher(self,teacher):
|
||||
self.teacher_list.append(teacher)
|
||||
|
||||
|
||||
class Teacher:
|
||||
|
||||
def __init__(self,name,school):
|
||||
self.name = name
|
||||
self.school = school
|
||||
|
||||
s1 = School('北京校区','北京')
|
||||
s2 = School('上海校区','上海')
|
||||
s3 = School('深圳校区','深圳')
|
||||
|
||||
t1 = Teacher('T1',s1)
|
||||
t2 = Teacher('T2',s2)
|
||||
t3 = Teacher('T3',s3)
|
||||
|
||||
s1.append_teacher(t1.name)
|
||||
s1.append_teacher(t2.name)
|
||||
s1.append_teacher(t3.name)
|
||||
|
||||
print(s1.teacher_list)
|
||||
```
|
||||
|
||||
**组合:将一个类的对象封装到另一个类的对象的属性中,就叫组合。**
|
||||
|
||||
例:设计一个游戏,让游戏里面的人物互殴
|
||||
|
||||
```python
|
||||
class Gamerole:
|
||||
def __init__(self,name,ad,hp):
|
||||
self.name = name
|
||||
self.ad = ad
|
||||
self.hp = hp
|
||||
|
||||
def attack(self,p1):
|
||||
p1.hp -= self.ad
|
||||
print('%s攻击%s,%s掉了%s血,还剩%s'%(self.name,p1.name,p1.name,self.ad,p1.hp))
|
||||
|
||||
man = Gamerole('人',10,100)
|
||||
dog = Gamerole('狗',50,100)
|
||||
|
||||
dog.attack(man)
|
||||
man.attack(dog)
|
||||
```
|
||||
|
||||
加上一个武器类,让人使用武器攻击
|
||||
|
||||
```python
|
||||
class Gamerole:
|
||||
def __init__(self,name,ad,hp):
|
||||
self.name = name
|
||||
self.ad = ad
|
||||
self.hp = hp
|
||||
|
||||
def attack(self,p1):
|
||||
p1.hp -= self.ad
|
||||
print('%s攻击%s,%s掉了%s血,还剩%s'%(self.name,p1.name,p1.name,self.ad,p1.hp))
|
||||
|
||||
def equip_weapon(self,wea):
|
||||
self.wea = wea
|
||||
|
||||
class Weapon:
|
||||
def __init__(self,name,ad):
|
||||
self.name = name
|
||||
self.ad = ad
|
||||
def weapon_attack(self,p1,p2):
|
||||
p2.hp = p2.hp - self.ad - p1.ad
|
||||
print('%s利用%s攻击了%s,%s还剩%s血'%(p1.name,self.name,p2.name,p2.name,p2.hp))
|
||||
|
||||
|
||||
|
||||
man = Gamerole('人',10,100)
|
||||
dog = Gamerole('狗',50,100)
|
||||
stick = Weapon('木棍',40)
|
||||
|
||||
man.equip_weapon(stick)
|
||||
man.wea.weapon_attack(man,dog)
|
||||
# 人利用木棍攻击了狗,狗还剩50血
|
||||
```
|
422
02.面向对象/03.类的继承.md
Normal file
@@ -0,0 +1,422 @@
|
||||
# 类的继承
|
||||
|
||||
## 面向对象的继承
|
||||
|
||||
不用继承创建对象
|
||||
|
||||
```python
|
||||
class Person:
|
||||
def __init__(self,name,sex,age):
|
||||
self.name = name
|
||||
self.age = age
|
||||
self.sex = sex
|
||||
|
||||
class Cat:
|
||||
def __init__(self,name,sex,age):
|
||||
self.name = name
|
||||
self.age = age
|
||||
self.sex = sex
|
||||
|
||||
class Dog:
|
||||
def __init__(self,name,sex,age):
|
||||
self.name = name
|
||||
self.age = age
|
||||
self.sex = sex
|
||||
```
|
||||
|
||||
使用继承的方式
|
||||
|
||||
```python
|
||||
class Aniaml(object):
|
||||
def __init__(self,name,sex,age):
|
||||
self.name = name
|
||||
self.age = age
|
||||
self.sex = sex
|
||||
|
||||
class Person(Aniaml):
|
||||
pass
|
||||
|
||||
class Cat(Aniaml):
|
||||
pass
|
||||
|
||||
class Dog(Aniaml):
|
||||
pass
|
||||
```
|
||||
|
||||
继承的有点也是显而易见的:
|
||||
|
||||
1. 增加了类的耦合性(耦合性不宜多,宜精)。
|
||||
|
||||
2. 减少了重复代码。
|
||||
|
||||
3. 使得代码更加规范化,合理化。
|
||||
|
||||
## 继承的分类
|
||||
|
||||
上面的那个例子:
|
||||
|
||||
- Aminal 叫做父类,基类,超类。
|
||||
- Person Cat Dog: 子类,派生类。
|
||||
|
||||
继承:可以分**单继承,多继承**。
|
||||
|
||||
这里需要补充一下python中类的种类(继承需要):
|
||||
|
||||
在python2x版本中存在两种类.:
|
||||
|
||||
- ⼀个叫**经典类**. 在python2.2之前. ⼀直使⽤的是经典类. 经典类在基类的根如果什么都不写.
|
||||
- ⼀个叫**新式类**. 在python2.2之后出现了新式类. 新式类的特点是基类的根是object类。
|
||||
python3x版本中只有一种类:
|
||||
python3中使⽤的都是**新式类**. 如果基类谁都不继承. 那这个类会默认继承 object
|
||||
|
||||
## 单继承
|
||||
|
||||
### 类名,对象执行父类方法
|
||||
|
||||
```python
|
||||
class Aniaml(object):
|
||||
type_name = '动物类'
|
||||
|
||||
def __init__(self,name,sex,age):
|
||||
self.name = name
|
||||
self.age = age
|
||||
self.sex = sex
|
||||
|
||||
def eat(self):
|
||||
print('吃',self)
|
||||
|
||||
class Person(Aniaml):
|
||||
pass
|
||||
|
||||
class Cat(Aniaml):
|
||||
pass
|
||||
|
||||
class Dog(Aniaml):
|
||||
pass
|
||||
|
||||
print(Person.type_name)
|
||||
Person.eat('东西')
|
||||
print(Person.type_name)
|
||||
|
||||
p1 = Person('aaron','男',18)
|
||||
print(p1.__dict__)
|
||||
print(p1.type_name)
|
||||
p1.type_name = '666'
|
||||
print(p1)
|
||||
p1.eat()
|
||||
```
|
||||
|
||||
### 执行顺序
|
||||
|
||||
```python
|
||||
class Aniaml(object):
|
||||
type_name = '动物类'
|
||||
|
||||
def __init__(self,name,sex,age):
|
||||
self.name = name
|
||||
self.age = age
|
||||
self.sex = sex
|
||||
|
||||
def eat(self):
|
||||
print('吃',self)
|
||||
|
||||
class Person(Aniaml):
|
||||
|
||||
def eat(self):
|
||||
print('%s 用筷子吃饭'%self.name)
|
||||
|
||||
class Cat(Aniaml):
|
||||
pass
|
||||
|
||||
class Dog(Aniaml):
|
||||
pass
|
||||
|
||||
p1 = Person('eagle','男',18)
|
||||
p1.eat()
|
||||
```
|
||||
|
||||
### 同时执行类以及父类方法
|
||||
|
||||
方法一:如果想执行父类的func方法,这个方法并且子类中夜用,那么就在子类的方法中写上:父类.func(对象,其他参数)
|
||||
|
||||
```python
|
||||
class Aniaml(object):
|
||||
type_name = '动物类'
|
||||
def __init__(self,name,sex,age):
|
||||
self.name = name
|
||||
self.age = age
|
||||
self.sex = sex
|
||||
|
||||
def eat(self):
|
||||
print('吃东西')
|
||||
|
||||
class Person(Aniaml):
|
||||
def __init__(self,name,sex,age,mind):
|
||||
Aniaml.__init__(self,name,sex,age)
|
||||
self.mind = mind
|
||||
|
||||
def eat(self):
|
||||
Aniaml.eat(111)
|
||||
print('%s 吃饭'%self.name)
|
||||
class Cat(Aniaml):
|
||||
pass
|
||||
|
||||
class Dog(Aniaml):
|
||||
pass
|
||||
|
||||
p1 = Person('aaron','男',18,'想吃东西')
|
||||
p1.eat()
|
||||
```
|
||||
|
||||
方法二:利用super,super().func(参数)
|
||||
|
||||
```python
|
||||
class Aniaml(object):
|
||||
type_name = '动物类'
|
||||
def __init__(self,name,sex,age):
|
||||
self.name = name
|
||||
self.age = age
|
||||
self.sex = sex
|
||||
|
||||
def eat(self):
|
||||
print('吃东西')
|
||||
|
||||
class Person(Aniaml):
|
||||
def __init__(self,name,sex,age,mind):
|
||||
# super(Person,self).__init__(name,sex,age)
|
||||
super().__init__(name,sex,age)
|
||||
self.mind = mind
|
||||
|
||||
def eat(self):
|
||||
super().eat()
|
||||
print('%s 吃饭'%self.name)
|
||||
class Cat(Aniaml):
|
||||
pass
|
||||
|
||||
class Dog(Aniaml):
|
||||
pass
|
||||
|
||||
p1 = Person('aaron','男',18,'想吃东西')
|
||||
p1.eat()
|
||||
```
|
||||
|
||||
单继承练习题
|
||||
|
||||
```python
|
||||
class Base:
|
||||
def __init__(self,num):
|
||||
self.num = num
|
||||
def func1(self):
|
||||
print(self.num)
|
||||
|
||||
class Foo(Base):
|
||||
pass
|
||||
|
||||
obj = Foo(123)
|
||||
obj.func1()
|
||||
# 运⾏的是Base中的func1
|
||||
```
|
||||
|
||||
```python
|
||||
class Base:
|
||||
def __init__(self,num):
|
||||
self.num = num
|
||||
def func1(self):
|
||||
print(self.num)
|
||||
|
||||
class Foo(Base):
|
||||
def func1(self):
|
||||
print("Foo.func1",self.num)
|
||||
|
||||
obj = Foo(123)
|
||||
obj.func1()
|
||||
# 运⾏的是Foo中的func1
|
||||
```
|
||||
|
||||
```python
|
||||
class Base:
|
||||
def __init__(self, num):
|
||||
self.num = num
|
||||
def func1(self):
|
||||
print(self.num)
|
||||
self.func2()
|
||||
def func2(self):
|
||||
print("Base.func2")
|
||||
class Foo(Base):
|
||||
def func2(self):
|
||||
print("Foo.func2")
|
||||
|
||||
obj = Foo(123)
|
||||
obj.func1()
|
||||
# func1是Base中的 func2是⼦类中的
|
||||
```
|
||||
|
||||
```python
|
||||
class Base:
|
||||
def __init__(self, num):
|
||||
self.num = num
|
||||
def func1(self):
|
||||
print(self.num)
|
||||
self.func2()
|
||||
def func2(self):
|
||||
print(111, self.num)
|
||||
class Foo(Base):
|
||||
def func2(self):
|
||||
print(222, self.num)
|
||||
|
||||
lst = [Base(1), Base(2), Foo(3)]
|
||||
for obj in lst:
|
||||
obj.func2()
|
||||
```
|
||||
|
||||
```python
|
||||
class Base:
|
||||
def __init__(self, num):
|
||||
self.num = num
|
||||
def func1(self):
|
||||
print(self.num)
|
||||
self.func2()
|
||||
def func2(self):
|
||||
print(111, self.num)
|
||||
class Foo(Base):
|
||||
def func2(self):
|
||||
print(222, self.num)
|
||||
|
||||
lst = [Base(1), Base(2), Foo(3)]
|
||||
for obj in lst:
|
||||
obj.func1()
|
||||
```
|
||||
|
||||
## 多继承
|
||||
|
||||
```python
|
||||
class ShenXian: # 神仙
|
||||
def fei(self):
|
||||
print("神仙都会⻜")
|
||||
class Monkey: # 猴
|
||||
def chitao(self):
|
||||
print("猴⼦喜欢吃桃⼦")
|
||||
class SunWukong(ShenXian, Monkey): # 孙悟空是神仙, 同时也是⼀只猴
|
||||
pass
|
||||
|
||||
sxz = SunWukong() # 孙悟空
|
||||
sxz.chitao() # 会吃桃⼦
|
||||
sxz.fei() # 会⻜
|
||||
```
|
||||
|
||||
### 经典类的多继承
|
||||
|
||||
```python
|
||||
class A:
|
||||
pass
|
||||
class B(A):
|
||||
pass
|
||||
class C(A):
|
||||
pass
|
||||
class D(B, C):
|
||||
pass
|
||||
class E:
|
||||
pass
|
||||
class F(D, E):
|
||||
pass
|
||||
class G(F, D):
|
||||
pass
|
||||
class H:
|
||||
pass
|
||||
class Foo(H, G):
|
||||
pass
|
||||
```
|
||||
|
||||
画图
|
||||
|
||||

|
||||
|
||||
在经典类中采⽤的是深度优先,遍历⽅案. 什么是深度优先. 就是⼀条路走到头. 然后再回来. 继续找下⼀个.
|
||||
|
||||
类的MRO(method resolution order): Foo-> H -> G -> F -> E -> D -> B -> A -> C.
|
||||
|
||||
### 新式类的多继承
|
||||
|
||||
#### mro序列
|
||||
|
||||
MRO是一个有序列表L,在类被创建时就计算出来。
|
||||
|
||||
通用计算公式为:
|
||||
|
||||
mro(Child(Base1,Base2)) = [ Child ] + merge( mro(Base1), mro(Base2), [ Base1, Base2] )(其中Child继承自Base1, Base2)
|
||||
|
||||
如果继承至一个基类:class B(A)
|
||||
这时B的mro序列为
|
||||
|
||||
mro( B ) = mro( B(A) )
|
||||
= [B] + merge( mro(A) + [A] )
|
||||
= [B] + merge( [A] + [A] )
|
||||
= [B,A]
|
||||
|
||||
如果继承至多个基类:class B(A1, A2, A3 …)
|
||||
这时B的mro序列
|
||||
|
||||
mro(B) = mro( B(A1, A2, A3 …) )
|
||||
= [B] + merge( mro(A1), mro(A2), mro(A3) ..., [A1, A2, A3] )
|
||||
= ...
|
||||
|
||||
计算结果为列表,列表中至少有一个元素即类自己,如上述示例[A1,A2,A3]。merge操作是C3算法的核心。
|
||||
|
||||
#### 表头和表尾
|
||||
|
||||
表头:列表的第一个元素
|
||||
|
||||
表尾:列表中表头以外的元素集合(可以为空)
|
||||
|
||||
示例:列表:[A, B, C] 表头是A,表尾是B和C
|
||||
|
||||
#### 列表之间的+操作
|
||||
|
||||
[A] + [B] = [A, B]
|
||||
|
||||
merge操作示例:
|
||||
|
||||
如计算merge( [E,O], [C,E,F,O], [C] )
|
||||
有三个列表 : ① ② ③
|
||||
|
||||
1 merge不为空,取出第一个列表列表①的表头E,进行判断
|
||||
各个列表的表尾分别是[O], [E,F,O],E在这些表尾的集合中,因而跳过当前当前列表
|
||||
2 取出列表②的表头C,进行判断
|
||||
C不在各个列表的集合中,因而将C拿出到merge外,并从所有表头删除
|
||||
merge( [E,O], [C,E,F,O], [C]) = [C] + merge( [E,O], [E,F,O] )
|
||||
3 进行下一次新的merge操作 ......
|
||||
---------------------
|
||||
|
||||

|
||||
|
||||
计算mro(A)方式:
|
||||
|
||||
mro(A) = mro( A(B,C) )
|
||||
|
||||
原式= [A] + merge( mro(B),mro(C),[B,C] )
|
||||
|
||||
mro(B) = mro( B(D,E) )
|
||||
= [B] + merge( mro(D), mro(E), [D,E] ) # 多继承
|
||||
= [B] + merge( [D,O] , [E,O] , [D,E] ) # 单继承mro(D(O))=[D,O]
|
||||
= [B,D] + merge( [O] , [E,O] , [E] ) # 拿出并删除D
|
||||
= [B,D,E] + merge([O] , [O])
|
||||
= [B,D,E,O]
|
||||
|
||||
mro(C) = mro( C(E,F) )
|
||||
= [C] + merge( mro(E), mro(F), [E,F] )
|
||||
= [C] + merge( [E,O] , [F,O] , [E,F] )
|
||||
= [C,E] + merge( [O] , [F,O] , [F] ) # 跳过O,拿出并删除
|
||||
= [C,E,F] + merge([O] , [O])
|
||||
= [C,E,F,O]
|
||||
|
||||
原式= [A] + merge( [B,D,E,O], [C,E,F,O], [B,C])
|
||||
= [A,B] + merge( [D,E,O], [C,E,F,O], [C])
|
||||
= [A,B,D] + merge( [E,O], [C,E,F,O], [C]) # 跳过E
|
||||
= [A,B,D,C] + merge([E,O], [E,F,O])
|
||||
= [A,B,D,C,E] + merge([O], [F,O]) # 跳过O
|
||||
= [A,B,D,C,E,F] + merge([O], [O])
|
||||
= [A,B,D,C,E,F,O]
|
||||
|
||||
那既然python提供了. 为什么我们还要如此⿇烦的计算MRO呢? 因为笔
|
||||
试.......你在笔试的时候, 是没有电脑的. 所以这个算法要知道. 并且简单的计算要会. 正式项⽬
|
||||
开发的时候很少有⼈这么去写代码.
|
BIN
02.面向对象/03.类的继承/image-20210725220109579.png
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
02.面向对象/03.类的继承/image-20210725220122519.png
Normal file
After Width: | Height: | Size: 26 KiB |
313
02.面向对象/04.封装与多态.md
Normal file
@@ -0,0 +1,313 @@
|
||||
# 封装与多态
|
||||
|
||||
python面向对象的三大特性:继承,封装,多态。
|
||||
|
||||
1. **封装**: 把很多数据封装到⼀个对象中. 把固定功能的代码封装到⼀个代码块, 函数, 对象, 打包成模块. 这都属于封装的思想. 具体的情况具体分析. 比如. 你写了⼀个很⽜B的函数. 那这个也可以被称为封装. 在⾯向对象思想中. 是把⼀些看似⽆关紧要的内容组合到⼀起统⼀进⾏存储和使⽤. 这就是封装.
|
||||
|
||||
2. **继承**: ⼦类可以⾃动拥有⽗类中除了私有属性外的其他所有内容. 说⽩了, ⼉⼦可以随便⽤爹的东⻄. 但是朋友们, ⼀定要认清楚⼀个事情. 必须先有爹, 后有⼉⼦. 顺序不能乱, 在python中实现继承非常简单. 在声明类的时候, 在类名后⾯添加⼀个⼩括号,就可以完成继承关系. 那么什么情况可以使⽤继承呢? 单纯的从代码层⾯上来看. 两个类具有相同的功能或者特征的时候. 可以采⽤继承的形式. 提取⼀个⽗类, 这个⽗类中编写着两个类相同的部分. 然后两个类分别取继承这个类就可以了. 这样写的好处是我们可以避免写很多重复的功能和代码. 如果从语义中去分析的话. 会简单很多. 如果语境中出现了x是⼀种y. 这时, y是⼀种泛化的概念. x比y更加具体. 那这时x就是y的⼦类. 比如. 猫是⼀种动物. 猫继承动物. 动物能动. 猫也能动. 这时猫在创建的时候就有了动物的"动"这个属性. 再比如, ⽩骨精是⼀个妖怪. 妖怪天⽣就有⼀个比较不好的功能叫"吃⼈", ⽩骨精⼀出⽣就知道如何"吃⼈". 此时 ⽩骨精继承妖精.
|
||||
3. **多态**: 同⼀个对象, 多种形态. 这个在python中其实是很不容易说明⽩的. 因为我们⼀直在⽤. 只是没有具体的说. 比如. 我们创建⼀个变量a = 10 , 我们知道此时a是整数类型. 但是我们可以通过程序让a = "hello", 这时, a⼜变成了字符串类型. 这是我们都知道的. 但是, 我要告诉你的是. 这个就是多态性. 同⼀个变量a可以是多种形态。
|
||||
|
||||
## 封装
|
||||
|
||||
第一步:将内容封装到某处
|
||||
|
||||
```python
|
||||
class Foo:
|
||||
def __init__(self,name,age):
|
||||
self.name = name
|
||||
self.age = age
|
||||
|
||||
obj1 = Foo('chensong',18)
|
||||
obj2 = Foo('aaron',16)
|
||||
```
|
||||
|
||||
第二步:从某处调用被封装的内容
|
||||
|
||||
```python
|
||||
class Foo:
|
||||
def __init__(self,name,age):
|
||||
self.name = name
|
||||
self.age = age
|
||||
|
||||
def detail(self):
|
||||
print(self.name)
|
||||
print(self.age)
|
||||
|
||||
obj1 = Foo('chensong',18)
|
||||
obj2 = Foo('aaron',16)
|
||||
|
||||
print(obj1.name)
|
||||
print(obj2.age)
|
||||
# 通过对象直接调用被封装的内容
|
||||
|
||||
obj1.detail()
|
||||
obj2.detail()
|
||||
# 通过self间接调用被封装的内容
|
||||
```
|
||||
|
||||
## 多态
|
||||
|
||||
多态,同一个对象,多种形态。python默认支持多态。
|
||||
|
||||
python中有一句谚语说的好,你看起来像鸭子,那么你就是鸭子。
|
||||
对于代码上的解释其实很简答:
|
||||
|
||||
```python
|
||||
class A:
|
||||
def f1(self):
|
||||
print('in A f1')
|
||||
|
||||
def f2(self):
|
||||
print('in A f2')
|
||||
|
||||
|
||||
class B:
|
||||
def f1(self):
|
||||
print('in B f1')
|
||||
|
||||
def f2(self):
|
||||
print('in B f2')
|
||||
|
||||
obj = A()
|
||||
obj.f1()
|
||||
obj.f2()
|
||||
|
||||
obj2 = B()
|
||||
obj2.f1()
|
||||
obj2.f2()
|
||||
# A 和 B两个类完全没有耦合性,但是在某种意义上他们却统一了一个标准。
|
||||
# 对相同的功能设定了相同的名字,这样方便开发,这两个方法就可以互成为鸭子类型。
|
||||
|
||||
# 这样的例子比比皆是:str tuple list 都有 index方法,这就是统一了规范。
|
||||
# str bytes 等等 这就是互称为鸭子类型。
|
||||
```
|
||||
|
||||
## 类的约束
|
||||
|
||||
写一个支付功能
|
||||
|
||||
```python
|
||||
class QQpay:
|
||||
def pay(self,money):
|
||||
print('使用qq支付%s元' % money)
|
||||
|
||||
class Alipay:
|
||||
def pay(self,money):
|
||||
print('使用阿里支付%s元' % money)
|
||||
|
||||
a = Alipay()
|
||||
a.pay(100)
|
||||
|
||||
b = QQpay()
|
||||
b.pay(200)
|
||||
```
|
||||
|
||||
统一一下付款方式
|
||||
|
||||
```python
|
||||
class QQpay:
|
||||
def pay(self,money):
|
||||
print('使用qq支付%s元' % money)
|
||||
|
||||
class Alipay:
|
||||
def pay(self,money):
|
||||
print('使用阿里支付%s元' % money)
|
||||
|
||||
def pay(obj,money):
|
||||
obj.pay(money)
|
||||
|
||||
|
||||
a = Alipay()
|
||||
b = QQpay()
|
||||
|
||||
pay(a,100)
|
||||
pay(b,200)
|
||||
```
|
||||
|
||||
如果后期添加微信支付,但是没有统一标准,换个程序员就可能写成这样
|
||||
|
||||
```python
|
||||
class QQpay:
|
||||
def pay(self,money):
|
||||
print('使用qq支付%s元' % money)
|
||||
|
||||
class Alipay:
|
||||
def pay(self,money):
|
||||
print('使用阿里支付%s元' % money)
|
||||
|
||||
class Wechatpay:
|
||||
def fuqian(self,money):
|
||||
print('使用微信支付%s元' % money)
|
||||
|
||||
def pay(obj,money):
|
||||
obj.pay(money)
|
||||
|
||||
|
||||
a = Alipay()
|
||||
b = QQpay()
|
||||
|
||||
pay(a,100)
|
||||
pay(b,200)
|
||||
|
||||
c = Wechatpay()
|
||||
c.fuqian(300)
|
||||
```
|
||||
|
||||
所以此时我们要用到对类的约束,对类的约束有两种:
|
||||
|
||||
1. 提取⽗类. 然后在⽗类中定义好⽅法. 在这个⽅法中什么都不⽤⼲. 就抛⼀个异常就可以了. 这样所有的⼦类都必须重写这个⽅法. 否则. 访问的时候就会报错.
|
||||
|
||||
2. 使⽤元类来描述⽗类. 在元类中给出⼀个抽象⽅法. 这样⼦类就不得不给出抽象⽅法的具体实现. 也可以起到约束的效果.
|
||||
|
||||
- 先用第一种方法解决问题
|
||||
|
||||
```python
|
||||
class Payment:
|
||||
"""
|
||||
此类什么都不做,就是制定一个标准,谁继承我,必须定义我里面的方法。
|
||||
"""
|
||||
def pay(self,money):
|
||||
raise Exception("你没有实现pay方法")
|
||||
|
||||
class QQpay(Payment):
|
||||
def pay(self,money):
|
||||
print('使用qq支付%s元' % money)
|
||||
|
||||
class Alipay(Payment):
|
||||
def pay(self,money):
|
||||
print('使用阿里支付%s元' % money)
|
||||
|
||||
class Wechatpay(Payment):
|
||||
def fuqian(self,money):
|
||||
print('使用微信支付%s元' % money)
|
||||
|
||||
|
||||
def pay(obj,money):
|
||||
obj.pay(money)
|
||||
|
||||
a = Alipay()
|
||||
b = QQpay()
|
||||
c = Wechatpay()
|
||||
pay(a,100)
|
||||
pay(b,200)
|
||||
pay(c,300)
|
||||
```
|
||||
|
||||
- 引入抽象类的概念处理
|
||||
|
||||
```python
|
||||
from abc import ABCMeta,abstractmethod
|
||||
class Payment(metaclass=ABCMeta): # 抽象类 接口类 规范和约束 metaclass指定的是一个元类
|
||||
@abstractmethod
|
||||
def pay(self):pass # 抽象方法
|
||||
|
||||
class Alipay(Payment):
|
||||
def pay(self,money):
|
||||
print('使用支付宝支付了%s元'%money)
|
||||
|
||||
class QQpay(Payment):
|
||||
def pay(self,money):
|
||||
print('使用qq支付了%s元'%money)
|
||||
|
||||
class Wechatpay(Payment):
|
||||
# def pay(self,money):
|
||||
# print('使用微信支付了%s元'%money)
|
||||
def recharge(self):pass
|
||||
|
||||
def pay(a,money):
|
||||
a.pay(money)
|
||||
|
||||
a = Alipay()
|
||||
a.pay(100)
|
||||
pay(a,100) # 归一化设计:不管是哪一个类的对象,都调用同一个函数去完成相似的功能
|
||||
q = QQpay()
|
||||
q.pay(100)
|
||||
pay(q,100)
|
||||
w = Wechatpay()
|
||||
pay(w,100) # 到用的时候才会报错
|
||||
|
||||
|
||||
|
||||
# 抽象类和接口类做的事情 :建立规范
|
||||
# 制定一个类的metaclass是ABCMeta,
|
||||
# 那么这个类就变成了一个抽象类(接口类)
|
||||
# 这个类的主要功能就是建立一个规范
|
||||
```
|
||||
|
||||
总结: 约束. 其实就是⽗类对⼦类进⾏约束. ⼦类必须要写xxx⽅法. 在python中约束的⽅式和⽅法有两种:
|
||||
|
||||
1. 使⽤抽象类和抽象⽅法, 由于该⽅案来源是java和c#. 所以使⽤频率还是很少的
|
||||
|
||||
2. 使⽤⼈为抛出异常的⽅案. 并且尽量抛出的是NotImplementError. 这样比较专业, ⽽且错误比较明确.(推荐)
|
||||
|
||||
## super()深入了解
|
||||
|
||||
**super是严格按照类的继承顺序执行!!!**
|
||||
|
||||
```python
|
||||
class A:
|
||||
def f1(self):
|
||||
print('in A f1')
|
||||
|
||||
def f2(self):
|
||||
print('in A f2')
|
||||
|
||||
|
||||
class Foo(A):
|
||||
def f1(self):
|
||||
super().f2()
|
||||
print('in A Foo')
|
||||
|
||||
|
||||
obj = Foo()
|
||||
obj.f1()
|
||||
```
|
||||
|
||||
```python
|
||||
class A:
|
||||
def f1(self):
|
||||
print('in A')
|
||||
|
||||
class Foo(A):
|
||||
def f1(self):
|
||||
super().f1()
|
||||
print('in Foo')
|
||||
|
||||
class Bar(A):
|
||||
def f1(self):
|
||||
print('in Bar')
|
||||
|
||||
class Info(Foo,Bar):
|
||||
def f1(self):
|
||||
super().f1()
|
||||
print('in Info f1')
|
||||
|
||||
obj = Info()
|
||||
obj.f1()
|
||||
|
||||
print(Info.mro())
|
||||
```
|
||||
|
||||
```python
|
||||
class A:
|
||||
def f1(self):
|
||||
print('in A')
|
||||
|
||||
class Foo(A):
|
||||
def f1(self):
|
||||
super().f1()
|
||||
print('in Foo')
|
||||
|
||||
class Bar(A):
|
||||
def f1(self):
|
||||
print('in Bar')
|
||||
|
||||
class Info(Foo,Bar):
|
||||
def f1(self):
|
||||
super(Foo,self).f1()
|
||||
print('in Info f1')
|
||||
|
||||
obj = Info()
|
||||
obj.f1()
|
||||
```
|
||||
|
497
02.面向对象/05.类的成员.md
Normal file
@@ -0,0 +1,497 @@
|
||||
# 类的成员
|
||||
|
||||
## 细分类的组成成员
|
||||
|
||||
之前咱们讲过类大致分两块区域
|
||||
|
||||
```python
|
||||
class A:
|
||||
name = '陈松'
|
||||
|
||||
# 第一部分:静态字段(静态变量)部分
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
def func(self):
|
||||
pass
|
||||
|
||||
# 第二部分:方法部分
|
||||
```
|
||||
|
||||
每个区域详细划分
|
||||
|
||||
```python
|
||||
class A:
|
||||
|
||||
company_name = '陈松' # 静态变量(静态字段)
|
||||
__iphone = '132333xxxx' # 私有静态变量(私有静态字段)
|
||||
|
||||
def __init__(self,name,age): #特殊方法
|
||||
self.name = name #对象属性(普通字段)
|
||||
self.__age = age # 私有对象属性(私有普通字段)
|
||||
|
||||
def func1(self): # 普通方法
|
||||
pass
|
||||
|
||||
def __func(self): #私有方法
|
||||
print(666)
|
||||
|
||||
@classmethod # 类方法
|
||||
def class_func(cls):
|
||||
""" 定义类方法,至少有一个cls参数 """
|
||||
print('类方法')
|
||||
|
||||
@staticmethod #静态方法
|
||||
def static_func():
|
||||
""" 定义静态方法 ,无默认参数"""
|
||||
print('静态方法')
|
||||
|
||||
@property # 属性
|
||||
def prop(self):
|
||||
pass
|
||||
```
|
||||
|
||||
## 类的私有成员
|
||||
|
||||
对于每一个类的成员而言都有两种形式:
|
||||
|
||||
- 公有成员,在任何地方都能访问
|
||||
- 私有成员,只有在类的内部才能方法
|
||||
|
||||
**私有成员和公有成员的访问限制不同:**
|
||||
|
||||
静态字段(静态属性)
|
||||
|
||||
- 公有静态字段:类可以访问;类内部可以访问;派生类中可以访问
|
||||
- 私有静态字段:仅类内部可以访问;
|
||||
|
||||
```python
|
||||
class C:
|
||||
|
||||
name = "公有静态字段"
|
||||
|
||||
def func(self):
|
||||
print (C.name)
|
||||
|
||||
class D(C):
|
||||
|
||||
def show(self):
|
||||
print (C.name)
|
||||
|
||||
|
||||
print(C.name) # 类访问
|
||||
|
||||
obj = C()
|
||||
obj.func() # 类内部可以访问
|
||||
|
||||
obj_son = D()
|
||||
obj_son.show() # 派生类中可以访问
|
||||
```
|
||||
|
||||
```python
|
||||
class C:
|
||||
|
||||
__name = "私有静态字段"
|
||||
|
||||
def func(self):
|
||||
print (C.__name)
|
||||
|
||||
class D(C):
|
||||
|
||||
def show(self):
|
||||
print (C.__name)
|
||||
|
||||
|
||||
print(C.__name) # 不可在外部访问
|
||||
|
||||
obj = C()
|
||||
print(C.__name) # 不可在外部访问
|
||||
obj.func() # 类内部可以访问
|
||||
|
||||
obj_son = D()
|
||||
obj_son.show() #不可在派生类中可以访问
|
||||
```
|
||||
|
||||
普通字段(对象属性)
|
||||
|
||||
公有普通字段:对象可以访问;类内部可以访问;派生类中可以访问
|
||||
私有普通字段:仅类内部可以访问;
|
||||
|
||||
```python
|
||||
class C:
|
||||
|
||||
def __init__(self):
|
||||
self.foo = "公有字段"
|
||||
|
||||
def func(self):
|
||||
print(self.foo) # 类内部访问
|
||||
|
||||
class D(C):
|
||||
|
||||
def show(self):
|
||||
print(self.foo) # 派生类中访问
|
||||
|
||||
obj = C()
|
||||
|
||||
obj.foo # 通过对象访问
|
||||
obj.func() # 类内部访问
|
||||
|
||||
obj_son = D();
|
||||
obj_son.show() # 派生类中访问
|
||||
```
|
||||
|
||||
```python
|
||||
class C:
|
||||
|
||||
def __init__(self):
|
||||
self.__foo = "私有字段"
|
||||
|
||||
def func(self):
|
||||
print self.foo # 类内部访问
|
||||
|
||||
class D(C):
|
||||
|
||||
def show(self):
|
||||
print self.foo # 派生类中访问
|
||||
|
||||
obj = C()
|
||||
|
||||
obj.__foo # 通过对象访问 ==> 错误
|
||||
obj.func() # 类内部访问 ==> 正确
|
||||
|
||||
obj_son = D();
|
||||
obj_son.show() # 派生类中访问 ==> 错误
|
||||
```
|
||||
|
||||
```python
|
||||
class C:
|
||||
|
||||
def __init__(self):
|
||||
self.__foo = "私有字段"
|
||||
|
||||
def func(self):
|
||||
print(self.__foo) # 类内部访问
|
||||
|
||||
class D(C):
|
||||
|
||||
def show(self):
|
||||
print(self.__foo) # 派生类中访问
|
||||
|
||||
obj = C()
|
||||
|
||||
print(obj.__foo) # 通过对象访问 ==> 错误
|
||||
obj.func() # 类内部访问 ==> 正确
|
||||
|
||||
obj_son = D()
|
||||
obj_son.show() # 派生类中访问 ==> 错误
|
||||
```
|
||||
|
||||
方法:
|
||||
|
||||
公有方法:对象可以访问;类内部可以访问;派生类中可以访问
|
||||
私有方法:仅类内部可以访问;
|
||||
|
||||
```python
|
||||
class C:
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def add(self):
|
||||
print('in C')
|
||||
|
||||
|
||||
class D(C):
|
||||
|
||||
def show(self):
|
||||
print('in D')
|
||||
|
||||
def func(self):
|
||||
self.show()
|
||||
|
||||
|
||||
obj = D()
|
||||
obj.show() # 通过对象访问
|
||||
obj.func() # 类内部访问
|
||||
obj.add() # 派生类中访问
|
||||
```
|
||||
|
||||
```python
|
||||
class C:
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __add(self):
|
||||
print('in C')
|
||||
|
||||
class D(C):
|
||||
|
||||
def __show(self):
|
||||
print('in D')
|
||||
|
||||
def func(self):
|
||||
self.__show()
|
||||
obj = D()
|
||||
obj.__show() # 通过不能对象访问
|
||||
obj.func() # 类内部可以访问
|
||||
obj.__add() # 派生类中不能访问
|
||||
```
|
||||
|
||||
总结:
|
||||
|
||||
对于这些私有成员来说,他们只能在类的内部使用,不能再类的外部以及派生类中使用.
|
||||
|
||||
**ps:非要访问私有成员的话,可以通过 对象._类__属性名,但是绝对不允许!!!**
|
||||
|
||||
为什么可以通过._类__私有成员名访问呢?因为类在创建时,如果遇到了私有成员(包括私有静态字段,私有普通字段,私有方法)它会将其保存在内存时自动在前面加上_类名.
|
||||
|
||||
## 类的其他成员
|
||||
|
||||
这里的其他成员主要就是类方法:
|
||||
|
||||
方法包括:普通方法、静态方法和类方法,三种方法在**内存中都归属于类**,区别在于调用方式不同。
|
||||
|
||||
**实例方法**
|
||||
|
||||
定义:第一个参数必须是实例对象,该参数名一般约定为“self”,通过它来传递实例的属性和方法(也可以传类的属性和方法);
|
||||
|
||||
调用:只能由实例对象调用。
|
||||
|
||||
**类方法**
|
||||
|
||||
定义:使用装饰器@classmethod。第一个参数必须是当前类对象,该参数名一般约定为“cls”,通过它来传递类的属性和方法(不能传实例的属性和方法);
|
||||
|
||||
调用:实例对象和类对象都可以调用。
|
||||
|
||||
**静态方法**
|
||||
|
||||
定义:使用装饰器@staticmethod。参数随意,没有“self”和“cls”参数,但是方法体中不能使用类或实例的任何属性和方法;
|
||||
|
||||
调用:实例对象和类对象都可以调用。
|
||||
|
||||
**双下方法(后面会讲到)**
|
||||
|
||||
定义:双下方法是特殊方法,他是解释器提供的 由爽下划线加方法名加爽下划线 __方法名__的具有特殊意义的方法,双下方法主要是python源码程序员使用的,
|
||||
|
||||
我们在开发中尽量不要使用双下方法,但是深入研究双下方法,更有益于我们阅读源码。
|
||||
|
||||
调用:不同的双下方法有不同的触发方式,就好比盗墓时触发的机关一样,不知不觉就触发了双下方法,例如:__init__
|
||||
|
||||
### 类方法
|
||||
|
||||
使用装饰器@classmethod。
|
||||
|
||||
原则上,类方法是将类本身作为对象进行操作的方法。假设有个方法,且这个方法在逻辑上采用类本身作为对象来调用更合理,那么这个方法就可以定义为类方法。另外,如果需要继承,也可以定义为类方法。
|
||||
|
||||
如下场景:
|
||||
|
||||
假设我有一个学生类和一个班级类,想要实现的功能为:
|
||||
执行班级人数增加的操作、获得班级的总人数;
|
||||
学生类继承自班级类,每实例化一个学生,班级人数都能增加;
|
||||
最后,我想定义一些学生,获得班级中的总人数。
|
||||
|
||||
**思考**:这个问题用类方法做比较合适,为什么?因为我实例化的是学生,但是如果我从学生这一个实例中获得班级总人数,在逻辑上显然是不合理的。同时,如果想要获得班级总人数,如果生成一个班级的实例也是没有必要的。
|
||||
|
||||
```python
|
||||
class Student:
|
||||
__num = 0
|
||||
|
||||
def __init__(self, name, age):
|
||||
self.name = name
|
||||
self.age = age
|
||||
Student.addNum() # 写在__new__方法中比较合适,但是现在还没有学,暂且放到这里
|
||||
|
||||
@classmethod
|
||||
def addNum(cls):
|
||||
cls.__num += 1
|
||||
|
||||
@classmethod
|
||||
def getNum(cls):
|
||||
return cls.__num
|
||||
|
||||
|
||||
Student('陈松', 18)
|
||||
Student('阿松', 36)
|
||||
Student('松松', 73)
|
||||
print(Student.getNum())
|
||||
```
|
||||
|
||||
### 静态方法
|
||||
|
||||
使用装饰器@staticmethod。
|
||||
|
||||
静态方法是类中的函数,不需要实例。静态方法主要是用来存放逻辑性的代码,逻辑上属于类,但是和类本身没有关系,也就是说在静态方法中,不会涉及到类中的属性和方法的操作。可以理解为,静态方法是个**独立的、单纯的**函数,它仅仅托管于某个类的名称空间中,便于使用和维护。
|
||||
|
||||
譬如,我想定义一个关于时间操作的类,其中有一个获取当前时间的函数。
|
||||
|
||||
```python
|
||||
import time
|
||||
|
||||
class TimeTest(object):
|
||||
def __init__(self, hour, minute, second):
|
||||
self.hour = hour
|
||||
self.minute = minute
|
||||
self.second = second
|
||||
|
||||
@staticmethod
|
||||
def showTime():
|
||||
return time.strftime("%H:%M:%S", time.localtime())
|
||||
|
||||
|
||||
print(TimeTest.showTime())
|
||||
t = TimeTest(2, 10, 10)
|
||||
nowTime = t.showTime()
|
||||
print(nowTime)
|
||||
```
|
||||
|
||||
### 属性
|
||||
|
||||
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
|
||||
|
||||
例一:BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)
|
||||
|
||||
成人的BMI数值:
|
||||
过轻:低于18.5
|
||||
正常:18.5-23.9
|
||||
过重:24-27
|
||||
肥胖:28-32
|
||||
非常肥胖, 高于32
|
||||
体质指数(BMI)=体重(kg)÷身高^2(m)
|
||||
EX:70kg÷(1.75×1.75)=22.86
|
||||
|
||||
```python
|
||||
class People:
|
||||
def __init__(self,name,weight,height):
|
||||
self.name=name
|
||||
self.weight=weight
|
||||
self.height=height
|
||||
@property
|
||||
def bmi(self):
|
||||
return self.weight / (self.height**2)
|
||||
|
||||
p1=People('陈松',75,1.85)
|
||||
print(p1.bmi)
|
||||
```
|
||||
|
||||
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了**统一访问的原则**
|
||||
|
||||
由于新式类中具有三种访问方式,我们可以根据他们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除
|
||||
|
||||
```python
|
||||
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
|
||||
```
|
||||
|
||||
或者
|
||||
|
||||
```python
|
||||
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
|
||||
```
|
||||
|
||||
商品的例子
|
||||
|
||||
```python
|
||||
class Goods(object):
|
||||
|
||||
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()
|
||||
print(obj.price) # 获取商品价格
|
||||
obj.price = 200 # 修改商品原价
|
||||
print(obj.price)
|
||||
del obj.price # 删除商品原价
|
||||
```
|
||||
|
||||
## isinstace 与 issubclass
|
||||
|
||||
isinstance(a,b):判断a是否是b类(或者b类的派生类)实例化的对象
|
||||
|
||||
```python
|
||||
class A:
|
||||
pass
|
||||
|
||||
class B(A):
|
||||
pass
|
||||
|
||||
obj = B()
|
||||
|
||||
print(isinstance(obj,B))
|
||||
print(isinstance(obj,A))
|
||||
```
|
||||
|
||||
issubclass(a,b): 判断a类是否是b类(或者b的派生类)的派生类
|
||||
|
||||
```python
|
||||
class A:
|
||||
pass
|
||||
|
||||
class B(A):
|
||||
pass
|
||||
|
||||
class C(B):
|
||||
pass
|
||||
|
||||
print(issubclass(B,A))
|
||||
print(issubclass(C,A))
|
||||
```
|
||||
|
||||
思考:那么 list str tuple dict等这些类与 Iterble类 的关系是什么?
|
||||
|
||||
```python
|
||||
from collections import Iterable
|
||||
|
||||
print(isinstance([1,2,3], list)) # True
|
||||
print(isinstance([1,2,3], Iterable)) # True
|
||||
print(issubclass(list,Iterable)) # True
|
||||
|
||||
# 由上面的例子可得,这些可迭代的数据类型,list str tuple dict等 都是 Iterable的子类。
|
||||
```
|
639
02.面向对象/06.反射与双下方法.md
Normal file
@@ -0,0 +1,639 @@
|
||||
# 反射与双下方法
|
||||
|
||||
## 反射
|
||||
|
||||
python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)
|
||||
|
||||
四个可以实现自省的函数
|
||||
|
||||
下列方法适用于类和对象(一切皆对象,类本身也是一个对象)
|
||||
|
||||
**对对象的反射**
|
||||
|
||||
```python
|
||||
class Foo:
|
||||
f = '类的静态变量'
|
||||
def __init__(self,name,age):
|
||||
self.name=name
|
||||
self.age=age
|
||||
|
||||
def say_hi(self):
|
||||
print('hi,%s'%self.name)
|
||||
|
||||
obj=Foo('陈松',73)
|
||||
|
||||
# 检测是否含有某属性
|
||||
print(hasattr(obj,'name'))
|
||||
print(hasattr(obj,'say_hi'))
|
||||
|
||||
# 获取属性
|
||||
n=getattr(obj,'name')
|
||||
print(n)
|
||||
func=getattr(obj,'say_hi')
|
||||
func()
|
||||
|
||||
print(getattr(obj,'aaaaaaaa','不存在啊')) # 报错
|
||||
|
||||
# 设置属性
|
||||
setattr(obj,'sb',True)
|
||||
setattr(obj,'show_name',lambda self:self.name+'sb')
|
||||
print(obj.__dict__)
|
||||
print(obj.show_name(obj))
|
||||
|
||||
# 删除属性
|
||||
delattr(obj,'age')
|
||||
delattr(obj,'show_name')
|
||||
# delattr(obj,'show_name111') # 不存在,则报错
|
||||
|
||||
print(obj.__dict__)
|
||||
```
|
||||
|
||||
**对类的反射**
|
||||
|
||||
```python
|
||||
class Foo(object):
|
||||
staticField = "test"
|
||||
|
||||
def __init__(self):
|
||||
self.name = '陈松'
|
||||
|
||||
def func(self):
|
||||
return 'func'
|
||||
|
||||
@staticmethod
|
||||
def bar():
|
||||
return 'bar'
|
||||
|
||||
|
||||
print(getattr(Foo, 'staticField'))
|
||||
print(getattr(Foo, 'func'))
|
||||
print(getattr(Foo, 'bar'))
|
||||
```
|
||||
|
||||
当前模块的反射
|
||||
|
||||
```python
|
||||
import sys
|
||||
|
||||
def s1():
|
||||
print('s1')
|
||||
|
||||
def s2():
|
||||
print('s2')
|
||||
|
||||
this_module = sys.modules[__name__]
|
||||
|
||||
print(hasattr(this_module, 's1'))
|
||||
print(getattr(this_module, 's2'))
|
||||
```
|
||||
|
||||
其他模块的反射
|
||||
|
||||
程序目录:
|
||||
module_test.py
|
||||
test.py
|
||||
|
||||
当前文件:
|
||||
test.py
|
||||
|
||||
```python
|
||||
import module_test as obj
|
||||
|
||||
obj.test()
|
||||
|
||||
print(hasattr(obj,'test'))
|
||||
|
||||
getattr(obj,'test')()
|
||||
```
|
||||
|
||||
举例:
|
||||
|
||||
使用反射前
|
||||
|
||||
```python
|
||||
class User:
|
||||
def login(self):
|
||||
print('欢迎来到登录页面')
|
||||
|
||||
def register(self):
|
||||
print('欢迎来到注册页面')
|
||||
|
||||
def save(self):
|
||||
print('欢迎来到存储页面')
|
||||
|
||||
|
||||
while 1:
|
||||
choose = input('>>>').strip()
|
||||
if choose == 'login':
|
||||
obj = User()
|
||||
obj.login()
|
||||
|
||||
elif choose == 'register':
|
||||
obj = User()
|
||||
obj.register()
|
||||
|
||||
elif choose == 'save':
|
||||
obj = User()
|
||||
obj.save()
|
||||
```
|
||||
|
||||
用了反射之后
|
||||
|
||||
```python
|
||||
class User:
|
||||
def login(self):
|
||||
print('欢迎来到登录页面')
|
||||
|
||||
def register(self):
|
||||
print('欢迎来到注册页面')
|
||||
|
||||
def save(self):
|
||||
print('欢迎来到存储页面')
|
||||
|
||||
|
||||
user = User()
|
||||
while 1:
|
||||
choose = input('>>>').strip()
|
||||
if hasattr(user, choose):
|
||||
func = getattr(user, choose)
|
||||
func()
|
||||
else:
|
||||
print('输入错误。。。。')
|
||||
```
|
||||
|
||||
## 函数 vs 方法
|
||||
|
||||
### 通过打印函数(方法)名确定
|
||||
|
||||
```python
|
||||
def func():
|
||||
pass
|
||||
|
||||
print(func)
|
||||
|
||||
class A:
|
||||
def func(self):
|
||||
pass
|
||||
|
||||
print(A.func)
|
||||
obj = A()
|
||||
print(obj.func)
|
||||
```
|
||||
|
||||
### 通过types模块验证
|
||||
|
||||
```python
|
||||
from types import FunctionType
|
||||
from types import MethodType
|
||||
|
||||
def func():
|
||||
pass
|
||||
|
||||
class A:
|
||||
def func(self):
|
||||
pass
|
||||
|
||||
obj = A()
|
||||
|
||||
print(isinstance(func,FunctionType))
|
||||
print(isinstance(A.func,FunctionType))
|
||||
print(isinstance(obj.func,FunctionType))
|
||||
print(isinstance(obj.func,MethodType))
|
||||
```
|
||||
|
||||
### 静态方法是函数
|
||||
|
||||
```python
|
||||
from types import FunctionType
|
||||
from types import MethodType
|
||||
|
||||
|
||||
class A:
|
||||
|
||||
def func(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def func1(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def func2(self):
|
||||
pass
|
||||
|
||||
|
||||
obj = A()
|
||||
|
||||
# 静态方法其实是函数
|
||||
print(isinstance(A.func2,FunctionType))
|
||||
print(isinstance(obj.func2,FunctionType))
|
||||
```
|
||||
|
||||
### 函数与方法的区别
|
||||
|
||||
那么,函数和方法除了上述的不同之处,我们还总结了一下几点区别。
|
||||
|
||||
1. 函数的是显式传递数据的。如我们要指明为len()函数传递一些要处理数据。
|
||||
|
||||
2. 函数则跟对象无关。
|
||||
|
||||
3. 方法中的数据则是隐式传递的。
|
||||
|
||||
4. 方法可以操作类内部的数据。
|
||||
|
||||
5. 方法跟对象是关联的。如我们在用strip()方法是,是不是都是要通过str对象调用,比如我们有字符串s,然后s.strip()这样调用。是的,strip()方法属于str对象。
|
||||
|
||||
我们或许在日常中会口语化称呼函数和方法时不严谨,但是我们心中要知道二者之间的区别。
|
||||
|
||||
在其他语言中,如Java中只有方法,C中只有函数,C++么,则取决于是否在类中
|
||||
|
||||
## 双下方法
|
||||
|
||||
### `__len__`
|
||||
|
||||
```python
|
||||
class B:
|
||||
def __len__(self):
|
||||
return 666
|
||||
|
||||
b = B()
|
||||
print(len(b)) # len 一个对象就会触发 __len__方法。
|
||||
|
||||
class A:
|
||||
def __init__(self):
|
||||
self.a = 1
|
||||
self.b = 2
|
||||
|
||||
def __len__(self):
|
||||
return len(self.__dict__)
|
||||
a = A()
|
||||
print(len(a))
|
||||
```
|
||||
|
||||
### `__hash__`
|
||||
|
||||
```python
|
||||
class A:
|
||||
def __init__(self):
|
||||
self.a = 1
|
||||
self.b = 2
|
||||
|
||||
def __hash__(self):
|
||||
return hash(str(self.a)+str(self.b))
|
||||
a = A()
|
||||
print(hash(a))
|
||||
```
|
||||
|
||||
### `__str__`
|
||||
|
||||
如果一个类中定义了__str__方法,那么在打印 对象 时,默认输出该方法的返回值。
|
||||
|
||||
```python
|
||||
class A:
|
||||
def __init__(self):
|
||||
pass
|
||||
def __str__(self):
|
||||
return '陈松'
|
||||
a = A()
|
||||
print(a)
|
||||
print('%s' % a)
|
||||
```
|
||||
|
||||
### `__repr__`
|
||||
|
||||
如果一个类中定义了__repr__方法,那么在repr(对象) 时,默认输出该方法的返回值。
|
||||
|
||||
```python
|
||||
class A:
|
||||
def __init__(self):
|
||||
pass
|
||||
def __repr__(self):
|
||||
return '陈松'
|
||||
a = A()
|
||||
print(repr(a))
|
||||
print('%r'%a)
|
||||
```
|
||||
|
||||
### `__call__`
|
||||
|
||||
对象后面加括号,触发执行。
|
||||
|
||||
注:构造方法__new__的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
|
||||
|
||||
```python
|
||||
class Foo:
|
||||
|
||||
def __init__(self):
|
||||
print('__init__')
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
print('__call__')
|
||||
|
||||
|
||||
obj = Foo() # 执行 __init__
|
||||
obj() # 执行 __call__
|
||||
```
|
||||
|
||||
### `__eq__`
|
||||
|
||||
```python
|
||||
class A:
|
||||
def __init__(self):
|
||||
self.a = 1
|
||||
self.b = 2
|
||||
|
||||
def __eq__(self,obj):
|
||||
if self.a == obj.a and self.b == obj.b:
|
||||
return True
|
||||
a = A()
|
||||
b = A()
|
||||
print(a == b)
|
||||
```
|
||||
|
||||
### `__del__`
|
||||
|
||||
析构方法,当对象在内存中被释放时,自动触发执行。
|
||||
|
||||
注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
|
||||
|
||||
### `__new__`
|
||||
|
||||
- `__new__()` 方法是在类准备将自身实例化时调用。
|
||||
- `__new__()` 方法始终都是类的静态方法,即使没有被加上静态方法装饰器。
|
||||
- 通常来说,新式类开始实例化时,`__new__()`方法会返回cls(cls指代当前类)的实例,然后该类的`__init__()`方法作为构造方法会接收这个实例(即self)作为自己的第一个参数,然后依次传入`__new__()`方法中接收的位置参数和命名参数。
|
||||
|
||||
```python
|
||||
class A:
|
||||
def __init__(self):
|
||||
self.x = 1
|
||||
print('in init function')
|
||||
def __new__(cls, *args, **kwargs):
|
||||
print('in new function')
|
||||
return object.__new__(A, *args, **kwargs)
|
||||
|
||||
a = A()
|
||||
print(a.x)
|
||||
```
|
||||
|
||||
单例模式
|
||||
|
||||
```python
|
||||
class A:
|
||||
__instance = None
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls.__instance is None:
|
||||
obj = object.__new__(cls)
|
||||
cls.__instance = obj
|
||||
return cls.__instance
|
||||
```
|
||||
|
||||
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
|
||||
**【采用单例模式动机、原因】**
|
||||
对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。
|
||||
如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的模式动机。
|
||||
**【单例模式优缺点】**
|
||||
**【优点】**
|
||||
一、实例控制
|
||||
单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。
|
||||
二、灵活性
|
||||
因为类控制了实例化过程,所以类可以灵活更改实例化过程。
|
||||
**【缺点】**
|
||||
一、开销
|
||||
虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。
|
||||
二、可能的开发混淆
|
||||
使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。
|
||||
三、对象生存期
|
||||
不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于.NET Framework的语言),只有单例类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C++),其他类可以删除对象实例,但这样会导致单例类中出现悬浮引用
|
||||
|
||||
### `__item__`系列
|
||||
|
||||
```python
|
||||
class Foo:
|
||||
def __init__(self,name):
|
||||
self.name=name
|
||||
|
||||
def __getitem__(self, item):
|
||||
print(self.__dict__[item])
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.__dict__[key]=value
|
||||
print('赋值成功')
|
||||
|
||||
def __delitem__(self, key):
|
||||
print('del obj[key]时,我执行')
|
||||
self.__dict__.pop(key)
|
||||
|
||||
def __delattr__(self, item):
|
||||
print('del obj.key时,我执行')
|
||||
self.__dict__.pop(item)
|
||||
|
||||
f1=Foo('sb')
|
||||
f1['age']=18
|
||||
f1['age1']=19
|
||||
del f1.age1
|
||||
del f1['age']
|
||||
f1['name']='mingzi'
|
||||
print(f1.__dict__)
|
||||
```
|
||||
|
||||
### 上下文管理器相关
|
||||
|
||||
`__enter__` `__exit__`
|
||||
|
||||
```python
|
||||
class A:
|
||||
|
||||
def __init__(self, text):
|
||||
self.text = text
|
||||
|
||||
def __enter__(self): # 开启上下文管理器对象时触发此方法
|
||||
self.text = self.text + '您来啦'
|
||||
return self # 将实例化的对象返回f1
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb): # 执行完上下文管理器对象f1时触发此方法
|
||||
self.text = self.text + '这就走啦'
|
||||
|
||||
|
||||
with A('大爷') as f1:
|
||||
print(f1.text)
|
||||
print(f1.text)
|
||||
```
|
||||
|
||||
自定义文件管理器
|
||||
|
||||
```python
|
||||
class Diycontextor:
|
||||
def __init__(self, name, mode):
|
||||
self.name = name
|
||||
self.mode = mode
|
||||
|
||||
def __enter__(self):
|
||||
print("Hi enter here!!")
|
||||
self.filehander = open(self.name, self.mode)
|
||||
return self.filehander
|
||||
|
||||
def __exit__(self,*args):
|
||||
print("Hi exit here")
|
||||
self.filehander.close()
|
||||
|
||||
|
||||
with Diycontextor('config', 'r') as f:
|
||||
for i in f:
|
||||
print(i.strip())
|
||||
```
|
||||
|
||||
案例
|
||||
|
||||
```python
|
||||
class StarkConfig:
|
||||
def __init__(self, num):
|
||||
self.num = num
|
||||
|
||||
def run(self):
|
||||
self()
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
print(self.num)
|
||||
|
||||
|
||||
class RoleConfig(StarkConfig):
|
||||
def __call__(self, *args, **kwargs):
|
||||
print(345)
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.num[item]
|
||||
|
||||
|
||||
v1 = RoleConfig('abcedf')
|
||||
v2 = StarkConfig('2333')
|
||||
print(v1[3])
|
||||
## print(v2[2])
|
||||
v1.run()
|
||||
```
|
||||
|
||||
```python
|
||||
class UserInfo:
|
||||
pass
|
||||
|
||||
|
||||
class Department:
|
||||
pass
|
||||
|
||||
|
||||
class StarkConfig:
|
||||
def __init__(self, num):
|
||||
self.num = num
|
||||
|
||||
def changelist(self, request):
|
||||
print(self.num, request)
|
||||
|
||||
def run(self):
|
||||
self.changelist(999)
|
||||
|
||||
|
||||
class RoleConfig(StarkConfig):
|
||||
def changelist(self, request):
|
||||
print(666, self.num)
|
||||
|
||||
|
||||
class AdminSite:
|
||||
|
||||
def __init__(self):
|
||||
self._registry = {}
|
||||
|
||||
def register(self, k, v):
|
||||
self._registry[k] = v
|
||||
|
||||
|
||||
site = AdminSite()
|
||||
site.register(UserInfo, StarkConfig)
|
||||
# 1
|
||||
obj = site._registry[UserInfo]()
|
||||
|
||||
# 2
|
||||
# obj = site._registry[UserInfo](100)
|
||||
obj.run()
|
||||
```
|
||||
|
||||
```python
|
||||
class UserInfo:
|
||||
pass
|
||||
|
||||
class Department:
|
||||
pass
|
||||
|
||||
class StarkConfig:
|
||||
def __init__(self,num):
|
||||
self.num = num
|
||||
|
||||
def changelist(self,request):
|
||||
print(self.num,request)
|
||||
|
||||
def run(self):
|
||||
self.changelist(999)
|
||||
|
||||
class RoleConfig(StarkConfig):
|
||||
def changelist(self,request):
|
||||
print(666,self.num)
|
||||
|
||||
|
||||
class AdminSite:
|
||||
|
||||
def __init__(self):
|
||||
self._registry = {}
|
||||
|
||||
def register(self,k,v):
|
||||
self._registry[k] = v(k)
|
||||
|
||||
site = AdminSite()
|
||||
site.register(UserInfo,StarkConfig)
|
||||
site.register(Department,RoleConfig)
|
||||
|
||||
for k,row in site._registry.items():
|
||||
row.run()
|
||||
```
|
||||
|
||||
```python
|
||||
class A:
|
||||
list_display = []
|
||||
|
||||
def get_list(self):
|
||||
self.list_display.insert(0, 33)
|
||||
return self.list_display
|
||||
|
||||
|
||||
s1 = A()
|
||||
print(s1.get_list())
|
||||
```
|
||||
|
||||
```python
|
||||
class A:
|
||||
list_display = [1, 2, 3]
|
||||
def __init__(self):
|
||||
self.list_display = []
|
||||
def get_list(self):
|
||||
self.list_display.insert(0, 33)
|
||||
return self.list_display
|
||||
|
||||
|
||||
s1 = A()
|
||||
print(s1.get_list())
|
||||
```
|
||||
|
||||
```python
|
||||
class A:
|
||||
list_display = []
|
||||
|
||||
def get_list(self):
|
||||
self.list_display.insert(0,33)
|
||||
return self.list_display
|
||||
|
||||
class B(A):
|
||||
list_display = [11,22]
|
||||
|
||||
|
||||
s1 = A()
|
||||
s2 = B()
|
||||
print(s1.get_list())
|
||||
print(s2.get_list())
|
||||
```
|
849
03.网络编程与并发/01.网络编程基础.md
Normal file
@@ -0,0 +1,849 @@
|
||||
# 网络编程基础
|
||||
|
||||
## 操作系统基础
|
||||
|
||||
操作系统:(Operating System,简称OS)是管理和控制计算机硬件与软件资源的计算机程序,是直接运行在“裸机”上的最基本的系统软件,任何其他软件都必须在操作系统的支持下才能运行。
|
||||
|
||||

|
||||
|
||||
操作系统应该分成两部分功能
|
||||
|
||||
1. 隐藏了丑陋的硬件调用接口(键盘、鼠标、音箱等等怎么实现的,就不需要你管了),为应用程序员提供调用硬件资源的更好,更简单,更清晰的模型(系统调用接口)。应用程序员有了这些接口后,就不用再考虑操作硬件的细节,专心开发自己的应用程序即可。
|
||||
例如:操作系统提供了文件这个抽象概念,对文件的操作就是对磁盘的操作,有了文件我们无需再去考虑关于磁盘的读写控制(比如控制磁盘转动,移动磁头读写数据等细节),
|
||||
|
||||
2. 将应用程序对硬件资源的竞态请求变得有序化
|
||||
例如:很多应用软件其实是共享一套计算机硬件,比方说有可能有三个应用程序同时需要申请打印机来输出内容,那么a程序竞争到了打印机资源就打印,然后可能是b竞争到打印机资源,也可能是c,这就导致了无序,打印机可能打印一段a的内容然后又去打印c...,操作系统的一个功能就是将这种无序变得有序。
|
||||
|
||||
## socket
|
||||
|
||||
回顾一下五层通讯流程
|
||||
|
||||

|
||||
|
||||
但实际上从传输层开始以及以下,都是操作系统帮咱们完成的,下面的各种包头封装的过程
|
||||
|
||||

|
||||
|
||||
Socket又称为套接字,它是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。当我们使用不同的协议进行通信时就得使用不同的接口,还得处理不同协议的各种细节,这就增加了开发的难度,软件也不易于扩展(就像我们开发一套公司管理系统一样,报账、会议预定、请假等功能不需要单独写系统,而是一个系统上多个功能接口,不需要知道每个功能如何去实现的)。于是UNIX BSD就发明了socket这种东西,socket屏蔽了各个协议的通信细节,使得程序员无需关注协议本身,直接使用socket提供的接口来进行互联的不同主机间的进程的通信。这就好比操作系统给我们提供了使用底层硬件功能的系统调用,通过系统调用我们可以方便的使用磁盘(文件操作),使用内存,而无需自己去进行磁盘读写,内存管理。socket其实也是一样的东西,就是提供了tcp/ip协议的抽象,对外提供了一套接口,同过这个接口就可以统一、方便的使用tcp/ip协议的功能了。
|
||||
|
||||
其实站在你的角度上看,socket就是一个模块。我们通过调用模块中已经实现的方法建立两个进程之间的连接和通信。也有人将socket说成ip+port,因为ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序。 所以我们只要确立了ip和port就能找到一个应用程序,并且使用socket模块来与之通信。
|
||||
|
||||
## 套接字家族
|
||||
|
||||
套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或 IPC。套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。
|
||||
|
||||
### 基于文件类型的套接字家族
|
||||
|
||||
套接字家族的名字:AF_UNIX
|
||||
|
||||
unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信
|
||||
|
||||
### 基于网络类型的套接字家族
|
||||
|
||||
套接字家族的名字:AF_INET
|
||||
|
||||
(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)
|
||||
|
||||
## 套接字的工作流程(基于TCP和 UDP两个协议)
|
||||
|
||||
### TCP和UDP对比
|
||||
|
||||
- TCP(Transmission Control Protocol)
|
||||
- 可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;文件传输程序。
|
||||
|
||||
- UDP(User Datagram Protocol)
|
||||
- 不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文(数据包),尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)。
|
||||
|
||||
### TCP协议下的socket
|
||||
|
||||

|
||||
|
||||
服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束
|
||||
|
||||
```python
|
||||
import socket
|
||||
socket.socket(socket_family,socket_type,protocal=0)
|
||||
# socket_family 可以是 AF_UNIX 或 AF_INET。socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。protocol 一般不填,默认值为 0。
|
||||
|
||||
# 获取tcp/ip套接字
|
||||
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
|
||||
# 获取udp/ip套接字
|
||||
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
|
||||
# 由于 socket 模块中有太多的属性。我们在这里破例使用了'from module import *'语句。使用 'from socket import *',我们就把 socket 模块里的所有属性都带到我们的命名空间里了,这样能 大幅减短我们的代码。
|
||||
# 例如
|
||||
tcpSock = socket(AF_INET, SOCK_STREAM)
|
||||
```
|
||||
|
||||
服务端套接字函数
|
||||
|
||||
| s.bind() | 绑定(主机,端口号)到套接字 |
|
||||
| ---------- | -------------------------------------------- |
|
||||
| s.listen() | 开始TCP监听 |
|
||||
| s.accept() | 被动接受TCP客户的连接,(阻塞式)等待连接的到来 |
|
||||
|
||||
客户端套接字函数
|
||||
|
||||
| s.connect() | 主动初始化TCP服务器连接 |
|
||||
| -------------- | ------------------------------------------------------- |
|
||||
| s.connect_ex() | connect()函数的扩展版本,出错时返回出错码,而不是抛出异常 |
|
||||
|
||||
公共用途的套接字函数
|
||||
|
||||
| s.recv() | 接收TCP数据 |
|
||||
| --------------- | ------------------------------------------------------------ |
|
||||
| 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() | 关闭套接字 |
|
||||
|
||||
面向锁的套接字方法
|
||||
|
||||
| s.setblocking() | 设置套接字的阻塞与非阻塞模式 |
|
||||
| --------------- | ---------------------------- |
|
||||
| s.settimeout() | 设置阻塞套接字操作的超时时间 |
|
||||
| s.gettimeout() | 得到阻塞套接字操作的超时时间 |
|
||||
|
||||
面向文件的套接字的函数
|
||||
|
||||
| s.fileno() | 套接字的文件描述符 |
|
||||
| ------------ | ---------------------------- |
|
||||
| s.makefile() | 创建一个与该套接字相关的文件 |
|
||||
|
||||
第一版,单个客户端与服务端通信
|
||||
|
||||
服务端
|
||||
|
||||
```python
|
||||
import socket
|
||||
|
||||
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 买电话
|
||||
|
||||
phone.bind(('127.0.0.1',8080)) # 0 ~ 65535 1024之前系统分配好的端口 绑定电话卡
|
||||
|
||||
phone.listen(5) # 同一时刻有5个请求,但是可以有N多个链接。 开机。
|
||||
|
||||
|
||||
conn, client_addr = phone.accept() # 接电话
|
||||
print(conn, client_addr, sep='\n')
|
||||
|
||||
from_client_data = conn.recv(1024) # 一次接收的最大限制 bytes
|
||||
print(from_client_data.decode('utf-8'))
|
||||
|
||||
conn.send(from_client_data.upper())
|
||||
|
||||
conn.close() # 挂电话
|
||||
|
||||
phone.close() # 关机
|
||||
```
|
||||
|
||||
客户端
|
||||
|
||||
```python
|
||||
import socket
|
||||
|
||||
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 买电话
|
||||
|
||||
phone.connect(('127.0.0.1',8080)) # 与客户端建立连接, 拨号
|
||||
|
||||
phone.send('hello'.encode('utf-8'))
|
||||
|
||||
from_server_data = phone.recv(1024)
|
||||
|
||||
print(from_server_data)
|
||||
|
||||
phone.close() # 挂电话
|
||||
```
|
||||
|
||||
第二版,通信循环
|
||||
|
||||
服务端
|
||||
|
||||
```python
|
||||
import socket
|
||||
|
||||
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
|
||||
phone.bind(('127.0.0.1',8080))
|
||||
phone.listen(5)
|
||||
|
||||
conn,client_addr = phone.accept()
|
||||
print(conn,client_addr,sep='\n')
|
||||
|
||||
while 1:
|
||||
try:
|
||||
from_client_data = conn.recv(1024)
|
||||
print(from_client_data.decode('utf-8'))
|
||||
|
||||
conn.send(from_client_data.upper())
|
||||
except ConnectionResetError:
|
||||
break
|
||||
|
||||
conn.close()
|
||||
phone.close()
|
||||
```
|
||||
|
||||
客户端
|
||||
|
||||
```python
|
||||
import socket
|
||||
|
||||
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
|
||||
phone.connect(('127.0.0.1',8080))
|
||||
|
||||
while 1:
|
||||
client_data = input('>>> ')
|
||||
phone.send(client_data.encode('utf-8'))
|
||||
|
||||
from_server_data = phone.recv(1024)
|
||||
print(from_server_data.decode('utf-8'))
|
||||
|
||||
phone.close()
|
||||
```
|
||||
|
||||
第三版, 通信,连接循环
|
||||
|
||||
服务端
|
||||
|
||||
```python
|
||||
import socket
|
||||
|
||||
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
|
||||
phone.bind(('127.0.0.1',8080))
|
||||
phone.listen(5)
|
||||
|
||||
while 1:
|
||||
conn,client_addr = phone.accept()
|
||||
print(conn,client_addr,sep='\n')
|
||||
|
||||
while 1:
|
||||
try:
|
||||
from_client_data = conn.recv(1024)
|
||||
print(from_client_data.decode('utf-8'))
|
||||
|
||||
conn.send(from_client_data.upper())
|
||||
except:
|
||||
break
|
||||
|
||||
conn.close()
|
||||
phone.close()
|
||||
```
|
||||
|
||||
客户端
|
||||
|
||||
```python
|
||||
import socket
|
||||
|
||||
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
|
||||
phone.connect(('127.0.0.1',8080))
|
||||
|
||||
while 1:
|
||||
client_data = input('>>> ')
|
||||
phone.send(client_data.encode('utf-8'))
|
||||
if client_data == 'q':break
|
||||
|
||||
from_server_data = phone.recv(1024)
|
||||
print(from_server_data.decode('utf-8'))
|
||||
|
||||
phone.close()
|
||||
```
|
||||
|
||||
远程执行命令的示例:
|
||||
|
||||
```python
|
||||
import socket
|
||||
import subprocess
|
||||
|
||||
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
|
||||
phone.bind(('127.0.0.1', 8080))
|
||||
|
||||
phone.listen(5)
|
||||
|
||||
while 1: # 循环连接客户端
|
||||
conn, client_addr = phone.accept()
|
||||
print(client_addr)
|
||||
|
||||
while 1:
|
||||
try:
|
||||
cmd = conn.recv(1024)
|
||||
ret = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
correct_msg = ret.stdout.read()
|
||||
error_msg = ret.stderr.read()
|
||||
conn.send(correct_msg + error_msg)
|
||||
except ConnectionResetError:
|
||||
break
|
||||
|
||||
conn.close()
|
||||
phone.close()
|
||||
```
|
||||
|
||||
客户端
|
||||
|
||||
```python
|
||||
import socket
|
||||
|
||||
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 买电话
|
||||
|
||||
phone.connect(('127.0.0.1', 8080)) # 与客户端建立连接, 拨号
|
||||
|
||||
while 1:
|
||||
cmd = input('>>>')
|
||||
phone.send(cmd.encode('utf-8'))
|
||||
|
||||
from_server_data = phone.recv(1024)
|
||||
|
||||
print(from_server_data.decode('gbk'))
|
||||
|
||||
phone.close() # 挂电话
|
||||
```
|
||||
|
||||
### UDP协议下的socket
|
||||
|
||||
**udp是无链接的,先启动哪一端都不会报错**
|
||||
|
||||

|
||||
|
||||
服务器端先初始化Socket,然后与端口绑定(bind),recvform接收消息,这个消息有两项,消息内容和对方客户端的地址,然后回复消息时也要带着你收到的这个客户端的地址,发送回去,最后关闭连接,一次交互结束
|
||||
|
||||
服务端
|
||||
|
||||
```python
|
||||
import socket
|
||||
udp_sk = socket.socket(type=socket.SOCK_DGRAM) #创建一个服务器的套接字
|
||||
udp_sk.bind(('127.0.0.1',9000)) #绑定服务器套接字
|
||||
msg,addr = udp_sk.recvfrom(1024)
|
||||
print(msg)
|
||||
udp_sk.sendto(b'hi',addr) # 对话(接收与发送)
|
||||
udp_sk.close() # 关闭服务器套接字
|
||||
```
|
||||
|
||||
客户端
|
||||
|
||||
```python
|
||||
import socket
|
||||
ip_port=('127.0.0.1',9000)
|
||||
udp_sk=socket.socket(type=socket.SOCK_DGRAM)
|
||||
udp_sk.sendto(b'hello',ip_port)
|
||||
back_msg,addr=udp_sk.recvfrom(1024)
|
||||
print(back_msg.decode('utf-8'),addr)
|
||||
```
|
||||
|
||||
类似于qq聊天的代码示例
|
||||
|
||||
服务端
|
||||
|
||||
```python
|
||||
import socket
|
||||
ip_port=('127.0.0.1',8081)
|
||||
udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #DGRAM:datagram 数据报文的意思,象征着UDP协议的通信方式
|
||||
udp_server_sock.bind(ip_port)#你对外提供服务的端口就是这一个,所有的客户端都是通过这个端口和你进行通信的
|
||||
|
||||
while True:
|
||||
qq_msg,addr=udp_server_sock.recvfrom(1024)# 阻塞状态,等待接收消息
|
||||
print('来自[%s:%s]的一条消息:\033[1;34;43m%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)
|
||||
```
|
||||
|
||||
客户端
|
||||
|
||||
```python
|
||||
import socket
|
||||
BUFSIZE=1024
|
||||
udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
|
||||
|
||||
qq_name_dic={
|
||||
'taibai':('127.0.0.1',8081),
|
||||
'Jedan':('127.0.0.1',8081),
|
||||
'Jack':('127.0.0.1',8081),
|
||||
'John':('127.0.0.1',8081),
|
||||
}
|
||||
|
||||
|
||||
while True:
|
||||
while 1:
|
||||
qq_name=input('请选择聊天对象: ').strip()
|
||||
if qq_name not in qq_name_dic:
|
||||
print('没有这个聊天对象')
|
||||
continue
|
||||
break
|
||||
while True:
|
||||
msg='发给'+ qq_name + ': ' + input('请输入消息,回车发送,输入q结束和他的聊天: ').strip()
|
||||
if msg == 'q':break
|
||||
if not msg:continue
|
||||
udp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name])# 必须带着自己的地址,这就是UDP不一样的地方,不需要建立连接,但是要带着自己的地址给服务端,否则服务端无法判断是谁给我发的消息,并且不知道该把消息回复到什么地方,因为我们之间没有建立连接通道
|
||||
|
||||
back_msg,addr=udp_client_socket.recvfrom(BUFSIZE)# 同样也是阻塞状态,等待接收消息
|
||||
print('来自[%s:%s]的一条消息:\033[1;34;43m%s\033[0m' %(addr[0],addr[1],back_msg.decode('utf-8')))
|
||||
|
||||
udp_client_socket.close()
|
||||
```
|
||||
|
||||
自制时间服务器
|
||||
|
||||
服务端
|
||||
|
||||
```python
|
||||
from socket import *
|
||||
from time import strftime
|
||||
import time
|
||||
ip_port = ('127.0.0.1', 9000)
|
||||
bufsize = 1024
|
||||
|
||||
tcp_server = socket(AF_INET, SOCK_DGRAM)
|
||||
tcp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
|
||||
tcp_server.bind(ip_port)
|
||||
|
||||
while True:
|
||||
msg, addr = tcp_server.recvfrom(bufsize)
|
||||
print('===>', msg.decode('utf-8'))
|
||||
stru_time = time.localtime() #当前的结构化时间
|
||||
if not msg:
|
||||
time_fmt = '%Y-%m-%d %X'
|
||||
else:
|
||||
time_fmt = msg.decode('utf-8')
|
||||
back_msg = strftime(time_fmt,stru_time)
|
||||
print(back_msg,type(back_msg))
|
||||
tcp_server.sendto(back_msg.encode('utf-8'), addr)
|
||||
|
||||
tcp_server.close()
|
||||
```
|
||||
|
||||
客户端
|
||||
|
||||
```python
|
||||
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('当前日期:',str(data,encoding='utf-8'))
|
||||
```
|
||||
|
||||
## 粘包
|
||||
|
||||

|
||||
|
||||
每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。
|
||||
|
||||
write()/send() 并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送到目标机器。一旦将数据写入到缓冲区,函数就可以成功返回,不管它们有没有到达目标机器,也不管它们何时被发送到网络,这些都是TCP协议负责的事情。
|
||||
|
||||
TCP协议独立于 write()/send() 函数,数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络,这取决于当时的网络情况、当前线程是否空闲等诸多因素,不由程序员控制。
|
||||
|
||||
read()/recv() 函数也是如此,也从输入缓冲区中读取数据,而不是直接从网络中读取。
|
||||
|
||||
这些I/O缓冲区特性可整理如下:
|
||||
|
||||
1. I/O缓冲区在每个TCP套接字中单独存在;
|
||||
2. I/O缓冲区在创建套接字时自动生成;
|
||||
3. 即使关闭套接字也会继续传送输出缓冲区中遗留的数据;
|
||||
4. 关闭套接字将丢失输入缓冲区中的数据。
|
||||
|
||||
## 两种情况下会发生粘包
|
||||
|
||||
1. 接收方没有及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
|
||||
|
||||
服务端
|
||||
|
||||
```python
|
||||
import socket
|
||||
import subprocess
|
||||
|
||||
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
|
||||
phone.bind(('127.0.0.1', 8080))
|
||||
|
||||
phone.listen(5)
|
||||
|
||||
while 1: # 循环连接客户端
|
||||
conn, client_addr = phone.accept()
|
||||
print(client_addr)
|
||||
|
||||
while 1:
|
||||
try:
|
||||
cmd = conn.recv(1024)
|
||||
ret = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
correct_msg = ret.stdout.read()
|
||||
error_msg = ret.stderr.read()
|
||||
conn.send(correct_msg + error_msg)
|
||||
except ConnectionResetError:
|
||||
break
|
||||
|
||||
conn.close()
|
||||
phone.close()
|
||||
```
|
||||
|
||||
客户端
|
||||
|
||||
```python
|
||||
import socket
|
||||
|
||||
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 买电话
|
||||
|
||||
phone.connect(('127.0.0.1',8080)) # 与客户端建立连接, 拨号
|
||||
|
||||
|
||||
while 1:
|
||||
cmd = input('>>>')
|
||||
phone.send(cmd.encode('utf-8'))
|
||||
|
||||
from_server_data = phone.recv(1024)
|
||||
|
||||
print(from_server_data.decode('gbk'))
|
||||
|
||||
phone.close()
|
||||
```
|
||||
|
||||
2. 发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据也很小,会合到一起,产生粘包)
|
||||
|
||||
服务端
|
||||
|
||||
```python
|
||||
import socket
|
||||
|
||||
|
||||
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
|
||||
phone.bind(('127.0.0.1', 8080))
|
||||
|
||||
phone.listen(5)
|
||||
|
||||
conn, client_addr = phone.accept()
|
||||
|
||||
frist_data = conn.recv(1024)
|
||||
print('1:',frist_data.decode('utf-8')) # 1: helloworld
|
||||
second_data = conn.recv(1024)
|
||||
print('2:',second_data.decode('utf-8'))
|
||||
|
||||
|
||||
conn.close()
|
||||
phone.close()
|
||||
```
|
||||
|
||||
客户端
|
||||
|
||||
```python
|
||||
import socket
|
||||
|
||||
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
|
||||
phone.connect(('127.0.0.1', 8080))
|
||||
|
||||
phone.send(b'hello')
|
||||
phone.send(b'world')
|
||||
|
||||
phone.close()
|
||||
```
|
||||
|
||||
## 粘包的解决方案
|
||||
|
||||
### struct模块
|
||||
|
||||
该模块可以把一个类型,如数字,转成固定长度的bytes
|
||||
|
||||

|
||||
|
||||
```python
|
||||
import struct
|
||||
# 将一个数字转化成等长度的bytes类型。
|
||||
ret = struct.pack('i', 183346)
|
||||
print(ret, type(ret), len(ret))
|
||||
|
||||
# 通过unpack反解回来
|
||||
ret1 = struct.unpack('i',ret)[0]
|
||||
print(ret1, type(ret1), len(ret1))
|
||||
|
||||
# 但是通过struct 处理不能处理太大
|
||||
```
|
||||
|
||||
方案一:
|
||||
|
||||
```python
|
||||
import socket
|
||||
import subprocess
|
||||
import struct
|
||||
|
||||
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
|
||||
phone.bind(('127.0.0.1', 8080))
|
||||
|
||||
phone.listen(5)
|
||||
|
||||
while 1:
|
||||
conn, client_addr = phone.accept()
|
||||
print(client_addr)
|
||||
|
||||
while 1:
|
||||
try:
|
||||
cmd = conn.recv(1024)
|
||||
ret = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
correct_msg = ret.stdout.read()
|
||||
error_msg = ret.stderr.read()
|
||||
|
||||
# 1 制作固定报头
|
||||
total_size = len(correct_msg) + len(error_msg)
|
||||
header = struct.pack('i', total_size)
|
||||
|
||||
# 2 发送报头
|
||||
conn.send(header)
|
||||
|
||||
# 发送真实数据:
|
||||
conn.send(correct_msg)
|
||||
conn.send(error_msg)
|
||||
except ConnectionResetError:
|
||||
break
|
||||
|
||||
conn.close()
|
||||
phone.close()
|
||||
|
||||
## 但是low版本有问题:
|
||||
## 1,报头不只有总数据大小,而是还应该有MD5数据,文件名等等一些数据。
|
||||
## 2,通过struct模块直接数据处理,不能处理太大。
|
||||
```
|
||||
|
||||
客户端
|
||||
|
||||
```python
|
||||
import socket
|
||||
import struct
|
||||
|
||||
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
|
||||
phone.connect(('127.0.0.1', 8080))
|
||||
|
||||
while 1:
|
||||
cmd = input('>>>').strip()
|
||||
if not cmd: continue
|
||||
phone.send(cmd.encode('utf-8'))
|
||||
|
||||
# 1,接收固定报头
|
||||
header = phone.recv(4)
|
||||
|
||||
# 2,解析报头
|
||||
total_size = struct.unpack('i', header)[0]
|
||||
|
||||
# 3,根据报头信息,接收真实数据
|
||||
recv_size = 0
|
||||
res = b''
|
||||
|
||||
while recv_size < total_size:
|
||||
recv_data = phone.recv(1024)
|
||||
res += recv_data
|
||||
recv_size += len(recv_data)
|
||||
|
||||
print(res.decode('gbk'))
|
||||
|
||||
phone.close()
|
||||
```
|
||||
|
||||
方案二:可自定制报头
|
||||
|
||||
整个流程的大致解释:
|
||||
我们可以把报头做成字典,字典里包含将要发送的真实数据的描述信息(大小啊之类的),然后json序列化,然后用struck将序列化后的数据长度打包成4个字节。
|
||||
我们在网络上传输的所有数据 都叫做数据包,数据包里的所有数据都叫做报文,报文里面不止有你的数据,还有ip地址、mac地址、端口号等等,其实所有的报文都有报头,这个报头是协议规定的,看一下
|
||||
|
||||
发送时:
|
||||
先发报头长度
|
||||
再编码报头内容然后发送
|
||||
最后发真实内容
|
||||
|
||||
接收时:
|
||||
先手报头长度,用struct取出来
|
||||
根据取出的长度收取报头内容,然后解码,反序列化
|
||||
从反序列化的结果中取出待取数据的描述信息,然后去取真实的数据内容
|
||||
|
||||
服务端
|
||||
|
||||
```python
|
||||
import socket
|
||||
import subprocess
|
||||
import struct
|
||||
import json
|
||||
|
||||
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
|
||||
phone.bind(('127.0.0.1', 8080))
|
||||
|
||||
phone.listen(5)
|
||||
|
||||
while 1:
|
||||
conn, client_addr = phone.accept()
|
||||
print(client_addr)
|
||||
|
||||
while 1:
|
||||
try:
|
||||
cmd = conn.recv(1024)
|
||||
ret = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
correct_msg = ret.stdout.read()
|
||||
error_msg = ret.stderr.read()
|
||||
|
||||
# 1 制作固定报头
|
||||
total_size = len(correct_msg) + len(error_msg)
|
||||
|
||||
header_dict = {
|
||||
'md5': 'fdsaf2143254f',
|
||||
'file_name': 'f1.txt',
|
||||
'total_size': total_size,
|
||||
}
|
||||
|
||||
header_dict_json = json.dumps(header_dict) # str
|
||||
bytes_headers = header_dict_json.encode('utf-8')
|
||||
|
||||
header_size = len(bytes_headers)
|
||||
|
||||
header = struct.pack('i', header_size)
|
||||
|
||||
# 2 发送报头长度
|
||||
conn.send(header)
|
||||
|
||||
# 3 发送报头
|
||||
conn.send(bytes_headers)
|
||||
|
||||
# 4 发送真实数据:
|
||||
conn.send(correct_msg)
|
||||
conn.send(error_msg)
|
||||
except ConnectionResetError:
|
||||
break
|
||||
|
||||
conn.close()
|
||||
phone.close()
|
||||
```
|
||||
|
||||
客户端
|
||||
|
||||
```python
|
||||
import socket
|
||||
import struct
|
||||
import json
|
||||
|
||||
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
|
||||
phone.connect(('127.0.0.1', 8080))
|
||||
|
||||
while 1:
|
||||
cmd = input('>>>').strip()
|
||||
if not cmd: continue
|
||||
phone.send(cmd.encode('utf-8'))
|
||||
|
||||
# 1,接收固定报头
|
||||
header_size = struct.unpack('i', phone.recv(4))[0]
|
||||
|
||||
# 2,解析报头长度
|
||||
header_bytes = phone.recv(header_size)
|
||||
|
||||
header_dict = json.loads(header_bytes.decode('utf-8'))
|
||||
|
||||
# 3,收取报头
|
||||
total_size = header_dict['total_size']
|
||||
|
||||
# 3,根据报头信息,接收真实数据
|
||||
recv_size = 0
|
||||
res = b''
|
||||
|
||||
while recv_size < total_size:
|
||||
recv_data = phone.recv(1024)
|
||||
res += recv_data
|
||||
recv_size += len(recv_data)
|
||||
|
||||
print(res.decode('gbk'))
|
||||
|
||||
phone.close()
|
||||
```
|
||||
|
||||
FTP上传下载文件的代码(简单版)
|
||||
|
||||
服务端
|
||||
|
||||
```python
|
||||
import socket
|
||||
import struct
|
||||
import json
|
||||
sk = socket.socket()
|
||||
## buffer = 4096 # 当双方的这个接收发送的大小比较大的时候,就像这个4096,就会丢数据,这个等我查一下再告诉大家,改小了就ok的,在linux上也是ok的。
|
||||
buffer = 1024 #每次接收数据的大小
|
||||
sk.bind(('127.0.0.1',8090))
|
||||
sk.listen()
|
||||
|
||||
conn,addr = sk.accept()
|
||||
#接收
|
||||
head_len = conn.recv(4)
|
||||
head_len = struct.unpack('i',head_len)[0] #解包
|
||||
json_head = conn.recv(head_len).decode('utf-8') #反序列化
|
||||
head = json.loads(json_head)
|
||||
filesize = head['filesize']
|
||||
with open(head['filename'],'wb') as f:
|
||||
while filesize:
|
||||
if filesize >= buffer: #>=是因为如果刚好等于的情况出现也是可以的。
|
||||
content = conn.recv(buffer)
|
||||
f.write(content)
|
||||
filesize -= buffer
|
||||
else:
|
||||
content = conn.recv(buffer)
|
||||
f.write(content)
|
||||
break
|
||||
|
||||
conn.close()
|
||||
sk.close()
|
||||
```
|
||||
|
||||
客户端
|
||||
|
||||
```python
|
||||
import os
|
||||
import json
|
||||
import socket
|
||||
import struct
|
||||
sk = socket.socket()
|
||||
sk.connect(('127.0.0.1',8090))
|
||||
buffer = 1024 #读取文件的时候,每次读取的大小
|
||||
head = {
|
||||
'filepath':r'C:\Users\Aaron\Desktop\新建文件夹', #需要下载的文件路径,也就是文件所在的文件夹
|
||||
'filename':'config', #改成上面filepath下的一个文件
|
||||
'filesize':None,
|
||||
}
|
||||
|
||||
file_path = os.path.join(head['filepath'],head['filename'])
|
||||
filesize = os.path.getsize(file_path)
|
||||
head['filesize'] = filesize
|
||||
# json_head = json.dumps(head,ensure_ascii=False) #字典转换成字符串
|
||||
json_head = json.dumps(head) #字典转换成字符串
|
||||
bytes_head = json_head.encode('utf-8') #字符串转换成bytes类型
|
||||
print(json_head)
|
||||
print(bytes_head)
|
||||
|
||||
#计算head的长度,因为接收端先接收我们自己定制的报头,对吧
|
||||
head_len = len(bytes_head) #报头长度
|
||||
pack_len = struct.pack('i',head_len)
|
||||
print(head_len)
|
||||
print(pack_len)
|
||||
sk.send(pack_len) #先发送报头长度
|
||||
sk.send(bytes_head) #再发送bytes类型的报头
|
||||
|
||||
#即便是视频文件,也是可以按行来读取的,也可以readline,也可以for循环,但是读取出来的数据大小就不固定了,影响效率,有可能读的比较小,也可能很大,像视频文件一般都是一行的二进制字节流。
|
||||
#所有我们可以用read,设定一个一次读取内容的大小,一边读一边发,一边收一边写
|
||||
with open(file_path,'rb') as f:
|
||||
while filesize:
|
||||
if filesize >= buffer: #>=是因为如果刚好等于的情况出现也是可以的。
|
||||
content = f.read(buffer) #每次读取出来的内容
|
||||
sk.send(content)
|
||||
filesize -= buffer #每次减去读取的大小
|
||||
else: #那么说明剩余的不够一次读取的大小了,那么只要把剩下的读取出来发送过去就行了
|
||||
content = f.read(filesize)
|
||||
sk.send(content)
|
||||
break
|
||||
|
||||
sk.close()
|
||||
```
|
BIN
03.网络编程与并发/01.网络编程基础/image-20210725220525763.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
03.网络编程与并发/01.网络编程基础/image-20210725220535013.png
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
03.网络编程与并发/01.网络编程基础/image-20210725220541285.png
Normal file
After Width: | Height: | Size: 65 KiB |
BIN
03.网络编程与并发/01.网络编程基础/image-20210725220552513.png
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
03.网络编程与并发/01.网络编程基础/image-20210725220612103.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
03.网络编程与并发/01.网络编程基础/image-20210725220622753.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
03.网络编程与并发/01.网络编程基础/image-20210725220635963.png
Normal file
After Width: | Height: | Size: 102 KiB |
BIN
03.网络编程与并发/01.网络编程基础/image-20210725220641602.png
Normal file
After Width: | Height: | Size: 102 KiB |
BIN
03.网络编程与并发/01.网络编程基础/image-20210725220708133.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
03.网络编程与并发/01.网络编程基础/image-20210725220726010.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
03.网络编程与并发/01.网络编程基础/image-20210725220750620.png
Normal file
After Width: | Height: | Size: 102 KiB |
174
03.网络编程与并发/02.操作系统发展史.md
Normal file
@@ -0,0 +1,174 @@
|
||||
# 操作系统发展史
|
||||
|
||||
## 手工操作 —— 穿孔卡片
|
||||
|
||||
1946年第一台计算机诞生--20世纪50年代中期,计算机工作还在采用手工操作方式。此时还没有操作系统的概念。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
程序员将对应于程序和数据的已穿孔的纸带(或卡片)装入输入机,然后启动输入机把程序和数据输入计算机内存,接着通过控制台开关启动程序针对数据运行;计算完毕,打印机输出计算结果;用户取走结果并卸下纸带(或卡片)后,才让下一个用户上机。
|
||||
|
||||
手工操作方式两个特点:
|
||||
|
||||
1. 用户独占全机。不会出现因资源已被其他用户占用而等待的现象,但资源的利用率低。
|
||||
2. CPU 等待手工操作。CPU的利用不充分。
|
||||
|
||||
20世纪50年代后期,出现人机矛盾:手工操作的慢速度和计算机的高速度之间形成了尖锐矛盾,手工操作方式已严重损害了系统资源的利用率(使资源利用率降为百分之几,甚至更低),不能容忍。唯一的解决办法:只有摆脱人的手工操作,实现作业的自动过渡。这样就出现了成批处理。
|
||||
|
||||
## 批处理 —— 磁带存储
|
||||
|
||||
批处理系统:加载在计算机上的一个系统软件,在它的控制下,计算机能够自动地、成批地处理一个或多个用户的作业(这作业包括程序、数据和命令)。
|
||||
|
||||
### 联机批处理系统
|
||||
|
||||
首先出现的是联机批处理系统,即作业的输入/输出由CPU来处理。
|
||||
|
||||

|
||||
|
||||
主机与输入机之间增加一个存储设备——磁带,在运行于主机上的监督程序的自动控制下,计算机可自动完成:成批地把输入机上的用户作业读入磁带,依次把磁带上的用户作业读入主机内存并执行并把计算结果向输出机输出。完成了上一批作业后,监督程序又从输入机上输入另一批作业,保存在磁带上,并按上述步骤重复处理。
|
||||
监督程序不停地处理各个作业,从而实现了作业到作业的自动转接,减少了作业建立时间和手工操作时间,有效克服了人机矛盾,提高了计算机的利用率。
|
||||
但是,在作业输入和结果输出时,主机的高速CPU仍处于空闲状态,等待慢速的输入/输出设备完成工作: 主机处于“忙等”状态。
|
||||
|
||||
### 脱机批处理系统
|
||||
|
||||
为克服与缓解:高速主机与慢速外设的矛盾,提高CPU的利用率,又引入了脱机批处理系统,即输入/输出脱离主机控制。
|
||||
|
||||

|
||||
|
||||
卫星机:一台不与主机直接相连而专门用于与输入/输出设备打交道的。
|
||||
|
||||
1. 从输入机上读取用户作业并放到输入磁带上。
|
||||
2. 从输出磁带上读取执行结果并传给输出机
|
||||
|
||||
不足:每次主机内存中仅存放一道作业,每当它运行期间发出输入/输出(I/O)请求后,高速的CPU便处于等待低速的I/O完成状态,致使CPU空闲。
|
||||
|
||||
## 多道程序系统
|
||||
|
||||
### 多道程序设计技术
|
||||
|
||||
所谓多道程序设计技术,就是指允许多个程序同时进入内存并运行。即同时把多个程序放入内存,并允许它们交替在CPU中运行,它们共享系统中的各种硬、软件资源。当一道程序因I/O请求而暂停运行时,CPU便立即转去运行另一道程序。
|
||||
|
||||

|
||||
|
||||
在A程序计算时,I/O空闲, A程序I/O操作时,CPU空闲(B程序也是同样);必须A工作完成后,B才能进入内存中开始工作,两者是串行的,全部完成共需时间=T1+T2。
|
||||
|
||||

|
||||
|
||||
将A、B两道程序同时存放在内存中,它们在系统的控制下,可相互穿插、交替地在CPU上运行:当A程序因请求I/O操作而放弃CPU时,B程序就可占用CPU运行,这样 CPU不再空闲,而正进行A I/O操作的I/O设备也不空闲,显然,CPU和I/O设备都处于“忙”状态,大大提高了资源的利用率,从而也提高了系统的效率,A、B全部完成所需时间<<T1+T2。
|
||||
|
||||
单处理机系统中多道程序运行时的特点:
|
||||
|
||||
1. 多道:计算机内存中同时存放几道相互独立的程序;
|
||||
2. 宏观上并行:同时进入系统的几道程序都处于运行过程中,即它们先后开始了各自的运行,但都未运行完毕;
|
||||
3. 微观上串行:实际上,各道程序轮流地用CPU,并交替运行。
|
||||
|
||||
多道程序系统的出现,标志着操作系统渐趋成熟的阶段,先后出现了作业调度管理、处理机管理、存储器管理、外部设备管理、文件系统管理等功能。
|
||||
由于多个程序同时在计算机中运行,开始有了**空间隔离**的概念,只有内存空间的隔离,才能让数据更加安全、稳定。
|
||||
出了空间隔离之外,多道技术还第一次体现了**时空复用**的特点,遇到IO操作就切换程序,使得cpu的利用率提高了,计算机的工作效率也随之提高。
|
||||
|
||||
### 多道批处理系统
|
||||
|
||||
20世纪60年代中期,在前述的批处理系统中,引入多道程序设计技术后形成多道批处理系统(简称:批处理系统)。
|
||||
它有两个特点:
|
||||
|
||||
1. 多道:系统内可同时容纳多个作业。这些作业放在外存中,组成一个后备队列,系统按一定的调度原则每次从后备作业队列中选取一个或多个作业进入内存运行,运行作业结束、退出运行和后备作业进入运行均由系统自动实现,从而在系统中形成一个自动转接的、连续的作业流。
|
||||
2. 成批:在系统运行过程中,不允许用户与其作业发生交互作用,即:作业一旦进入系统,用户就不能直接干预其作业的运行。
|
||||
|
||||
- 批处理系统的追求目标:提高系统资源利用率和系统吞吐量,以及作业流程的自动化。
|
||||
- 批处理系统的一个重要缺点:不提供人机交互能力,给用户使用计算机带来不便。
|
||||
- 虽然用户独占全机资源,并且直接控制程序的运行,可以随时了解程序运行情况。但这种工作方式因独占全机造成资源效率极低。
|
||||
- 一种新的追求目标:既能保证计算机效率,又能方便用户使用计算机。 20世纪60年代中期,计算机技术和软件技术的发展使这种追求成为可能。
|
||||
|
||||
## 分时系统
|
||||
|
||||
由于CPU速度不断提高和采用分时技术,一台计算机可同时连接多个用户终端,而每个用户可在自己的终端上联机使用计算机,好象自己独占机器一样。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
分时技术:把处理机的运行时间分成很短的时间片,按时间片轮流把处理机分配给各联机作业使用。
|
||||
若某个作业在分配给它的时间片内不能完成其计算,则该作业暂时中断,把处理机让给另一作业使用,等待下一轮时再继续其运行。由于计算机速度很快,作业运行轮转得很快,给每个用户的印象是,好象他独占了一台计算机。而每个用户可以通过自己的终端向系统发出各种操作控制命令,在充分的人机交互情况下,完成作业的运行。
|
||||
具有上述特征的计算机系统称为分时系统,它允许多个用户同时联机使用计算机。
|
||||
|
||||
特点:
|
||||
|
||||
1. 多路性。若干个用户同时使用一台计算机。微观上看是各用户轮流使用计算机;宏观上看是各用户并行工作。
|
||||
2. 交互性。用户可根据系统对请求的响应结果,进一步向系统提出新的请求。这种能使用户与系统进行人机对话的工作方式,明显地有别于批处理系统,因而,分时系统又被称为交互式系统。
|
||||
3. 独立性。用户之间可以相互独立操作,互不干扰。系统保证各用户程序运行的完整性,不会发生相互混淆或破坏现象。
|
||||
4. 及时性。系统可对用户的输入及时作出响应。分时系统性能的主要指标之一是响应时间,它是指:从终端发出命令到系统予以应答所需的时间。
|
||||
|
||||
**分时系统的主要目标**:对用户响应的及时性,即不至于用户等待每一个命令的处理时间过长。
|
||||
分时系统可以同时接纳数十个甚至上百个用户,由于内存空间有限,往往采用对换(又称交换)方式的存储方法。即将未“轮到”的作业放入磁盘,一旦“轮到”,再将其调入内存;而时间片用完后,又将作业存回磁盘(俗称“滚进”、“滚出“法),使同一存储区域轮流为多个用户服务。
|
||||
|
||||
**注意:分时系统的分时间片工作,在没有遇到IO操作的时候就用完了自己的时间片被切走了,这样的切换工作其实并没有提高cpu的效率,反而使得计算机的效率降低了。但是我们牺牲了一点效率,却实现了多个程序共同执行的效果,这样你就可以在计算机上一边听音乐一边聊qq了。**
|
||||
|
||||
## 实时系统
|
||||
|
||||
虽然多道批处理系统和分时系统能获得较令人满意的资源利用率和系统响应时间,但却不能满足实时控制与实时信息处理两个应用领域的需求。于是就产生了实时系统,即系统能够及时响应随机发生的外部事件,并在严格的时间范围内完成对该事件的处理。
|
||||
|
||||
实时系统在一个特定的应用中常作为一种控制设备来使用。
|
||||
|
||||
**实时系统可分成两类**:
|
||||
|
||||
1. 实时控制系统。当用于飞机飞行、导弹发射等的自动控制时,要求计算机能尽快处理测量系统测得的数据,及时地对飞机或导弹进行控制,或将有关信息通过显示终端提供给决策人员。当用于轧钢、石化等工业生产过程控制时,也要求计算机能及时处理由各类传感器送来的数据,然后控制相应的执行机构。
|
||||
2. 实时信息处理系统。当用于预定飞机票、查询有关航班、航线、票价等事宜时,或当用于银行系统、情报检索系统时,都要求计算机能对终端设备发来的服务请求及时予以正确的回答。此类对响应及时性的要求稍弱于第一类。
|
||||
**实时操作系统的主要特点**:
|
||||
3. 及时响应。每一个信息接收、分析处理和发送的过程必须在严格的时间限制内完成。
|
||||
4. 高可靠性。需采取冗余措施,双机系统前后台工作,也包括必要的保密措施等。
|
||||
|
||||
分时——现在流行的PC,服务器都是采用这种运行模式,即把CPU的运行分成若干时间片分别处理不同的运算请求 linux系统
|
||||
实时——一般用于单片机上、PLC等,比如电梯的上下控制中,对于按键等动作要求进行实时处理
|
||||
|
||||
## 通用操作系统
|
||||
|
||||
操作系统的三种基本类型:多道批处理系统、分时系统、实时系统。
|
||||
|
||||
- 通用操作系统:具有多种类型操作特征的操作系统。可以同时兼有多道批处理、分时、实时处理的功能,或其中两种以上的功能。
|
||||
- 例如:实时处理+批处理=实时批处理系统。首先保证优先处理实时任务,插空进行批处理作业。常把实时任务称为前台作业,批作业称为后台作业。
|
||||
- 再如:分时处理+批处理=分时批处理系统。即:时间要求不强的作业放入“后台”(批处理)处理,需频繁交互的作业在“前台”(分时)处理,处理机优先运行“前台”作业。
|
||||
从上世纪60年代中期,国际上开始研制一些大型的通用操作系统。这些系统试图达到功能齐全、可适应各种应用范围和操作方式变化多端的环境的目标。但是,这些系统过于复杂和庞大,不仅付出了巨大的代价,且在解决其可靠性、可维护性和可理解性方面都遇到很大的困难。
|
||||
相比之下,UNIX操作系统却是一个例外。这是一个通用的多用户分时交互型的操作系统。它首先建立的是一个精干的核心,而其功能却足以与许多大型的操作系统相媲美,在核心层以外,可以支持庞大的软件系统。它很快得到应用和推广,并不断完善,对现代操作系统有着重大的影响。
|
||||
至此,操作系统的基本概念、功能、基本结构和组成都已形成并渐趋完善。
|
||||
|
||||
## 操作系统的进一步发展
|
||||
|
||||
进入20世纪80年代,大规模集成电路工艺技术的飞跃发展,微处理机的出现和发展,掀起了计算机大发展大普及的浪潮。一方面迎来了个人计算机的时代,同时又向计算机网络、分布式处理、巨型计算机和智能化方向发展。于是,操作系统有了进一步的发展,如:个人计算机操作系统、网络操作系统、分布式操作系统等。
|
||||
|
||||
### 个人计算机操作系统
|
||||
|
||||
个人计算机上的操作系统是联机交互的单用户操作系统,它提供的联机交互功能与通用分时系统提供的功能很相似。
|
||||
由于是个人专用,因此一些功能会简单得多。然而,由于个人计算机的应用普及,对于提供更方便友好的用户接口和丰富功能的文件系统的要求会愈来愈迫切。
|
||||
|
||||
### 网络操作系统
|
||||
|
||||
计算机网络:通过通信设施,将地理上分散的、具有自治功能的多个计算机系统互连起来,实现信息交换、资源共享、互操作和协作处理的系统。
|
||||
网络操作系统:在原来各自计算机操作系统上,按照网络体系结构的各个协议标准增加网络管理模块,其中包括:通信、资源共享、系统安全和各种网络应用服务。
|
||||
|
||||
### 分布式操作系统
|
||||
|
||||
表面上看,分布式系统与计算机网络系统没有多大区别。分布式操作系统也是通过通信网络,将地理上分散的具有自治功能的数据处理系统或计算机系统互连起来,实现信息交换和资源共享,协作完成任务。——硬件连接相同。
|
||||
但有如下一些明显的区别:
|
||||
|
||||
1. 分布式系统要求一个统一的操作系统,实现系统操作的统一性。
|
||||
2. 分布式操作系统管理分布式系统中的所有资源,它负责全系统的资源分配和调度、任务划分、信息传输和控制协调工作,并为用户提供一个统一的界面。
|
||||
3. 用户通过这一界面,实现所需要的操作和使用系统资源,至于操作定在哪一台计算机上执行,或使用哪台计算机的资源,则是操作系统完成的,用户不必知道,此谓:系统的透明性。
|
||||
4. 分布式系统更强调分布式计算和处理,因此对于多机合作和系统重构、坚强性和容错能力有更高的要求,希望系统有:更短的响应时间、高吞吐量和高可靠性。
|
||||
|
||||
## 操作系统的作用
|
||||
|
||||
现代的计算机系统主要是由一个或者多个处理器,主存,硬盘,键盘,鼠标,显示器,打印机,网络接口及其他输入输出设备组成。
|
||||
|
||||
一般而言,现代计算机系统是一个复杂的系统。
|
||||
|
||||
其一:如果每位应用程序员都必须掌握该系统所有的细节,那就不可能再编写代码了(严重影响了程序员的开发效率:全部掌握这些细节可能需要一万年....)
|
||||
|
||||
其二:并且管理这些部件并加以优化使用,是一件极富挑战性的工作,于是,计算安装了一层软件(系统软件),称为操作系统。它的任务就是为用户程序提供一个更好、更简单、更清晰的计算机模型,并管理刚才提到的所有设备。
|
||||
|
||||
**总结:**
|
||||
|
||||
1. 程序员无法把所有的硬件操作细节都了解到,管理这些硬件并且加以优化使用是非常繁琐的工作,这个繁琐的工作就是操作系统来干的,有了他,程序员就从这些繁琐的工作中解脱了出来,只需要考虑自己的应用软件的编写就可以了,应用软件直接使用操作系统提供的功能来间接使用硬件。
|
||||
|
||||
2. 精简的说的话,操作系统就是一个协调、管理和控制计算机硬件资源和软件资源的控制程序。
|
BIN
03.网络编程与并发/02.操作系统发展史/image-20210725220908985.png
Normal file
After Width: | Height: | Size: 153 KiB |
BIN
03.网络编程与并发/02.操作系统发展史/image-20210725220917981.png
Normal file
After Width: | Height: | Size: 86 KiB |
BIN
03.网络编程与并发/02.操作系统发展史/image-20210725220928953.png
Normal file
After Width: | Height: | Size: 43 KiB |
BIN
03.网络编程与并发/02.操作系统发展史/image-20210725220936340.png
Normal file
After Width: | Height: | Size: 67 KiB |
BIN
03.网络编程与并发/02.操作系统发展史/image-20210725220944897.png
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
03.网络编程与并发/02.操作系统发展史/image-20210725220951109.png
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
03.网络编程与并发/02.操作系统发展史/image-20210725221001607.png
Normal file
After Width: | Height: | Size: 190 KiB |
BIN
03.网络编程与并发/02.操作系统发展史/image-20210725221010282.png
Normal file
After Width: | Height: | Size: 190 KiB |
1291
03.网络编程与并发/03.多进程.md
Normal file
BIN
03.网络编程与并发/03.多进程/image-20210725221132043.png
Normal file
After Width: | Height: | Size: 74 KiB |
BIN
03.网络编程与并发/03.多进程/image-20210725221139272.png
Normal file
After Width: | Height: | Size: 207 KiB |
636
03.网络编程与并发/04.多线程.md
Normal file
@@ -0,0 +1,636 @@
|
||||
# 多线程
|
||||
|
||||
## 操作系统线程理论
|
||||
|
||||
### 进程
|
||||
|
||||
进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
|
||||
|
||||
进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。
|
||||
|
||||
### 线程
|
||||
|
||||
60年代,在OS中能拥有资源和独立运行的基本单位是进程,然而随着计算机技术的发展,进程出现了很多弊端
|
||||
|
||||
1. 是由于进程是资源拥有者,创建、撤消与切换存在较大的时空开销,因此需要引入轻型进程;
|
||||
2. 是由于对称多处理机(SMP)出现,可以满足多个运行单位,而多个进程并行开销过大。
|
||||
|
||||
因此在80年代,出现了能独立运行的基本单位——线程(Threads)。
|
||||
注意:进程是资源分配的最小单位,线程是CPU调度的最小单位.每一个进程中至少有一个线程。
|
||||
|
||||
### 进程和线程的关系
|
||||
|
||||

|
||||
|
||||
线程与进程的区别可以归纳为以下4点:
|
||||
|
||||
1. 地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
|
||||
2. 通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
|
||||
3. 调度和切换:线程上下文切换比进程上下文切换要快得多。
|
||||
4. 在多线程操作系统中,进程不是一个可执行的实体。
|
||||
|
||||
### 使用线程的实际场景
|
||||
|
||||
开启一个字处理软件进程,该进程肯定需要办不止一件事情,比如监听键盘输入,处理文字,定时自动将文字保存到硬盘,这三个任务操作的都是同一块数据,因而不能用多进程。只能在一个进程里并发地开启三个线程,如果是单线程,那就只能是,键盘输入时,不能处理文字和自动保存,自动保存时又不能输入和处理文字。
|
||||
|
||||
### 内存中的线程
|
||||
|
||||

|
||||
|
||||
线程通常是有益的,但是带来了不小程序设计难度,线程的问题是:
|
||||
|
||||
1. 父进程有多个线程,那么开启的子线程是否需要同样多的线程
|
||||
|
||||
2. 在同一个进程中,如果一个线程关闭了文件,而另外一个线程正准备往该文件内写内容呢?
|
||||
|
||||
因此,在多线程的代码中,需要更多的心思来设计程序的逻辑、保护程序的数据。
|
||||
|
||||
## python使用线程
|
||||
|
||||
### 全局解释器锁GIL
|
||||
|
||||
Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。虽然 Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。
|
||||
对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。
|
||||
|
||||
在多线程环境中,Python 虚拟机按以下方式执行:
|
||||
|
||||
1. 设置 GIL;
|
||||
|
||||
2. 切换到一个线程去运行;
|
||||
|
||||
3. 运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep(0));
|
||||
|
||||
4. 把线程设置为睡眠状态;
|
||||
|
||||
5. 解锁 GIL;
|
||||
|
||||
6. 再次重复以上所有步骤。
|
||||
在调用外部代码(如 C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于在这期间没有Python的字节码被运行,所以不会做线程切换)编写扩展的程序员可以主动解锁GIL。
|
||||
|
||||
### python线程模块的选择
|
||||
|
||||
Python提供了几个用于多线程编程的模块,包括thread、threading和Queue等。thread和threading模块允许程序员创建和管理线程。thread模块提供了基本的线程和锁的支持,threading提供了更高级别、功能更强的线程管理的功能。Queue模块允许用户创建一个可以用于多个线程之间共享数据的队列数据结构。
|
||||
|
||||
避免使用thread模块,因为更高级别的threading模块更为先进,对线程的支持更为完善,而且使用thread模块里的属性有可能会与threading出现冲突;其次低级别的thread模块的同步原语很少(实际上只有一个),而threading模块则有很多;再者,thread模块中当主线程结束时,所有的线程都会被强制结束掉,没有警告也不会有正常的清除工作,至少threading模块能确保重要的子线程退出后进程才退出。
|
||||
|
||||
thread模块不支持守护线程,当主线程退出时,所有的子线程不论它们是否还在工作,都会被强行退出。而threading模块支持守护线程,守护线程一般是一个等待客户请求的服务器,如果没有客户提出请求它就在那等着,如果设定一个线程为守护线程,就表示这个线程是不重要的,在进程退出的时候,不用等待这个线程退出。
|
||||
|
||||
## threading模块
|
||||
|
||||
### 线程的创建
|
||||
|
||||
```python
|
||||
from threading import Thread
|
||||
import time
|
||||
def sayhi(name):
|
||||
time.sleep(2)
|
||||
print('%s say hello' %name)
|
||||
|
||||
if __name__ == '__main__':
|
||||
t=Thread(target=sayhi,args=('aaron',))
|
||||
t.start()
|
||||
print('主线程')
|
||||
```
|
||||
|
||||
另一种创建进程的方式
|
||||
|
||||
```python
|
||||
from threading import Thread
|
||||
import time
|
||||
class Sayhi(Thread):
|
||||
def __init__(self,name):
|
||||
super().__init__()
|
||||
self.name=name
|
||||
def run(self):
|
||||
time.sleep(2)
|
||||
print('%s say hello' % self.name)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
t = Sayhi('aaron')
|
||||
t.start()
|
||||
print('主线程')
|
||||
```
|
||||
|
||||
### 多线程与多进程
|
||||
|
||||
```python
|
||||
from threading import Thread
|
||||
from multiprocessing import Process
|
||||
import os
|
||||
|
||||
def work():
|
||||
print('hello',os.getpid())
|
||||
|
||||
if __name__ == '__main__':
|
||||
#part1:在主进程下开启多个线程,每个线程都跟主进程的pid一样
|
||||
t1=Thread(target=work)
|
||||
t2=Thread(target=work)
|
||||
t1.start()
|
||||
t2.start()
|
||||
print('主线程/主进程pid',os.getpid())
|
||||
|
||||
#part2:开多个进程,每个进程都有不同的pid
|
||||
p1=Process(target=work)
|
||||
p2=Process(target=work)
|
||||
p1.start()
|
||||
p2.start()
|
||||
print('主线程/主进程pid',os.getpid())
|
||||
```
|
||||
|
||||
效率对比
|
||||
|
||||
```python
|
||||
from threading import Thread
|
||||
from multiprocessing import Process
|
||||
import os
|
||||
|
||||
def work():
|
||||
print('hello')
|
||||
|
||||
if __name__ == '__main__':
|
||||
#在主进程下开启线程
|
||||
t=Thread(target=work)
|
||||
t.start()
|
||||
print('主线程/主进程')
|
||||
'''
|
||||
打印结果:
|
||||
hello
|
||||
主线程/主进程
|
||||
'''
|
||||
|
||||
#在主进程下开启子进程
|
||||
t=Process(target=work)
|
||||
t.start()
|
||||
print('主线程/主进程')
|
||||
```
|
||||
|
||||
内存数据共享
|
||||
|
||||
```python
|
||||
from threading import Thread
|
||||
from multiprocessing import Process
|
||||
from threading import Thread
|
||||
import os
|
||||
def work():
|
||||
global n
|
||||
n=0
|
||||
|
||||
if __name__ == '__main__':
|
||||
# n=100
|
||||
# p=Process(target=work)
|
||||
# p.start()
|
||||
# p.join()
|
||||
# print('主',n) #毫无疑问子进程p已经将自己的全局的n改成了0,但改的仅仅是它自己的,查看父进程的n仍然为100
|
||||
|
||||
|
||||
n=1
|
||||
t=Thread(target=work)
|
||||
t.start()
|
||||
t.join()
|
||||
print('主',n) #查看结果为0,因为同一进程内的线程之间共享进程内的数据
|
||||
```
|
||||
|
||||
### 多线程实现socket
|
||||
|
||||
服务端
|
||||
|
||||
```python
|
||||
import multiprocessing
|
||||
import threading
|
||||
|
||||
import socket
|
||||
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
|
||||
s.bind(('127.0.0.1',8080))
|
||||
s.listen(5)
|
||||
|
||||
def action(conn):
|
||||
while True:
|
||||
data=conn.recv(1024)
|
||||
print(data)
|
||||
conn.send(data.upper())
|
||||
|
||||
if __name__ == '__main__':
|
||||
while True:
|
||||
conn,addr=s.accept()
|
||||
|
||||
p=threading.Thread(target=action,args=(conn,))
|
||||
p.start()
|
||||
```
|
||||
|
||||
客户端
|
||||
|
||||
```python
|
||||
import socket
|
||||
|
||||
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
|
||||
s.connect(('127.0.0.1',8080))
|
||||
|
||||
while True:
|
||||
msg=input('>>: ').strip()
|
||||
if not msg:continue
|
||||
|
||||
s.send(msg.encode('utf-8'))
|
||||
data=s.recv(1024)
|
||||
print(data)
|
||||
```
|
||||
|
||||
### Thread类的其他方法
|
||||
|
||||
- Thread实例对象的方法
|
||||
- isAlive(): 返回线程是否活动的。
|
||||
- getName(): 返回线程名。
|
||||
- setName(): 设置线程名。
|
||||
|
||||
- threading模块提供的一些方法:
|
||||
- threading.currentThread(): 返回当前的线程变量。
|
||||
- threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
|
||||
- threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
|
||||
|
||||
```python
|
||||
from threading import Thread
|
||||
import threading
|
||||
from multiprocessing import Process
|
||||
import os
|
||||
|
||||
def work():
|
||||
import time
|
||||
time.sleep(3)
|
||||
print(threading.current_thread().getName())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
#在主进程下开启线程
|
||||
t=Thread(target=work)
|
||||
t.start()
|
||||
|
||||
print(threading.current_thread().getName())
|
||||
print(threading.current_thread()) #主线程
|
||||
print(threading.enumerate()) #连同主线程在内有两个运行的线程
|
||||
print(threading.active_count())
|
||||
print('主线程/主进程')
|
||||
```
|
||||
|
||||
使用join
|
||||
|
||||
```python
|
||||
from threading import Thread
|
||||
import time
|
||||
def sayhi(name):
|
||||
time.sleep(2)
|
||||
print('%s say hello' %name)
|
||||
|
||||
if __name__ == '__main__':
|
||||
t=Thread(target=sayhi,args=('aaron',))
|
||||
t.start()
|
||||
t.join()
|
||||
print('主线程')
|
||||
print(t.is_alive())
|
||||
```
|
||||
|
||||
### 守护线程
|
||||
|
||||
无论是进程还是线程,都遵循:守护xx会等待主xx运行完毕后被销毁。需要强调的是:运行完毕并非终止运行
|
||||
|
||||
1. 对主进程来说,运行完毕指的是主进程代码运行完毕
|
||||
主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束,
|
||||
2. 对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕
|
||||
主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。
|
||||
|
||||
```python
|
||||
from threading import Thread
|
||||
import time
|
||||
def sayhi(name):
|
||||
time.sleep(2)
|
||||
print('%s say hello' %name)
|
||||
|
||||
if __name__ == '__main__':
|
||||
t=Thread(target=sayhi,args=('aaron',))
|
||||
t.setDaemon(True) #必须在t.start()之前设置
|
||||
t.start()
|
||||
|
||||
print('主线程')
|
||||
print(t.is_alive())
|
||||
```
|
||||
|
||||
```python
|
||||
from threading import Thread
|
||||
import time
|
||||
def foo():
|
||||
print(123)
|
||||
time.sleep(1)
|
||||
print("end123")
|
||||
|
||||
def bar():
|
||||
print(456)
|
||||
time.sleep(3)
|
||||
print("end456")
|
||||
|
||||
if __name__ == '__main__':
|
||||
t1=Thread(target=foo)
|
||||
t2=Thread(target=bar)
|
||||
|
||||
t1.daemon=True
|
||||
t1.start()
|
||||
t2.start()
|
||||
print("main")
|
||||
```
|
||||
|
||||
## 锁
|
||||
|
||||
### 同步锁
|
||||
|
||||
没有锁的情况下
|
||||
|
||||
```python
|
||||
from threading import Thread
|
||||
import os,time
|
||||
def work():
|
||||
global n
|
||||
temp=n
|
||||
time.sleep(0.1)
|
||||
n=temp-1
|
||||
if __name__ == '__main__':
|
||||
n=100
|
||||
l=[]
|
||||
for i in range(100):
|
||||
p=Thread(target=work)
|
||||
l.append(p)
|
||||
p.start()
|
||||
for p in l:
|
||||
p.join()
|
||||
|
||||
print(n)
|
||||
```
|
||||
|
||||
同步锁
|
||||
|
||||
```python
|
||||
import threading
|
||||
R=threading.Lock()
|
||||
R.acquire()
|
||||
'''
|
||||
对公共数据的操作
|
||||
'''
|
||||
R.release()
|
||||
```
|
||||
|
||||
```python
|
||||
from threading import Thread,Lock
|
||||
import os,time
|
||||
def work():
|
||||
global n
|
||||
lock.acquire()
|
||||
temp=n
|
||||
time.sleep(0.1)
|
||||
n=temp-1
|
||||
lock.release()
|
||||
if __name__ == '__main__':
|
||||
lock=Lock()
|
||||
n=100
|
||||
l=[]
|
||||
for i in range(100):
|
||||
p=Thread(target=work)
|
||||
l.append(p)
|
||||
p.start()
|
||||
for p in l:
|
||||
p.join()
|
||||
|
||||
print(n) #结果肯定为0,由原来的并发执行变成串行,牺牲了执行效率保证了数据安全
|
||||
```
|
||||
|
||||
```python
|
||||
#不加锁:并发执行,速度快,数据不安全
|
||||
from threading import current_thread,Thread,Lock
|
||||
import os,time
|
||||
def task():
|
||||
global n
|
||||
print('%s is running' %current_thread().getName())
|
||||
temp=n
|
||||
time.sleep(0.5)
|
||||
n=temp-1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
n=100
|
||||
lock=Lock()
|
||||
threads=[]
|
||||
start_time=time.time()
|
||||
for i in range(100):
|
||||
t=Thread(target=task)
|
||||
threads.append(t)
|
||||
t.start()
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
stop_time=time.time()
|
||||
print('主:%s n:%s' %(stop_time-start_time,n))
|
||||
```
|
||||
|
||||
```python
|
||||
#不加锁:未加锁部分并发执行,加锁部分串行执行,速度慢,数据安全
|
||||
from threading import current_thread,Thread,Lock
|
||||
import os,time
|
||||
def task():
|
||||
#未加锁的代码并发运行
|
||||
time.sleep(3)
|
||||
print('%s start to run' %current_thread().getName())
|
||||
global n
|
||||
#加锁的代码串行运行
|
||||
lock.acquire()
|
||||
temp=n
|
||||
time.sleep(0.5)
|
||||
n=temp-1
|
||||
lock.release()
|
||||
|
||||
if __name__ == '__main__':
|
||||
n=100
|
||||
lock=Lock()
|
||||
threads=[]
|
||||
start_time=time.time()
|
||||
for i in range(100):
|
||||
t=Thread(target=task)
|
||||
threads.append(t)
|
||||
t.start()
|
||||
for t in threads:
|
||||
t.join()
|
||||
stop_time=time.time()
|
||||
print('主:%s n:%s' %(stop_time-start_time,n))
|
||||
```
|
||||
|
||||
有的同学可能有疑问:既然加锁会让运行变成串行,那么我在start之后立即使用join,就不用加锁了啊,也是串行的效果啊
|
||||
没错:在start之后立刻使用jion,肯定会将100个任务的执行变成串行,毫无疑问,最终n的结果也肯定是0,是安全的,但问题是
|
||||
start后立即join:任务内的所有代码都是串行执行的,而加锁,只是加锁的部分即修改共享数据的部分是串行的
|
||||
单从保证数据安全方面,二者都可以实现,但很明显是加锁的效率更高.
|
||||
|
||||
```python
|
||||
from threading import current_thread,Thread,Lock
|
||||
import os,time
|
||||
def task():
|
||||
time.sleep(3)
|
||||
print('%s start to run' %current_thread().getName())
|
||||
global n
|
||||
temp=n
|
||||
time.sleep(0.5)
|
||||
n=temp-1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
n=100
|
||||
lock=Lock()
|
||||
start_time=time.time()
|
||||
for i in range(100):
|
||||
t=Thread(target=task)
|
||||
t.start()
|
||||
t.join()
|
||||
stop_time=time.time()
|
||||
print('主:%s n:%s' %(stop_time-start_time,n))
|
||||
```
|
||||
|
||||
### 死锁与递归锁
|
||||
|
||||
两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为**死锁进程**
|
||||
|
||||
```python
|
||||
from threading import Lock as Lock
|
||||
import time
|
||||
mutexA=Lock()
|
||||
mutexA.acquire()
|
||||
mutexA.acquire() # 上面已经拿过一次key了,这边就拿不到了
|
||||
print(123)
|
||||
mutexA.release()
|
||||
mutexA.release()
|
||||
```
|
||||
|
||||
解决方法,递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。
|
||||
|
||||
这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁
|
||||
|
||||
```python
|
||||
from threading import RLock as Lock
|
||||
import time
|
||||
mutexA=Lock()
|
||||
mutexA.acquire()
|
||||
mutexA.acquire()
|
||||
print(123)
|
||||
mutexA.release()
|
||||
mutexA.release()
|
||||
```
|
||||
|
||||
吃面的问题
|
||||
|
||||
```python
|
||||
import time
|
||||
from threading import Thread,Lock
|
||||
noodle_lock = Lock()
|
||||
fork_lock = Lock()
|
||||
def eat1(name):
|
||||
noodle_lock.acquire()
|
||||
print('%s 抢到了面条'%name)
|
||||
fork_lock.acquire()
|
||||
print('%s 抢到了叉子'%name)
|
||||
print('%s 吃面'%name)
|
||||
fork_lock.release()
|
||||
noodle_lock.release()
|
||||
|
||||
def eat2(name):
|
||||
fork_lock.acquire()
|
||||
print('%s 抢到了叉子' % name)
|
||||
time.sleep(1)
|
||||
noodle_lock.acquire()
|
||||
print('%s 抢到了面条' % name)
|
||||
print('%s 吃面' % name)
|
||||
noodle_lock.release()
|
||||
fork_lock.release()
|
||||
|
||||
for name in ['顾客1','顾客2','顾客3']:
|
||||
t1 = Thread(target=eat1,args=(name,))
|
||||
t2 = Thread(target=eat2,args=(name,))
|
||||
t1.start()
|
||||
t2.start()
|
||||
```
|
||||
|
||||
使用递归锁解决问题
|
||||
|
||||
```python
|
||||
import time
|
||||
from threading import Thread,RLock
|
||||
|
||||
noodle_lock = fork_lock = RLock()
|
||||
|
||||
def eat1(name):
|
||||
noodle_lock.acquire()
|
||||
print('%s 抢到了面条'%name)
|
||||
fork_lock.acquire()
|
||||
print('%s 抢到了叉子'%name)
|
||||
print('%s 吃面'%name)
|
||||
fork_lock.release()
|
||||
noodle_lock.release()
|
||||
|
||||
def eat2(name):
|
||||
fork_lock.acquire()
|
||||
print('%s 抢到了叉子' % name)
|
||||
time.sleep(1)
|
||||
noodle_lock.acquire()
|
||||
print('%s 抢到了面条' % name)
|
||||
print('%s 吃面' % name)
|
||||
noodle_lock.release()
|
||||
fork_lock.release()
|
||||
|
||||
for name in ['顾客1','顾客2','顾客3']:
|
||||
t1 = Thread(target=eat1,args=(name,))
|
||||
t2 = Thread(target=eat2,args=(name,))
|
||||
t1.start()
|
||||
t2.start()
|
||||
```
|
||||
|
||||
### 线程队列
|
||||
|
||||
queue队列 :使用import queue,用法与进程Queue一样
|
||||
|
||||
先进先出
|
||||
|
||||
```python
|
||||
import queue
|
||||
|
||||
q=queue.Queue()
|
||||
q.put('first')
|
||||
q.put('second')
|
||||
q.put('third')
|
||||
|
||||
print(q.get())
|
||||
print(q.get())
|
||||
print(q.get())
|
||||
```
|
||||
|
||||
先进后出
|
||||
|
||||
```python
|
||||
import queue
|
||||
|
||||
q=queue.LifoQueue()
|
||||
q.put('first')
|
||||
q.put('second')
|
||||
q.put('third')
|
||||
|
||||
print(q.get())
|
||||
print(q.get())
|
||||
print(q.get())
|
||||
```
|
||||
|
||||
优先级队列
|
||||
|
||||
```python
|
||||
|
||||
import queue
|
||||
|
||||
q=queue.PriorityQueue()
|
||||
##put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
|
||||
q.put((20,'a'))
|
||||
q.put((10,'b'))
|
||||
q.put((30,'c'))
|
||||
|
||||
print(q.get())
|
||||
print(q.get())
|
||||
print(q.get())
|
||||
```
|
BIN
03.网络编程与并发/04.多线程/image-20210725221459812.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
03.网络编程与并发/04.多线程/image-20210725221509219.png
Normal file
After Width: | Height: | Size: 15 KiB |
425
03.网络编程与并发/05.多协程.md
Normal file
@@ -0,0 +1,425 @@
|
||||
# 多协程
|
||||
|
||||
## 协程理论
|
||||
|
||||
进程是资源分配的最小单位,线程是CPU调度的最小单位
|
||||
|
||||
无论是创建多进程还是创建多线程来解决问题,都要消耗一定的时间来创建进程、创建线程、以及管理他们之间的切换。
|
||||
|
||||
随着我们对于效率的追求不断提高,基于单线程来实现并发又成为一个新的课题,即只用一个主线程(很明显可利用的cpu只有一个)情况下实现并发。这样就可以节省创建线进程所消耗的时间。
|
||||
|
||||
cpu正在运行一个任务,会在两种情况下切走去执行其他的任务(切换由操作系统强制控制),一种情况是该任务发生了阻塞,另外一种情况是该任务计算的时间过长
|
||||
|
||||

|
||||
|
||||
其中第二种情况并不能提升效率,只是为了让cpu能够雨露均沾,实现看起来所有任务都被“同时”执行的效果,如果多个任务都是纯计算的,这种切换反而会降低效率。
|
||||
|
||||
为此我们可以基于yield来验证。yield本身就是一种在单线程下可以保存任务运行状态的方法
|
||||
|
||||
1. yield可以保存状态,yield的状态保存与操作系统的保存线程状态很像,但是yield是代码级别控制的,更轻量级
|
||||
2. send可以把一个函数的结果传给另外一个函数,以此实现单线程内程序之间的切换
|
||||
|
||||
```python
|
||||
#串行执行
|
||||
import time
|
||||
def consumer(res):
|
||||
'''任务1:接收数据,处理数据'''
|
||||
pass
|
||||
|
||||
def producer():
|
||||
'''任务2:生产数据'''
|
||||
res=[]
|
||||
for i in range(10000000):
|
||||
res.append(i)
|
||||
return res
|
||||
|
||||
start=time.time()
|
||||
#串行执行
|
||||
res=producer()
|
||||
consumer(res) #写成consumer(producer())会降低执行效率
|
||||
stop=time.time()
|
||||
print(stop-start) #1.5536692142486572
|
||||
|
||||
|
||||
|
||||
#基于yield并发执行
|
||||
import time
|
||||
def consumer():
|
||||
'''任务1:接收数据,处理数据'''
|
||||
while True:
|
||||
x=yield
|
||||
|
||||
def producer():
|
||||
'''任务2:生产数据'''
|
||||
g=consumer()
|
||||
next(g)
|
||||
for i in range(10000000):
|
||||
g.send(i)
|
||||
|
||||
start=time.time()
|
||||
#基于yield保存状态,实现两个任务直接来回切换,即并发的效果
|
||||
#PS:如果每个任务中都加上打印,那么明显地看到两个任务的打印是你一次我一次,即并发执行的.
|
||||
producer()
|
||||
|
||||
stop=time.time()
|
||||
print(stop-start) #2.0272178649902344
|
||||
```
|
||||
|
||||
第一种情况的切换。在任务一遇到io情况下,切到任务二去执行,这样就可以利用任务一阻塞的时间完成任务二的计算,效率的提升就在于此。
|
||||
|
||||
```python
|
||||
import time
|
||||
def consumer():
|
||||
'''任务1:接收数据,处理数据'''
|
||||
while True:
|
||||
x=yield
|
||||
|
||||
def producer():
|
||||
'''任务2:生产数据'''
|
||||
g=consumer()
|
||||
next(g)
|
||||
for i in range(10000000):
|
||||
g.send(i)
|
||||
time.sleep(2)
|
||||
|
||||
start=time.time()
|
||||
producer() #并发执行,但是任务producer遇到io就会阻塞住,并不会切到该线程内的其他任务去执行
|
||||
|
||||
stop=time.time()
|
||||
print(stop-start)
|
||||
```
|
||||
|
||||
1. 可以控制多个任务之间的切换,切换之前将任务的状态保存下来,以便重新运行时,可以基于暂停的位置继续执行。
|
||||
2. 作为1的补充:可以检测io操作,在遇到io操作的情况下才发生切换
|
||||
|
||||
## 协程
|
||||
|
||||
协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。、
|
||||
|
||||
1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
|
||||
2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)
|
||||
|
||||
对比操作系统控制线程的切换,用户在单线程内控制协程的切换
|
||||
|
||||
优点:
|
||||
|
||||
1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
|
||||
2. 单线程内就可以实现并发的效果,最大限度地利用cpu
|
||||
|
||||
缺点:
|
||||
|
||||
1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
|
||||
2. 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程
|
||||
|
||||
协程特点:
|
||||
|
||||
1. 必须在只有一个单线程里实现并发
|
||||
2. 修改共享数据不需加锁
|
||||
3. 用户程序里自己保存多个控制流的上下文栈
|
||||
4. 附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制)
|
||||
|
||||
## Greenlet模块
|
||||
|
||||
pip install greenlet
|
||||
|
||||
```python
|
||||
from greenlet import greenlet
|
||||
|
||||
def eat(name):
|
||||
print('%s eat 1' %name)
|
||||
g2.switch('aaron')
|
||||
print('%s eat 2' %name)
|
||||
g2.switch()
|
||||
def play(name):
|
||||
print('%s play 1' %name)
|
||||
g1.switch()
|
||||
print('%s play 2' %name)
|
||||
|
||||
g1=greenlet(eat)
|
||||
g2=greenlet(play)
|
||||
|
||||
g1.switch('aaron') # 可以在第一次switch时传入参数,以后都不需要
|
||||
```
|
||||
|
||||
单纯的切换(在没有io的情况下或者没有重复开辟内存空间的操作),反而会降低程序的执行速度
|
||||
|
||||
```python
|
||||
#顺序执行
|
||||
import time
|
||||
def f1():
|
||||
res=1
|
||||
for i in range(100000000):
|
||||
res+=i
|
||||
|
||||
def f2():
|
||||
res=1
|
||||
for i in range(100000000):
|
||||
res*=i
|
||||
|
||||
start=time.time()
|
||||
f1()
|
||||
f2()
|
||||
stop=time.time()
|
||||
print('run time is %s' %(stop-start))
|
||||
|
||||
#切换
|
||||
from greenlet import greenlet
|
||||
import time
|
||||
def f1():
|
||||
res=1
|
||||
for i in range(100000000):
|
||||
res+=i
|
||||
g2.switch()
|
||||
|
||||
def f2():
|
||||
res=1
|
||||
for i in range(100000000):
|
||||
res*=i
|
||||
g1.switch()
|
||||
|
||||
start=time.time()
|
||||
g1=greenlet(f1)
|
||||
g2=greenlet(f2)
|
||||
g1.switch()
|
||||
stop=time.time()
|
||||
print('run time is %s' %(stop-start))
|
||||
```
|
||||
|
||||
greenlet只是提供了一种比generator更加便捷的切换方式,当切到一个任务执行时如果遇到io,那就原地阻塞,仍然是没有解决遇到IO自动切换来提升效率的问题。
|
||||
|
||||
单线程里的这20个任务的代码通常会既有计算操作又有阻塞操作,我们完全可以在执行任务1时遇到阻塞,就利用阻塞的时间去执行任务2。。。。如此,才能提高效率,这就用到了Gevent模块。
|
||||
|
||||
## Gevent模块
|
||||
|
||||
pip install gevent
|
||||
|
||||
Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。
|
||||
|
||||
用法介绍
|
||||
|
||||
```python
|
||||
g1=gevent.spawn(func,1,2,3,x=4,y=5) # 创建一个协程对象g1,spawn括号内第一个参数是函数名,如eat,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数eat的
|
||||
|
||||
g2=gevent.spawn(func2)
|
||||
|
||||
g1.join() #等待g1结束
|
||||
|
||||
g2.join() #等待g2结束
|
||||
|
||||
#或者上述两步合作一步:gevent.joinall([g1,g2])
|
||||
|
||||
g1.value#拿到func1的返回值
|
||||
```
|
||||
|
||||
```python
|
||||
import gevent
|
||||
def eat(name):
|
||||
print('%s eat 1' %name)
|
||||
gevent.sleep(2)
|
||||
print('%s eat 2' %name)
|
||||
|
||||
def play(name):
|
||||
print('%s play 1' %name)
|
||||
gevent.sleep(1)
|
||||
print('%s play 2' %name)
|
||||
|
||||
|
||||
g1=gevent.spawn(eat,'aaron')
|
||||
g2=gevent.spawn(play,name='aaron')
|
||||
g1.join()
|
||||
g2.join()
|
||||
#或者gevent.joinall([g1,g2])
|
||||
print('主')
|
||||
```
|
||||
|
||||
上例gevent.sleep(2)模拟的是gevent可以识别的io阻塞,而time.sleep(2)或其他的阻塞,gevent是不能直接识别的需要用下面一行代码,打补丁,就可以识别了
|
||||
|
||||
from gevent import monkey;monkey.patch_all()必须放到被打补丁者的前面,如time,socket模块之前
|
||||
|
||||
或者我们干脆记忆成:要用gevent,需要将from gevent import monkey;monkey.patch_all()放到文件的开头
|
||||
|
||||
```python
|
||||
from gevent import monkey;monkey.patch_all()
|
||||
|
||||
import gevent
|
||||
import time
|
||||
def eat():
|
||||
print('eat food 1')
|
||||
time.sleep(2)
|
||||
print('eat food 2')
|
||||
|
||||
def play():
|
||||
print('play 1')
|
||||
time.sleep(1)
|
||||
print('play 2')
|
||||
|
||||
g1=gevent.spawn(eat)
|
||||
g2=gevent.spawn(play)
|
||||
gevent.joinall([g1,g2])
|
||||
print('主')
|
||||
```
|
||||
|
||||
用threading.current_thread().getName()来查看每个g1和g2,查看的结果为DummyThread-n,即假线程
|
||||
|
||||
```python
|
||||
from gevent import monkey;monkey.patch_all()
|
||||
import threading
|
||||
import gevent
|
||||
import time
|
||||
def eat():
|
||||
print(threading.current_thread().getName())
|
||||
print('eat food 1')
|
||||
time.sleep(2)
|
||||
print('eat food 2')
|
||||
|
||||
def play():
|
||||
print(threading.current_thread().getName())
|
||||
print('play 1')
|
||||
time.sleep(1)
|
||||
print('play 2')
|
||||
|
||||
g1=gevent.spawn(eat)
|
||||
g2=gevent.spawn(play)
|
||||
gevent.joinall([g1,g2])
|
||||
print('主')
|
||||
```
|
||||
|
||||
## Gevent之同步与异步
|
||||
|
||||
```python
|
||||
from gevent import spawn,joinall,monkey;monkey.patch_all()
|
||||
|
||||
import time
|
||||
def task(pid):
|
||||
"""
|
||||
Some non-deterministic task
|
||||
"""
|
||||
time.sleep(0.5)
|
||||
print('Task %s done' % pid)
|
||||
|
||||
|
||||
def synchronous(): # 同步
|
||||
for i in range(10):
|
||||
task(i)
|
||||
|
||||
def asynchronous(): # 异步
|
||||
g_l=[spawn(task,i) for i in range(10)]
|
||||
joinall(g_l)
|
||||
print('DONE')
|
||||
|
||||
if __name__ == '__main__':
|
||||
print('Synchronous:')
|
||||
synchronous()
|
||||
print('Asynchronous:')
|
||||
asynchronous()
|
||||
# 上面程序的重要部分是将task函数封装到Greenlet内部线程的gevent.spawn。
|
||||
# 初始化的greenlet列表存放在数组threads中,此数组被传给gevent.joinall 函数,
|
||||
# 后者阻塞当前流程,并执行所有给定的greenlet任务。执行流程只会在 所有greenlet执行完后才会继续向下走。
|
||||
```
|
||||
|
||||
## Gevent之应用举例一
|
||||
|
||||
```python
|
||||
from gevent import monkey;monkey.patch_all()
|
||||
import gevent
|
||||
import requests
|
||||
import time
|
||||
|
||||
def get_page(url):
|
||||
print('GET: %s' %url)
|
||||
response=requests.get(url)
|
||||
if response.status_code == 200:
|
||||
print('%d bytes received from %s' %(len(response.text),url))
|
||||
|
||||
|
||||
start_time=time.time()
|
||||
gevent.joinall([
|
||||
gevent.spawn(get_page,'https://www.python.org/'),
|
||||
gevent.spawn(get_page,'https://www.yahoo.com/'),
|
||||
gevent.spawn(get_page,'https://github.com/'),
|
||||
])
|
||||
stop_time=time.time()
|
||||
print('run time is %s' %(stop_time-start_time))
|
||||
```
|
||||
|
||||
## Gevent之应用举例二
|
||||
|
||||
通过gevent实现单线程下的socket并发
|
||||
|
||||
注意 :from gevent import monkey;monkey.patch_all()一定要放到导入socket模块之前,否则gevent无法识别socket的阻塞
|
||||
|
||||
服务端
|
||||
|
||||
```python
|
||||
from gevent import monkey;monkey.patch_all()
|
||||
from socket import *
|
||||
import gevent
|
||||
|
||||
#如果不想用money.patch_all()打补丁,可以用gevent自带的socket
|
||||
# from gevent import socket
|
||||
# s=socket.socket()
|
||||
|
||||
def server(server_ip,port):
|
||||
s=socket(AF_INET,SOCK_STREAM)
|
||||
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
|
||||
s.bind((server_ip,port))
|
||||
s.listen(5)
|
||||
while True:
|
||||
conn,addr=s.accept()
|
||||
gevent.spawn(talk,conn,addr)
|
||||
|
||||
def talk(conn,addr):
|
||||
try:
|
||||
while True:
|
||||
res=conn.recv(1024)
|
||||
print('client %s:%s msg: %s' %(addr[0],addr[1],res))
|
||||
conn.send(res.upper())
|
||||
except Exception as e:
|
||||
print(e)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
server('127.0.0.1',8088)
|
||||
```
|
||||
|
||||
客户端
|
||||
|
||||
```python
|
||||
from socket import *
|
||||
|
||||
client=socket(AF_INET,SOCK_STREAM)
|
||||
client.connect(('127.0.0.1',8080))
|
||||
|
||||
|
||||
while True:
|
||||
msg=input('>>: ').strip()
|
||||
if not msg:continue
|
||||
|
||||
client.send(msg.encode('utf-8'))
|
||||
msg=client.recv(1024)
|
||||
print(msg.decode('utf-8'))
|
||||
```
|
||||
|
||||
多线程并发客户端
|
||||
|
||||
```python
|
||||
from threading import Thread
|
||||
from socket import *
|
||||
import threading
|
||||
|
||||
def client(server_ip,port):
|
||||
c=socket(AF_INET,SOCK_STREAM) #套接字对象一定要加到函数内,即局部名称空间内,放在函数外则被所有线程共享,则大家公用一个套接字对象,那么客户端端口永远一样了
|
||||
c.connect((server_ip,port))
|
||||
|
||||
count=0
|
||||
while True:
|
||||
c.send(('%s say hello %s' %(threading.current_thread().getName(),count)).encode('utf-8'))
|
||||
msg=c.recv(1024)
|
||||
print(msg.decode('utf-8'))
|
||||
count+=1
|
||||
if __name__ == '__main__':
|
||||
for i in range(500):
|
||||
t=Thread(target=client,args=('127.0.0.1',8088))
|
||||
t.start()
|
||||
```
|
BIN
03.网络编程与并发/05.多协程/image-20210725221729223.png
Normal file
After Width: | Height: | Size: 23 KiB |
252
04.Flask/01.URL与视图.md
Normal file
@@ -0,0 +1,252 @@
|
||||
# URL与视图
|
||||
|
||||
## 1. Flask简介
|
||||
|
||||
`flask`是一款非常流行的`Python Web`框架,出生于2010年,作者是`Armin Ronacher`,本来这个项目只是作者在愚人节的一个玩笑,后来由于非常受欢迎,进而成为一个正式的项目。
|
||||
|
||||
`flask`自2010年发布第一个版本以来,大受欢迎,深得开发者的喜爱,目前在`Github`上的Star数已经超过`55.5k`了,有超`Django`之趋势。`flask`能如此流行的原因,可以分为以下几点:
|
||||
|
||||
- 微框架、简洁、只做他需要做的,给开发者提供了很大的扩展性。
|
||||
- Flask和相应的插件写得很好,用起来很爽。
|
||||
- 开发效率非常高,比如使用`SQLAlchemy`的`ORM`操作数据库可以节省开发者大量书写`sql`的时间。
|
||||
|
||||
`Flask`的灵活度非常之高,他不会帮你做太多的决策,一些你都可以按照自己的意愿进行更改。比如:
|
||||
|
||||
- 使用`Flask`开发数据库的时候,具体是使用`SQLAlchemy`还是`MongoEngine`,选择权完全掌握在你自己的手中。区别于`Django`,`Django`内置了非常完善和丰富的功能,并且如果你想替换成你自己想要的,要么不支持,要么非常麻烦。
|
||||
- 把默认的`Jinija2`模板引擎替换成其他模板引擎都是非常容易的。
|
||||
|
||||
### 1.1 安装Flask
|
||||
|
||||
通过`pip install flask`即可安装。
|
||||
|
||||
### 1.2 第一个flask程序
|
||||
|
||||
用`pycharm`新建一个`flask`项目,新建项目的截图如下:
|
||||
|
||||

|
||||
|
||||
点击`create`后创建一个新项目,然后在`helloworld.py`文件中书写代码:
|
||||
|
||||
```python
|
||||
#coding: utf8
|
||||
|
||||
# 从flask框架中导入Flask类
|
||||
from flask import Flask
|
||||
|
||||
# 传入__name__初始化一个Flask实例
|
||||
app = Flask(__name__)
|
||||
|
||||
# app.route装饰器映射URL和执行的函数。这个设置将根URL映射到了hello_world函数上
|
||||
@app.route('/')
|
||||
def hello_world():
|
||||
return 'Hello World!'
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 运行本项目,host=0.0.0.0可以让其他电脑也能访问到该网站,port指定访问的端口。默认的host是127.0.0.1,port为5000
|
||||
app.run(host='0.0.0.0',port=9000)
|
||||
```
|
||||
|
||||
然后点击运行,在浏览器中输入`http://127.0.0.1:9000`就能看到`hello world`了。需要说明一点的是,`app.run`这种方式只适合于开发,如果在生产环境中,应该使用`Gunicorn`或者`uWSGI`来启动。如果是在终端运行的,可以按`ctrl+c`来让服务停止。
|
||||
|
||||
## 2. 项目配置
|
||||
|
||||
### 2.1 设置为DEBUG模式
|
||||
|
||||
默认情况下`flask`不会开启`DEBUG`模式,开启`DEBUG`模式后,flask会在每次保存代码的时候自动的重新载入代码,并且如果代码有错误,会在终端进行提示。
|
||||
|
||||

|
||||
|
||||
如果一切正常,会在终端打印以下信息:
|
||||
|
||||
```
|
||||
* Restarting with stat
|
||||
* Debugger is active!
|
||||
* Debugger pin code: 294-745-044
|
||||
* Running on http://0.0.0.0:9000/ (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
需要注意的是,只能在开发环境下开启`DEBUG`模式,因为`DEBUG`模式会带来非常大的安全隐患。
|
||||
|
||||
### 2.2 配置文件
|
||||
|
||||
`Flask`项目的配置,都是通过`app.config`对象来进行配置的。比如要配置一个项目的`SECRET_KEY`,那么可以使用`app.config['SECRET_KEY'] = "xxx"`来进行设置,在`Flask`项目中,有四种方式进行项目的配置:
|
||||
|
||||
1. 直接硬编码:
|
||||
|
||||
```python
|
||||
app = Flask(__name__)
|
||||
app.config['SECRET_KEY'] = "xxx"
|
||||
```
|
||||
|
||||
2. 因为`app.config`是`flask.config.Config`的实例,而`Config`类是继承自`dict`,因此可以通过`update`方法:
|
||||
|
||||
```python
|
||||
app.config.update(
|
||||
DEBUG=True,
|
||||
SECRET_KEY='...'
|
||||
)
|
||||
```
|
||||
|
||||
3. 如果你的配置项特别多,你可以把所有的配置项都放在一个模块中,然后通过加载模块的方式进行配置,假设有一个`settings.py`模块,专门用来存储配置项的,此时你可以通过`app.config.from_object()`方法进行加载,并且该方法既可以接收模块的的字符串名称,也可以模块对象:
|
||||
|
||||
```python
|
||||
# 1. 通过模块字符串
|
||||
app.config.from_object('settings')
|
||||
# 2. 通过模块对象
|
||||
import settings
|
||||
app.config.from_object(settings)
|
||||
```
|
||||
|
||||
4. 也可以通过另外一个方法加载,该方法就是`app.config.from_pyfile()`,该方法传入一个文件名,通常是以`.py`结尾的文件,但也不限于只使用`.py`后缀的文件:
|
||||
|
||||
```python
|
||||
app.config.from_pyfile('settings.py',silent=True)
|
||||
# silent=True表示如果配置文件不存在的时候不抛出异常,默认是为False,会抛出异常。
|
||||
```
|
||||
|
||||
## 3. URL与视图
|
||||
|
||||
### 3.1 URL与函数的映射
|
||||
|
||||
从之前的`helloworld.py`文件中,我们已经看到,一个`URL`要与执行函数进行映射,使用的是`@app.route`装饰器。`@app.route`装饰器中,可以指定`URL`的规则来进行更加详细的映射,比如现在要映射一个文章详情的`URL`,文章详情的`URL`是`/article/id/`,id有可能为1、2、3…,那么可以通过以下方式:
|
||||
|
||||
```python
|
||||
@app.route('/article/<id>/')
|
||||
def article(id):
|
||||
return '%s article detail' % id
|
||||
```
|
||||
|
||||
其中`<id>`,尖括号是固定写法,语法为`<variable>`,`variable`默认的数据类型是字符串。如果需要指定类型,则要写成`<converter:variable>`,其中`converter`就是类型名称,可以有以下几种:
|
||||
|
||||
- string: 默认的数据类型,接受没有任何斜杠`/`的字符串。
|
||||
- int: 整形
|
||||
- float: 浮点型。
|
||||
- path: 和`string`类似,但是可以传递斜杠`/`。
|
||||
- uuid: `uuid`类型的字符串。
|
||||
- any:可以指定多种路径,这个通过一个例子来进行说明:
|
||||
|
||||
```python
|
||||
@app.route('/<any(article,blog):url_path>/')
|
||||
def item(url_path):
|
||||
return url_path
|
||||
```
|
||||
|
||||
以上例子中,`item`这个函数可以接受两个`URL`,一个是`/article/`,另一个是`/blog/`。并且,一定要传`url_path`参数,当然这个`url_path`的名称可以随便。
|
||||
|
||||
如果不想定制子路径来传递参数,也可以通过传统的`?=`的形式来传递参数,例如:`/article?id=xxx`,这种情况下,可以通过`request.args.get('id')`来获取`id`的值。如果是`post`方法,则可以通过`request.form.get('id')`来进行获取。
|
||||
|
||||
### 3.2 构造URL(url_for)
|
||||
|
||||
一般我们通过一个`URL`就可以执行到某一个函数。如果反过来,我们知道一个函数,怎么去获得这个`URL`呢?`url_for`函数就可以帮我们实现这个功能。`url_for()`函数接收两个及以上的参数,他接收**函数名**作为第一个参数,接收对应**URL规则的命名参数**,如果还出现其他的参数,则会添加到`URL`的后面作为**查询参数**。
|
||||
|
||||
通过构建`URL`的方式而选择直接在代码中拼`URL`的原因有两点:
|
||||
|
||||
1. 将来如果修改了`URL`,但没有修改该`URL`对应的函数名,就不用到处去替换`URL`了。
|
||||
2. `url_for()`函数会转义一些特殊字符和`unicode`字符串,这些事情`url_for`会自动的帮我们搞定。
|
||||
|
||||
下面用一个例子来进行解释:
|
||||
|
||||
```python
|
||||
from flask import Flask,url_for
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/article/<id>/')
|
||||
def article(id):
|
||||
return '%s article detail' % id
|
||||
|
||||
@app.route('/')
|
||||
def index(request):
|
||||
print(url_for("article",id=1))
|
||||
return "首页"
|
||||
```
|
||||
|
||||
在访问index的时候,会打印出`/article/1/`。
|
||||
|
||||
### 3.3 指定URL末尾的斜杠
|
||||
|
||||
有些`URL`的末尾是有斜杠的,有些`URL`末尾是没有斜杠的。这其实是两个不同的`URL`。
|
||||
|
||||
举个例子:
|
||||
|
||||
```python
|
||||
@app.route('/article/')
|
||||
def articles():
|
||||
return '文章列表页'
|
||||
```
|
||||
|
||||
上述例子中,当访问一个结尾不带斜线的`URL`:`/article`,会被重定向到带斜线的`URL`:`/article/`上去。但是当我们在定义`article`的`url`的时候,如果在末尾没有加上斜杠,但是在访问的时候又加上了斜杠,这时候就会抛出一个`404`错误页面了:
|
||||
|
||||
```python
|
||||
@app.route("/article")
|
||||
def articles(request):
|
||||
return "文章列表页面"
|
||||
```
|
||||
|
||||
以上没有在末尾加斜杠,因此在访问`/article/`的时候,就会抛出一个`404`错误。
|
||||
|
||||
### 3.4 指定HTTP方法
|
||||
|
||||
在`@app.route()`中可以传入一个关键字参数`methods`来指定本方法支持的`HTTP`方法,默认情况下,只能使用`GET`请求,看以下例子:
|
||||
|
||||
```python
|
||||
@app.route('/login/',methods=['GET','POST'])
|
||||
def login():
|
||||
return 'login'
|
||||
```
|
||||
|
||||
以上装饰器将让`login`的`URL`既能支持`GET`又能支持`POST`。
|
||||
|
||||
### 3.5 页面跳转和重定向
|
||||
|
||||
重定向分为永久性重定向和暂时性重定向,在页面上体现的操作就是浏览器会从一个页面自动跳转到另外一个页面。比如用户访问了一个需要权限的页面,但是该用户当前并没有登录,因此我们应该给他重定向到登录页面。
|
||||
|
||||
- 永久性重定向:`http`的状态码是`301`,多用于旧网址被废弃了要转到一个新的网址确保用户的访问,最经典的就是京东网站,你输入`www.jingdong.com`的时候,会被重定向到`www.jd.com`,因为`jingdong.com`这个网址已经被废弃了,被改成`jd.com`,所以这种情况下应该用永久重定向。
|
||||
- 暂时性重定向:`http`的状态码是`302`,表示页面的暂时性跳转。比如访问一个需要权限的网址,如果当前用户没有登录,应该重定向到登录页面,这种情况下,应该用暂时性重定向。
|
||||
|
||||
在`flask`中,重定向是通过`flask.redirect(location,code=302)`这个函数来实现的,`location`表示需要重定向到的`URL`,应该配合之前讲的`url_for()`函数来使用,`code`表示采用哪个重定向,默认是`302`也即`暂时性重定向`,可以修改成`301`来实现永久性重定向。
|
||||
|
||||
以下来看一个例子,关于在`flask`中怎么使用重定向:
|
||||
|
||||
```python
|
||||
from flask import Flask,url_for,redirect
|
||||
|
||||
app = Flask(__name__)
|
||||
app.debug = True
|
||||
|
||||
@app.route('/login/',methods=['GET','POST'])
|
||||
def login():
|
||||
return 'login page'
|
||||
|
||||
@app.route('/profile/',methods=['GET','POST'])
|
||||
def profile():
|
||||
name = request.args.get('name')
|
||||
|
||||
if not name:
|
||||
# 如果没有name,说明没有登录,重定向到登录页面
|
||||
return redirect(url_for('login'))
|
||||
else:
|
||||
return name
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
BIN
04.Flask/01.URL与视图/2UZz2JSDySff5o8CPeqPRN.png
Normal file
After Width: | Height: | Size: 142 KiB |
BIN
04.Flask/01.URL与视图/87L8AaNBNKGcAAn7oomKYT.png
Normal file
After Width: | Height: | Size: 177 KiB |
493
04.Flask/02.Jinja2模板.md
Normal file
@@ -0,0 +1,493 @@
|
||||
# Jinja2模板
|
||||
|
||||
## 1. 模板简介
|
||||
|
||||
模板是一个`web`开发必备的模块。因为我们在渲染一个网页的时候,并不是只渲染一个纯文本字符串,而是需要渲染一个有富文本标签的页面。这时候我们就需要使用模板了。在`Flask`中,配套的模板是`Jinja2`,`Jinja2`的作者也是`Flask`的作者。这个模板非常的强大,并且执行效率高。以下对`Jinja2`做一个简单介绍!
|
||||
|
||||
### 1.1 Flask渲染`Jinja`模板:
|
||||
|
||||
要渲染一个模板,通过`render_template`方法即可,以下将用一个简单的例子进行讲解:
|
||||
|
||||
```python
|
||||
from flask import Flask,render_template
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/about/')
|
||||
def about():
|
||||
return render_template('about.html')
|
||||
```
|
||||
|
||||
当访问`/about/`的时候,`about()`函数会在当前目录下的`templates`文件夹下寻找`about.html`模板文件。如果想更改模板文件地址,应该在创建`app`的时候,给`Flask`传递一个关键字参数`template_folder`,指定具体的路径,再看以下例子:
|
||||
|
||||
```python
|
||||
from flask import Flask,render_template
|
||||
app = Flask(__name__,template_folder=r'C:\templates')
|
||||
|
||||
@app.route('/about/')
|
||||
def about():
|
||||
return render_template('about.html')
|
||||
```
|
||||
|
||||
以上例子将会在C盘的`templates`文件夹中寻找模板文件。还有最后一点是,如果模板文件中有参数需要传递,应该怎么传呢,我们再来看一个例子:
|
||||
|
||||
```python
|
||||
from flask import Flask,render_template
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/about/')
|
||||
def about():
|
||||
# return render_template('about.html',user='zhiliao')
|
||||
return render_template('about.html',**{'user':'zhiliao'})
|
||||
```
|
||||
|
||||
以上例子介绍了两种传递参数的方式,因为`render_template`需要传递的是一个关键字参数,所以第一种方式是顺其自然的。但是当你的模板中要传递的参数过多的时候,把所有参数放在一个函数中显然不是一个好的选择,因此我们使用字典进行包装,并且加两个`*`号,来转换成关键字参数。
|
||||
|
||||
## 2. Jinja2模版概述
|
||||
|
||||
### 2.1 概要:
|
||||
|
||||
先看一个简单例子:
|
||||
|
||||
```html
|
||||
1. <html lang="en">
|
||||
2. <head>
|
||||
3. <title>My Webpage</title>
|
||||
4. </head>
|
||||
5. <body>
|
||||
6. <ul id="navigation">
|
||||
7. {% for item in navigation %}
|
||||
8. <li><a href="{{ item.href }}">{{ item.caption }}</a></li>
|
||||
9. {% endfor %}
|
||||
10. </ul>
|
||||
11.
|
||||
12. {{ a_variable }}
|
||||
13. {{ user.name }}
|
||||
14. {{ user['name'] }}
|
||||
15.
|
||||
16. {# a comment #}
|
||||
17. </body>
|
||||
18.</html>
|
||||
```
|
||||
|
||||
以上示例有需要进行解释:
|
||||
|
||||
- 第12~14行的`{{ ... }}`:用来装载一个变量,模板渲染的时候,会把这个变量代表的值替换掉。并且可以间接访问一个变量的属性或者一个字典的`key`。关于点`.`号访问和`[]`中括号访问,没有任何区别,都可以访问属性和字典的值。
|
||||
- 第7~9行的`{% ... %}`:用来装载一个控制语句,以上装载的是`for`循环,以后只要是要用到控制语句的,就用`{% ... %}`。
|
||||
- 第14行的`{# ... #}`:用来装载一个注释,模板渲染的时候会忽视这中间的值。
|
||||
|
||||
### 2.2 属性访问规则:
|
||||
|
||||
1. 比如在模板中有一个变量这样使用:`foo.bar`,那么在`Jinja2`中是这样进行访问的:
|
||||
- 先去查找`foo`的`bar`这个属性,也即通过`getattr(foo,'bar')`。
|
||||
- 如果没有,就去通过`foo.__getitem__('bar')`的方式进行查找。
|
||||
- 如果以上两种方式都没有找到,返回一个`undefined`。
|
||||
2. 在模板中有一个变量这样使用:`foo['bar']`,那么在`Jinja2`中是这样进行访问:
|
||||
- 通过`foo.__getitem__('bar')`的方式进行查找。
|
||||
- 如果没有,就通过`getattr(foo,'bar')`的方式进行查找。
|
||||
- 如果以上没有找到,则返回一个`undefined`。
|
||||
|
||||
## 3. Jinja2模版过滤器
|
||||
|
||||
过滤器是通过管道符号(`|`)进行使用的,例如:`{{ name|length }}`,将返回name的长度。过滤器相当于是一个函数,把当前的变量传入到过滤器中,然后过滤器根据自己的功能,再返回相应的值,之后再将结果渲染到页面中。`Jinja2`中内置了许多过滤器,在[这里](https://jinja.palletsprojects.com/en/3.0.x/templates/#list-of-builtin-filters)可以看到所有的过滤器,现对一些常用的过滤器进行讲解:
|
||||
|
||||
1. `abs(value)`:返回一个数值的绝对值。 例如:`-1|abs`。
|
||||
|
||||
2. `default(value,default_value,boolean=false)`:如果当前变量没有值,则会使用参数中的值来代替。`name|default('xiaotuo')`——如果name不存在,则会使用`xiaotuo`来替代。`boolean=False`默认是在只有这个变量为`undefined`的时候才会使用`default`中的值,如果想使用`python`的形式判断是否为`false`,则可以传递`boolean=true`。也可以使用`or`来替换。
|
||||
|
||||
3. `escape(value)或e`:转义字符,会将`<`、`>`等符号转义成HTML中的符号。例如:`content|escape`或`content|e`。
|
||||
|
||||
4. `first(value)`:返回一个序列的第一个元素。`names|first`。
|
||||
|
||||
5. `format(value,*arags,**kwargs)`:格式化字符串。例如以下代码:
|
||||
|
||||
```jinja2
|
||||
{{ "%s" - "%s"|format('Hello?',"Foo!") }}
|
||||
```
|
||||
|
||||
将输出:Helloo? - Foo!
|
||||
|
||||
6. `last(value)`:返回一个序列的最后一个元素。示例:`names|last`。
|
||||
|
||||
7. `length(value)`:返回一个序列或者字典的长度。示例:`names|length`。
|
||||
|
||||
8. `join(value,d=u'')`:将一个序列用`d`这个参数的值拼接成字符串。
|
||||
|
||||
9. `safe(value)`:如果开启了全局转义,那么`safe`过滤器会将变量关掉转义。示例:`content_html|safe`。
|
||||
|
||||
10. `int(value)`:将值转换为`int`类型。
|
||||
|
||||
11. `float(value)`:将值转换为`float`类型。
|
||||
|
||||
12. `lower(value)`:将字符串转换为小写。
|
||||
|
||||
13. `upper(value)`:将字符串转换为小写。
|
||||
|
||||
14. `replace(value,old,new)`: 替换将`old`替换为`new`的字符串。
|
||||
|
||||
15. `truncate(value,length=255,killwords=False)`:截取`length`长度的字符串。
|
||||
|
||||
16. `striptags(value)`:删除字符串中所有的HTML标签,如果出现多个空格,将替换成一个空格。
|
||||
|
||||
17. `trim`:截取字符串前面和后面的空白字符。
|
||||
|
||||
18. `string(value)`:将变量转换成字符串。
|
||||
|
||||
19. `wordcount(s)`:计算一个长字符串中单词的个数。
|
||||
|
||||
## 4. 控制语句
|
||||
|
||||
所有的控制语句都是放在`{% ... %}`中,并且有一个语句`{% endxxx %}`来进行结束,`Jinja`中常用的控制语句有`if/for..in..`,现对他们进行讲解:
|
||||
|
||||
1. `if`:if语句和`python`中的类似,可以使用`>,<,<=,>=,==,!=`来进行判断,也可以通过`and,or,not,()`来进行逻辑合并操作,以下看例子:
|
||||
|
||||
```jinja2
|
||||
{% if kenny.sick %}
|
||||
Kenny is sick.
|
||||
{% elif kenny.dead %}
|
||||
You killed Kenny! You bastard!!!
|
||||
{% else %}
|
||||
Kenny looks okay --- so far
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
2. `for...in...`:`for`循环可以遍历任何一个序列包括列表、字典、元组。并且可以进行反向遍历,以下将用几个例子进行解释:
|
||||
|
||||
- 普通的遍历:
|
||||
|
||||
```jinja2
|
||||
<ul>
|
||||
{% for user in users %}
|
||||
<li>{{ user.username|e }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
```
|
||||
|
||||
- 遍历字典:
|
||||
|
||||
```jinja2
|
||||
<dl>
|
||||
{% for key, value in my_dict.iteritems() %}
|
||||
<dt>{{ key|e }}</dt>
|
||||
<dd>{{ value|e }}</dd>
|
||||
{% endfor %}
|
||||
</dl>
|
||||
```
|
||||
|
||||
- 如果序列中没有值的时候,进入`else`:
|
||||
|
||||
```jinja2
|
||||
<ul>
|
||||
{% for user in users %}
|
||||
<li>{{ user.username|e }}</li>
|
||||
{% else %}
|
||||
<li><em>no users found</em></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
```
|
||||
|
||||
并且`Jinja`中的`for`循环还包含以下变量,可以用来获取当前的遍历状态:
|
||||
|
||||
| 变量 | 描述 |
|
||||
| :---------- | :---------------------------------- |
|
||||
| loop.index | 当前迭代的索引(从1开始) |
|
||||
| loop.index0 | 当前迭代的索引(从0开始) |
|
||||
| loop.first | 是否是第一次迭代,返回True或False |
|
||||
| loop.last | 是否是最后一次迭代,返回True或False |
|
||||
| loop.length | 序列的长度 |
|
||||
|
||||
另外,**不可以**使用`continue`和`break`表达式来控制循环的执行。
|
||||
|
||||
## 5. 测试器
|
||||
|
||||
测试器主要用来判断一个值是否满足某种类型,并且这种类型一般通过普通的`if`判断是有很大的挑战的。语法是:`if...is...`,先来简单的看个例子:
|
||||
|
||||
```jinja2
|
||||
{% if variable is escaped%}
|
||||
value of variable: {{ escaped }}
|
||||
{% else %}
|
||||
variable is not escaped
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
以上判断`variable`这个变量是否已经被转义了,`Jinja`中内置了许多的测试器,看以下列表:
|
||||
|
||||
| 测试器 | 说明 |
|
||||
| :----------------- | :----------------- |
|
||||
| `callable(object)` | 是否可调用 |
|
||||
| `defined(object)` | 是否已经被定义了。 |
|
||||
| `escaped(object)` | 是否已经被转义了。 |
|
||||
| `upper(object)` | 是否全是大写。 |
|
||||
| `lower(object)` | 是否全是小写。 |
|
||||
| `string(object)` | 是否是一个字符串。 |
|
||||
| `sequence(object)` | 是否是一个序列。 |
|
||||
| `number(object)` | 是否是一个数字。 |
|
||||
| `odd(object)` | 是否是奇数。 |
|
||||
| `even(object)` | 是否是偶数。 |
|
||||
|
||||
## 6. 宏和import语句
|
||||
|
||||
### 6.1 宏:
|
||||
|
||||
模板中的宏跟python中的函数类似,可以传递参数,但是不能有返回值,可以将一些经常用到的代码片段放到宏中,然后把一些不固定的值抽取出来当成一个变量,以下将用一个例子来进行解释:
|
||||
|
||||
```jinja2
|
||||
{% macro input(name, value='', type='text') %}
|
||||
<input type="{{ type }}" name="{{ name }}" value="{{ value|e }}">
|
||||
{% endmacro %}
|
||||
```
|
||||
|
||||
以上例子可以抽取出了一个input标签,指定了一些默认参数。那么我们以后创建`input`标签的时候,可以通过他快速的创建:
|
||||
|
||||
```jinja2
|
||||
<p>{{ input('username') }}</p>
|
||||
<p>{{ input('password', type='password') }}</p>
|
||||
```
|
||||
|
||||
### 6.2 import语句:
|
||||
|
||||
在真实的开发中,会将一些常用的宏单独放在一个文件中,在需要使用的时候,再从这个文件中进行导入。`import`语句的用法跟`python`中的`import`类似,可以直接`import...as...`,也可以`from...import...`或者`from...import...as...`,假设现在有一个文件,叫做`forms.html`,里面有两个宏分别为`input`和`textarea`,如下:
|
||||
|
||||
```jinja2
|
||||
{% macro input(name, value='', type='text') %}
|
||||
<input type="{{ type }}" value="{{ value|e }}" name="{{ name }}">
|
||||
{% endmacro %}
|
||||
|
||||
{% macro textarea(name, value='', rows=10, cols=40) %}
|
||||
<textarea name="{{ name }}" rows="{{ rows }}" cols="{{ cols
|
||||
}}">{{ value|e }}</textarea>
|
||||
{% endmacro %}
|
||||
```
|
||||
|
||||
### 6.3 导入宏的例子:
|
||||
|
||||
1. `import...as...`形式:
|
||||
|
||||
```jinja2
|
||||
{% import 'forms.html' as forms %}
|
||||
<dl>
|
||||
<dt>Username</dt>
|
||||
<dd>{{ forms.input('username') }}</dd>
|
||||
<dt>Password</dt>
|
||||
<dd>{{ forms.input('password', type='password') }}</dd>
|
||||
</dl>
|
||||
<p>{{ forms.textarea('comment') }}</p>
|
||||
```
|
||||
|
||||
2. `from...import...as.../from...import...`形式:
|
||||
|
||||
```jinja2
|
||||
{% from 'forms.html' import input as input_field, textarea %}
|
||||
<dl>
|
||||
<dt>Username</dt>
|
||||
<dd>{{ input_field('username') }}</dd>
|
||||
<dt>Password</dt>
|
||||
<dd>{{ input_field('password', type='password') }}</dd>
|
||||
</dl>
|
||||
<p>{{ textarea('comment') }}</p>
|
||||
```
|
||||
|
||||
另外需要注意的是,导入模板并不会把当前上下文中的变量添加到被导入的模板中,如果你想要导入一个需要访问当前上下文变量的宏,有两种可能的方法:
|
||||
|
||||
- 显式地传入请求或请求对象的属性作为宏的参数。
|
||||
|
||||
- 与上下文一起(with context)导入宏。
|
||||
|
||||
与上下文中一起(with context)导入的方式如下:
|
||||
|
||||
```jinja2
|
||||
{% from '_helpers.html' import my_macro with context %}
|
||||
```
|
||||
|
||||
## 7. include和set语句
|
||||
|
||||
### 7.1 include语句:
|
||||
|
||||
`include`语句可以把一个模板引入到另外一个模板中,类似于把一个模板的代码copy到另外一个模板的指定位置,看以下例子:
|
||||
|
||||
```jinja2
|
||||
{% include 'header.html' %}
|
||||
主体内容
|
||||
{% include 'footer.html' %}
|
||||
```
|
||||
|
||||
### 7.2 赋值(set)语句:
|
||||
|
||||
有时候我们想在在模板中添加变量,这时候赋值语句(set)就派上用场了,先看以下例子:
|
||||
|
||||
```jinja2
|
||||
{% set name='zhiliao' %}
|
||||
```
|
||||
|
||||
那么以后就可以使用`name`来代替`zhiliao`这个值了,同时,也可以给他赋值为列表和元组:
|
||||
|
||||
```jinja2
|
||||
{% set navigation = [('index.html', 'Index'), ('about.html', 'About')] %}
|
||||
```
|
||||
|
||||
赋值语句创建的变量在其之后都是有效的,如果不想让一个变量污染全局环境,可以使用`with`语句来创建一个内部的作用域,将`set`语句放在其中,这样创建的变量只在`with`代码块中才有效,看以下示例:
|
||||
|
||||
```jinja2
|
||||
{% with %}
|
||||
{% set foo = 42 %}
|
||||
{{ foo }} foo is 42 here
|
||||
{% endwith %}
|
||||
```
|
||||
|
||||
也可以在`with`的后面直接添加变量,比如以上的写法可以修改成这样:
|
||||
|
||||
```jinja2
|
||||
{% with foo = 42 %}
|
||||
{{ foo }}
|
||||
{% endwith %}
|
||||
```
|
||||
|
||||
这两种方式都是等价的,一旦超出`with`代码块,就不能再使用`foo`这个变量了。
|
||||
|
||||
## 8. 模版继承
|
||||
|
||||
`Flask`中的模板可以继承,通过继承可以把模板中许多重复出现的元素抽取出来,放在父模板中,并且父模板通过定义`block`给子模板开一个口,子模板根据需要,再实现这个`block`,假设现在有一个`base.html`这个父模板,代码如下:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<link rel="stylesheet" href="base.css" />
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<div id="body">{% block body %}{% endblock %}</div>
|
||||
<div id="footer">
|
||||
{% block footer %}
|
||||
© Copyright 2008 by <a href="http://domain.invalid/">you</a>
|
||||
{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
以上父模板中,抽取了所有模板都需要用到的元素`html`、`body`等,并且对于一些所有模板都要用到的样式文件`style.css`也进行了抽取,同时对于一些子模板需要重写的地方,比如`title`、`head`、`body`都定义成了`block`,然后子模板可以根据自己的需要,再具体的实现。以下再来看子模板的代码:
|
||||
|
||||
```jinja2
|
||||
{% extends "base.html" %}
|
||||
{% block title %}首页{% endblock %}
|
||||
{% block head %}
|
||||
{{ super() }}
|
||||
<style type="text/css">
|
||||
.detail{
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<h1>这里是首页</h1>
|
||||
<p class="detail">
|
||||
首页的内容
|
||||
</p>
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
首先第一行就定义了子模板继承的父模板,并且可以看到子模板实现了`title`这个`block`,并填充了自己的内容,再看`head`这个`block`,里面调用了`super()`这个函数,这个函数的目的是执行父模板中的代码,把父模板中的内容添加到子模板中,如果没有这一句,则父模板中处在`head`这个`block`中的代码将会被子模板中的代码给覆盖掉。
|
||||
|
||||
另外,模板中不能出现重名的`block`,如果一个地方需要用到另外一个`block`中的内容,可以使用`self.blockname`的方式进行引用,比如以下示例:
|
||||
|
||||
```html
|
||||
<title>
|
||||
{% block title %}
|
||||
这是标题
|
||||
{% endblock %}
|
||||
</title>
|
||||
<h1>{{ self.title() }}</h1>
|
||||
```
|
||||
|
||||
以上示例中`h1`标签重用了`title`这个`block`中的内容,子模板实现了`title`这个`block`,`h1`标签也能拥有这个值。
|
||||
|
||||
另外,在子模板中,所有的文本标签和代码都要添加到从父模板中继承的`block`中。否则,这些文本和标签将不会被渲染。
|
||||
|
||||
## 9. 转义
|
||||
|
||||
转义的概念是,在模板渲染字符串的时候,字符串有可能包括一些非常危险的字符比如`<`、`>`等,这些字符会破坏掉原来`HTML`标签的结构,更严重的可能会发生`XSS`跨域脚本攻击,因此如果碰到`<`、`>`这些字符的时候,应该转义成`HTML`能正确表示这些字符的写法,比如`>`在`HTML`中应该用`<`来表示等。
|
||||
|
||||
但是`Flask`中默认没有开启全局自动转义,针对那些以`.html`、`.htm`、`.xml`和`.xhtml`结尾的文件,如果采用`render_template`函数进行渲染的,则会开启自动转义。并且当用`render_template_string`函数的时候,会将所有的字符串进行转义后再渲染。而对于`Jinja2`默认没有开启全局自动转义,作者有自己的原因:
|
||||
|
||||
1. 渲染到模板中的字符串并不是所有都是危险的,大部分还是没有问题的,如果开启自动转义,那么将会带来大量的不必要的开销。
|
||||
2. `Jinja2`很难获取当前的字符串是否已经被转义过了,因此如果开启自动转义,将对一些已经被转义过的字符串发生二次转义,在渲染后会破坏原来的字符串。
|
||||
|
||||
在没有开启自动转义的模式下(比如以`.conf`结尾的文件),对于一些不信任的字符串,可以通过`{{ content_html|e }}`或者是`{{ content_html|escape }}`的方式进行转义。在开启了自动转义的模式下,如果想关闭自动转义,可以通过`{{ content_html|safe }}`的方式关闭自动转义。而`{%autoescape true/false%}...{%endautoescape%}`可以将一段代码块放在中间,来关闭或开启自动转义,例如以下代码关闭了自动转义:
|
||||
|
||||
```jinja2
|
||||
{% autoescape false %}
|
||||
<p>autoescaping is disabled here
|
||||
<p>{{ will_not_be_escaped }}
|
||||
{% endautoescape %}
|
||||
```
|
||||
|
||||
## 10. 数据类型和运算符
|
||||
|
||||
### 10.1 数据类型:
|
||||
|
||||
`Jinja`支持许多数据类型,包括:**字符串、整型、浮点型、列表、元组、字典、True/False**。
|
||||
|
||||
### 10.2 运算符:
|
||||
|
||||
- `+`号运算符:可以完成数字相加,字符串相加,列表相加。但是并不推荐使用`+`运算符来操作字符串,字符串相加应该使用`~`运算符。
|
||||
- `-`号运算符:只能针对两个数字相减。
|
||||
- `/`号运算符:对两个数进行相除。
|
||||
- `%`号运算符:取余运算。
|
||||
- `*`号运算符:乘号运算符,并且可以对字符进行相乘。
|
||||
- `**`号运算符:次幂运算符,比如2**3=8。
|
||||
- `in`操作符:跟python中的`in`一样使用,比如`{{1 in [1,2,3]}}`返回`true`。
|
||||
- `~`号运算符:拼接多个字符串,比如`{{"Hello" ~ "World"}}`将返回`HelloWorld`。
|
||||
|
||||
## 11. 静态文件的配置
|
||||
|
||||
`Web`应用中会出现大量的静态文件来使得网页更加生动美观。类似于`CSS`样式文件、`JavaScript`脚本文件、图片文件、字体文件等静态资源。在`Jinja`中加载静态文件非常简单,只需要通过`url_for`全局函数就可以实现,看以下代码:
|
||||
|
||||
```html
|
||||
<link href="{{ url_for('static',filename='about.css') }}">
|
||||
```
|
||||
|
||||
`url_for`函数默认会在项目根目录下的`static`文件夹中寻找`about.css`文件,如果找到了,会生成一个相对于项目根目录下的`/static/about.css`路径。当然我们也可以把静态文件不放在`static`文件夹中,此时就需要具体指定了,看以下代码:
|
||||
|
||||
```python
|
||||
app = Flask(__name__,static_folder='C:\static')
|
||||
```
|
||||
|
||||
那么访问静态文件的时候,将会到`/static`这个文件夹下寻找。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
182
04.Flask/03.视图高级.md
Normal file
@@ -0,0 +1,182 @@
|
||||
# 视图高级
|
||||
|
||||
## 1. 类视图
|
||||
|
||||
之前我们接触的视图都是函数,所以一般简称视图函数。其实视图也可以基于类来实现,类视图的好处是支持继承,但是类视图不能跟函数视图一样,写完类视图还需要通过`app.add_url_rule(url_rule,view_func)`来进行注册。以下将对两种类视图进行讲解:
|
||||
|
||||
### 1.1 标准类视图:
|
||||
|
||||
标准类视图是继承自`flask.views.View`,并且在子类中必须实现`dispatch_request`方法,这个方法类似于视图函数,也要返回一个基于`Response`或者其子类的对象。以下将用一个例子进行讲解:
|
||||
|
||||
```python
|
||||
from flask.views import View
|
||||
class PersonalView(View):
|
||||
def dispatch_request(self):
|
||||
return "知了课堂"
|
||||
# 类视图通过add_url_rule方法和url做映射
|
||||
app.add_url_rule('/users/',view_func=PersonalView.as_view('personalview'))
|
||||
```
|
||||
|
||||
### 1.2 基于调度方法的视图:
|
||||
|
||||
`Flask`还为我们提供了另外一种类视图`flask.views.MethodView`,对每个HTTP方法执行不同的函数(映射到对应方法的小写的同名方法上),以下将用一个例子来进行讲解:
|
||||
|
||||
```python
|
||||
class LoginView(views.MethodView):
|
||||
# 当客户端通过get方法进行访问的时候执行的函数
|
||||
def get(self):
|
||||
return render_template("login.html")
|
||||
|
||||
# 当客户端通过post方法进行访问的时候执行的函数
|
||||
def post(self):
|
||||
email = request.form.get("email")
|
||||
password = request.form.get("password")
|
||||
if email == 'xx@qq.com' and password == '111111':
|
||||
return "登录成功!"
|
||||
else:
|
||||
return "用户名或密码错误!"
|
||||
|
||||
## 通过add_url_rule添加类视图和url的映射,并且在as_view方法中指定该url的名称,方便url_for函数调用
|
||||
app.add_url_rule('/myuser/',view_func=LoginView.as_view('loginview'))
|
||||
```
|
||||
|
||||
如果用类视图,我们怎么使用装饰器呢?比如有时候需要做权限验证的时候,比如看以下例子:
|
||||
|
||||
```python
|
||||
from flask import session
|
||||
def login_required(func):
|
||||
def wrapper(*args,**kwargs):
|
||||
if not session.get("user_id"):
|
||||
return 'auth failure'
|
||||
return func(*args,**kwargs)
|
||||
return wrapper
|
||||
```
|
||||
|
||||
装饰器写完后,可以在类视图中定义一个属性叫做`decorators`,然后存储装饰器。以后每次调用这个类视图的时候,就会执行这个装饰器。示例代码如下:
|
||||
|
||||
```python
|
||||
class UserView(views.MethodView):
|
||||
decorators = [user_required]
|
||||
...
|
||||
```
|
||||
|
||||
## 2. 蓝图和子域名
|
||||
|
||||
### 2.1 蓝图:
|
||||
|
||||
之前我们写的`url`和视图函数都是处在同一个文件,如果项目比较大的话,这显然不是一个合理的结构,而蓝图可以优雅的帮我们实现这种需求。以下看一个使用蓝图的文件的例子:
|
||||
|
||||
```
|
||||
from flask import Blueprint
|
||||
bp = Blueprint('user',__name__,url_prefix='/user/')
|
||||
|
||||
@bp.route('/')
|
||||
def index():
|
||||
return "用户首页"
|
||||
|
||||
@bp.route('profile/')
|
||||
def profile():
|
||||
return "个人简介"
|
||||
```
|
||||
|
||||
然后我们在主程序中,通过`app.register_blueprint()`方法将这个蓝图注册进url映射中,看下主`app`的实现:
|
||||
|
||||
```
|
||||
from flask import Flask
|
||||
import user
|
||||
|
||||
app = Flask(__name__)
|
||||
app.register_blueprint(user.bp)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0',port=9000)
|
||||
```
|
||||
|
||||
以后访问`/user/`,`/user/profile/`,都是执行的`user.py`文件中的视图函数,这样就实现了项目的模块化。
|
||||
|
||||
以上是对蓝图的一个简单介绍,但是使用蓝图还有几个需要注意的地方,就是在蓝图如何寻找静态文件、模板文件,`url_for`函数如何反转`url`,以下分别进行解释:
|
||||
|
||||
#### 2.1.1 寻找静态文件:
|
||||
|
||||
默认不设置任何静态文件路径,`Jinja2`会在项目的`static`文件夹中寻找静态文件。也可以设置其他的路径,在初始化蓝图的时候,`Blueprint`这个构造函数,有一个参数`static_folder`可以指定静态文件的路径,如:
|
||||
|
||||
```python
|
||||
bp = Blueprint('admin',__name__,url_prefix='/admin',static_folder='static')
|
||||
```
|
||||
|
||||
`static_folder`可以是相对路径(相对蓝图文件所在的目录),也可以是绝对路径。在配置完蓝图后,还有一个需要注意的地方是如何在模板中引用静态文件。在模板中引用蓝图,应该要使用`蓝图名+.+static`来引用,如下所示:
|
||||
|
||||
```python
|
||||
<link href="{{ url_for('admin.static',filename='about.css') }}">
|
||||
```
|
||||
|
||||
#### 2.1.2 寻找模板文件:
|
||||
|
||||
跟静态文件一样,默认不设置任何模板文件的路径,将会在项目的`templates`中寻找模板文件。也可以设置其他的路径,在构造函数`Blueprint`中有一个`template_folder`参数可以设置模板的路径,如下所示:
|
||||
|
||||
```python
|
||||
bp = Blueprint('admin',__name__,url_prefix='/admin',template_folder='templates')
|
||||
```
|
||||
|
||||
模板文件和静态文件有点区别,以上代码写完以后,如果你渲染一个模板`return render_template('admin.html')`,`Flask`默认会去项目根目录下的`templates`文件夹中查找`admin.html`文件,如果找到了就直接返回,如果没有找到,才会去蓝图文件所在的目录下的`templates`文件夹中寻找。
|
||||
|
||||
#### 2.1.3 url_for生成`url`:
|
||||
|
||||
用`url_for`生成蓝图的`url`,使用的格式是:`蓝图名称+.+视图函数名称`。比如要获取`admin`这个蓝图下的`index`视图函数的`url`,应该采用以下方式:
|
||||
|
||||
```python
|
||||
url_for('admin.index')
|
||||
```
|
||||
|
||||
其中这个**蓝图名称**是在创建蓝图的时候,传入的第一个参数。`bp = Blueprint('admin',__name__,url_prefix='/admin',template_folder='templates')`
|
||||
|
||||
### 2.2 子域名:
|
||||
|
||||
子域名在许多网站中都用到了,比如一个网站叫做`xxx.com`,那么我们可以定义一个子域名`cms.xxx.com`来作为`cms`管理系统的网址,子域名的实现一般也是通过蓝图来实现,在之前章节中,我们创建蓝图的时候添加了一个`url_prefix=/user`作为url前缀,那样我们就可以通过`/user/`来访问`user`下的url。但使用子域名则不需要。另外,还需要配置`SERVER_NAME`,比如`app.config[SERVER_NAME]='example.com:9000'`。并且在注册蓝图的时候,还需要添加一个`subdomain`的参数,这个参数就是子域名的名称,先来看一下蓝图的实现(admin.py):
|
||||
|
||||
```python
|
||||
from flask import Blueprint
|
||||
bp = Blueprint('admin',__name__,subdomain='admin')
|
||||
|
||||
@bp.route('/')
|
||||
def admin():
|
||||
return 'Admin Page'
|
||||
```
|
||||
|
||||
这个没有多大区别,接下来看主`app`的实现:
|
||||
|
||||
```python
|
||||
from flask import Flask
|
||||
import admin
|
||||
|
||||
# 配置`SERVER_NAME`
|
||||
app.config['SERVER_NAME'] = 'example.com:8000'
|
||||
# 注册蓝图,指定了subdomain
|
||||
app.register_blueprint(admin.bp)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0',port=8000,debug=True)
|
||||
```
|
||||
|
||||
写完以上两个文件后,还是不能正常的访问`admin.example.com:8000`这个子域名,因为我们没有在`host`文件中添加域名解析,你可以在最后添加一行`127.0.0.1 admin.example.com`,就可以访问到了。另外,子域名不能在`127.0.0.1`上出现,也不能在`localhost`上出现。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
919
04.Flask/04.SQLAlchemy.md
Normal file
@@ -0,0 +1,919 @@
|
||||
# SQLAlchemy
|
||||
|
||||
## 1. MySQL数据库
|
||||
|
||||
在网站开发中,数据库是网站的重要组成部分。只有提供数据库,数据才能够动态的展示,而不是在网页中显示一个静态的页面。数据库有很多,比如有`SQL Server`、`Oracle`、`PostgreSQL`以及`MySQL`等等。`MySQL`由于价格实惠、简单易用、不受平台限制、灵活度高等特性,目前已经取得了绝大多数的市场份额。因此我们在`Flask`中,也是使用`MySQL`来作为数据存储。
|
||||
|
||||
### 1.1 MySQL数据库安装
|
||||
|
||||
1. 在`MySQL`的官网下载`MySQL`数据库安装文件:`https://dev.mysql.com/downloads/mysql/`。
|
||||
2. 然后双击安装,如果出现以下错误,则到`http://www.microsoft.com/en-us/download/details.aspx?id=17113`下载`.net framework`。
|
||||

|
||||
3. 在安装过程中,如果提示没有`Microsoft C++ 2013`,那么就到以下网址下载安装即可:`http://download.microsoft.com/download/9/0/5/905DBD86-D1B8-4D4B-8A50-CB0E922017B9/vcredist_x64.exe`。
|
||||

|
||||
4. 接下来就是做好用户名和密码的配置即可。
|
||||
|
||||
### 1.2 navicat数据库操作软件
|
||||
|
||||
安装完`MySQL`数据库以后,就可以使用`MySQL`提供的终端客户端软件来操作数据库。如下:
|
||||

|
||||
这个软件所有的操作都是基于`sql`语言,对于想要熟练`sql`语言的同学来讲是非常合适的。但是对于在企业中可能不是一款好用的工具。在企业中我们推荐使用`mysql workbench`以及`navicat`这种图形化操作的软件。而`mysql workbench`是`mysql`官方提供的一个免费的软件,正因为是免费,所以在一些功能上不及`navicat`。`navicat for mysql`是一款收费的软件。官网地址如下:`https://www.navicat.com.cn/products`。使用的截图如下:
|
||||

|
||||
|
||||
### 1.3 MySQL驱动程序安装
|
||||
|
||||
我们使用`Django`来操作`MySQL`,实际上底层还是通过`Python`来操作的。因此我们想要用`Flask`来操作`MySQL`,首先还是需要安装一个驱动程序。在`Python3`中,驱动程序有多种选择。比如有`pymysql`以及`mysqlclient`等。这里我们就使用`mysqlclient`来操作。`mysqlclient`安装非常简单。只需要通过`pip install mysqlclient`即可安装。
|
||||
|
||||
常见`MySQL`驱动介绍:
|
||||
|
||||
1. `MySQL-python`:也就是`MySQLdb`。是对`C`语言操作`MySQL`数据库的一个简单封装。遵循了`Python DB API v2`。但是只支持`Python2`,目前还不支持`Python3`。
|
||||
2. `mysqlclient`:是`MySQL-python`的另外一个分支。支持`Python3`并且修复了一些`bug`。
|
||||
3. `pymysql`:纯`Python`实现的一个驱动。因为是纯`Python`编写的,因此执行效率不如`MySQL-python`。并且也因为是纯`Python`编写的,因此可以和`Python`代码无缝衔接。
|
||||
4. `MySQL Connector/Python`:`MySQL`官方推出的使用纯`Python`连接`MySQL`的驱动。因为是纯`Python`开发的。效率不高。
|
||||
|
||||
## 2. SQLAlchemy介绍
|
||||
|
||||
### 2.1 安装:
|
||||
|
||||
`SQLAlchemy`是一个数据库的`ORM`框架,让我们操作数据库的时候不要再用`SQL`语句了,跟直接操作模型一样。安装命令为:`pip install SQLAlchemy`。
|
||||
|
||||
### 2.2 通过`SQLAlchemy`连接数据库:
|
||||
|
||||
首先来看一段代码:
|
||||
|
||||
```python
|
||||
from sqlalchemy import create_engine
|
||||
|
||||
# 数据库的配置变量
|
||||
HOSTNAME = '127.0.0.1'
|
||||
PORT = '3306'
|
||||
DATABASE = 'xt_flask'
|
||||
USERNAME = 'root'
|
||||
PASSWORD = 'root'
|
||||
DB_URI = 'mysql+pymysql://{}:{}@{}:{}/{}'.format(USERNAME,PASSWORD,HOSTNAME,PORT,DATABASE)
|
||||
|
||||
# 创建数据库引擎
|
||||
engine = create_engine(DB_URI)
|
||||
|
||||
#创建连接
|
||||
with engine.connect() as con:
|
||||
rs = con.execute('SELECT 1')
|
||||
print rs.fetchone()
|
||||
```
|
||||
|
||||
首先从`sqlalchemy`中导入`create_engine`,用这个函数来创建引擎,然后用`engine.connect()`来连接数据库。其中一个比较重要的一点是,通过`create_engine`函数的时候,需要传递一个满足某种格式的字符串,对这个字符串的格式来进行解释:
|
||||
|
||||
```
|
||||
dialect+driver://username:password@host:port/database?charset=utf8
|
||||
```
|
||||
|
||||
`dialect`是数据库的实现,比如`MySQL`、`PostgreSQL`、`SQLite`,并且转换成小写。`driver`是`Python`对应的驱动,如果不指定,会选择默认的驱动,比如MySQL的默认驱动是`MySQLdb`。`username`是连接数据库的用户名,`password`是连接数据库的密码,`host`是连接数据库的域名,`port`是数据库监听的端口号,`database`是连接哪个数据库的名字。
|
||||
|
||||
如果以上输出了`1`,说明`SQLAlchemy`能成功连接到数据库。
|
||||
|
||||
### 2.3 用SQLAlchemy执行原生SQL:
|
||||
|
||||
我们将上一个例子中的数据库配置选项单独放在一个`constants.py`的文件中,看以下例子:
|
||||
|
||||
```python
|
||||
from sqlalchemy import create_engine
|
||||
from constants import DB_URI
|
||||
|
||||
#连接数据库
|
||||
engine = create_engine(DB_URI,echo=True)
|
||||
|
||||
# 使用with语句连接数据库,如果发生异常会被捕获
|
||||
with engine.connect() as con:
|
||||
# 先删除users表
|
||||
con.execute('drop table if exists authors')
|
||||
# 创建一个users表,有自增长的id和name
|
||||
con.execute('create table authors(id int primary key auto_increment,'name varchar(25))')
|
||||
# 插入两条数据到表中
|
||||
con.execute('insert into persons(name) values("abc")')
|
||||
con.execute('insert into persons(name) values("xiaotuo")')
|
||||
# 执行查询操作
|
||||
results = con.execute('select * from persons')
|
||||
# 从查找的结果中遍历
|
||||
for result in results:
|
||||
print(result)
|
||||
```
|
||||
|
||||
## 3. SQLAlchemy基本使用
|
||||
|
||||
### 3.1 使用SQLAlchemy:
|
||||
|
||||
#### 3.1.1 创建`ORM`模型:
|
||||
|
||||
要使用`ORM`来操作数据库,首先需要创建一个类来与对应的表进行映射。现在以`User表`来做为例子,它有`自增长的id`、`name`、`fullname`、`password`这些字段,那么对应的类为:
|
||||
|
||||
```python
|
||||
from sqlalchemy import Column,Integer,String
|
||||
from constants import DB_URI
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
engine = create_engine(DB_URI,echo=True)
|
||||
|
||||
# 所有的类都要继承自`declarative_base`这个函数生成的基类
|
||||
Base = declarative_base(engine)
|
||||
class User(Base):
|
||||
# 定义表名为users
|
||||
__tablename__ = 'users'
|
||||
|
||||
# 将id设置为主键,并且默认是自增长的
|
||||
id = Column(Integer,primary_key=True)
|
||||
# name字段,字符类型,最大的长度是50个字符
|
||||
name = Column(String(50))
|
||||
fullname = Column(String(50))
|
||||
password = Column(String(100))
|
||||
|
||||
# 让打印出来的数据更好看,可选的
|
||||
def __repr__(self):
|
||||
return "<User(id='%s',name='%s',fullname='%s',password='%s')>" % (self.id,self.name,self.fullname,self.password)
|
||||
```
|
||||
|
||||
#### 3.1.2 映射到数据库中:
|
||||
|
||||
`SQLAlchemy`会自动的设置第一个`Integer`的主键并且没有被标记为外键的字段添加自增长的属性。因此以上例子中`id`自动的变成自增长的。以上创建完和表映射的类后,还没有真正的映射到数据库当中,执行以下代码将类映射到数据库中:
|
||||
|
||||
```python
|
||||
Base.metadata.create_all()
|
||||
```
|
||||
|
||||
#### 3.1.3 添加数据到表中:
|
||||
|
||||
在创建完数据表,并且做完和数据库的映射后,接下来让我们添加数据进去:
|
||||
|
||||
```python
|
||||
ed_user = User(name='ed',fullname='Ed Jones',password='edspassword')
|
||||
# 打印名字
|
||||
print ed_user.name
|
||||
> ed
|
||||
# 打印密码
|
||||
print ed_user.password
|
||||
> edspassword
|
||||
# 打印id
|
||||
print(ed_user.id)
|
||||
> None
|
||||
```
|
||||
|
||||
可以看到,name和password都能正常的打印,唯独`id`为`None`,这是因为`id`是一个自增长的主键,还未插入到数据库中,`id`是不存在的。接下来让我们把创建的数据插入到数据库中。和数据库打交道的,是一个叫做`Session`的对象:
|
||||
|
||||
```python
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
Session = sessionmaker(bind=engine)
|
||||
# 或者
|
||||
# Session = sessionmaker()
|
||||
# Session.configure(bind=engine)
|
||||
session = Session()
|
||||
ed_user = User(name='ed',fullname='Ed Jones',password='edspassword')
|
||||
session.add(ed_user)
|
||||
```
|
||||
|
||||
现在只是把数据添加到`session`中,但是并没有真正的把数据存储到数据库中。如果需要把数据存储到数据库中,还要做一次`commit`操作:
|
||||
|
||||
```python
|
||||
session.commit()
|
||||
# 打印ed_user的id
|
||||
print ed_user.id
|
||||
> 1
|
||||
```
|
||||
|
||||
#### 3.1.4 回滚:
|
||||
|
||||
这时候,`ed_user`就已经有id。 说明已经插入到数据库中了。有人肯定有疑问了,为什么添加到`session`中后还要做一次`commit`操作呢,这是因为,在`SQLAlchemy`的`ORM`实现中,在做`commit`操作之前,所有的操作都是在事务中进行的,因此如果你要将事务中的操作真正的映射到数据库中,还需要做`commit`操作。既然用到了事务,这里就并不能避免的提到一个回滚操作了,那么看以下代码展示了如何使用回滚(接着以上示例代码):
|
||||
|
||||
```python
|
||||
# 修改ed_user的用户名
|
||||
ed_user.name = 'Edwardo'
|
||||
# 创建一个新的用户
|
||||
fake_user = User(name='fakeuser',fullname='Invalid',password='12345')
|
||||
# 将新创建的fake_user添加到session中
|
||||
session.add(fake_user)
|
||||
# 判断`fake_user`是否在`session`中存在
|
||||
print fake_user in session
|
||||
> True
|
||||
# 从数据库中查找name=Edwardo的用户
|
||||
tmp_user = session.query(User).filter_by(name='Edwardo')
|
||||
# 打印tmp_user的name
|
||||
print tmp_user
|
||||
# 打印出查找到的tmp_user对象,注意这个对象的name属性已经在事务中被修改为Edwardo了。
|
||||
> <User(name='Edwardo', fullname='Ed Jones', password='edspassword')>
|
||||
# 刚刚所有的操作都是在事务中进行的,现在来做回滚操作
|
||||
session.rollback()
|
||||
# 再打印tmp_user
|
||||
print tmp_user
|
||||
> <User(name='ed', fullname='Ed Jones', password='edspassword')>
|
||||
# 再看fake_user是否还在session中
|
||||
print fake_user in session
|
||||
> False
|
||||
```
|
||||
|
||||
#### 3.1.5 查找数据:
|
||||
|
||||
接下来看下如何进行查找操作,查找操作是通过`session.query()`方法实现的,这个方法会返回一个`Query`对象,`Query`对象相当于一个数组,装载了查找出来的数据,并且可以进行迭代。具体里面装的什么数据,就要看向`session.query()`方法传的什么参数了,如果只是传一个`ORM`的类名作为参数,那么提取出来的数据就是都是这个类的实例,比如:
|
||||
|
||||
```python
|
||||
for instance in session.query(User).order_by(User.id):
|
||||
print instance
|
||||
# 输出所有的user实例
|
||||
> <User (id=2,name='ed',fullname='Ed Json',password='12345')>
|
||||
> <User (id=3,name='be',fullname='Be Engine',password='123456')>
|
||||
```
|
||||
|
||||
如果传递了两个及其两个以上的对象,或者是传递的是`ORM`类的属性,那么查找出来的就是元组,例如:
|
||||
|
||||
```python
|
||||
for instance in session.query(User.name):
|
||||
print instance
|
||||
# 输出所有的查找结果
|
||||
> ('ed',)
|
||||
> ('be',)
|
||||
```
|
||||
|
||||
以及:
|
||||
|
||||
```python
|
||||
for instance in session.query(User.name,User.fullname):
|
||||
print instance
|
||||
# 输出所有的查找结果
|
||||
> ('ed', 'Ed Json')
|
||||
> ('be', 'Be Engine')
|
||||
```
|
||||
|
||||
或者是:
|
||||
|
||||
```python
|
||||
for instance in session.query(User,User.name).all():
|
||||
print instance
|
||||
# 输出所有的查找结果
|
||||
> (<User (id=2,name='ed',fullname='Ed Json',password='12345')>, 'Ed Json')
|
||||
> (<User (id=3,name='be',fullname='Be Engine',password='123456')>, 'Be Engine')
|
||||
```
|
||||
|
||||
另外,还可以对查找的结果(`Query`)做切片操作:
|
||||
|
||||
```python
|
||||
for instance in session.query(User).order_by(User.id)[1:3]
|
||||
instance
|
||||
```
|
||||
|
||||
如果想对结果进行过滤,可以使用`filter_by`和`filter`两个方法,这两个方法都是用来做过滤的,区别在于,`filter_by`是传入关键字参数,`filter`是传入条件判断,并且`filter`能够传入的条件更多更灵活,请看以下例子:
|
||||
|
||||
```python
|
||||
# 第一种:使用filter_by过滤:
|
||||
for name in session.query(User.name).filter_by(fullname='Ed Jones'):
|
||||
print name
|
||||
# 输出结果:
|
||||
> ('ed',)
|
||||
|
||||
# 第二种:使用filter过滤:
|
||||
for name in session.query(User.name).filter(User.fullname=='Ed Jones'):
|
||||
print name
|
||||
# 输出结果:
|
||||
> ('ed',)
|
||||
```
|
||||
|
||||
### 3.2 Column常用参数:
|
||||
|
||||
- `default`:默认值。
|
||||
- `nullable`:是否可空。
|
||||
- `primary_key`:是否为主键。
|
||||
- `unique`:是否唯一。
|
||||
- `autoincrement`:是否自动增长。
|
||||
- `onupdate`:更新的时候执行的函数。
|
||||
- `name`:该属性在数据库中的字段映射。
|
||||
|
||||
### 3.3 sqlalchemy常用数据类型:
|
||||
|
||||
- `Integer`:整形。
|
||||
- `Float`:浮点类型。
|
||||
- `Boolean`:传递`True/False`进去。
|
||||
- `DECIMAL`:定点类型。
|
||||
- `enum`:枚举类型。
|
||||
- `Date`:传递`datetime.date()`进去。
|
||||
- `DateTime`:传递`datetime.datetime()`进去。
|
||||
- `Time`:传递`datetime.time()`进去。
|
||||
- `String`:字符类型,使用时需要指定长度,区别于`Text`类型。
|
||||
- `Text`:文本类型。
|
||||
- `LONGTEXT`:长文本类型。
|
||||
|
||||
## 4. 查找操作
|
||||
|
||||
### 4.1 query可用参数:
|
||||
|
||||
1. 模型对象。指定查找这个模型中所有的对象。
|
||||
2. 模型中的属性。可以指定只查找某个模型的其中几个属性。
|
||||
3. 聚合函数。
|
||||
- `func.count`:统计行的数量。
|
||||
- `func.avg`:求平均值。
|
||||
- `func.max`:求最大值。
|
||||
- `func.min`:求最小值。
|
||||
- `func.sum`:求和。
|
||||
|
||||
### 4.2 过滤条件:
|
||||
|
||||
过滤是数据提取的一个很重要的功能,以下对一些常用的过滤条件进行解释,并且这些过滤条件都是只能通过`filter`方法实现的:
|
||||
|
||||
1. `equals`:
|
||||
|
||||
```python
|
||||
query.filter(User.name == 'ed')
|
||||
```
|
||||
|
||||
2. `not equals`:
|
||||
|
||||
```python
|
||||
query.filter(User.name != 'ed')
|
||||
```
|
||||
|
||||
3. `like`:
|
||||
|
||||
```python
|
||||
query.filter(User.name.like('%ed%'))
|
||||
```
|
||||
|
||||
4. `in`:
|
||||
|
||||
```python
|
||||
query.filter(User.name.in_(['ed','wendy','jack']))
|
||||
# 同时,in也可以作用于一个Query
|
||||
query.filter(User.name.in_(session.query(User.name).filter(User.name.like('%ed%'))))
|
||||
```
|
||||
|
||||
5. `not in`:
|
||||
|
||||
```python
|
||||
query.filter(~User.name.in_(['ed','wendy','jack']))
|
||||
```
|
||||
|
||||
6. `is null`:
|
||||
|
||||
```python
|
||||
query.filter(User.name==None)
|
||||
# 或者是
|
||||
query.filter(User.name.is_(None))
|
||||
```
|
||||
|
||||
7. `is not null`:
|
||||
|
||||
```python
|
||||
query.filter(User.name != None)
|
||||
# 或者是
|
||||
query.filter(User.name.isnot(None))
|
||||
```
|
||||
|
||||
8. `and`:
|
||||
|
||||
```python
|
||||
from sqlalchemy import and_
|
||||
query.filter(and_(User.name=='ed',User.fullname=='Ed Jones'))
|
||||
# 或者是传递多个参数
|
||||
query.filter(User.name=='ed',User.fullname=='Ed Jones')
|
||||
# 或者是通过多次filter操作
|
||||
query.filter(User.name=='ed').filter(User.fullname=='Ed Jones')
|
||||
```
|
||||
|
||||
9. `or`:
|
||||
|
||||
```python
|
||||
from sqlalchemy import or_ query.filter(or_(User.name=='ed',User.name=='wendy'))
|
||||
```
|
||||
|
||||
### 4.3 查找方法:
|
||||
|
||||
介绍完过滤条件后,有一些经常用到的查找数据的方法也需要解释一下:
|
||||
|
||||
1. `all()`:返回一个`Python`列表(`list`):
|
||||
|
||||
```python
|
||||
query = session.query(User).filter(User.name.like('%ed%').order_by(User.id)
|
||||
# 输出query的类型
|
||||
print type(query)
|
||||
> <type 'list'>
|
||||
# 调用all方法
|
||||
query = query.all()
|
||||
# 输出query的类型
|
||||
print type(query)
|
||||
> <class 'sqlalchemy.orm.query.Query'>
|
||||
```
|
||||
|
||||
2. `first()`:返回`Query`中的第一个值:
|
||||
|
||||
```python
|
||||
user = session.query(User).first()
|
||||
print user
|
||||
> <User(name='ed', fullname='Ed Jones', password='f8s7ccs')>
|
||||
```
|
||||
|
||||
3. `one()`:查找所有行作为一个结果集,如果结果集中只有一条数据,则会把这条数据提取出来,如果这个结果集少于或者多于一条数据,则会抛出异常。总结一句话:有且只有一条数据的时候才会正常的返回,否则抛出异常:
|
||||
|
||||
```python
|
||||
# 多于一条数据
|
||||
user = query.one()
|
||||
> Traceback (most recent call last):
|
||||
> ...
|
||||
> MultipleResultsFound: Multiple rows were found for one()
|
||||
# 少于一条数据
|
||||
user = query.filter(User.id == 99).one()
|
||||
> Traceback (most recent call last):
|
||||
> ...
|
||||
> NoResultFound: No row was found for one()
|
||||
# 只有一条数据
|
||||
> query(User).filter_by(name='ed').one()
|
||||
```
|
||||
|
||||
4. `one_or_none()`:跟`one()`方法类似,但是在结果集中没有数据的时候也不会抛出异常。
|
||||
|
||||
5. `scalar()`:底层调用`one()`方法,并且如果`one()`方法没有抛出异常,会返回查询到的第一列的数据:
|
||||
|
||||
```python
|
||||
session.query(User.name,User.fullname).filter_by(name='ed').scalar()
|
||||
```
|
||||
|
||||
### 4.4 文本SQL:
|
||||
|
||||
`SQLAlchemy`还提供了使用**文本SQL**的方式来进行查询,这种方式更加的灵活。而文本SQL要装在一个`text()`方法中,看以下例子:
|
||||
|
||||
```python
|
||||
from sqlalchemy import text
|
||||
for user in session.query(User).filter(text("id<244")).order_by(text("id")).all():
|
||||
print user.name
|
||||
```
|
||||
|
||||
如果过滤条件比如上例中的244存储在变量中,这时候就可以通过传递参数的形式进行构造:
|
||||
|
||||
```python
|
||||
session.query(User).filter(text("id<:value and name=:name")).params(value=224,name='ed').order_by(User.id)
|
||||
```
|
||||
|
||||
在文本SQL中的变量前面使用了`:`来区分,然后使用`params`方法,指定需要传入进去的参数。另外,使用`from_statement`方法可以把过滤的函数和条件函数都给去掉,使用纯文本的SQL:
|
||||
|
||||
```python
|
||||
sesseion.query(User).from_statement(text("select * from users where name=:name")).params(name='ed').all()
|
||||
```
|
||||
|
||||
使用`from_statement`方法一定要注意,`from_statement`返回的是一个`text`里面的查询语句,一定要记得调用`all()`方法来获取所有的值。
|
||||
|
||||
### 4.5 计数(Count):
|
||||
|
||||
`Query`对象有一个非常方便的方法来计算里面装了多少数据:
|
||||
|
||||
```python
|
||||
session.query(User).filter(User.name.like('%ed%')).count()
|
||||
```
|
||||
|
||||
当然,有时候你想明确的计数,比如要统计`users`表中有多少个不同的姓名,那么简单粗暴的采用以上`count`是不行的,因为姓名有可能会重复,但是处于两条不同的数据上,如果在原生数据库中,可以使用`distinct`关键字,那么在`SQLAlchemy`中,可以通过`func.count()`方法来实现:
|
||||
|
||||
```python
|
||||
from sqlalchemy import func
|
||||
session.query(func.count(User.name),User.name).group_by(User.name).all()
|
||||
## 输出的结果
|
||||
> [(1, u'ed'), (1, u'fred'), (1, u'mary'), (1, u'wendy')]
|
||||
```
|
||||
|
||||
另外,如果想实现`select count(*) from users`,可以通过以下方式来实现:
|
||||
|
||||
```python
|
||||
session.query(func.count(*)).select_from(User).scalar()
|
||||
```
|
||||
|
||||
当然,如果指定了要查找的表的字段,可以省略`select_from()`方法:
|
||||
|
||||
```python
|
||||
session.query(func.count(User.id)).scalar()
|
||||
```
|
||||
|
||||
## 5. 表关系:
|
||||
|
||||
表之间的关系存在三种:一对一、一对多、多对多。而`SQLAlchemy`中的`ORM`也可以模拟这三种关系。因为一对一其实在`SQLAlchemy`中底层是通过一对多的方式模拟的,所以先来看下一对多的关系:
|
||||
|
||||
### 5.1 外键:
|
||||
|
||||
在Mysql中,外键可以让表之间的关系更加紧密。而SQLAlchemy同样也支持外键。通过ForeignKey类来实现,并且可以指定表的外键约束。相关示例代码如下:
|
||||
|
||||
```python
|
||||
class Article(Base):
|
||||
__tablename__ = 'article'
|
||||
id = Column(Integer,primary_key=True,autoincrement=True)
|
||||
title = Column(String(50),nullable=False)
|
||||
content = Column(Text,nullable=False)
|
||||
uid = Column(Integer,ForeignKey('user.id'))
|
||||
|
||||
def __repr__(self):
|
||||
return "<Article(title:%s)>" % self.title
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = 'user'
|
||||
id = Column(Integer,primary_key=True,autoincrement=True)
|
||||
username = Column(String(50),nullable=False)
|
||||
```
|
||||
|
||||
外键约束有以下几项:
|
||||
|
||||
1. `RESTRICT`:父表数据被删除,会阻止删除。默认就是这一项。
|
||||
2. `NO ACTION`:在MySQL中,同`RESTRICT`。
|
||||
3. `CASCADE`:级联删除。
|
||||
4. `SET NULL`:父表数据被删除,子表数据会设置为NULL。
|
||||
|
||||
### 5.2 一对多:
|
||||
|
||||
拿之前的`User`表为例,假如现在要添加一个功能,要保存用户的邮箱帐号,并且邮箱帐号可以有多个,这时候就必须创建一个新的表,用来存储用户的邮箱,然后通过`user.id`来作为外键进行引用,先来看下邮箱表的实现:
|
||||
|
||||
```python
|
||||
from sqlalchemy import ForeignKey
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
class Address(Base):
|
||||
__tablename__ = 'address'
|
||||
id = Column(Integer,primary_key=True)
|
||||
email_address = Column(String,nullable=False)
|
||||
# User表的外键,指定外键的时候,是使用的是数据库表的名称,而不是类名
|
||||
user_id = Column(Integer,ForeignKey('users.id'))
|
||||
# 在ORM层面绑定两者之间的关系,第一个参数是绑定的表的类名,
|
||||
# 第二个参数back_populates是通过User反向访问时的字段名称
|
||||
user = relationship('User',back_populates="addresses")
|
||||
|
||||
def __repr__(self):
|
||||
return "<Address(email_address='%s')>" % self.email_address
|
||||
|
||||
# 重新修改User表,添加了addresses字段,引用了Address表的主键
|
||||
class User(Base):
|
||||
__tablename__ = 'users'
|
||||
id = Column(Integer,primary_key=True)
|
||||
name = Column(String(50))
|
||||
fullname = Column(String(50))
|
||||
password = Column(String(100))
|
||||
# 在ORM层面绑定和`Address`表的关系
|
||||
addresses = relationship("Address",order_by=Address.id,back_populates="user")
|
||||
```
|
||||
|
||||
其中,在`User`表中添加的`addresses`字段,可以通过`User.addresses`来访问和这个user相关的所有address。在`Address`表中的`user`字段,可以通过`Address.user`来访问这个user。达到了双向绑定。表关系已经建立好以后,接下来就应该对其进行操作,先看以下代码:
|
||||
|
||||
```python
|
||||
jack = User(name='jack',fullname='Jack Bean',password='gjffdd')
|
||||
jack.addresses = [Address(email_address='jack@google.com'),
|
||||
Address(email_address='j25@yahoo.com')]
|
||||
session.add(jack)
|
||||
session.commit()
|
||||
```
|
||||
|
||||
首先,创建一个用户,然后对这个`jack`用户添加两个邮箱,最后再提交到数据库当中,可以看到这里操作`Address`并没有直接进行保存,而是先添加到用户里面,再保存。
|
||||
|
||||
### 5.3 一对一:
|
||||
|
||||
一对一其实就是一对多的特殊情况,从以上的一对多例子中不难发现,一对应的是`User`表,而多对应的是`Address`,也就是说一个`User`对象有多个`Address`。因此要将一对多转换成一对一,只要设置一个`User`对象对应一个`Address`对象即可,看以下示例:
|
||||
|
||||
```python
|
||||
class User(Base):
|
||||
__tablename__ = 'users'
|
||||
id = Column(Integer,primary_key=True)
|
||||
name = Column(String(50))
|
||||
fullname = Column(String(50))
|
||||
password = Column(String(100))
|
||||
# 设置uselist关键字参数为False
|
||||
addresses = relationship("Address",back_populates='addresses',uselist=False)
|
||||
class Address(Base):
|
||||
__tablename__ = 'addresses'
|
||||
id = Column(Integer,primary_key=True)
|
||||
email_address = Column(String(50))
|
||||
user_id = Column(Integer,ForeignKey('users.id')
|
||||
user = relationship('Address',back_populates='user')
|
||||
```
|
||||
|
||||
从以上例子可以看到,只要在`User`表中的`addresses`字段上添加`uselist=False`就可以达到一对一的效果。设置了一对一的效果后,就不能添加多个邮箱到`user.addresses`字段了,只能添加一个:
|
||||
|
||||
```python
|
||||
user.addresses = Address(email_address='ed@google.com')
|
||||
```
|
||||
|
||||
### 5.4 多对多:
|
||||
|
||||
多对多需要一个中间表来作为连接,同理在`sqlalchemy`中的`orm`也需要一个中间表。假如现在有一个`Teacher`和一个`Classes`表,即老师和班级,一个老师可以教多个班级,一个班级有多个老师,是一种典型的多对多的关系,那么通过`sqlalchemy`的`ORM`的实现方式如下:
|
||||
|
||||
```python
|
||||
association_table = Table('teacher_classes',Base.metadata,
|
||||
Column('teacher_id',Integer,ForeignKey('teacher.id')),
|
||||
Column('classes_id',Integer,ForeignKey('classes.id'))
|
||||
)
|
||||
|
||||
class Teacher(Base):
|
||||
__tablename__ = 'teacher'
|
||||
id = Column(Integer,primary_key=True)
|
||||
tno = Column(String(10))
|
||||
name = Column(String(50))
|
||||
age = Column(Integer)
|
||||
classes = relationship('Classes',secondary=association_table,back_populates='teachers')
|
||||
|
||||
class Classes(Base):
|
||||
__tablename__ = 'classes'
|
||||
id = Column(Integer,primary_key=True)
|
||||
cno = Column(String(10))
|
||||
name = Column(String(50))
|
||||
teachers = relationship('Teacher',secondary=association_table,back_populates='classes')
|
||||
```
|
||||
|
||||
要创建一个多对多的关系表,首先需要一个中间表,通过`Table`来创建一个中间表。上例中第一个参数`teacher_classes`代表的是中间表的表名,第二个参数是`Base`的元类,第三个和第四个参数就是要连接的两个表,其中`Column`第一个参数是表示的是连接表的外键名,第二个参数表示这个外键的类型,第三个参数表示要外键的表名和字段。
|
||||
创建完中间表以后,还需要在两个表中进行绑定,比如在`Teacher`中有一个`classes`属性,来绑定`Classes`表,并且通过`secondary`参数来连接中间表。同理,`Classes`表连接`Teacher`表也是如此。定义完类后,之后就是添加数据,请看以下示例:
|
||||
|
||||
```python
|
||||
teacher1 = Teacher(tno='t1111',name='xiaotuo',age=10)
|
||||
teacher2 = Teacher(tno='t2222',name='datuo',age=10)
|
||||
classes1 = Classes(cno='c1111',name='english')
|
||||
classes2 = Classes(cno='c2222',name='math')
|
||||
teacher1.classes = [classes1,classes2]
|
||||
teacher2.classes = [classes1,classes2]
|
||||
classes1.teachers = [teacher1,teacher2]
|
||||
classes2.teachers = [teacher1,teacher2]
|
||||
session.add(teacher1)
|
||||
session.add(teacher2)
|
||||
session.add(classes1)
|
||||
session.add(classes2)
|
||||
```
|
||||
|
||||
### 5.5 `ORM`层面的`CASCADE`:
|
||||
|
||||
如果将数据库的外键设置为`RESTRICT`,那么在`ORM`层面,删除了父表中的数据,那么从表中的数据将会`NULL`。如果不想要这种情况发生,那么应该将这个值的`nullable=False`。
|
||||
|
||||
在`SQLAlchemy`,只要将一个数据添加到`session`中,和他相关联的数据都可以一起存入到数据库中了。这些是怎么设置的呢?其实是通过`relationship`的时候,有一个关键字参数`cascade`可以设置这些属性:
|
||||
|
||||
1. `save-update`:默认选项。在添加一条数据的时候,会把其他和他相关联的数据都添加到数据库中。这种行为就是`save-update`属性影响的。
|
||||
2. `delete`:表示当删除某一个模型中的数据的时候,是否也删掉使用`relationship`和他关联的数据。
|
||||
3. `delete-orphan`:表示当对一个ORM对象解除了父表中的关联对象的时候,自己便会被删除掉。当然如果父表中的数据被删除,自己也会被删除。这个选项只能用在一对多上,不能用在多对多以及多对一上。并且还需要在子模型中的`relationship`中,增加一个`single_parent=True`的参数。
|
||||
4. `merge`:默认选项。当在使用`session.merge`,合并一个对象的时候,会将使用了`relationship`相关联的对象也进行`merge`操作。
|
||||
5. `expunge`:移除操作的时候,会将相关联的对象也进行移除。这个操作只是从session中移除,并不会真正的从数据库中删除。
|
||||
6. `all`:是对`save-update, merge, refresh-expire, expunge, delete`几种的缩写。
|
||||
|
||||
## 6. 查询高级
|
||||
|
||||
### 6.1 排序:
|
||||
|
||||
1. `order_by`:可以指定根据这个表中的某个字段进行排序,如果在前面加了一个`-`,代表的是降序排序。
|
||||
|
||||
2. 在模型定义的时候指定默认排序:有些时候,不想每次在查询的时候都指定排序的方式,可以在定义模型的时候就指定排序的方式。有以下两种方式:
|
||||
|
||||
- relationship的order_by参数:在指定`relationship`的时候,传递`order_by`参数来指定排序的字段。
|
||||
|
||||
- 在模型定义中,添加以下代码:
|
||||
|
||||
```
|
||||
__mapper_args__ = {
|
||||
"order_by": title
|
||||
}
|
||||
```
|
||||
|
||||
即可让文章使用标题来进行排序。
|
||||
|
||||
3. 正向排序和反向排序:默认情况是从小到大,从前到后排序的,如果想要反向排序,可以调用排序的字段的`desc`方法。
|
||||
|
||||
### 6.2 limit、offset和切片:
|
||||
|
||||
1. `limit`:可以限制每次查询的时候只查询几条数据。
|
||||
2. `offset`:可以限制查找数据的时候过滤掉前面多少条。
|
||||
3. 切片:可以对`Query`对象使用切片操作,来获取想要的数据。
|
||||
|
||||
### 6.3 懒加载:
|
||||
|
||||
在一对多,或者多对多的时候,如果想要获取多的这一部分的数据的时候,往往能通过一个属性就可以全部获取了。比如有一个作者,想要或者这个作者的所有文章,那么可以通过`user.articles`就可以获取所有的。但有时候我们不想获取所有的数据,比如只想获取这个作者今天发表的文章,那么这时候我们可以给`relationship`传递一个`lazy='dynamic'`,以后通过`user.articles`获取到的就不是一个列表,而是一个`AppendQuery`对象了。这样就可以对这个对象再进行一层过滤和排序等操作。
|
||||
|
||||
### 6.4 查询高级:
|
||||
|
||||
1. group_by:
|
||||
|
||||
根据某个字段进行分组。比如想要根据性别进行分组,来统计每个分组分别有多少人,那么可以使用以下代码来完成:
|
||||
|
||||
```python
|
||||
session.query(User.gender,func.count(User.id)).group_by(User.gender).all()
|
||||
```
|
||||
|
||||
2. having:
|
||||
|
||||
`having`是对查找结果进一步过滤。比如只想要看未成年人的数量,那么可以首先对年龄进行分组统计人数,然后再对分组进行`having`过滤。示例代码如下:
|
||||
|
||||
```python
|
||||
result = session.query(User.age,func.count(User.id)).group_by(User.age).having(User.age >= 18).all()
|
||||
```
|
||||
|
||||
3. join方法:
|
||||
|
||||
`join`查询分为两种,一种是`inner join`,另一种是`outer join`。默认的是`inner join`,如果指定`left join`或者是`right join`则为`outer join`。如果想要查询`User`及其对应的`Address`,则可以通过以下方式来实现:
|
||||
|
||||
```python
|
||||
for u,a in session.query(User,Address).filter(User.id==Address.user_id).all():
|
||||
print(u)
|
||||
print(a)
|
||||
# 输出结果:
|
||||
> <User (id=1,name='ed',fullname='Ed Jason',password='123456')>
|
||||
> <Address id=4,email=ed@google.com,user_id=1>
|
||||
```
|
||||
|
||||
这是通过普通方式的实现,也可以通过`join`的方式实现,更加简单:
|
||||
|
||||
```python
|
||||
for u,a in session.query(User,Address).join(Address).all():
|
||||
print(u)
|
||||
print(a)
|
||||
# 输出结果:
|
||||
> <User (id=1,name='ed',fullname='Ed Jason',password='123456')>
|
||||
> <Address id=4,email=ed@google.com,user_id=1>
|
||||
```
|
||||
|
||||
当然,如果采用`outerjoin`,可以获取所有`user`,而不用在乎这个`user`是否有`address`对象,并且`outerjoin`默认为左外查询:
|
||||
|
||||
```python
|
||||
for instance in session.query(User,Address).outerjoin(Address).all():
|
||||
print(instance)
|
||||
|
||||
# 输出结果:
|
||||
(<User (id=1,name='ed',fullname='Ed Jason',password='123456')>, <Address id=4,email=ed@google.com,user_id=1>)
|
||||
(<User (id=2,name='xt',fullname='xiaotuo',password='123')>, None)
|
||||
```
|
||||
|
||||
4. 别名:
|
||||
|
||||
当多表查询的时候,有时候同一个表要用到多次,这时候用别名就可以方便的解决命名冲突的问题了:
|
||||
|
||||
```python
|
||||
from sqlalchemy.orm import aliased
|
||||
adalias1 = aliased(Address)
|
||||
adalias2 = aliased(Address)
|
||||
for username,email1,email2 in session.query(User.name,adalias1.email_address,adalias2.email_address).join(adalias1).join(adalias2).all():
|
||||
print(username,email1,email2)
|
||||
```
|
||||
|
||||
5. 子查询:
|
||||
|
||||
`sqlalchemy`也支持子查询,比如现在要查找一个用户的用户名以及该用户的邮箱地址数量。要满足这个需求,可以在子查询中找到所有用户的邮箱数(通过group by合并同一用户),然后再将结果放在父查询中进行使用:
|
||||
|
||||
```python
|
||||
from sqlalchemy.sql import func
|
||||
# 构造子查询
|
||||
stmt = session.query(Address.user_id.label('user_id'),func.count(*).label('address_count')).group_by(Address.user_id).subquery()
|
||||
|
||||
# 将子查询放到父查询中
|
||||
for u,count in session.query(User,stmt.c.address_count).outerjoin(stmt,User.id==stmt.c.user_id).order_by(User.id):
|
||||
print(u,count)
|
||||
```
|
||||
|
||||
从上面我们可以看到,一个查询如果想要变为子查询,则是通过`subquery()`方法实现,变成子查询后,通过`子查询.c`属性来访问查询出来的列。以上方式只能查询某个对象的具体字段,如果要查找整个实体,则需要通过`aliased`方法,示例如下:
|
||||
|
||||
```python
|
||||
stmt = session.query(Address)
|
||||
adalias = aliased(Address,stmt)
|
||||
for user,address in session.query(User,stmt).join(stmt,User.addresses):
|
||||
print(user,address)
|
||||
```
|
||||
|
||||
## 7. Flask-SQLAlchemy库
|
||||
|
||||
另外一个库,叫做`Flask-SQLAlchemy`,`Flask-SQLAlchemy`是对`SQLAlchemy`进行了一个简单的封装,使得我们在`flask`中使用`sqlalchemy`更加的简单。可以通过`pip install flask-sqlalchemy`。使用`Flask-SQLAlchemy`的流程如下:
|
||||
|
||||
1. 数据库初始化:数据库初始化不再是通过`create_engine`,请看以下示例:
|
||||
|
||||
```python
|
||||
from flask import Flask
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from constants import DB_URI
|
||||
app = Flask(__name__)
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URI
|
||||
db = SQLAlchemy(app)
|
||||
```
|
||||
|
||||
2. `ORM`类:之前都是通过`Base = declarative_base()`来初始化一个基类,然后再继承,在`Flask-SQLAlchemy`中更加简单了(代码依赖以上示例):
|
||||
|
||||
```python
|
||||
class User(db.Model):
|
||||
id = db.Column(db.Integer,primary_key=True)
|
||||
username = db.Column(db.String(80),unique=True)
|
||||
email = db.Column(db.String(120),unique=True)
|
||||
def __init__(self,username,email):
|
||||
self.username = username
|
||||
self.email = email
|
||||
def __repr__(self):
|
||||
return '<User %s>' % self.username
|
||||
```
|
||||
|
||||
3. 映射模型到数据库表:使用`Flask-SQLAlchemy`所有的类都是继承自`db.Model`,并且所有的`Column`和数据类型也都成为`db`的一个属性。但是有个好处是不用写表名了,`Flask-SQLAlchemy`会自动将类名小写化,然后映射成表名。
|
||||
写完类模型后,要将模型映射到数据库的表中,使用以下代码创建所有的表:
|
||||
|
||||
```python
|
||||
db.create_all()
|
||||
```
|
||||
|
||||
4. 添加数据:这时候就可以在数据库中看到已经生成了一个`user`表了。接下来添加数据到表中:
|
||||
|
||||
```python
|
||||
admin = User('admin','admin@example.com')
|
||||
guest = User('guest','guest@example.com')
|
||||
db.session.add(admin)
|
||||
db.session.add(guest)
|
||||
db.session.commit()
|
||||
```
|
||||
|
||||
添加数据和之前的没有区别,只是`session`成为了一个`db`的属性。
|
||||
|
||||
5. 查询数据:查询数据不再是之前的`session.query`了,而是将`query`属性放在了`db.Model`上,所以查询就是通过`Model.query`的方式进行查询了:
|
||||
|
||||
```python
|
||||
users = User.query.all()
|
||||
# 再如:
|
||||
admin = User.query.filter_by(username='admin').first()
|
||||
# 或者:
|
||||
admin = User.query.filter(User.username='admin').first()
|
||||
```
|
||||
|
||||
6. 删除数据:删除数据跟添加数据类似,只不过`session`是`db`的一个属性而已:
|
||||
|
||||
```python
|
||||
db.session.delete(admin)
|
||||
db.session.commit()
|
||||
```
|
||||
|
||||
## 8. MySQL8图文教程
|
||||
|
||||
### 8.1 下载:
|
||||
|
||||
在以下链接中下载`MySQL8 Community`:https://dev.mysql.com/downloads/windows/installer/8.0.html
|
||||

|
||||
|
||||
### 8.2 安装:
|
||||
|
||||
下载完成后,就可以双击`msi`文件进行安装了。安装步骤如下。
|
||||
|
||||
1. 第一步:选择`Developer Default`:
|
||||

|
||||
2. 点击Execute:
|
||||

|
||||
然后再点击`Next`执行下一步
|
||||

|
||||
3. 点击Next:
|
||||

|
||||
4. 保持默认值,点击Next:
|
||||

|
||||
5. 设置密码加密方式,用默认的即可,然后点击Next:
|
||||

|
||||
6. 设置密码,两次密码输入保持一致,以后登录`MySQL`就要用这个密码了,所以要记住:
|
||||

|
||||
7. 保持默认值,点击Next:
|
||||

|
||||
8. 点击Next:
|
||||

|
||||
执行完后点击Finish
|
||||

|
||||
9. 重新点击Next:
|
||||

|
||||
10. 点击Next:
|
||||

|
||||
11. 输入密码,点击Check:
|
||||

|
||||
12. 点击Finish,完成安装:
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
BIN
04.Flask/04.SQLAlchemy/2wrbfuJzg3RVPL8UaLnw8U.png
Normal file
After Width: | Height: | Size: 138 KiB |
BIN
04.Flask/04.SQLAlchemy/6jGy66928WdhmKVvX6PtgY.png
Normal file
After Width: | Height: | Size: 97 KiB |
BIN
04.Flask/04.SQLAlchemy/8P5sFSmXzjYHNiN76MStNS.png
Normal file
After Width: | Height: | Size: 169 KiB |
BIN
04.Flask/04.SQLAlchemy/JeVsr6NJbo73HJhN2qEC5o.png
Normal file
After Width: | Height: | Size: 106 KiB |
BIN
04.Flask/04.SQLAlchemy/JkerUJzxSe9gkEZM8kq4td.png
Normal file
After Width: | Height: | Size: 138 KiB |
BIN
04.Flask/04.SQLAlchemy/MW2uxnkekaQG5SiWFcLcfX.png
Normal file
After Width: | Height: | Size: 101 KiB |
BIN
04.Flask/04.SQLAlchemy/PYHwMTRXxKjfBKyYG6b2Vi.png
Normal file
After Width: | Height: | Size: 166 KiB |
BIN
04.Flask/04.SQLAlchemy/TCiEyKKyv5b9iJRT48HbMY.png
Normal file
After Width: | Height: | Size: 117 KiB |
BIN
04.Flask/04.SQLAlchemy/XYQtENVVzPvVVARq7Qt74D.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
04.Flask/04.SQLAlchemy/Yfgrhaxrx6NCj9gSHFbHuV.png
Normal file
After Width: | Height: | Size: 160 KiB |
BIN
04.Flask/04.SQLAlchemy/ZhBkRdPgvRrnfAQrseTfs3.png
Normal file
After Width: | Height: | Size: 118 KiB |
BIN
04.Flask/04.SQLAlchemy/axbEskKZYY2XaaKfPgrcnD.png
Normal file
After Width: | Height: | Size: 209 KiB |
BIN
04.Flask/04.SQLAlchemy/eyw6PKK6tH8uAXPtd9AwiD.png
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
04.Flask/04.SQLAlchemy/hT2fsYR8NemfdcVhUpjYSQ.png
Normal file
After Width: | Height: | Size: 157 KiB |
BIN
04.Flask/04.SQLAlchemy/mZEdUQoEPJuyXBtZbWsyRF.png
Normal file
After Width: | Height: | Size: 107 KiB |
BIN
04.Flask/04.SQLAlchemy/p2YQ6oveDpNYrYQsnejFyV.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
04.Flask/04.SQLAlchemy/rHS8Ny8FHDrwhvbUWv4XAC.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
04.Flask/04.SQLAlchemy/wYbVmHwu4Crndi4mMMjH9Z.png
Normal file
After Width: | Height: | Size: 142 KiB |
BIN
04.Flask/04.SQLAlchemy/wkgYVxahsubMtRv4NrMk9h.png
Normal file
After Width: | Height: | Size: 146 KiB |
284
04.Flask/05.ORM迁移.md
Normal file
@@ -0,0 +1,284 @@
|
||||
# ORM迁移
|
||||
|
||||
## 1. alembic
|
||||
|
||||
`alembic`是`sqlalchemy`的作者开发的。用来做`OMR`模型与数据库的迁移与映射。`alembic`使用方式跟`git`有点了类似,表现在两个方面。
|
||||
|
||||
1. 第一个,`alembic`的所有命令都是以`alembic`开头
|
||||
2. 第二,`alembic`的迁移文件也是通过版本进行控制的。首先,通过`pip install alembic`进行安装。
|
||||
|
||||
以下将解释`alembic`的用法。
|
||||
|
||||
### 1.1 操作步骤
|
||||
|
||||
#### 1.1.1 初始化仓库:
|
||||
|
||||
初始化`alembic`仓库。在终端中,`cd`到你的项目目录中,然后执行命令`alembic init alembic`,创建一个名叫`alembic`的仓库。
|
||||
|
||||
#### 1.1.2 创建模型(ORM)类:
|
||||
|
||||
比如这里创建一个`models.py`模块,然后在里面定义你的模型类,示例代码如下:
|
||||
|
||||
```python
|
||||
from sqlalchemy import Column,Integer,String,create_engine,Text
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
Base = declarative_base()
|
||||
class User(Base):
|
||||
__tablename__ = 'user'
|
||||
|
||||
id = Column(Integer,primary_key=True)
|
||||
username = Column(String(20),nullable=False)
|
||||
password = Column(String(100),nullable=False)
|
||||
|
||||
class Article(Base):
|
||||
__tablename__ = 'article'
|
||||
|
||||
id = Column(Integer,primary_key=True)
|
||||
title = Column(String(100),nullable=False)
|
||||
content = Column(Text, nullable=False)
|
||||
```
|
||||
|
||||
#### 1.1.3 修改配置文件:
|
||||
|
||||
- 在`alembic.ini`中设置数据库的连接,`sqlalchemy.url = driver://user:pass@localhost/dbname`,比如以`mysql`数据库为例,则配置后的代码为:
|
||||
|
||||
```python
|
||||
sqlalchemy.url = mysql+mysqldb://root:root@localhost/alembic_demo?charset=utf8
|
||||
```
|
||||
|
||||
- 为了使用模型类更新数据库,需要在`env.py`文件中设置`target_metadata`,默认为`target_metadata=None`。使用`sys`模块把当前项目的路径导入到`path`中:
|
||||
|
||||
```python
|
||||
import os
|
||||
import sys
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../")
|
||||
from models import Base
|
||||
... #省略代码
|
||||
target_metadata = Base.metadata # 设置创建模型的元类
|
||||
... #省略代码
|
||||
```
|
||||
|
||||
#### 1.1.4 生成迁移文件
|
||||
|
||||
使用命令`alembic revision --autogenerate -m "message"`可以将当前模型中的状态生成迁移文件。
|
||||
|
||||
#### 1.1.5 更新数据库
|
||||
|
||||
使用`alembic upgrade head`将刚刚生成的迁移文件,真正映射到数据库中。同理,如果要降级,那么使用`alembic downgrade head`。
|
||||
|
||||
#### 1.1.6 重复
|
||||
|
||||
如果以后修改了代码,则重复4~5的步骤。
|
||||
|
||||
### 1.2 命令和参数解释:
|
||||
|
||||
- init:创建一个`alembic`仓库。
|
||||
- revision:创建一个新的版本文件。
|
||||
- –autogenerate:自动将当前模型的修改,生成迁移脚本。
|
||||
- -m:本次迁移做了哪些修改,用户可以指定这个参数,方便回顾。
|
||||
- upgrade:将指定版本的迁移文件映射到数据库中,会执行版本文件中的`upgrade`函数。如果有多个迁移脚本没有被映射到数据库中,那么会执行多个迁移脚本。
|
||||
- [head]:代表最新的迁移脚本的版本号。
|
||||
- downgrade:会执行指定版本的迁移文件中的`downgrade`函数。
|
||||
- heads:展示head指向的脚本文件版本号。
|
||||
- history:列出所有的迁移版本及其信息。
|
||||
- current:展示当前数据库中的版本号。
|
||||
|
||||
另外,在你第一次执行`upgrade`的时候,就会在数据库中创建一个名叫`alembic_version`表,这个表只会有一条数据,记录当前数据库映射的是哪个版本的迁移文件。
|
||||
|
||||
### 1.3 经典错误:
|
||||
|
||||
| 错误描述 | 原因 | 解决办法 |
|
||||
| :----------------------------------------------------------- | :----------------------------------------------------------- | :----------------------------------------------------------- |
|
||||
| `FAILED: Target database is not up to date.` | 主要是`heads`和`current`不相同。`current`落后于`heads`的版本。 | 将current移动到head上。alembic upgrade head |
|
||||
| `FAILED: Can't locate revision identified by '77525ee61b5b'` | 数据库中存的版本号不在迁移脚本文件中 | 删除数据库的`alembic_version`表中的数据,重新执行`alembic upgrade head` |
|
||||
|
||||
## 2. Flask-Migrate插件
|
||||
|
||||
在实际的开发环境中,经常会发生数据库修改的行为。一般我们修改数据库不会直接手动的去修改,而是去修改`ORM`对应的模型,然后再把模型映射到数据库中。这时候如果有一个工具能专门做这种事情,就显得非常有用了,而`flask-migrate`就是做这个事情的。`flask-migrate`是基于`Alembic`进行的一个封装,并集成到`Flask`中,而所有的迁移操作其实都是`Alembic`做的,他能跟踪模型的变化,并将变化映射到数据库中。
|
||||
|
||||
使用`Flask-Migrate`需要安装,命令如下:
|
||||
|
||||
```
|
||||
pip install flask-migrate
|
||||
```
|
||||
|
||||
### 2.1 基本用法
|
||||
|
||||
要让`Flask-Migrate`能够管理`app`中的数据库,需要使用`Migrate(app,db)`来绑定`app`和数据库。假如现在有以下`app`文件:
|
||||
|
||||
```python
|
||||
from flask import Flask
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from constants import DB_URI
|
||||
from flask_migrate import Migrate
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URI
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
|
||||
db = SQLAlchemy(app)
|
||||
## 绑定app和数据库
|
||||
migrate = Migrate(app,db)
|
||||
|
||||
class User(db.Model):
|
||||
id = db.Column(db.Integer,primary_key=True)
|
||||
username = db.Column(db.String(20))
|
||||
|
||||
addresses = db.relationship('Address',backref='user')
|
||||
|
||||
class Address(db.Model):
|
||||
id = db.Column(db.Integer,primary_key=True)
|
||||
email_address = db.Column(db.String(50))
|
||||
user_id = db.Column(db.Integer,db.ForeignKey('user.id'))
|
||||
|
||||
db.create_all()
|
||||
|
||||
@app.route('/')
|
||||
def hello_world():
|
||||
return 'Hello World!'
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run()
|
||||
```
|
||||
|
||||
之后,就可以在命令行中映射`ORM`了。首先需要初始化一个迁移文件夹:
|
||||
|
||||
```python
|
||||
flask db init
|
||||
```
|
||||
|
||||
然后再把当前的模型添加到迁移文件中:
|
||||
|
||||
```python
|
||||
flask db migrate
|
||||
```
|
||||
|
||||
最后再把迁移文件中对应的数据库操作,真正的映射到数据库中:
|
||||
|
||||
```python
|
||||
flask db upgrade
|
||||
```
|
||||
|
||||
### 2.2 项目重构
|
||||
|
||||
现在是所有代码都写在一个文件中,这样会导致文件会越来越乱。所以进行一下项目重构,设置为以下的目录结构:
|
||||

|
||||
以下对各个文件的作用进行解释。
|
||||
|
||||
#### 2.2.1 constants.py文件
|
||||
|
||||
常量文件,用来存放数据库配置。
|
||||
|
||||
```python
|
||||
## constants.py
|
||||
HOSTNAME = '127.0.0.1'
|
||||
PORT = '3306'
|
||||
DATABASE = 'xt_flask_migrate'
|
||||
USERNAME = 'root'
|
||||
PASSWORD = 'root'
|
||||
DB_URI = 'mysql+mysqldb://{}:{}@{}:{}/{}'.format(USERNAME,PASSWORD,HOSTNAME,PORT,DATABASE)
|
||||
```
|
||||
|
||||
#### 2.2.2 ext.py文件
|
||||
|
||||
把`db`变量放到一个单独的文件,而不是放在主`app`文件。这样做的目的是为了在大型项目中如果`db`被多个模型文件引用的话,会造成`from your_app import db`这样的方式,但是往往也在`your_app.py`中也会引入模型文件定义的类,这就造成了循环引用。所以最好的办法是把它放在不依赖其他模块的独立文件中。
|
||||
|
||||
```python
|
||||
## ext.py
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
db = SQLAlchemy()
|
||||
```
|
||||
|
||||
#### 2.2.3 models.py文件
|
||||
|
||||
模型文件,用来存放所有的模型,并且注意,因为这里使用的是`flask-script`的方式进行模型和表的映射,因此不需要使用`db.create_all()`的方式创建数据库。
|
||||
|
||||
```python
|
||||
## models.py
|
||||
from ext import db
|
||||
class User(db.Model):
|
||||
id = db.Column(db.Integer,primary_key=True)
|
||||
username = db.Column(db.String(50))
|
||||
addresses = db.relationship('Address',backref='user')
|
||||
|
||||
def __init__(self,username):
|
||||
self.username = username
|
||||
|
||||
class Address(db.Model):
|
||||
id = db.Column(db.Integer,primary_key=True)
|
||||
email_address = db.Column(db.String(50))
|
||||
user_id = db.Column(db.Integer,db.ForeignKey('user.id'))
|
||||
|
||||
def __init__(self,email_address):
|
||||
self.email_address = email_address
|
||||
```
|
||||
|
||||
#### 2.2.4 app.py文件
|
||||
|
||||
这个是主`app`文件,运行文件。并且因为`db`被放到另外一个文件中,所以使用`db.init_app(app)`的方式来绑定数据库。
|
||||
|
||||
```python
|
||||
## app.py
|
||||
from flask import Flask
|
||||
from ext import db
|
||||
|
||||
app = Flask(__name__)
|
||||
db.init_app(app)
|
||||
|
||||
@app.route('/')
|
||||
def hello_world():
|
||||
return 'Hello World!'
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run()
|
||||
```
|
||||
|
||||
### 2.3 迁移命令:
|
||||
|
||||
通过以上项目重构后,后续我们就只要通过以下三个命令即可完成迁移操作了。
|
||||
|
||||
1. 运行命令来初始化迁移文件:
|
||||
|
||||
```python
|
||||
python manage.py db init
|
||||
```
|
||||
|
||||
2. 运行命令来将模型的映射添加到文件中:
|
||||
|
||||
```python
|
||||
python manage.py db migrate
|
||||
```
|
||||
|
||||
3. 添加将映射文件真正的映射到数据库中:
|
||||
|
||||
```python
|
||||
python manage.py db upgrade
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
BIN
04.Flask/05.ORM迁移/rKAhcte9Fcf6DXAeQATfaV.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
319
04.Flask/06.Flask-WTF插件.md
Normal file
@@ -0,0 +1,319 @@
|
||||
# Flask-WTF插件
|
||||
|
||||
## 1. Flask-WTF表单验证
|
||||
|
||||
`Flask-WTF`是简化了`WTForms`操作的一个第三方库。`WTForms`表单的两个主要功能是验证用户提交数据的合法性以及渲染模板。当然还包括一些其他的功能:`CSRF保护`,文件上传等。安装`Flask-WTF`默认也会安装`WTForms`,因此使用以下命令来安装`Flask-WTF`:
|
||||
|
||||
```
|
||||
pip install flask-wtf
|
||||
```
|
||||
|
||||
### 1.1 表单验证:
|
||||
|
||||
安装完`Flask-WTF`后。来看下第一个功能,就是用表单来做数据验证,现在有一个`forms.py`文件,然后在里面创建一个`RegistForm`的注册验证表单:
|
||||
|
||||
```python
|
||||
class RegistForm(Form):
|
||||
name = StringField(validators=[length(min=4,max=25)])
|
||||
email = StringField(validators=[email()])
|
||||
password = StringField(validators=[DataRequired(),length(min=6,max=10),EqualTo('confirm')])
|
||||
confirm = StringField()
|
||||
```
|
||||
|
||||
在这个里面指定了需要上传的参数,并且指定了验证器,比如`name`的长度应该在`4-25`之间。`email`必须要满足邮箱的格式。`password`长度必须在`6-10`之间,并且应该和`confirm`相等才能通过验证。
|
||||
|
||||
写完表单后,接下来就是`regist.html`文件:
|
||||
|
||||
```python
|
||||
<form action="/regist/" method="POST">
|
||||
<table>
|
||||
<tr>
|
||||
<td>用户名:</td>
|
||||
<td><input type="text" name="name"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>邮箱:</td>
|
||||
<td><input type="email" name="email"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>密码:</td>
|
||||
<td><input type="password" name="password"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>确认密码:</td>
|
||||
<td><input type="password" name="confirm"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><input type="submit" value="提交"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
```
|
||||
|
||||
再来看视图函数`regist`:
|
||||
|
||||
```python
|
||||
@app.route('/regist/',methods=['POST','GET'])
|
||||
def regist():
|
||||
form = RegistForm(request.form)
|
||||
if request.method == 'POST' and form.validate():
|
||||
user = User(name=form.name.data,email=form.email.data,password=form.password.data)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
return u'注册成功!'
|
||||
return render_template('regist.html')
|
||||
```
|
||||
|
||||
`RegistForm`传递的是`request.form`进去进行初始化,并且判断`form.validate`会返回用户提交的数据是否满足表单的验证。
|
||||
|
||||
### 1.2 渲染模板:
|
||||
|
||||
`form`还可以渲染模板,让你少写了一丢丢的代码,比如重写以上例子,`RegistForm`表单代码如下:
|
||||
|
||||
```python
|
||||
class RegistForm(Form):
|
||||
name = StringField(u'用户名:',validators=[length(min=4,max=25)])
|
||||
email = StringField(u'邮箱:'validators=[email()])
|
||||
password = StringField(u'密码:',validators=[DataRequired(),length(min=6,max=10),EqualTo('confirm')])
|
||||
confirm = StringField(u'确认密码:')
|
||||
```
|
||||
|
||||
以上增加了第一个位置参数,用来在html文件中,做标签提示作用。
|
||||
|
||||
在`app`中的视图函数中,修改为如下:
|
||||
|
||||
```python
|
||||
@app.route('/regist/',methods=['POST','GET'])
|
||||
def regist():
|
||||
form = RegistForm(request.form)
|
||||
if request.method == 'POST' and form.validate():
|
||||
user = User(name=form.name.data,email=form.email.data,password=form.password.data)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
return u'注册成功!'
|
||||
return render_template('regist.html',form=form)
|
||||
```
|
||||
|
||||
以上唯一的不同是在渲染模板的时候传入了`form`表单参数进去,这样在模板中就可以使用表单`form`变量了。
|
||||
|
||||
接下来看下`regist.html`文件:
|
||||
|
||||
```python
|
||||
<form action="/regist/" method="POST">
|
||||
<table>
|
||||
<tr>
|
||||
<td>{{ form.name.label }}</td>
|
||||
<td>{{ form.name() }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ form.email.label }}</td>
|
||||
<td>{{ form.email() }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ form.password.label }}</td>
|
||||
<td>{{ form.password() }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ form.confirm.label }}</td>
|
||||
<td>{{ form.confirm() }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><input type="submit" value="提交"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
```
|
||||
|
||||
## 2. Flask-WTF常用字段和验证器
|
||||
|
||||
### 2.1 Field常用参数:
|
||||
|
||||
在使用`Field`的时候,经常需要传递一些参数进去,以下将对一些常用的参数进行解释:
|
||||
|
||||
- label(第一个参数):`Field`的label的文本。
|
||||
- validators:验证器。
|
||||
- id:`Field`的id属性,默认不写为该属性名。
|
||||
- default:默认值。
|
||||
- widget:指定的`html`控件。
|
||||
|
||||
### 2.2 常用Field:
|
||||
|
||||
- BooleanField:布尔类型的Field,渲染出去是`checkbox`。
|
||||
|
||||
- FileField:文件上传Field。
|
||||
|
||||
```python
|
||||
# forms.py
|
||||
from flask_wtf.file import FileField,FileAllowed,FileRequired
|
||||
class UploadForm(FlaskForm):
|
||||
avatar = FileField(u'头像:',validators=[FileRequired(),FileAllowed([])])
|
||||
|
||||
# app.py
|
||||
@app.route('/profile/',methods=('POST','GET'))
|
||||
def profile():
|
||||
form = ProfileForm()
|
||||
if form.validate_on_submit():
|
||||
filename = secure_filename(form.avatar.data.filename)
|
||||
form.avatar.data.save(os.path.join(app.config['UPLOAD_FOLDER'],filename))
|
||||
return u'上传成功'
|
||||
|
||||
return render_template('profile.html',form=form)
|
||||
```
|
||||
|
||||
- FloatField:浮点数类型的Field,但是渲染出去的时候是`text`的input。
|
||||
|
||||
- IntegerField:整形的Field。同FloatField。
|
||||
|
||||
- RadioField:`radio`类型的`input`。表单例子如下:
|
||||
|
||||
```python
|
||||
# form.py
|
||||
class RegistrationForm(FlaskForm):
|
||||
gender = wtforms.RadioField(u'性别:',validators=[DataRequired()])
|
||||
```
|
||||
|
||||
模板文件代码如下:
|
||||
|
||||
```python
|
||||
<tr>
|
||||
<td>
|
||||
{{ form.gender.label }}
|
||||
</td>
|
||||
<td>
|
||||
{% for gender in form.gender %}
|
||||
{{ gender.label }}
|
||||
{{ gender }}
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
```
|
||||
|
||||
`app.py`文件的代码如下,给`gender`添加了`choices`:
|
||||
|
||||
```python
|
||||
@app.route('/register/',methods=['POST','GET'])
|
||||
def register():
|
||||
form = RegistrationForm()
|
||||
form.gender.choices = [('1',u'男'),('2',u'女')]
|
||||
if form.validate_on_submit():
|
||||
return u'success'
|
||||
|
||||
return render_template('register.html',form=form)
|
||||
```
|
||||
|
||||
- SelectField:类似于`RadioField`。看以下示例:
|
||||
|
||||
```python
|
||||
# forms.py
|
||||
class ProfileForm(FlaskForm):
|
||||
language = wtforms.SelectField('Programming Language',choices=[('cpp','C++'),('py','python'),('text','Plain Text')],validators=[DataRequired()])
|
||||
```
|
||||
|
||||
再来看`app.py`文件:
|
||||
|
||||
```python
|
||||
@app.route('/profile/',methods=('POST','GET'))
|
||||
def profile():
|
||||
form = ProfileForm()
|
||||
if form.validate_on_submit():
|
||||
print form.language.data
|
||||
return u'上传成功'
|
||||
return render_template('profile.html',form=form)
|
||||
```
|
||||
|
||||
模板文件为:
|
||||
|
||||
```html
|
||||
<form action="/profile/" method="POST">
|
||||
{{ form.csrf_token }}
|
||||
{{ form.language.label }}
|
||||
{{ form.language() }}
|
||||
<input type="submit">
|
||||
</form>
|
||||
```
|
||||
|
||||
- StringField:渲染到模板中的类型为`<input type='text'>`,并且是最基本的文本验证。
|
||||
|
||||
- PasswordField:渲染出来的是一个`password`的`input`标签。
|
||||
|
||||
- TextAreaField:渲染出来的是一个`textarea`。
|
||||
|
||||
### 2.3 常用的验证器:
|
||||
|
||||
数据发送过来,经过表单验证,因此需要验证器来进行验证,以下对一些常用的内置验证器进行讲解:
|
||||
|
||||
- Email:验证上传的数据是否为邮箱。
|
||||
- EqualTo:验证上传的数据是否和另外一个字段相等,常用的就是密码和确认密码两个字段是否相等。
|
||||
- InputRequired:原始数据的需要验证。如果不是特殊情况,应该使用`InputRequired`。
|
||||
- Length:长度限制,有min和max两个值进行限制。
|
||||
- NumberRange:数字的区间,有min和max两个值限制,如果处在这两个数字之间则满足。
|
||||
- Regexp:自定义正则表达式。
|
||||
- URL:必须要是`URL`的形式。
|
||||
- UUID:验证`UUID`。
|
||||
|
||||
### 2.4 自定义验证字段:
|
||||
|
||||
使用`validate_fieldname(self,field)`可以对某个字段进行更加详细的验证,如下:
|
||||
|
||||
```python
|
||||
class ProfileForm(FlaskForm):
|
||||
name = wtforms.StringField('name',[validators.InputRequired()])
|
||||
def validate_name(self,field):
|
||||
if len(field.data) > 5:
|
||||
raise wtforms.ValidationError(u'超过5个字符')
|
||||
```
|
||||
|
||||
## 3. CSRF保护:
|
||||
|
||||
在flask的表单中,默认是开启了`csrf`保护功能的,如果你想关闭表单的`csrf`保护,可以在初始化表单的时候传递`csrf_enabled=False`进去来关闭`csrf`保护。如果你想关闭这种默认的行为。如果你想在没有表单存在的请求视图函数中也添加`csrf`保护,可以开启全局的`csrf`保护功能:
|
||||
|
||||
```python
|
||||
csrf = CsrfProtect()
|
||||
csrf.init_app(app)
|
||||
```
|
||||
|
||||
或者是针对某一个视图函数,使用`csrf.protect`装饰器来开启`csrf`保护功能。并且如果已经开启了全局的`csrf`保护,想要关闭某个视图函数的`csrf`保护功能,可以使用`csrf.exempt`装饰器来取消本视图函数的保护功能。
|
||||
|
||||
### 3.1 AJAX的CSRF保护:
|
||||
|
||||
在`AJAX`中要使用`csrf`保护,则必须手动的添加`X-CSRFToken`到`Header`中。但是`CSRF`从哪里来,还是需要通过模板给渲染,而`Flask`比较推荐的方式是在`meta`标签中渲染`csrf`,如下:
|
||||
|
||||
```html
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
```
|
||||
|
||||
如果要发送`AJAX`请求,则在发送之前要添加`CSRF`,代码如下(使用了jQuery):
|
||||
|
||||
```javascript
|
||||
var csrftoken = $('meta[name=csrf-token]').attr('content')
|
||||
$.ajaxSetup({
|
||||
beforeSend: function(xhr, settings) {
|
||||
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
|
||||
xhr.setRequestHeader("X-CSRFToken", csrftoken)
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
174
04.Flask/07.上下文与信号.md
Normal file
@@ -0,0 +1,174 @@
|
||||
# 上下文与信号
|
||||
|
||||
## 1. Flask上下文
|
||||
|
||||
`Flask`项目中有两个上下文,一个是应用上下文(app),另外一个是请求上下文(request)。请求上下文`request`和应用上下文`current_app`都是一个全局变量。所有请求都共享的。`Flask`有特殊的机制可以保证每次请求的数据都是隔离的,即A请求所产生的数据不会影响到B请求。所以可以直接导入`request`对象,也不会被一些脏数据影响了,并且不需要在每个函数中使用request的时候传入`request`对象。这两个上下文具体的实现方式和原理可以没必要详细了解。只要了解这两个上下文的四个属性就可以了:
|
||||
|
||||
- `request`:请求上下文上的对象。这个对象一般用来保存一些请求的变量。比如`method`、`args`、`form`等。
|
||||
- `session`:请求上下文上的对象。这个对象一般用来保存一些会话信息。
|
||||
- `current\_app`:返回当前的app。
|
||||
- `g`:应用上下文上的对象。处理请求时用作临时存储的对象。
|
||||
|
||||
### 1.1 常用的钩子函数
|
||||
|
||||
- before_first_request:处理第一次请求之前执行。例如以下代码:
|
||||
|
||||
```python
|
||||
@app.before_first_request
|
||||
def first_request():
|
||||
print 'first time request'
|
||||
```
|
||||
|
||||
- before_request:在每次请求之前执行。通常可以用这个装饰器来给视图函数增加一些变量。例如以下代码:
|
||||
|
||||
```python
|
||||
@app.before_request
|
||||
def before_request():
|
||||
if not hasattr(g,'user'):
|
||||
setattr(g,'user','xxxx')
|
||||
```
|
||||
|
||||
- teardown_appcontext:不管是否有异常,注册的函数都会在每次请求之后执行。
|
||||
|
||||
```python
|
||||
@app.teardown_appcontext
|
||||
def teardown(exc=None):
|
||||
if exc is None:
|
||||
db.session.commit()
|
||||
else:
|
||||
db.session.rollback()
|
||||
db.session.remove()
|
||||
```
|
||||
|
||||
- template_filter:在使用`Jinja2` 模板的时候自定义过滤器。比如可以增加一个`upper` 的过滤器(当然Jinja2已经存在这个过滤器,本示例只是为了演示作用):
|
||||
|
||||
```python
|
||||
@app.template_filter
|
||||
def upper_filter(s):
|
||||
return s.upper()
|
||||
```
|
||||
|
||||
- context_processor:上下文处理器。返回的字典中的键可以在模板上下文中使用。例如:
|
||||
|
||||
```python
|
||||
@app.context_processor
|
||||
return {'current_user':'xxx'}
|
||||
```
|
||||
|
||||
- errorhandler:errorhandler接收状态码,可以自定义返回这种状态码的响应的处理方法。例如:
|
||||
|
||||
```python
|
||||
@app.errorhandler(404)
|
||||
def page_not_found(error):
|
||||
return 'This page does not exist',404
|
||||
```
|
||||
|
||||
## 2. flask信号:
|
||||
|
||||
### 2.1 安装:
|
||||
|
||||
`flask`中的信号使用的是一个第三方插件,叫做`blinker`。通过`pip list`看一下,如果没有安装,通过以下命令即可安装`blinker`:
|
||||
|
||||
```
|
||||
pip install blinker
|
||||
```
|
||||
|
||||
### 2.2 内置信号:
|
||||
|
||||
`flask`内置集中常用的信号:
|
||||
|
||||
1. `flask.template_rendered`:模版渲染完毕后发送,示例如下:
|
||||
|
||||
```python
|
||||
from flask import template_rendered
|
||||
def log_template_renders(sender,template,context,*args):
|
||||
print 'sender:',sender
|
||||
print 'template:',template
|
||||
print 'context:',context
|
||||
|
||||
template_rendered.connect(log_template_renders,app)
|
||||
```
|
||||
|
||||
2. `flask.request_started`:请求开始之前,在到达视图函数之前发送,订阅者可以调用`request`之类的标准全局代理访问请求。示例如下:
|
||||
|
||||
```python
|
||||
def log_request_started(sender,**extra):
|
||||
print 'sender:',sender
|
||||
print 'extra:',extra
|
||||
request_started.connect(log_request_started,app)
|
||||
```
|
||||
|
||||
3. `flask.request_finished`:请求结束时,在响应发送给客户端之前发送,可以传递`response`,示例代码如下:
|
||||
|
||||
```python
|
||||
def log_request_finished(sender,response,*args):
|
||||
print 'response:',response
|
||||
request_finished.connect(log_request_finished,app)
|
||||
```
|
||||
|
||||
4. `flask.got_request_exception`:在请求过程中抛出异常时发送,异常本身会通过`exception`传递到订阅的函数。示例代码如下:
|
||||
|
||||
```python
|
||||
def log_exception_finished(sender,exception,*args):
|
||||
print 'sender:',sender
|
||||
print type(exception)
|
||||
got_request_exception.connect(log_exception_finished,app)
|
||||
```
|
||||
|
||||
5. `flask.request_tearing_down`:请求被销毁的时候发送,即使在请求过程中发生异常,也会发送,示例代码如下:
|
||||
|
||||
```python
|
||||
def log_request_tearing_down(sender,**kwargs):
|
||||
print 'coming...'
|
||||
request_tearing_down.connect(log_request_tearing_down,app)
|
||||
```
|
||||
|
||||
6. `flask.appcontext_tearing_down`:在应用上下文销毁的时候发送,它总是会被调用,即使发生异常。示例代码如下:
|
||||
|
||||
```python
|
||||
def log_appcontext_tearing_down(sender,**kwargs):
|
||||
print 'coming...'
|
||||
appcontext_tearing_down.connect(log_appcontext_tearing_down,app)
|
||||
```
|
||||
|
||||
### 2.3 自定义信号:
|
||||
|
||||
自定义信号分为3步,第一是定义一个信号,第二是监听一个信号,第三是发送一个信号。以下将对这三步进行讲解:
|
||||
|
||||
1. 定义信号:定义信号需要使用到`blinker`这个包的`Namespace`类来创建一个命名空间。比如定义一个在访问了某个视图函数的时候的信号。示例代码如下:
|
||||
|
||||
```python
|
||||
from blinker import Namespace
|
||||
|
||||
mysignal = Namespace()
|
||||
visit_signal = mysignal.signal('visit-signal')
|
||||
```
|
||||
|
||||
2. 监听信号:监听信号使用`singal`对象的`connect`方法,在这个方法中需要传递一个函数,用来接收以后监听到这个信号该做的事情。示例代码如下:
|
||||
|
||||
```python
|
||||
def visit_func(sender,username):
|
||||
print(sender)
|
||||
print(username)
|
||||
|
||||
mysignal.connect(visit_func)
|
||||
```
|
||||
|
||||
3. 发送信号:发送信号使用`singal`对象的`send`方法,这个方法可以传递一些其他参数过去。示例代码如下:
|
||||
|
||||
```python
|
||||
mysignal.send(username='zhiliao')
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
195
04.Flask/08.Flask-Restful.md
Normal file
@@ -0,0 +1,195 @@
|
||||
# Flask-Restful
|
||||
|
||||
## 1. Restful API规范
|
||||
|
||||
`restful api`是用于在前端与后台进行通信的一套规范。使用这个规范可以让前后端开发变得更加轻松。以下将讨论这套规范的一些设计细节。
|
||||
|
||||
### 1.1 协议:
|
||||
|
||||
采用`http`或者`https`协议。
|
||||
|
||||
### 1.2 数据传输格式:
|
||||
|
||||
数据之间传输的格式应该都使用`json`,而不使用`xml`。
|
||||
|
||||
### 1.3 url链接:
|
||||
|
||||
url链接中,不能有动词,只能有名词。并且对于一些名词,如果出现复数,那么应该在后面加`s`。
|
||||
|
||||
比如:获取文章列表,应该使用`articles`,而不应该使用`get_article`
|
||||
|
||||
### 1.4 HTTP请求的方法:
|
||||
|
||||
1. `GET`:从服务器上获取资源。
|
||||
2. `POST`:在服务器上新创建一个资源。
|
||||
3. `PUT`:在服务器上更新资源。(客户端提供所有改变后的数据)
|
||||
4. `PATCH`:在服务器上更新资源。(客户端只提供需要改变的属性)
|
||||
5. `DELETE`:从服务器上删除资源。
|
||||
|
||||
**示例如下:**
|
||||
|
||||
- `GET /users/`:获取所有用户。
|
||||
- `POST /user/`:新建一个用户。
|
||||
- `GET /user/id/`:根据id获取一个用户。
|
||||
- `PUT /user/id/`:更新某个id的用户的信息(需要提供用户的所有信息)。
|
||||
- `PATCH /user/id/`:更新某个id的用户信息(只需要提供需要改变的信息)。
|
||||
- `DELETE /user/id/`:删除一个用户。
|
||||
|
||||
### 1.5 状态码:
|
||||
|
||||
| 状态码 | 原生描述 | 描述 |
|
||||
| :----- | :-------------------- | :----------------------------------------------------------- |
|
||||
| 200 | OK | 服务器成功响应客户端的请求。 |
|
||||
| 400 | INVALID REQUEST | 用户发出的请求有错误,服务器没有进行新建或修改数据的操作 |
|
||||
| 401 | Unauthorized | 用户没有权限访问这个请求 |
|
||||
| 403 | Forbidden | 因为某些原因禁止访问这个请求 |
|
||||
| 404 | NOT FOUND | 用户发送的请求的url不存在 |
|
||||
| 406 | NOT Acceptable | 用户请求不被服务器接收(比如服务器期望客户端发送某个字段,但是没有发送)。 |
|
||||
| 500 | Internal server error | 服务器内部错误,比如出现了bug |
|
||||
|
||||
## 2. Flask-Restful插件
|
||||
|
||||
### 2.1 介绍:
|
||||
|
||||
`Flask-Restful`是一个专门用来写`restful api`的一个插件。使用他可以快速的集成`restful api`功能。在`app`的后台以及纯`api`的后台中,这个插件可以帮助我们节省很多时间。当然,如果在普通的网站中,这个插件就显得有些鸡肋了,因为在普通的网页开发中,是需要去渲染HTML代码的,而`Flask-Restful`在每个请求中都是返回`json`格式的数据。
|
||||
|
||||
### 2.2 安装:
|
||||
|
||||
`Flask-Restful`需要在`Flask 0.8`以上的版本,在`Python2.6`或者`Python3.3`上运行。通过`pip install flask-restful`即可安装。
|
||||
|
||||
### 2.3 定义Restful的视图:
|
||||
|
||||
如果使用`Flask-Restful`,那么定义视图函数的时候,就要继承自`flask_restful.Resource`类,然后再根据当前请求的`method`来定义相应的方法。比如期望客户端是使用`get`方法发送过来的请求,那么就定义一个`get`方法。类似于`MethodView`。示例代码如下:
|
||||
|
||||
```python
|
||||
from flask import Flask,render_template,url_for
|
||||
from flask_restful import Api,Resource
|
||||
|
||||
app = Flask(__name__)
|
||||
## 用Api来绑定app
|
||||
api = Api(app)
|
||||
|
||||
class IndexView(Resource):
|
||||
def get(self):
|
||||
return {"username":"zhiliao"}
|
||||
|
||||
api.add_resource(IndexView,'/',endpoint='index')
|
||||
```
|
||||
|
||||
注意事项:
|
||||
|
||||
1. `endpoint`是用来给`url_for`反转`url`的时候指定的。如果不写`endpoint`,那么将会使用视图的名字的小写来作为`endpoint`。
|
||||
2. `add_resource`的第二个参数是访问这个视图函数的`url`,这个`url`可以跟之前的`route`一样,可以传递参数。并且还有一点不同的是,这个方法可以传递多个`url`来指定这个视图函数。
|
||||
|
||||
### 2.4 参数解析:
|
||||
|
||||
`Flask-Restful`插件提供了类似`WTForms`来验证提交的数据是否合法的包,叫做`reqparse`。以下是基本用法:
|
||||
|
||||
```python
|
||||
parser = reqparse.RequestParser()
|
||||
parser.add_argument('username',type=str,help='请输入用户名')
|
||||
args = parser.parse_args()
|
||||
```
|
||||
|
||||
`add_argument`可以指定这个字段的名字,这个字段的数据类型等。以下将对这个方法的一些参数做详细讲解:
|
||||
|
||||
1. `default`:默认值,如果这个参数没有值,那么将使用这个参数指定的值。
|
||||
2. `required`:是否必须。默认为False,如果设置为`True`,那么这个参数就必须提交上来。
|
||||
3. `type`:这个参数的数据类型,如果指定,那么将使用指定的数据类型来强制转换提交上来的值。
|
||||
4. `choices`:选项。提交上来的值只有满足这个选项中的值才符合验证通过,否则验证不通过。
|
||||
5. `help`:错误信息。如果验证失败后,将会使用这个参数指定的值作为错误信息。
|
||||
6. `trim`:是否要去掉前后的空格。
|
||||
|
||||
其中的`type`,可以使用`python`自带的一些数据类型,也可以使用`flask_restful.inputs`下的一些特定的数据类型来强制转换。比如一些常用的:
|
||||
|
||||
1. `url`:会判断这个参数的值是否是一个url,如果不是,那么就会抛出异常。
|
||||
2. `regex`:正则表达式。
|
||||
3. `date`:将这个字符串转换为`datetime.date`数据类型。如果转换不成功,则会抛出一个异常。
|
||||
|
||||
### 2.5 输出字段:
|
||||
|
||||
对于一个视图函数,你可以指定好一些字段用于返回。以后可以使用ORM模型或者自定义的模型的时候,他会自动的获取模型中的相应的字段,生成`json`数据,然后再返回给客户端。这其中需要导入`flask_restful.marshal_with`装饰器。并且需要写一个字典,来指示需要返回的字段,以及该字段的数据类型。示例代码如下:
|
||||
|
||||
```python
|
||||
class ProfileView(Resource):
|
||||
resource_fields = {
|
||||
'username': fields.String,
|
||||
'age': fields.Integer,
|
||||
'school': fields.String
|
||||
}
|
||||
|
||||
@marshal_with(resource_fields)
|
||||
def get(self,user_id):
|
||||
user = User.query.get(user_id)
|
||||
return user
|
||||
```
|
||||
|
||||
在`get`方法中,返回`user`的时候,`flask_restful`会自动的读取`user`模型上的`username`以及`age`还有`school`属性。组装成一个`json`格式的字符串返回给客户端。
|
||||
|
||||
#### 2.5.1 重命名属性:
|
||||
|
||||
很多时候你面向公众的字段名称是不同于内部的属性名。使用 attribute可以配置这种映射。比如现在想要返回`user.school`中的值,但是在返回给外面的时候,想以`education`返回回去,那么可以这样写:
|
||||
|
||||
```python
|
||||
resource_fields = {
|
||||
'education': fields.String(attribute='school')
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.5.2 默认值:
|
||||
|
||||
在返回一些字段的时候,有时候可能没有值,那么这时候可以在指定`fields`的时候给定一个默认值,示例代码如下:
|
||||
|
||||
```python
|
||||
resource_fields = {
|
||||
'age': fields.Integer(default=18)
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.5.3 复杂结构:
|
||||
|
||||
有时候想要在返回的数据格式中,形成比较复杂的结构。那么可以使用一些特殊的字段来实现。比如要在一个字段中放置一个列表,那么可以使用`fields.List`,比如在一个字段下面又是一个字典,那么可以使用`fields.Nested`。以下将讲解下复杂结构的用法:
|
||||
|
||||
```python
|
||||
class ProfileView(Resource):
|
||||
resource_fields = {
|
||||
'username': fields.String,
|
||||
'age': fields.Integer,
|
||||
'school': fields.String,
|
||||
'tags': fields.List(fields.String),
|
||||
'more': fields.Nested({
|
||||
'signature': fields.String
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
21
04.Flask/09.Cookie和Session.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Cookie和Session
|
||||
|
||||
1. `cookie`:在网站中,http请求是无状态的。也就是说即使第一次和服务器连接后并且登录成功后,第二次请求服务器依然不能知道当前请求是哪个用户。`cookie`的出现就是为了解决这个问题,第一次登录后服务器返回一些数据(cookie)给浏览器,然后浏览器保存在本地,当该用户发送第二次请求的时候,就会自动的把上次请求存储的`cookie`数据自动的携带给服务器,服务器通过浏览器携带的数据就能判断当前用户是哪个了。`cookie`存储的数据量有限,不同的浏览器有不同的存储大小,但一般不超过4KB。因此使用`cookie`只能存储一些小量的数据。
|
||||
2. `session`: session和cookie的作用有点类似,都是为了存储用户相关的信息。不同的是,`cookie`是存储在本地浏览器,`session`是一个思路、一个概念、一个服务器存储授权信息的解决方案,不同的服务器,不同的框架,不同的语言有不同的实现。虽然实现不一样,但是他们的目的都是服务器为了方便存储数据的。`session`的出现,是为了解决`cookie`存储数据不安全的问题的。
|
||||
3. `cookie`和`session`结合使用:`web`开发发展至今,`cookie`和`session`的使用已经出现了一些非常成熟的方案。在如今的市场或者企业里,一般有两种存储方式:
|
||||
- 存储在服务端:通过`cookie`存储一个`session_id`,然后具体的数据则是保存在`session`中。如果用户已经登录,则服务器会在`cookie`中保存一个`session_id`,下次再次请求的时候,会把该`session_id`携带上来,服务器根据`session_id`在`session`库中获取用户的`session`数据。就能知道该用户到底是谁,以及之前保存的一些状态信息。这种专业术语叫做`server side session`。存储在服务器的数据会更加的安全,不容易被窃取。但存储在服务器也有一定的弊端,就是会占用服务器的资源,但现在服务器已经发展至今,一些`session`信息还是绰绰有余的。
|
||||
- 将`session`数据加密,然后存储在`cookie`中。这种专业术语叫做`client side session`。`flask`采用的就是这种方式,但是也可以替换成其他形式。
|
||||
|
||||
### flask中使用cookie和session
|
||||
|
||||
1. cookies:在`Flask` 中操作`cookie` ,是通过`response` 对象来操作,可以在`response` 返回之前,通过`response.set_cookie` 来设置,这个方法有以下几个参数需要注意:
|
||||
|
||||
- key:设置的cookie的key。
|
||||
- value:key对应的value。
|
||||
- max_age:改cookie的过期时间,如果不设置,则浏览器关闭后就会自动过期。
|
||||
- expires:过期时间,应该是一个`datetime`类型。
|
||||
- domain:该cookie在哪个域名中有效。一般设置子域名,比如`cms.example.com`。
|
||||
- path:该cookie在哪个路径下有效。
|
||||
|
||||
2. session:`Flask`中的`session`是通过`from flask import session`。然后添加值key和value进去即可。并且,`Flask`中的`session`机制是将`session`信息加密,然后存储在`cookie`中。专业术语叫做`client side session`。
|
||||
|
46
index.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Python教程大纲
|
||||
|
||||
## 第一部分: 基础语法
|
||||
|
||||
- [01.Python基础](/python/01.基础语法/01.python基础.html)
|
||||
- [02.python文件操作](/python/01.基础语法/02.python文件操作.html)
|
||||
- [03.python认识函数](/python/01.基础语法/03.python认识函数.html)
|
||||
- [04.装饰器](/python/01.基础语法/04.装饰器.html)
|
||||
- [05.迭代器与生成器](/python/01.基础语法/05.迭代器与生成器.html)
|
||||
- [06.推导式](/python/01.基础语法/06.推导式.html)
|
||||
- [07.内置函数与匿名函数](/python/01.基础语法/07.内置函数与匿名函数.html)
|
||||
- [08.递归函数](/python/01.基础语法/08.递归函数.html)
|
||||
- [09.模块和包](/python/01.基础语法/09.模块和包.html)
|
||||
- [10.常用模块](/python/01.基础语法/10.常用模块.html)
|
||||
- [11.异常处理](/python/01.基础语法/12.异常处理.html)
|
||||
- [12.垃圾回收机制](/python/01.基础语法/13.垃圾回收机制.html)
|
||||
|
||||
## 第二部分: 面向对象
|
||||
|
||||
- [01.初识面向对象](/python/02.面向对象/01.初识面向对象.html)
|
||||
- [02.类空间与类之间的关系](/python/02.面向对象/02.类空间与类之间的关系.html)
|
||||
- [03.类的继承](/python/02.面向对象/03.类的继承.html)
|
||||
- [04.封装与多态](/python/02.面向对象/04.封装与多态.html)
|
||||
- [05.类的成员](/python/02.面向对象/05.类的成员.html)
|
||||
- [06.反射与双下方法](/python/02.面向对象/06.反射与双下方法.html)
|
||||
|
||||
## 第三部分: 网络编程与并发
|
||||
|
||||
- [01.网络编程基础](/python/03.网络编程与并发/01.网络编程基础.html)
|
||||
- [02.操作系统发展史](/python/03.网络编程与并发/02.操作系统发展史.html)
|
||||
- [03.多进程](/python/03.网络编程与并发/03.多进程.html)
|
||||
- [04.多线程](/python/03.网络编程与并发/04.多线程.html)
|
||||
- [05.多协程](/python/03.网络编程与并发/05.多协程.html)
|
||||
|
||||
|
||||
## 第四部分: Flask框架
|
||||
|
||||
- [01.URL与视图](/python/04.Flask/01.URL与视图.html)
|
||||
- [02.Jinja2模板](/python/04.Flask/02.Jinja2模板.html)
|
||||
- [03.视图高级](/python/04.Flask/03.视图高级.html)
|
||||
- [04.SQLAlchemy](/python/04.Flask/04.SQLAlchemy.html)
|
||||
- [05.ORM迁移](/python/04.Flask/05.ORM迁移.html)
|
||||
- [06.Flask-WTF插件](/python/04.Flask/06.Flask-WTF插件.html)
|
||||
- [07.上下文与信号](/python/04.Flask/07.上下文与信号.html)
|
||||
- [08.Flask-Restful](/python/04.Flask/08.Flask-Restful.html)
|
||||
- [09.Cookie和Session](/python/04.Flask/09.Cookie和Session.html)
|