python基本知识
一、python基础知识
python介绍
# 介绍:1.强类型 2.动态型 3.解释器语言
# 用途:web开发、桌面开发、数据分析、人工智能、大数据、测试、运维、办公自动化
变量
# 介绍:描述客观事物变化的量,如姓名、年龄、性别、身高、体重等
# 使用:先定义,后引用
# 变量名(栈区) = 变量值(堆区) - 引用:在内存中开辟一片空间存变量值,绑定给变量名
name = 'zs' # 存
print(name) # 取
# 内存管理 - 垃圾回收机制
1.引用计数 - 直接引用 a = 1 间接引用 b=a
2.标记清除 - 解决循环引用带来的内存泄露问题(删除栈区绑定) - a = [1] b = [2] a.append(b) b.append(a)
3.分代回收:用来降低引用计数的烧苗频率,提升垃圾回收的效率
# 命名规范
数字、字母、下划线,开头第一个字符不能为数字,也不能是关键字
推荐风格: 变量、函数(下划线) - xxx_yyy_zzz 类(大驼峰) - AbcDef
# 三个基本概念
# 1. == 比较值
a == b
# 2. id 查询内存地址id
id(a)
# 3. is id是否相等
a is b
# 数据类型
a = 1.11
type(a)
# 小整数池[-5,256]
pycharm 特有,解释器启动的那一刻开始,就在内存中事先申请
常量
# python中无规定,可约定变量全大写字母作为常量标识
PI = 3.14 - 圆周率
深浅拷贝
# 深浅拷贝
深拷贝 - 完全独立 - 重新开辟新空间
# 浅拷贝 - 只拷贝第一层的内存地址给新变量 - a = [1,2,3] b = [a,'c']
1.b只拷贝了c的内存地址(不可变) 和 列表a内存地址
2.a列表的内存地址是指向 1 2 3 ,但b 没有拷贝到a列表中的 1 2 3 元素的内存地址
3.那么也就是说,就算 a = [2,3] 那么 b = [2,3,'c']
#总结:1.用法: 读、写使用浅拷贝,改使用深拷贝
# 2.简单理解: 浅拷贝 只拷贝第一层,容器改变才改变
# 深拷贝 遇到容器就重造,一直找到不可变类型
数据类型
基本数据类型
# 数字类型
1.整型(int) - 不可变类型 - chr(65):A 97 - a / bin(20)、oct、hex /int('0xb',16)
2.浮点型(float) - 不可变类型 - round 精确计算: v1 = decimal.Decimal("0.1")
# 布尔类型
1.布尔值(bool):True False
# 字符串类型
1.字符串(str) - 不可变类型
- 大写/小写/去除空白/分割/替换/join/是否数字/左右填充
- 长度/索引/切片/for循环/in是否包含
# 容器类型
1.列表(list) - 可变类型 - 有序
- 追加/插入/删除(值)/删除(索引)/清空/排序
- 长度/索引(读、删、改)/切片(读、删、改)/for循环/in是否包含
2.元组(tuple) - 有序- 原则上不可变类型,但是里面有可变类型是可以修改的,但是强烈不建议
- 长度/索引/切片/for循环/in是否包含
3.字典(dict) - 可变类型 - key -value - 不连续 - 无序- 键值不重复
- get/keys/values/items
- len/索引键(读、删、改、添加)/for循环/in是否包含(键)
4.集合(set) - - 无序 - 不重复
数字类型
# 1.整型(int) - 不可变类型
- i = 65
- chr(65):A 97 - a / bin(20)、oct、hex /int('0xb',16)
# 2.浮点型(float) - 不可变类型
- round(1.23,2) # 四舍五入
- 精密计算
import decimal
v1 = decimal.Decimal("0.1")
v2 = decimal.Decimal("0.2")
v3 = v1 + v2
print(v3) # 0.3
字符串类型
# 字符串类型
1.字符串(str) - 不可变类型
- s = ' hello,world '
- s1 = 'python'
- s2 = 'nb'
独有功能
s = ' hello,world ' s1 = 'python' s2 = 'nb'
# 1.大小写
s.upper()/s.lower()
# 2.是否为数字、字母
s.isdecimal() s.isdigit() s.isalnum()
# 3.替换
s.replace('o','O')
# 4.去除左右两边的空白字符
s.strip()/lstrip()/rstrip()
# 5.分割字符-以列表的形式返回
s.split(',')
# 6.拼接
s1 + s2
','.join([s1,s2] -迭代对象
# 7.字节 -字符串 unicode ||| 字节 utf-8/gbk(文件存储、网络传输)
s.encode("utf-8") # 编码 - b'\xe4 \xb8'
s.decode("utf-8") # 解码 - str
# 8.补齐
s = 'nb'
s.center(6,'*') - **nb**
s.ljust(6,"#") - nb****
s.rjust(6,"#") - ****nb
s.zfill(5) - 000nb
# 9. 以什么开头和结尾
s.startwith('hellow')/s.endwith('world')
公共功能
s = 'hello world'
# 1.长度 - 空格占一个
len(s) - 11
# 2.索引
s[1] - e
# 3.切片 - - 顾头不顾尾 - 同向
s[2:4:1] - llo
s[::-1] - dlrow olleh
s[2:-1] - llo worl
s[-1:5:-1] - dlrow
# 4.循环 for / while
index = 0
while index < len(s):print(s[index]) index +=1
for i in s:print(i)
for i in range(len(s)):print(s[i])
# 5.成员运算
'hello' in s
'nihao' not in s
# 6.查找 - find 找不到返回 -1 index 找不到抛出异常
s.find('o') - 4 只找到第一个并返回
s.rfind( 'o') - 7 从右开始
s.index('e') - 1
# 计数
s.count('o') - 2
列表类型
# 容器类型 - 可直接改变容器元素
1.列表(list) - 可变类型 - 可存所有类型 - 建议同类型元素
- ll = [1,2.2,'ssss',True,[1,2,3],{'name':'zs'}]
独特功能
ll = [1,'a',True]
lll = [2,4,1,3]
# 1.添加 - 末尾添加
ll.append(2) - [1, 'a', True, 2]
# 2.插入 - 按索引位置添加
index_b = ll.index('a') - # 前插
index_a = ll.index('a')+1 - # 后插
ll.insert(index_b,3) - [1, 3, 'a', True, 2]
ll.insert(index_b,4) - [1, 'a', 4, True, 2]
# 3.删除
# 索引删除
ll.pop() - 默认删除最后一个
ll.pop(0) - 每次删除列表第一个元素
# 值删除
ll.remove('a') - 删除指定元素 - 不存在会报错
# 4.清空列表
ll.clear()
# 5.排序
lll.sort() - [1, 2, 3, 4]
# 6.修改/赋值
lll[0] = 111 - [111, 2, 3, 4]
# 7.合并多个列表
ll + lll - [1, 'a', True, 2, 4, 1, 3] - 有返回
ll.extends(lll) - [1, 'a', True, 2, 4, 1, 3] - 直接在ll上修改
# 8.翻转
ll.reverse() -[True, 'a', 1]
公共功能
ll = [1,'a',True]
# 1.长度
len(ll) - 3
# 2.索引
ll[1] - a
# 3.切片 - 顾头不顾尾 - 同向
ll[1:2] - ['a', True]
ll[:-1] - [1, 'a']
ll[::-1] - [True, 'a', 1]
# 4.循环 while for
index = 0
while index < len(ll):print(ll[index]) index += 1
for i in ll:print(i)
for i in range(len(ll)):print(ll[i])
# 5.成员运算
'a' in ll
'b' not in ll
# 6.查找
ll.index('a') -1
# 7.计数
ll.count(1) - 1
栈和队列
# 补充一种思想 - 栈和队列
# 实现栈功能- 后进先出
ll = []
ll.append()
ll.pop()
# 实现队列功能 - 先进先出
ll = []
ll.append('元素')
ll.pop(0)
元组类型
# 容器类型 - 可直接改变容器元素
1.元组(tuple) - 不可变但不建议变 - 可存所有类型 - 建议同类型元素
- tt = (1,2.2,'ssss',True,[1,2,3],{'name':'zs'})
# 因为元组类型默认是不可修改类型,所以即使里面有可变类型,也不要动它,就把它当作不可变类型对待
# 小提示:元组类型,可作为字典的key
公共功能
tt = (1,'a',True)
# 1.长度
len(tt) - 3
# 2.索引
tt[1] - a
# 3.切片 - 顾头不顾尾 - 同向
tt[1:2] - ['a', True]
tt[:-1] - [1, 'a']
tt[::-1] - [True, 'a', 1]
# 4.循环 while for
index = 0
while index < len(tt):print(tt[index]) index += 1
for i in tt:print(i)
for i in range(len(tt)):print(tt[i])
# 5.成员运算
'a' in tt
'b' not in tt
# 6.查找
tt.index('a') -1
# 7.计数
tt.count(1) - 1
字典类型
# 容器类型 - 可直接改变容器元素
字典(dict) - 可变类型 - key -value - 不连续 - 无序- 键值不重复
- 键不能重复,重复时数据会被覆盖。
- 键必须是可哈希类型
- 无序(Python3.6前,字典无序;Python3.6之后就是有序)
可哈希:int、bool、str、tuple
不可哈希:列表、字典
d = {
'name':'zs',
'age':18,
'isTrue':True,
'hobby':['读书','看报']
'foreign':{'name':'zs','age':18}
}
独有功能
# 字典的创建
d1 = { "K1":1, "K2":123 } # 方式一
d2 = dict(x=1, y=2, z=3) # 方式二
d3 = dict( [ ('a',1), ('b',2) ] ) # 数据类型转换
d4 = {}.fromkeys( ['a','b','c'],None ) # 根据键创建默认值
d5 = dict( zip( ('a','b','c') , [1,2,3] ) ) # 通过拉链法创建
d = {
'name':'zs',
'age':18,
'isTrue':True,
'hobby':['读书','看报']
}
# 1.获取值
d.get('name') - zs
d.get('info') - None
# 2.键 - 生成器
d.keys() - dict_keys(['name', 'age', 'isTrue', 'hobby'])
# 3.值 - 生成器
d.values() - dict_values(['zs', 18, True, ['读书', '看报']])
# 4.键值对 -生成器
d.items() - dict_items([('name', 'zs'), ('age', 18), ('isTrue', True), ('hobby', ['读书', '看报'])])
# 5.新增和修改
d.setdefault('newkey',1) - 有则不变,无则添加
d.update({'k1':1,'k2':'a'}) - 批量更新
d['info'] = '我是新添加的一个键值对' - 增加
d['info'] = '这个值我修改了' - 修改
# 6.删除键值对
d.pop('age') - 根据key值删除 - 有返回值
d.popitem() - 随机删除
公共功能
d = {
'name':'zs',
'age':18,
'isTrue':True,
'hobby':['读书','看报']
}
# 1.长度
len(d) # 返回键值对数
# 2.循环
for item in info.keys():
print(item)
for item in info.values():
print(item)
for k,v in info.items():
print(k,v)
# 3.成员运算 - 指的是对key的成员运算
'name' in d
'price' not in d
集合类型
# 容器类型 - 可直接改变容器元素
集合(set) - 无序 - 不重复 - 元素为不可变类型 - 可哈希
- 不可哈希:list、dict、set
- 可哈希:int、bool、str、tuple
ss = set(1,2.3,True,'sss')
ss = {1,2,3} # k-v 是字典,只有v是集合
独有功能
s = {1,2,3}
# 1.添加元素
s.add(4) - {1, 2, 3, 4}
s.update({1,3,5}) - {1, 2, 3, 4, 5}
# 2.删除元素
s.discard(6) - 删除元素不存在 - 不做任何操作 - 有返回值
s.remove(6) - 删除元素不存在 - 抛出异常
s.pop() - 删除第一个元素 - 集合为空抛出异常
# 3.去重
ll = [1,2,3,2,4,3]
list(set(ll)) - 去重后无序
new_ll = []
def unique_list():
for one in ll:
if one not in new_ll:
new_ll.append(one)
# 4.关系运算符
s1={1,2,3}
s2={1,2,4}
# 4.1 交集
s1 & s2 - {1, 2}
s1.intersection(s2) - {1, 2}
# 4.2 并集/合集
s1 | s2 - {1, 2, 3, 4}
s1.union(s2) - {1, 2, 3, 4}
# 4.3 差集
s1 - s2 - {3}
s1 - (s1 & s2) - {3}
s1.difference(s2) - {3}
# 4.4 对称差集 - a、b 独有
s1 ^ s2 - {3, 4}
s1.symmetric_difference(s2) - {3, 4}
# 4.5 父子集 - 包含的关系
s1={1,2,3}
s2={1,2,4}
s3={1,2}
s4 = {1,2}
print(s1 > s2) - False
print(s1 > s3) - True
print(s1 == s2) - True
公共功能
s = {1,2,3}
# 1.长度
len(s) - 3
# 2.循环
for i in s:
print(i)
# 3.成员运算
1 in s
4 not in s
布尔类型
# 显式布尔值:True,False, 比较运算符
# 隐式布尔值:0、None、空(空字符串、空列表、空字典)=》代表的布尔值为False,其余都为真
0、""、[]、()、{}、set()、None -> False
len(xx) == 0 -> False
其他均为True。
用户交互
输入input
# 关于输入的几个用法总结
a = input('请输入:')
b = input('请输入:').split(',') # 以逗号分割,以列表形式返回
a,b = input('请输入:').split(',') # 以逗号分割,解压缩,可以输入两个以逗号分隔的字符或数字
c = int(a) # 转换数据类型,通常需要判断
d = eval(a) # 可以识别输入的数据类型
输出 print
print(任意数据类型,end='分割符号') # 若不指定分割符号,会默认打印后换行
字符串格式化
占位符 %
# 1.占位符 %
# 常用几种 %s:字符串 %d:整型
# 按位置顺序
print('%s age is %d' %('tom',18) ) #tom age is 18
# 按字典
print('%(name)s age is %(age)s' %{'name':'tom','age':'18'}) # tom age is 18
# %S的使用 ,接受任意类型数据
print('%s' % [1,2,3]) # [1, 2, 3]
print('%s' % {'a':1,'b':2}) # {'a': 1, 'b': 2}
f.format(推荐)
# 按位置顺序
print('{} age is {}'.format('tom',18) ) #tom age is 18
# 按照索引
print('{1} age is {0}'.format(18,'tom') ) #tom age is 18
# 按照key=value传值
print('{name} age is {age}'.format(age = 18,name='tom') # tom age is 18
f.str python3.5后推出(自己习惯)
# 按位置顺序
print(f'{tom} age is {age}' ) #tom age is 18
补充 - 填充与格式化
# 填充与格式化
a = 1
print(f"{a:0>10}") # 0000000001
print(f"{a:0<10}") # 1000000000
print(f"{a:0^10}") # 0000100000
# 精度控制
b = 3.1415926
print(f'{b:.2f}') #3.14
# 千分位
c = 123456789
print(f'{c:,}') #123,456,789
# 综合一个
d = 123456789.1415926
print(f'{d:*^30,.2f}') # ********123,456,789.14********
# 学生序号
e = 22
print(f'{e:0>3}') #022
print(f'{100:0>3}') # 100
流程控制
if 条件语句
if 条件1:
符合条件1,执行代码1
elif 条件2:
不符合条件1,符合条件2,执行代码2
else
以上都不符合条件,执行代码3
while 循环
index = 0
while index < 100:
执行代码1
index += 1
for 循环
for i in range(100):
print(i)
break与continue
break 在循环中使用,可以结束这一层的循环
continue 在循环中使用,可以中止这一层的这一次的后续代码,并执行下一次
补充:break比较暴力,可以通过设置一个变量值,控制多个循环循环的结束
# 提示,这里的代码,会导致死循环
flag = 0
while 0 < 100:
print('hello world')
if flag > 88:
break # 中止循环
if flag > 50:
continue # 结束本次循环
flag += 1
for i in [i for i in range(100)]:
if flag > 88:
break # 中止循环
if flag > 50:
continue # 结束本次循环
flag += 1
# 每个循环执行一次后就结束了
flag = 1
while flag:
print('hello world')
while flag:
print('你好,世界')
while flag:
print('666')
flag = 0
运算符
运算符种类
算术运算符
比较运算符
赋值运算符(有惊喜)
逻辑运算符
身份运算符
成员运算符
布尔运算符(拓展讲解,要了解假条件)
算术运算符
print(10 + 1)
print(10 - 1)
print(10 * 1)
print(10 / 3) #3.3333333333333335
print(10 //3) # 取整 3
print(10 % 3) # 取余 1 10 /3 = 3 余1
print(10 **3) # 幂 1000 10的三次方
比较运算符
print( 1 > 2) #False
print( 1 >= 2) #False
print( 1 < 2) #True
print( 1 <= 2) #True
print( 1 == 2) # False
print( 1 != 2) #True
赋值运算符(重要)
# 1.普通赋值
a = 10 #单赋值
a,b = 1,'a' # 多赋值(按顺序对应)
# 2.增量赋值
# 先解释下:以 a += 1 举例 a = a +1 a = 10 + 1 = 11
# 那么问题来了,a += 2*3 等于多少呢?
# 实际上,可以把 += 是最后算的
# 所以: a = a + (2*3) ---> a = 10 + (2*3) = 16 而不是 a = (a + 2) *3 = (10+2) *3 =36
a += 1
a -= 1
a *= 3
a /= 3
a %= 3
a //= 3
a **= 3
# 3.链式赋值
# 通过把一个变量值 赋值 给个变量名
# 多个变量赋值为 0 更为常见 - c = 0 b = c a = b
a = b = c = 0
e = f = g = '你好'
# 4.交叉赋值
# 交叉赋值实现 a - b
# | |
# t
a = 10
b = 11
# 找个临时变量
temp = a # 这时候 temp,a 都指向 10 内存地址
a = b # 这时候 a,b 指向 11 内存地址
b = temp # 这时候 temp,b 指向 10 内存地址
a,b = 1,2
a,b = b,a
print(a) # 2
print(b) # 1
# 5.解压赋值
# 针对列表,元组
l1 = ['aa','bb','cc','dd']
# 按顺序赋值
a,b,c,d = l1
print(a,b,c,d) # aa bb cc dd
# 按照位置获取,取首尾
a,*qita,d = ('aa','bb','cc','dd')
print(a,d) # aa dd
print(qita) # ['bb', 'cc'] 剩余元素会按照列表的形式返回
# 解释这件事, * 是任意位置,qita 是剩余元素,这里的qita 是自己定义的
# 再来一遍
*qita1,c,*qita2 = ('aa','bb','cc','dd') # 错误,为什么?因为你不知道qita1取了几个,也不知道qita2取了几个,所以不能判断c取得位置是哪个
# 针对字典,只能取到key
x,y,z=dic={'a':1,'b':2,'c':3}
print(x,y,z) # ('a', 'b', 'c')
逻辑运算符
优先级:非(not) > 且 (and) > 或(or) 短路:多条件复合时,从头开始找到那个符合条件的地方 并返回其条件
# 用法:
# not : not 就是条件取反的意思
print(not 1 ==2) # true 对1==2取反,因为 1==2 为 false 取反 true
# and : 且的意思,全真才真,有假就假
print(1 > 0 and 1 < 2) # true 1>0 true 1<2 true -->true
print(1 < 0 and 1 < 2) # false 1<0 false 1<2 true -->false
# or :或的意思,全假才假,有真就真
print(1 > 0 or 1 < 2) # true 1>0 true 1<2 true -->true
print(1 < 0 or 1 < 2) # true 1<0 false 1<2 true -->true
print(1 < 0 or 1 > 2) # false 1<0 false 1<2 false -->false
# 优先级比较 :not > and > or
'''
3>4 and not 4>3 or 1==3 and 'x' == 'x' or 4 >3
请用括号分清条件:
步骤1:先找出not
(not 4 >3)
步骤2:再找出and
(3>4) and (not 4>3) (1==3) and ()'x' == 'x')
步骤3:最后将not和and条件合并,再去找or
(3>4) and (not 4>3) or (1==3) and ('x' == 'x')
((3>4) and (not 4>3) or (1==3) and ('x' == 'x')) or 4>3
假 not 假 假 真 真
假 假 真
假 真
真
'''
短路运算符
# 短路原则:懒惰原则
# 什么叫短路呢?多条件复合,从头到尾执行,符合条件就退出,并返回条件
# 短路用法1:and 使用 : 条件为假 就跳出
print( 1==2 and 1> 0 and 2>1 and 1) # 执行到哪结束? 1==2 为什么?因为 1==2 条件为假
print( 1> 0 and 1 and 2>1) # 执行到哪结束? 2>1 为什么?因为执行到最后一个条件位置,可判断条件为真
# 短路用法2:or 使用 : 条件为真 就跳出
print( 1==2 or 1> 0 or 2>1 or 1) # 执行到哪结束? 1> 0 为什么?因为 1> 0 条件为真
print( 1< 0 or 1 or 2<1) # 执行到哪结束? 2<1 为什么?因为执行到最后一个条件位置,可判断条件为假
身份运算符 is
# 主要用来判断id 即内存地址
# 在哪使用? str 、list、tuple、dict、set
a = 10
b =11
print(a is b ) # True 竟然是对的?因为小整数池,-1 -256 使用的内存地址都是一样的,pycharm特有。
成员运算符 in not in
# 从两个关键字来说,in 是在什么里面吗? not in 不在什么里面?
# 所以:a in b 表示 a 是 b 的一部分?
# 主要用来判断某个元素是否在另一个大集合中存在
# 在哪使用? str 、list、tuple、dict、set
# 以列表为例
l1 = ['1','2','3']
print('1' in l1) # True
print('2' not in l1) # False
# 以字典为例
d1 = {'a':1,'b':2,'c':3}
print(1 in d1) # False 为什么呀?1 不是在字典里面吗? 原因:因为字典一般用来判断key不行试试
print('a' in d1) # True
# 提醒一下:其实好多情况下,对字典进行转换操作,只是对key生效,想要直接访问value 需要用其他方法
print(1 in d1.values()) # True
布尔运算符
# 布尔类型,作为条件来说,其实只有两种,True 和False
# 但是一般来说,我们并不会直接得出True 和False
# 那么隐型来了:
0 , NONE ,空(str,list,tuple,dict,set)
# 当符合上诉条件的时候,我们可以判定这条件为假
# 使用:
if list:
print('列表不为空')
else:
print('列表为空')
if not str:
print('字符串为空')
else:
print('字符串不为空')
# 推荐一种方式:-但是这个有一个问题,如果元素本身是空字符那么该方法不适用
if not len(xxx) == 0:
print('xxx不为空')
else
print('xxx为空')
字符编码
# 三大核心硬件
1、软件运行前,软件的代码及其相关数据都是存放于硬盘中的
2、任何软件的启动都是将数据从硬盘中读入内存,然后cpu从内存中取出指令并执行
3、软件运行过程中产生的数据最先都是存放于内存中的,若想永久保存软件产生的数据,则需要将数据由内存写入硬盘
# 文本编辑器读取文件内容的流程
阶段1、启动一个文件编辑器(文本编辑器如nodepad++,pycharm,word)
阶段2、文件编辑器会将文件内容从硬盘读入内存
阶段3、文本编辑器会将刚刚读入内存中的内容显示到屏幕上
# python解释器执行文件的流程
阶段1、启动python解释器,此时就相当于启动了一个文本编辑器
阶段2、python解释器相当于文本编辑器,从硬盘上将test.py的内容读入到内存中
阶段3、python解释器解释执行刚刚读入的内存的内容,开始识别python语法
# 字符编码
# ASCII表的特点:
1、只有英文字符与数字的一一对应关系
2、一个英文字符对应1Bytes,1Bytes=8bit,8bit最多包含256个数字,可以对应256个字符,足够表示所有英文字符
# GBK表的特点:
1、只有中文字符、英文字符与数字的一一对应关系
2、一个英文字符对应1Bytes
一个中文字符对应2Bytes
补充说明:
1Bytes=8bit,8bit最多包含256个数字,可以对应256个字符,足够表示所有英文字符
2Bytes=16bit,16bit最多包含65536个数字,可以对应65536个字符,足够表示所有中文字符
# 文本编辑器输入任何字符都是最新存在于内存中,是unicode编码的,
# 存放于硬盘中,则可以转换成任意其他编码,只要该编码可以支持相应的字符
字符<----解码/ -->编码 内存(unicode)二进制
^ | ^ |
解码 | _ 编码 解码 | _ 编码
(硬盘)GBK二进制 (硬盘)ASCII 二进制
# utf-8
utf-8是针对Unicode的可变长度字符编码:一个英文字符占1Bytes,一个中文字符占3Bytes,生僻字用更多的Bytes存储
内存(unicode)二进制
^ _
decode encode
(硬盘)utf-8二进制
#总结一句话 - 咋存咋取
#1. 保证存的时候不乱:在由内存写入硬盘时,必须将编码格式设置为支持所输入字符的编码格式
#2. 保证存的时候不乱:在由硬盘读入内存时,必须采用与写入硬盘时相同的编码格式
# python解释器执行文件的前两个阶段
'''
执行py文件的前两个阶段就是python解释器读文本文件的过程,与文本编辑读文本文件的前两个阶段没人任何区别,要保证读不乱码,则必须将python解释器读文件时采用的编码方式设置为文件当初写入硬盘时的编码格式,如果没有设置,python解释器则才用默认的编码方式,在python3中默认为utf-8,在python2中默认为ASCII,我们可以通过指定文件头来修改默认的编码
'''
# 解释器会先用默认的编码方式读取文件的首行内容,由于首行是纯英文组成,而任何编码方式都可以识别英文字符。
# coding: 当初文件写入硬盘时采用的编码格式
# python解释器执行文件的第三个阶段
在经历第三个阶段时开始识别python语法,当遇到特定的语法name = '上'(代码本身也都全都是unicode格式存的)时,需要申请内存空间来存储字符串'上',这就又涉及到应该以什么编码存储‘上’的问题了。
#python2后推出了一种补救措施,就是在字符串类型前加u,则会将字符串类型强制存储unicode,这就与python3保持一致了,对于unicode格式无论丢给任何终端进行打印,都可以直接对应字符不会出现乱码问题
# coding:utf-8
x = u'上' # 即便文件头为utf-8,x的值依然存成unicode
# 字符串encode编码与decode解码的使用
# 1、unicode格式------编码encode-------->其它编码格式
>>> x='上' # 在python3在'上'被存成unicode
>>> res=x.encode('utf-8')
>>> res,type(res) # unicode编码成了utf-8格式,而编码的结果为bytes类型,可以当作直接当作二进制去使用
(b'\xe4\xb8\x8a', <class 'bytes'>)
# 2、其它编码格式------解码decode-------->unicode格式
>>> res.decode('utf-8')
'上'
文件管理
基本操作
1.打开文件 f = open('filepath+filename','mode'encoding='utf-8')
- 打开文件,由应用程序向操作系统发起系统调用open(...),
- 操作系统打开该文件,对应一块硬盘空间,并返回一个文件对象赋值给一个变量f
- 如果打开的是文本文件,会涉及到字符编码问题,如果没有为open指定编码,
- 那么打开文本文件的默认编码很明显是操作系统说了算了,
- 操作系统会用自己的默认编码去打开文件,在windows下是gbk,在linux下是utf-8。
2.操作文件 f.read()
- 调用文件对象下的读/写方法,会被操作系统转换为读/写硬盘的操作
3.关闭文件 f.close()
- 向操作系统发起关闭文件的请求,回收系统资源
del f
- 回收应用程序级的变量
# with上下文管理 - 操作执行后,自动回收资源
with open('a.txt','r') as read_f,
open('b.txt','w') as write_f:
data = read_f.read()
write_f.write(data)
# 控制文件读写操作的模式
r(默认的) - 只读
w - 只写
a - 只追加写
+ - 可读可写 - 一般不用
# t 和 b 模式 - 大前提: tb模式均不能单独使用,必须与r/w/a之一结合使用
t(默认的):文本模式
1. 读写文件都是以字符串为单位的
2. 只能针对文本文件
3. 必须指定encoding参数
b:二进制模式: - 可配合 encode / decode 使用
1.读写文件都是以bytes/二进制为单位的
2. 可以针对所有文件
3. 一定不能指定encoding参数
重点(批量数据)
# 文件读写操作 - 重要思路
# 读操作
f.read() # 读取所有内容,执行完该操作后,文件指针会移动到文件末尾
f.readline() # 读取一行内容,光标移动到第二行首部
f.readlines() # 读取每一行内容,存放于列表中
# 强调:
# f.read()与f.readlines()都是将内容一次性读入内容,如果内容过大会导致内存溢出,若还想将内容全读入内存,则必须分多次读入,有两种实现方式:
# 方式一
with open('a.txt',mode='rt',encoding='utf-8') as f:
for line in f:
print(line) # 同一时刻只读入一行内容到内存中
# 方式二
with open('1.mp4',mode='rb') as f:
while True:
data=f.read(1024) # 同一时刻只读入1024个Bytes到内存中
if len(data) == 0:
break
print(data)
# 写操作
f.write('1111\n222\n') # 针对文本模式的写,需要自己写换行符
f.write('1111\n222\n'.encode('utf-8')) # 针对b模式的写,需要自己写换行符
f.writelines(['333\n','444\n']) # 文件模式
f.writelines([bytes('333\n',encoding='utf-8'),'444\n'.encode('utf-8')]) #b模式
了解
# 了解
f.readable() # 文件是否可读
f.writable() # 文件是否可读
f.closed # 文件是否关闭
f.encoding # 如果文件打开模式为b,则没有该属性
f.flush() # 立刻将文件内容从内存刷到硬盘
f.name
控制文件指针移动
#大前提:文件内指针的移动都是Bytes为单位的,唯一例外的是t模式下的read(n),n以字符为单位
with open('a.txt',mode='rt',encoding='utf-8') as f:
data=f.read(3) # 读取3个字符
with open('a.txt',mode='rb') as f:
data=f.read(3) # 读取3个Bytes
# 之前文件内指针的移动都是由读/写操作而被动触发的,若想读取文件某一特定位置的数据,则则需要用f.seek方法主动控制文件内指针的移动,详细用法如下:
# f.seek(指针移动的字节数,模式控制):
# 模式控制:
# 0: 默认的模式,该模式代表指针移动的字节数是以文件开头为参照的
# 1: 该模式代表指针移动的字节数是以当前所在的位置为参照的
# 2: 该模式代表指针移动的字节数是以文件末尾的位置为参照的
a.txt内容: abc你好
# 0模式的使用
with open('a.txt',mode='rt',encoding='utf-8') as f:
f.seek(3,0) # 参照文件开头移动了3个字节
print(f.tell()) # 查看当前文件指针距离文件开头的位置,输出结果为3
print(f.read()) # 从第3个字节的位置读到文件末尾,输出结果为:你好
# 注意:由于在t模式下,会将读取的内容自动解码,所以必须保证读取的内容是一个完整中文数据,否则解码失败
with open('a.txt',mode='rb') as f:
f.seek(6,0)
print(f.read().decode('utf-8')) #输出结果为: 好
# 1模式的使用
with open('a.txt',mode='rb') as f:
f.seek(3,1) # 从当前位置往后移动3个字节,而此时的当前位置就是文件开头
print(f.tell()) # 输出结果为:3
f.seek(4,1) # 从当前位置往后移动4个字节,而此时的当前位置为3
print(f.tell()) # 输出结果为:7
# 2模式的使用
with open('a.txt',mode='rb') as f:
f.seek(0,2) # 参照文件末尾移动0个字节, 即直接跳到文件末尾
print(f.tell()) # 输出结果为:9
f.seek(-3,2) # 参照文件末尾往前移动了3个字节
print(f.read().decode('utf-8')) # 输出结果为:好
监控日志小案例
# 小练习:实现动态查看最新一条日志的效果
import time
with open('access.log',mode='rb') as f:
f.seek(0,2)
while True:
line=f.readline()
if len(line) == 0:
# 没有内容
time.sleep(0.5)
else:
print(line.decode('utf-8'),end='')
# 强调:其中0模式可以在t或者b模式使用,而1跟2模式只能在b模式下用
文件的修改
# 强调:
- 1、硬盘空间是无法修改的,硬盘中数据的更新都是用新内容覆盖旧内容
- 2、内存中的数据是可以修改的
# 文件修改方式一
- 实现思路:将文件内容发一次性全部读入内存,然后在内存中修改完毕后再覆盖写回原文件
- 优点: 在文件修改过程中同一份数据只有一份
- 缺点: 会过多地占用内存
with open('db.txt',mode='rt',encoding='utf-8') as f:
data=f.read()
with open('db.txt',mode='wt',encoding='utf-8') as f:
f.write(data.replace('kevin','SB'))
# 文件修改方式二
- 实现思路:
- 以读的方式打开原文件,以写的方式打开一个临时文件,一行行读取原文件内容,
- 修改完后写入临时文件...,删掉原文件,将临时文件重命名原文件名
- 优点: 不会占用过多的内存
- 缺点: 在文件修改过程中同一份数据存了两份
import os
with open('db.txt',mode='rt',encoding='utf-8') as read_f,\
open('.db.txt.swap',mode='wt',encoding='utf-8') as wrife_f:
for line in read_f:
wrife_f.write(line.replace('SB','kevin'))
os.remove('db.txt')
os.rename('.db.txt.swap','db.txt')
函数
函数的基本使用
# 介绍:
- 1.什么是函数? - 2.为什么用函数 - 3.如何用函数
- 函数就相当于具备某一功能的工具 - (1).组织结构不清晰,可读性差 - 先定义:三种定义方式
- 函数的使用必须遵循一个原则: - (2).代码冗余 - 后调用:三种调用方式
- (1).先定义 - (3).可维护性、扩展性差 - 返回值:三种返回值的形式
- (2).后调用
# 定义函数发生的事情
1、申请内存空间保存函数体代码
2、将上述内存地址绑定函数名
3、定义函数不会执行函数体代码,但是会检测函数体语法
# 调用函数发生的事情
1、通过函数名找到函数的内存地址
2、然后加括号就是在触发函数体代码的执行
# 函数返回值发生的是事情
1、return是函数结束的标志,即函数体代码一旦运行到return会立刻
2、终止函数的运行,并且会将return后的值当做本次运行的结果返回
3、return None return res - 单个值、多个值(元组的形式返回)
# 使用
def 函数名(参数1,参数2,...):
"""文档描述"""
函数体
return 值
# 定义三种方式 - 有参、无参、空函数
- def func1():pass -- 无参
- def func2(a,b):pass -- 有参
- def func3():pass -- 空函数 - 没想好写什么
# 调用三种方式 - 加括号直接调用 、表达式:赋值表达式、数学表达式、函数当参数调用
- def add(a,b):return a+b
- add(1,2) -- 加括号直接调用
- res = add(1,2) -- 赋值表达式调用
- res = add(1,2)+3 -- 数学表达式
- res=add(add(1,2),10) -- 函数当参数调用
# 返回 - None 、单值、多值(元组的形式返回)
- return / return None -- 返回空值
- return 1+2 -- 返回单值
- return 1,2, -- 返回多值,以元组的形式返回
函数的参数
# 介绍:函数的参数分为形式参数和实际参数,简称形参和实参:
1、形参即在定义函数时,括号内声明的参数。形参本质就是一个变量名,用来接收外部传来的值。
2、实参即在调用函数时,括号内传入的值,值可以是常量、变量、表达式或三者的组合:
# 形参与实参的具体使用
# 1.位置参数
- 形参:定义函数,按照从左到右的顺序执行
- 实参:调用函数,按照形参从左到右的顺序一一对应
def func1(a,b):
return a+b
func1(1,2)
# 2.关键字参数 - 调用函数,放在位置参数的后面
- 实参:调用函数时,以key=value的形式,与形参对应,可以不按照顺序,但一定要放在位置参数的后面
def func2(a,b,c):
return a+b+c
func2(1,2,c=3)
func2(a=1,b=2,c=3)
func2(a=1,b=2,3) # 报错
# 3.默认参数 - 定音函数/调用函数,放在位置参数的后面 - 默认参数的值仅在函数定义阶段被赋值一次
- 形参:定义函数时,给参数指定一个值
- 实参:调用函数,可以不用给值
def func3(a,b,c=0):
return a+b+c
# 4.可变长度之位置参数 - 调用时,多余参数被*args接收,并以元组的形式保持下来
def func4(a,b,*args):
print(a,b,args)
func4(1,2,3,4) - 1 2 (3,4)
# 5.可变长度之关键字参数 - 调用时,多余参数被**kwargs接收,并以字典的形式保持下来
def func4(a,b,**kwargs):
print(a,b,kwargs)
func4(1,b=2,c=3,d=4) - 1 2 {'c':3,'d':4}
# 6.关于 * 的使用
1、*args 形参中接受多余位置参数 实参中将列表或元组打散(也可以说是扁平化)
func4(1,*[,2,3,4]) - func4(1,2,3,4)
2、 **kwargs 形参中接受多余关键字参数 实参中将字典打散(也可以说是扁平化)
func5(1,**{'b':2,'c':3,'d':4}) - func4(1,b=2,c=3,d=4)
# 7.混用 - 位置参数、默认参数、args、命名关键字参数、*kwargs
def func7(a,b,*args,c=0,**kwargs):
print(a,b)
print(args)
print(c)
print(kwargs)
func7(1,2,*[3,4],**{'c':'c','d':'d'}) - 1 2 (3, 4) c {'d': 'd'}
常用内置函数
# 1.abs,绝对值
data = abs(-10) # 10
# 2.pow,次方
data = pow(2,10) # 1024
# 3.sum,求和
num_list = [1,2,3,4,5]
res = sum(num_list) # 15
# 4.divmod,商和余数 - 分页
page_count,page_size = 10,3
page_nums= divmod(page_count,page_size) - (3, 1)
# 5.round,保留小数点后几位 - 常用 - 但是不够精确,使用decimal提高精确率
import decimal
a = decimal.Decimal('0.111111111111111111111111')
b = decimal.Decimal('0.24444444')
print(round(a+b,3))
# 6.min、max 最大值、最小值
data = [11,22,-19,99,10]
res = min(data) # -19
res = max(data) # 99
# 7.all - 是否所有元素转换成布尔值都是True
data = [1,-1,88,9]
v1 = all(data) # True
data = [1,-1,88,0,9]
v1 = all(data) # False
# 8.any - 只要有转换为布尔值为True
data = [1,-1,88,9]
v1 = any(data) # True
data = [0,"",[],{}]
v1 = any(data) # False
# 9.进制相关
#(1)bin,十进制转换成二进制
res = bin(100) # 0b101001001
#(2)oct,十进制转换成八进制
res = oct(100) # 0o1221
#(3)hex,十进制转换成十六进制
res = hex(100) # 0x1221
#(4)int
int(res,2) # 100
int(res,8) # 100
int(res,16) # 100
# 10.编码相关
#(1)数字转字符
chr(65) # 'A'
#(2)字符装数字
ord('a') # 97
# 11.range - 生成器
range(start,end,step)
# 12.enumerate - 循环过程中,自动提供自增的一列
for index,time in enumerate(['a','b','c'],100)
msg = "{} {}".format(index, item)
print(msg)
# 输出结果
100 - 'a'
101 - 'b'
102 - 'c'
# 13.sorted - 排序
data_list = [
'5 编译器和解释器.mp4',
'17 今日作业.mp4',
'9 Python解释器种类.mp4']
res = sorted(data_list, key=lambda arg : int(arg.split(" ")[0]) )
# 14.callable - 是否可执行
函数的类型提示(为了规范)
def register(name:"必须传入名字傻叉",age:1111111,hobbbies:"必须传入爱好元组")->"返回的是整型":
print(name)
print(age)
print(hobbbies)
return 111
名称空间与作用域
名称空间
#名称空间 - 存放名字与对象映射/绑定关系的地方
- 介绍:存放名字的地方,是对栈区的划分
- 有了名称空间之后,就可以在栈区中存放相同的名字,详细的,名称空间
- 举例: x=3 ,python会申请内存空间存放对象3 ,然后将名字x与3的绑定关系存放于名称空间中
- 理解: x栈区 3 放在堆区
#名称空间分类
# 1.内置名称空间 - 全局作用域
- 存放的python解释器内置的名字
- 存活周期:python解释器启动时产生,python解释器关闭时销毁
print(max) - <built-in function max>
# 2.全局名称空间 - 全局作用域
- 只要不是函数内定义,不是内置的,剩下都是全局名称空间的名字
- 存活周期:python文件执行则产生,python文件运行完毕后销毁
a.py
g = '全局名称空间'
def func():print(g)
func() - 全局名称空间
# 3.局部名称空间 - 局部作用域
- 在调用函数时,运行函数代码过程中产生的函数内的名字
- 存活周期:调用函数时存活,函数调用完毕后销毁
def func1():f = '局部名称空间'
def func2():print(f)
func2() - 报错 因为f只作用于func1中,func2中根本获取不到这个变量
# 名称空间加载顺序
内置名称空间 > 全局名称空间 > 局部名称空间
# 名称空间销毁顺序
局部名称空间 > 全局名空间 > 内置名称空间
# 名称空间查找优先级 - 内置名称空间 > 全局名称空间 > 局部名称空间
- 口诀:根据所在位置往上一层一层找,调用阶段看定义。
- 注意:名称空间在函数定义阶段就决定,定义阶段才是重点,调用不算
# 小案例 - 提示 定义函数 + 定义函数后 - 调用函数前 - 变量变换情况 + 函数的多嵌套
x = 111
def func1():
x = 222
def func2():
print(x)
x = 333
func2()
def func3():
x = 444
func1()
x = 555
def func4():
print(x)
func3() - 333
func4() - 555
作用域
# 按照名字作用范围的不同可以将三个名称空间划分为两个区域:
# 全局作用域:内置名称空间、全局名称空间
1、全局存活
2、全局有效:被所有函数共享
# 局部作用域: 局部名称空间的名字
1、临时存活
2、局部有效:函数内有效
# LEGB 原则
# # LEGB
print(max)# builtin
g = '全局'# global
def f1():
# enclosing
def f2():
# enclosing
def f3():
# local
pass
# global与nonlocal的使用
# global - 如果再局部想要修改全局的名字对应的值(不可变类型),需要用global
x=111
def func():
global x # 声明x这个名字是全局的名字,不要再造新的名字了
x=222
func()
print(x) -- 222
# nonlocal(了解) - 修改函数外层函数包含的名字对应的值(不可变类型)
def f1():
x=11
def f2():
nonlocal x
x=22
f2()
print('f1内的x:',x)
f1() -- 22
函数对象与闭包
函数对象
# 函数对象指的是函数可以被当做’数据’来处理
- 精髓:可以把函数当成变量去用
# 1.函数可以被引用 -可以赋值
def add(x,y):
return x+y
f = add
print(f(1,2),add) -- 3 <function add at 0x0000019171346708>
# 2.可以当做函数当做参数传给另外一个函数
def foo(x,y,add):
return add(x,y)
foo(1,2,add) -- 3 其中add是函数的内存地址
# 3.可以当做函数当做另外一个函数的返回值
def bar():
return add
func=bar() - 拿到到add函数的内存地址
func(1,2) -- 3 拿到add函数的内存地址后,加括号调用
# 4.可以当做容器类型的一个元素
ll = [1,'a',add]
ll[2](1,2) - 3 ll[2] 保存的是add函数的内存地址,加括号可直接调用
# 小案例
def login():
print('登录功能')
def register():
print('注册')
func_dic = {
'0':['退出',None],
'1':['登录',login], # 拿到login函数的内存地址,可加括号调用
'2':['注册',register],# 拿到register函数的内存地址,可加括号调用
}
while True:
for k,v in func_dic.items():
print(k,v[0])
choice = input('请输入命名编号').strip()
if not choice.isdigit():
print('必须输入编号')
continue
if choice == '0':
break
if choice in func_dic:
func_dic[choice][1]() # 拿到对应功能的函数内存地址,加括号调用
else:
print('输入指令不存在')
函数嵌套
# 函数嵌套
函数的嵌套调用:在调用一个函数的过程中又调用其他函数
# 小案例
# 圆形
# 求圆形的求周长:2*pi*radius
def circle(radius,action=0):
from math import pi
def perimiter(radius):
return 2*pi*radius
# 求圆形的求面积:pi*(radius**2)
def area(radius):
return pi*(radius**2)
if action == 0:
return 2*pi*radius
elif action == 1:
return area(radius)
circle(33,action=0)
闭包函数
# 闭包函数 - 名称空间与作用域 + 函数嵌套 + 函数对象
- 基于函数对象的概念,可以将函数返回到任意位置去调用
- 但作用域的关系是在定义完函数时就已经被确定了的,与函数的调用位置无关
- "闭"函数指的该函数是内嵌函数
- "包"函数指的该函数包含对外层函数作用域名字的引用(不是对全局作用域)
- 理解,闭包函数就是内嵌函数对外部函数变量的引用,可在任意位置调用而不变
# 可以通过函数的closure属性,查看到闭包函数所包裹的外部变量
func.__closure__
# 示例1
x=1
def f1():
def f2():
print(x)
x = 2
return f2
def f3():
x=3
f2=f1() #调用f1()返回函数f2
f2() #需要按照函数定义时的作用关系去执行,与调用位置无关
f3() #结果为2
# 示例2 - django的经典CBV源码解析
def as_view(cls, **initkwargs):
def view(request, *args, **kwargs):
self = cls(**initkwargs) # 这句才是最主要的
self.setup(request, *args, **kwargs)
return self.dispatch(request, *args, **kwargs)
return view
def dispatch(self, request, *args, **kwargs):
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
# 闭包函数的两种用法
import requests
#方式一:直接将值以参数的形式传入
def get(url):
return requests.get(url).text
#方式二:值包给函数
def page(url):
def get():
return requests.get(url).text
return get
# 方式一下载同一页面
get('https://www.python.org')
get('https://www.python.org')
python=page('https://www.python.org')
python()
python()
装饰器
# 知识储备 *args, **kwargs + 名称空间与作用域+ 函数对象 + 函数的嵌套定义 + 闭包函数
# 装饰器介绍
# 1.什么是装饰器
- 器指的是工具,可以定义成成函数
- 装饰指的是为其他事物添加额外的东西点缀
- 装饰器指的定义一个函数,该函数是用来为其他函数添加额外的功能
# 2.为什么要用装饰器
- 开放封闭原则
- 开放:指的是对拓展功能是开放的
- 封闭:指的是对修改源代码是封闭的
- 装饰器就是在不修改被装饰器对象源代码以及调用方式的前提下为被装饰对象添加新功能
- 添加额外功能 + 不修改源代码和调用方式
# 3.装饰器种类
- 1、函数装饰器:有参装饰器 + 无参装饰器
- 2、类装饰器
- 3、对象装饰器
无参装饰器
# 函数版装饰器
- 本质来说,就是偷梁换柱,虽然是内嵌函数wrapper的内地地址,但是调用起来和原函数没什么区别
# 1.无参装饰器 - 计算函数运行时间
import time
def timer(func):
def wrapper(*args,**kwargs):
start_time=time.time()
res=func(*args,**kwargs)
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))
return res
return wrapper
@timer # wrapper内存地址= timer(home) - home = timmer(home)
def home(name):
time.sleep(5)
print('Welcome to the home page',name)
# 无参装饰器使用
#方式一:
func1 = timer(home) # 这个时候拿到的是wrapper函数的内存地址
func1(1,2) # 通过加括号调用,完成home函数的执行
#方式二:语法糖
@timer # wrapper内存地址= timer(home) - home = timmer(home)
执行顺序及修饰
def deco1 | deco2 | deco3 (func):
def wrapper(*args,**kwargs):
print('deco1') | print('deco2') | print('deco3')
res = func(*args,**kwargs)
return res
return wrapper
# 叠加多个装饰器,加载顺序(自下而上)与运行顺序(自上而下的)
@deco1 # index=deco1(deco2.wrapper的内存地址)
@deco2 # deco2.wrapper的内存地址=deco2(deco3.wrapper的内存地址)
@deco3 # deco3.wrapper的内存地址=deco3(index)
def index():
pass
# 将无参装饰器伪装的跟原来函数一样
# 自动版
from functools import wraps
def outter(func):
@wraps(func) # 别忘记指定函数
def wrapper(*args, **kwargs):
"""这个是主页功能"""
res = func(*args, **kwargs) # res=index(1,2)
return res
# 手动版
def outter(func):
def wrapper(*args, **kwargs):
"""这个是主页功能"""
res = func(*args, **kwargs) # res=index(1,2)
return res
# 手动将原函数的属性赋值给wrapper函数
# 1、函数wrapper.__name__ = 原函数.__name__
# 2、函数wrapper.__doc__ = 原函数.__doc__
# wrapper.__name__ = func.__name__
# wrapper.__doc__ = func.__doc__
# 不常用
# wrapper.__annotations__ = func.__annotations__
# wrapper.__dict__.update(func.__dict__)
return wrapper
# 调用
print(add.__annotations__)
print(add.__dict__) - 属性
print(add.__name__) - 函数名
print(add.__doc__) - 描述文档
print(add.__closure__) - 所属外层函数
print(add.__module__)
help(add) - 描述文档
# 偷梁换柱之后
# index的参数什么样子,wrapper的参数就应该什么样子
# index的返回值什么样子,wrapper的返回值就应该什么样子
# index的属性什么样子,wrapper的属性就应该什么样子==》from functools import wraps
有参装饰器
# 2.有参装饰器 - 模拟从不同文件中查询验证用户登录
# 有参数
from functools import wraps
def auth(db_type): # 第一层
# 无参数
def outter(func): #第二层
@wraps(func)
def wrapper(*args,**kwargs): # 第三层
name = input('your name>>>: ').strip()
pwd = input('your password>>>: ').strip()
if db_type == 'file':
print('基于文件的验证')
if name == 'zs' and pwd == '123':
res = func(*args, **kwargs) # index(1,2)
return res
else:
print('user or password error')
elif db_type == 'mysql':
print('基于mysql的验证')
elif db_type == 'ldap':
print('基于ldap的验证')
else:
print('不支持该db_type')
res = func(*args,**kwargs)
return res
return wrapper
return outter
@auth('mysql') # outtter = auth('mysql') wrapper = outter(index) - wrapper 的内存地址
def index():
print('index页面')
# 使用
# 方式一
func1 = auth(db_type='mysql') # 拿到的是outter的内存地址
func2 = func1(index) # 拿到的是wrapper的内存地址
func2() # 加函数调用index函数执行
# 方式二:语法糖
@auth('mysql') # outtter = auth('mysql') wrapper = outter(index) - wrapper 的内存地址
类装饰器
# 一般会在类的__init__()方法中记录传入的函数,再在__call__()调用修饰的函数及其它额外处理。
class myDecorator(object):
def __init__(self, f):
print("inside myDecorator.__init__()")
f() # Prove that function definition has completed
def __call__(self):
print("inside myDecorator.__call__()")
@myDecorator
def aFunction():
print("inside aFunction()")
print("Finished decorating aFunction()")
aFunction()
# 输出结果
inside myDecorator.__init__()
inside aFunction()
Finished decorating aFunction()
inside myDecorator.__call__()
@myDecorator 等价于 def aFunction():pass
def aFunction():pass aFunction = myDecorator(aFunction)
对象装饰器
# 对比使用类作为装饰器,使用对象作为装饰器有些时候更有灵活性,例如能够方便定制和添加参数
class Decorator:
def __init__(self, arg1, arg2):
print('执行类Decorator的__init__()方法')
self.arg1 = arg1
self.arg2 = arg2
def __call__(self, f):
print('执行类Decorator的__call__()方法')
def wrap(*args):
print('执行wrap()')
print('装饰器参数:', self.arg1, self.arg2)
print('执行' + f.__name__ + '()')
f(*args)
print(f.__name__ + '()执行完毕')
return wrap
@Decorator('Hello', 'World')
def example(a1, a2, a3):
print('传入example()的参数:', a1, a2, a3)
print('装饰完毕')
print('准备调用example()')
example('Wish', 'Happy', 'EveryDay')
print('测试代码执行完毕')
# 输出结果
执行类Decorator的__init__()方法
执行类Decorator的__call__()方法
装饰完毕
准备调用example()
执行wrap()
装饰器参数: Hello World
执行example()
传入example()的参数: Wish Happy EveryDay
example()执行完毕
测试代码执行完毕
迭代器
# 可迭代对象 - 通过索引的方式进行迭代取值
- 内置有__iter__方法对象
- 可迭代对象.__iter__(): 得到迭代器对象
- 字符串、列表、元组、字典、集合、文件对象(本身也是迭代器对象)
# 迭代器对象 - 不依赖索引来进行迭代取值的方式
- 调用obj.iter() 返回一个迭代器对象(Iterator)
- 内置有__next__方法并且内置有__iter__方法的对象
- 迭代器对象.__next__():得到迭代器的下一个值
- 迭代器对象.__iter__():得到迭代器的本身,避免了重复调用会出错
# 可迭代对象和可迭代器对象的使用
d={'a':1,'b':2,'c':3}
d_iter = iter(d) # 本质上在调用 d.__iter__() , 返回d的迭代器对象 d_iter
print(next(d_iter)) # 本质上在调用 d_iter.__next__() ,每次从迭代器中取出一个对象
# while循环,适用于有索引的数据类型:列表、字符串、元组
ll = [1,2,3]
index = 0
while index < len(ll):
print(ll[index])
# for循环的工作原理,也叫迭代器循环
# 流程:
- 1. ll.__iter__() 得到一个迭代器对象
- 2. 迭代器对象.__next__()得到一个返回值,然后将值赋值给 i
- 3. 循环步骤2,直到抛出StopIteration异常for循环会捕捉异常然后结束循环
ll = [1,2,3]
for i in ll:
print(i)
生成器
生成器
# 什么是生成器
- 1.若函数体包含yield关键字,再调用函数,并不会执行函数体代码,得到的返回值即是生成器对象
- 2.生成器内置有__iter__和__next__方法,所以生成器本身就是一个迭代器
- 3.因而我们可以用next(生成器)触发生成器所对应函数的执行
- 4.有了yield关键字,我们就有了一种自定义迭代器的实现方式。yield可以用于返回值,
- 5.但不同于return,函数一旦遇到return就结束了,而yield可以保存函数的运行状态挂起函数,用来返回多次值
# 定义
def my_range(start,stop,step=1):
print('start...')
while start < stop:
yield start
start+=step
print('end....')
# 调用
g=my_range(1,5,2) - 1 3 # 触发函数执行直到遇到yield则停止,将yield后的值返回,并在当前位置挂起函数
print(next(g)) - 1 -- # 再次调用next(g),函数从上次暂停的位置继续执行,直到重新遇到yield...
print(next(g)) - 3 -- # 周而复始...
print(next(g)) - StopIteration # 触发函数执行没有遇到yield则无值返回,即取值完毕抛出异常结束迭代
yield表达式
# 在函数内可以采用表达式形式的yield,记得要初始化一次
def eater():
print('Ready to eat')
food_list=[]
while True:
food=yield food_list
print('food:%s' %(food))
food_list.append(food)
e=eater() # 得到生成器对象
next(e) # g.send(None)等同于next(g)
# 要事先”初始化”一次,让函数挂起在food=yield,等待调用g.send()方法为其传值
print(e.send('包子')) # 使用send 是为给yield前面的变量food传参,返回的是yield后面的变量food_list
print(e.send('饺子'))
# 输出结果是
Ready to eat
food:包子
['包子']
food:饺子
['包子', '饺子']
# 可用装饰器去初始化,但我觉得没必要了
def init(func):
def wrapper(*args,**kwargs):
g=func(*args,**kwargs)
next(g)
return g
return wrapper
@init
def eater():
print('Ready to eat')
while True:
food=yield
print('get the food: %s, and start to eat' %food)
e = eater()
e.send('油条')
三元表达式
# 三元表达式是python为我们提供的一种简化代码的解决方案
# res = 条件成立时返回的值 if 条件 else 条件不成立时返回的值
# 使用: - 简化代码
res = x if x > y else y # 三元表达式
列表生成式
# 创建一个生成器对象有两种方式,
- 一种是调用带yield关键字的函数,
- 另一种就是生成器表达式,与列表生成式的语法格式相同,只需要将[]换成()
# 使用
# 1.列表生成式
a = [i for i in range(100) if i%2 == 0]
b = [chr(i)+'{}'.format(chr(i).lower()) for i in range(65,65+26)]
print(a) - [0, 2, 4, 6, 8]
print(b) - ['Aa', 'Bb', 'Cc', ..., 'Xx', 'Yy', 'Zz']
# 2.字典生成式
c = { f'{chr(i)}':i for i in range(97,97+26)}
print(c) - {'a': 97, 'b': 98, 'c': 99, ... , 'x': 120, 'y': 121, 'z': 122}
# 3.集合生成式
d = { i for i in range(1,10)}
print(d) - {1, 2, 3, 4, 5, 6, 7, 8, 9}
# 4.生成器表达式 - 即元组表达式,但不是真正的元组表达式
e = ( i for i in range(10))
print(e) # <generator object <genexpr> at 0x0000025FB40C9CC8>
print(next(e)) # 0
print(next(e)) # 1
# 小案例
with open('a.txt', mode='rt', encoding='utf-8') as f:
# res = 0 for line in f:res += len(line) print(res) # 遍历 + 计算长度
# res=sum([len(line) for line in f]) # 使用列表生成式计算长度
res = sum(len(line) for line in f) # 使用生成器表达式计算长度,效率最高
函数递归
# 函数不仅可以嵌套定义,还可以嵌套调用,即在调用一个函数的过程中,
# 函数内部又调用另一个函数,而函数的递归调用指的是在调用一个函数的过程中又直接或间接地调用该函数本身
- sys.setrecursionlimit() # 设置递归层数
- 递归调用不应该无限地调用下去,必须在满足某种条件下结束递归调用 # 必须设定某种终止条件
# 回溯与递推
- 1.回溯:一层一层调用下去
- 2.递推:满足某种结束条件,结束递归调用,然后一层一层返回
items=[[1,2],3,[4,[5,[6,7]]]]
# 每个员工都比前一个员工高1000,算出第四个人的薪水
def salary(n):
if n==1:
return 5000
return salary(n-1)+1000
s=salary(4)
print(s)
# 递推 回溯
- salary(4) = salary(4-1)+1000 ( (5000 + 1000) + 1000 ) + 10000
- salary(3) = salary(3-1)+1000 (5000 + 1000) + 1000
- salary(2) = salary(2-1)+1000 5000 + 1000
- salary(1) = 5000 ---> 5000
# 总结
salary(n)=salary(n-1)+1000 (n>1)
salary(1)=5000 (n=1)
# 提醒
- 1.使用递归,我们只需要分析出要重复执行的代码逻辑,
- 2.然后提取进入下一次递归调用的条件或者说递归结束的条件即可,代码实现起来简洁清晰
# 小案例 - 二分法 - 必备条件:列表必须是有序序列
nums=[-3,4,7,10,13,21,43,77,89]
find_num=8
def binary_search(find_num,l):
print(l)
if len(l) == 0:
print('找的值不存在')
return
mid_index=len(l) // 2
if find_num > l[mid_index]:
# 接下来的查找应该是在列表的右半部分
l=l[mid_index+1:]
binary_search(find_num,l)
elif find_num < l[mid_index]:
# 接下来的查找应该是在列表的左半部分
l=l[:mid_index]
binary_search(find_num,l)
else:
print('find it')
binary_search(find_num,nums)
面向过程与函数式
面向过程编程思想
# 面向过程的编程思想:- 步骤式实现功能
1.核心是"过程"二字,过程即流程,指的是做事的步骤:先什么、再什么、后干什么
2.基于该思想编写程序就好比在设计一条流水线
# 优点:复杂的问题流程化、进而简单化
# 缺点:扩展性非常差
# 面向过程的编程思想应用场景解析:
1、不是所有的软件都需要频繁更迭:比如编写脚本
2、即便是一个软件需要频繁更迭,也不并不代表这个软件所有的组成部分都需要一起更迭
# 小案例 - 写一个数据远程备份程序,分三步:本地数据打包,上传至云服务器,检测备份文件可用性
import os,time
# 一:基于本章所学,我们可以用函数去实现这一个个的步骤
# 1、本地数据打包
def data_backup(folder):
print("找到备份目录: %s" %folder)
print('正在备份...')
zip_file='/tmp/backup_%s.zip' %time.strftime('%Y%m%d')
print('备份成功,备份文件为: %s' %zip_file)
return zip_file
#2、上传至云服务器
def cloud_upload(file):
print("\nconnecting cloud storage center...")
print("cloud storage connected")
print("upload [%s] to cloud..." %file)
link='https://www.xxx.com/bak/%s' %os.path.basename(file)
print('close connection')
return link
#3、检测备份文件可用性
def data_backup_check(link):
print("\n下载文件: %s , 验证文件是否无损..." %link)
#二:依次调用
# 步骤一:本地数据打包
zip_file = data_backup(r"/Users/egon/欧美100G高清无码")
# 步骤二:上传至云服务器
link=cloud_upload(zip_file)
# 步骤三:检测备份文件的可用性
data_backup_check(link)
函数式
# 匿名函数与lambda
- 匿名用于临时调用一次的场景:更多的是将匿名与其他函数配合使用
- 语法:lambda 参数1,参数2,...: expression
- 使用一次就是释放
# 定义
lambda x,y,z:x+y+z
def func(x,y,z):
return x+y+z
# 调用
# 方式一
res=(lambda x,y,z:x+y+z)(1,2,3)
# 方式二
func=lambda x,y,z:x+y+z # “匿名”的本质就是要没有名字,所以此处为匿名函数指定名字是没有意义的
res=func(1,2,3)
# 高级用法
# 1.max、min用法
salaries={
'two':3000,
'three':7000,
'four':10000,
'one':2000
}
max(salaries) - two 按照字典的key
- 1.函数max会迭代字典salaries,每取出一个“人名”就会当做参数传给指定的匿名函数,
- 2.然后将匿名函数的返回值当做比较依据,最终返回薪资最高的那个人的名字
res = min(salaries,key=lambda k:salaries[k]) - one 根据字典的key取出value
# 2.sorted用法
sorted(salaries,key=lambda k:salaries[k],reverse=False) - ['one', 'two', 'three', 'four']
# 3.map、reduce、filter
# (1) map - map函数可以接收两个参数,一个是函数,另外一个是可迭代对象
array=[1,2,3,4,5]
res = map(lambda x:x**2,array) - 对array的每个元素做平方处理,可以使用map函数
print(list(res)) - [1, 4, 9, 16, 25]
# (2) reduce - reduce函数可以接收三个参数,一个是函数,第二个是可迭代对象,第三个是初始值
- 1.初始值 + 第一个迭代对象 -->合并成一个 + 第二个迭代对象 -->以此类推
res = reduce(lambda x,y:x+y,array,100) - 对array进行合并操作,比如求和运算,这就用到了reduce函数
print(res) - 115 累加和
# (3) filter - 过滤
- 1.filter函数会依次迭代array,得到的值依次传给匿名函数,
- 2.如果匿名函数的返回值为真,则过滤出该元素,而filter函数得到的结果仍然是迭代器。
res=filter(lambda x:x>3,array) - [4, 5] - 定义过滤函数的条件
模块与包
模块
# 模块 - 在python中,一个py文件就是一个模块
# py文件用途 - 1.被当成程序运行 2.被当作模块导入
# 模块种类 - 1.自定义(py文件)模块 2.内置模块 3.第三方模块
# 区分py文件的使用场景
#foo.py
...
if __name__ == '__main__':
foo.py被当做脚本执行时运行的代码
else:
foo.py被当做模块导入时运行的代码
# 模块推荐用法
#!/usr/bin/env python #通常只在类unix环境有效,作用是可以使用脚本名来执行,而无需直接调用解释器。
"The module is used to..." #模块的文档描述
import sys #导入模块
x=1 #定义全局变量,如果非必须,则最好使用局部变量,这样可以提高代码的易维护性,并且可以节省内存提高性能
class Foo: #定义类,并写好类的注释
'Class Foo is used to...'
pass
def test(): #定义函数,并写好函数的注释
'Function test is used to…'
pass
if __name__ == '__main__': #主程序
test() #在被当做脚本执行时,执行此处的代码
模块导入之import
# 模块导入的几种方式
# 1.import - 拿到的是 模块foo名称空间的 foo内存地址 - 执行文件改变变量 - 影响模块名称空间
- 首次导入模块会做三件事:
- 1.执行源文件代码
- 2.产生一个新的名称空间,用于存放源文件执行过程中产生的名字
- 3.在当前执行文件所在的名称空间得到一个名字foo,该名字指向新创建的名称空间,
- 若要引用模块名称空间中的名字,需要加上该前缀
- 强调:第一层导入模块已经,将器加载到内存空间中,
- 之后的重复导入会直接引用内存中已存在的模块,不会重复执行文件
# foo.py
'''
x=1
def get():
print(x)
def change():
global x
x=0
class Foo:
def func(self):
print('from the func')
'''
# fun.py
#1.导入单个模块
import foo #导入模块foo
a=foo.x #引用模块foo中变量x的值赋值给当前名称空间中的名字a
foo.get() #调用模块foo的get函数
foo.change() #调用模块foo中的change函数
obj=foo.Foo() #使用模块foo的类Foo来实例化,进一步可以执行obj.func()
# 2.导入多个模块 - 但是推荐分开使用,显得更规范
import module1,module2,module3
模块导入之from... import ...
# 2.from ... import ... - 拿到的是 模块foo名称空间的 变量的内存地址 执行文件改变变量 - 不影响模块名称空间
- 导入模块会做三件事:
- 1.产生一个模块的名称空间
- 2.运行foo.py 将运行过程中产生的名字都丢到模块的名称空间中
- 3.在当前名称空间拿到一个名字,该名字是模块名称空间中的某一个内存地址
# foo.py
'''
__all__=['x','get'] #该列表中所有的元素必须是字符串类型,每个元素对应foo.py中的一个名字
x=1
def get():
print(x)
def change():
global x
x=0
class Foo:
def func(self):
print('from the func')
'''
# fun.py
from foo import * - 拿到 foo模块的所有变量的内存地址
from foo import get,change - 拿到 foo模块的函数get、change的内存地址
from foo import Foo - 拿到 foo模块的类Foo的内存地址
# 当定义__all__后,只能导入 x get
模块导入之import 和 from...import... 区别
# import 和 from...import...
- 两者的区别就是,
- import 拿到的是模块foo名称空间的名字 - 原模块
- from...import... 拿到的是模块foo名称空间的内存地址 - 新模块
# a.py
'''
x=111
def get():
print(x)
'''
# b.py
import a
print(a.x) - 111
a.x = 222
a.get() - 222
from a import x,get
x = 333
get() - 111
模块导入之其它导入语法(as)
# as 的使用,就是为了简化模块名称过长的问题
import numpy as ny # 使用别名代替模块使用
import pandas as pd
模块导入之循环导入的问题
# 循环导入问题指的是
- 一个模块加载/导入的过程中导入另一个模块,
- 而在另外一个模块中又返回导入第一个模块中的名字,
- 由于第一个模块尚未加载完毕,所以引用失败,抛出异常
- 解决思路:就是要让其中一个模块提前完成加载,或者说把导入模块放在最后
# m1.py
'''
print('正在导入m1')
from m2 import y
x='m1'
'''
# m2.py
'''
print('正在导入m2')
from m1 import x
y='m2'
'''
# 1.通过run.py运行
import m1
# 执行过程
先执行run.py--->执行import m1,开始导入m1并运行其内部代码--->打印内容"正在导入m1"
--->执行from m2 import y 开始导入m2并运行其内部代码--->打印内容“正在导入m2”--->执行from m1 import x,由于m1已经被导入过了,所以不会重新导入,所以直接去m1中拿x,然而x此时并没有存在于m1中,所以报错
# 输出结果
正在导入m1
正在导入m2
ImportError: cannot import name 'x' from 'm1'
# 2.通过m1.py运行
# 输出结果
正在导入m1
正在导入m2
正在导入m1
直接执行m1.py抛出异常 - ImportError: cannot import name 'y' from 'm2'
# 解决一 - 入语句放到最后,保证在导入时,所有名字都已经加载过
# 文件:m1.py
'''
print('正在导入m1')
x='m1'
from m2 import y
'''
# 文件:m2.py
'''
print('正在导入m2')
y='m2'
from m1 import x
'''
# 解决二 - 导入语句放到函数中,只有在调用函数时才会执行其内部代码
# 文件:m1.py
'''
print('正在导入m1')
def f1():
from m2 import y
print(x,y)
x = 'm1'
'''
# 文件:m2.py
'''
print('正在导入m2')
def f2():
from m1 import x
print(x,y)
y = 'm2'
'''
模块导入之搜索模块的路径和优先级
# 导入模块后,搜索模块顺序
- 1.内存(内置模块)
- 2.硬盘,按照sys.path中存放的文件的顺序依次查找要导入的模块
- 运行:
- 1.在导入一个模块时,如果该模块已加载到内存中,则直接引用
- 2.否则会优先查找内置模块
- 3.然后按照从左到右的顺序依次检索sys.path中定义的路径,直到找模块对应的文件为止,否则抛出异常
- 4.sys.path也被称为模块的搜索路径,它是一个列表类型
# 使用 - 只要把没有在sys.path 的顶级目录 添加到 sys.path
import sys
sys.path # 查看路径
sys.path.append(r'/pythoner/projects/') # 添加路径
包
# 随着模块数目的增多,把所有模块不加区分地放到一起也是极不合理的,
# 于是Python为我们提供了一种把模块组织到一起的方法,即创建一个包。
# 包就是一个含有__init__.py文件的文件夹,文件夹内可以组织子模块或子包,例如:
pool/ #顶级包
├── __init__.py
├── futures #子包
│ ├── __init__.py
│ ├── process.py
│ └── thread.py
└── versions.py #子模块
# process.py
'''
class ProcessPoolExecutor:
def __init__(self,max_workers):
self.max_workers=max_workers
def submit(self):
print('ProcessPool submit')
'''
# thread.py
'''
class ThreadPoolExecutor:
def __init__(self, max_workers):
self.max_workers = max_workers
def submit(self):
print('ThreadPool submit')
'''
# versions.py
'''
def check():
print('check versions')
'''
# __init__.py文件内容均为空
# 强调:
- 1.创建包的目的不是为了运行,而是被导入使用,
- 2.记住,包只是模块的一种形式而已,包的本质就是一种模块
- 3.import 顶级包.子包.子模块,
- 4.from 包1.包2.模块 import 变量
# 执行流程
- 1.执行包下的__init__.py文件
- 2.产生一个新的名称空间用于存放__init__.py执行过程中产生的名字
- 3.在当前执行文件所在的名称空间中得到一个名字pool,
- 该名字指向__init__.py的名称空间
# 绝对导入与相对导入(值得注意)
import pool
pool.versions.check() #抛出异常AttributeError - 因为pool中已经有versions
pool.futures.process.ProcessPoolExecutor(3) #抛出异常AttributeError - 因为pool中已经有process
# 1.绝对导入:以顶级编号为起始
#pool下的__init__.py
from pool import versions
# 2.相对导入: . 代表当前文件所在目录, .. 代表当前目录的上级目录
#pool下的__init__.py
from . import versions
# 3.#futures下的__init__.py 可控制*
__all__=['process','thread']
# .针对包导入,推荐使用相对位置 - 注意:如果在包中测试运行的话,推荐使用绝对
- 相对导入只能在包内部使用,用相对导入不同目录下的模块是非法的
- 无论是import还是from-import,但凡是在导入时带点的,点的左边必须是包,否则语法错误
#操作pool下的__init__.py,保证pool.futures
# 包作为模块,使用前者,否则使用后者
from . import process #或from pool.futures import process
from . import thread #或from pool.futures import thread
#操作futrues下的__init__.py,保证pool.futures.process
# 包作为模块,使用前者,否则使用后者
from . import futures #或from pool import futures
from . import versions
# 作为包使用
# 方式一
import pool
pool.versions.check()
# 方式二
from pool import *
versions.check()
# 最后总结一句话,
包内,推荐使用相对导入
包外,推荐绝对导入
切记,一定要把路径添加到 sys.path.append()
要知道什么是顶级包
软件开发的目录规范
Foo/
|-- core/ # 存放业务逻辑相关代码
| |-- core.py
|
|-- api # 存放接口文件,接口主要用于为业务逻辑提供数据操作。
| |-- api.py
|
|-- db/ # 存放操作数据库相关文件,主要用于与数据库交互
| |-- db_handle.py
|
|-- lib/ #存放程序中常用的自定义模块 - 第三方的也行
| |-- common.py
|
|-- conf/ # 存放配置文件 - 生产 + 开发
| |-- settings.py
|
|-- run.py # 程序的启动文件,一般放在项目的根目录下,默认运行文件作为sys.path的第一个路径
|-- setup.py # 安装、部署、打包的脚本。 -用Python流行的打包工具setuptools来管理这些事情
|-- requirements.txt # 存放软件依赖的外部Python包列表。
|-- README # 项目说明文件
- 1、软件定位,软件的基本功能;
- 2、运行代码的方法: 安装环境、启动命令等;
- 3、简要的使用说明;
- 4、代码目录结构说明,更详细点可以说明软件的基本原理;
- 5、常见问题说明。
# requirements.txt - python项目的版本依赖信息
- 1.提取 pip freeze -> requirements.txt
- 2.安装 pip install -r requirements.txt
常用模块
时间模块之time
# time模块的使用
import time
# 1.时间的三种格式
# (1) 时间戳(timestamp) - 从1970年到现在经过的秒数
print(time.time()) # 1693638597.3243444
# (2) 格式化(Format String) - 按照某种格式显示
print(time.strftime("%Y-%m-%d %X")) # 2023-09-02 15:09:57
print(time.strftime('%Y-%m-%d %H:%M:%S %p')) # 2023-09-02 15:09:57 PM
# (3) 结构化(struct_time) - struct_time元组显示(年,月,日,时,分,秒,一年中第几周,一年中第几天,夏令时)
# time.struct_time(tm_year=2023, tm_mon=9, tm_mday=2, tm_hour=15, tm_min=7, tm_sec=27,
# tm_wday=5, tm_yday=245, tm_isdst=0)
print(time.localtime()) # 本地时区
# print(time.gmtime()) # UTC时区
# 2.三种格式之间的转换 - 以结构化为中转
# 关系1: 结构化时间 (strftime) --> 格式化字符串时间
# 结构化时间 <-- 格式化字符串时间
print(time.strftime('%Y-%m-%d',time.localtime())) # 2023-09-02
print(time.strptime('2023-09-02 15:26:15','%Y-%m-%d %X'))
# 关系2: 结构化时间 (mktime) --> 时间戳
# 结构化时间 <--(localtime/gmtime) 时间戳
print(time.mktime(time.localtime())) # 结构化 转 时间戳
print(time.localtime(1693638597.3243444)) # 时间戳 转 结构化
# 关系3:格式化字符串时间 --> 结构化时间 --> 时间戳
# 格式化字符串时间 <--> 结构化时间 <-- 时间戳
# 3.补充
time.sleep() # 线程推迟指定的时间运行
时间模块之datetime
# datetime模块的使用
import datetime
import time
# 1.datetime的两种格式
# (1) datetime格式
print(datetime.datetime.now()) # 本地 - 2023-09-02 15:45:52.896833
print(datetime.datetime.utcnow()) # UTC - 2023-09-02 15:45:52.896833
print(datetime.datetime.fromtimestamp(time.time())) # 2023-09-02 15:45:52.896833
# (2) 字符串格式
# 转换1:datetime格式 --->字符串格式
ctime = datetime.datetime.now()
print(ctime.strftime('%Y-%m-%d')) # 2023-09-02
# 转换2:字符串格式 ---> datetime格式
ftime = ctime.strftime('%Y-%m-%d %X')
print(datetime.datetime.strptime(ftime,'%Y-%m-%d %H:%M:%S')) # 2023-09-02 15:45:14
# 2.时间的加减 - 这个模块的精彩所在,但是注意格式得是datetime格式
# - 所以需要time -->datetime 可以用过 时间戳 --->datetime
ctimestamp = time.time() # 当前时间戳
cdatetime = datetime.datetime.fromtimestamp(ctimestamp) # 时间戳转datetime格式
res1 = cdatetime + datetime.timedelta(days= -30) # days / seconds - 其它通过计算
res2 = cdatetime + datetime.timedelta(seconds= 60 * 60 * 24 * 30)
print('一个月前:{}\n一个月后:{}'.format(res1,res2))
# 一个月前:2023-08-03 15:54:23.874773
# 一个月后:2023-10-02 15:54:23.874773
随机模块之random
# random模块的使用
import random
# 1. 生成 0 - 1 之间得浮点数 - 可通过空值两边变量空值范围
print(random.random()) # 0.5767163371143376
# 2. 生成 1<= x <= 100 的整数
print(random.randint(1, 100)) # 90
# 3. 生成 1<= x < 10 的整数 # 9
print(random.randrange(1, 100))
# 4.从迭代对象中取出一个元素
ll = [1, 2.2, 'a', True, [1, 2], {'a': 1}, {3, 4, 3}, (5,)]
print(random.choice(ll)) # [1, 2]
# 5.一次从迭代对象中取出指定个数
print(random.sample(ll, 2)) # ['a', [1, 2]]
# 6.均匀分布
print(random.uniform(1, 3))
# 7.洗牌(打乱) - shuffle - 无返回值 - 直接影响原列表
random.shuffle(ll)
print(ll)
# 案例1:生成1-100得随机浮点数
print(1 + random.random() * 100)
# 案例二:生成随机验证码 - 数字 + 字母(大小写)
def make_code(count: int) -> list:
code = ''
for i in range(count):
num = random.randint(0, 9) # type:int
up_chr = chr(random.randrange(65, 65 + 26)) # type:str
low_chr = chr(random.randrange(97, 97 + 26)) # type:str
temp_list = [num, up_chr, low_chr] # type:list
code += str(random.choice(temp_list))
return code
code = make_code(6)
print(code)
操作系统模块之os
# os模块的使用
import os
# 1.os的常用功能
# (1) 获取路径 - path
file_path = os.path.abspath(__file__) # xxx/yy.py 获取运行文件的路径
print(file_path)
# 获取当前工作目录
# 方式一
print(os.getcwd()) # 获取当前工作目录
# 方式二 - 推荐
print(os.path.dirname(os.path.abspath(__file__))) # 先获取文件路径,再获取文件的目录路径
# 添加路径 - join
file_name = file_path.rsplit('\\')[-1]
path_name = os.path.dirname(file_path)
cpath = os.path.join(path_name,file_name)
print(cpath)
# 补充
path = ''
os.path.exists(path) #如果path存在,返回True;如果path不存在,返回False
os.path.isabs(path) #如果path是绝对路径,返回True
os.path.isfile(path) #如果path是一个存在的文件,返回True。否则返回False
os.path.getsize(path) #返回path的大小
# (2) 文件夹(目录) dir
# 创建
os.mkdir('dirname') # 单级目录
os.makedirs('dirname1/dirname2') # 多级目录
os.makedirs('dirname3/dirname4')
# 删除
os.rmdir('dirname') # 删除单级目录
os.removedirs('dirname1/dirname2') # 删除多级目录
# 切换
os.chdir('dirname3') # 切换工作目录
# 查看
print(os.listdir()) # 子文件夹 + 子文件的名字,以列表的形式返回
# (3) 其它
os.remove('filename') # 删除一个文件
os.remove('old_filename','new_filename') # 重命名文件/目录
os.system('pwd') # 运行shell命令
print(os.environ) # 获取系统环境变量
# 在python3.5之后,推出了一个新的模块pathlib
from pathlib import Path
res = Path(__file__).parent.parent
# 案例
# 查看某个目录下的所有的文件和文件夹(一级目录)
for item in os.listdir("/Users/wupeiqi/Documents/视频教程/路飞Python/mp4/开篇"):
print(item)
# 查看某个目录下的所有的文件和文件夹
for in_path, folder_list, name_list in os.walk("/Users/wupeiqi/Documents/视频教程/路飞Python/mp4/开篇"):
for name in name_list:
abs_path = os.path.join(in_path, name)
print(abs_path)
# 路径规范
window系统: C:\xx\xxx\xxx
Mac系统: /user/xxx/xxx/xxx
Linux系统: /user/xxx/xxx/xxx
os.path.normpath(a) # 返回符合操作系统的路径规范
# os路径处理
# 方式一:推荐使用
import os
# 具体应用
import os, sys
possible_topdir = os.path.normpath(os.path.join(
os.path.abspath(__file__),
os.pardir, # 上一级
os.pardir,
os.pardir
))
sys.path.insert(0, possible_topdir)
# 方式二:不推荐使用
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
操作系统模块之sys
# sys模块的使用
import sys
import os
from pathlib import Path
# 添加到环境变量中,django中重构项目结构可以感受到
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
path = Path(BASE_DIR).parent.parent
print(path)
sys.path.append(path)
# 了解
sys.argv #命令行参数List,第一个元素是程序本身路径
sys.exit(0) #退出程序,正常退出时exit(0)
sys.version #获取Python解释程序的版本信息
sys.maxint #最大的Int值
sys.path #返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值
sys.platform #返回操作系统平台名称
文件处理模块之shutil
# shutil模块的使用 -高级的 文件、文件夹、压缩包 处理模块
import shutil
# 0.常用
# (1) 删除文件夹
shutil.rmtree("xx/xxx/xxx/xxx")
# (2) 拷贝文件夹
shutil.copytree("原文件夹","目标文件夹路径")
# (3) 拷贝文件 - 拷贝源文件到文件夹(保证文件夹存在)
shutil.copy("原文件夹","目标文件夹路径/")
# (4) 重命名
shutil.move("x10", 'x10.txt') # 文件重命名
shutil.move("x1", 'x100') # 文件件重命名
# (5) 压缩和解压
shutil.make_archive(base_name='1116', format='zip', root_dir="ppp") # 压缩
shutil.unpack_archive(filename="1116.zip", extract_dir="1117", format='zip') #解压缩
# 1.将文件内容拷贝到另一文件中
file1_obj = open('a.txt','r')
file2_obj = open('b.txt','w')
shutil.copyfileobj(file1_obj,file2_obj)
# 2.拷贝 - 其它操作 - 权限、内容、组、用户
shutil.copyfile('b.txt','c.txt') # 拷贝文件
shutil.copymode('b.txt','c.txt') # 仅拷贝权限
shutil.copystat('b.txt','c.txt') # 仅拷贝状态信息
shutil.copy('b.txt','c.txt') # 仅拷贝文件和权限
shutil.copy2('b.txt','c.txt') # 仅拷贝文件和状态信息
# 3.递归操作
# (1)递归的去拷贝文件夹 - 目标目录不能存在,注意对folder2目录父级目录要有可写权限,ignore的意思是排除
shutil.copytree('f1', 'f2', symlinks=True, ignore=shutil.ignore_patterns('*.pyc', 'tmp*'))
# (2)删除递归文件
shutil.rmtree('folder1')
# (3)递归的去移动文件,它类似mv命令,其实就是重命名。
shutil.move('folder1', 'folder3')
# 4.压缩操作
# 知识储备
'''
format: 压缩包种类,“zip”, “tar”, “bztar”,“gztar”
root_dir: 要压缩的文件夹路径(默认当前目录)
owner: 用户,默认当前用户
group: 组,默认当前组
logger: 用于记录日志,通常是logging.Logger对象
'''
#将 /data 下的文件打包 放置当前程序目录
ret = shutil.make_archive("data_bak", 'gztar', root_dir='/data')
#将 /data下的文件打包 放置 /tmp/目录
ret = shutil.make_archive("/tmp/data_bak", 'gztar', root_dir='/data')
# 其它两种压缩工具
# 工具1 - zipfile
import zipfile
# (1) 压缩
z = zipfile.ZipFile('file1.zip','w')
z.write('a.log')
z.write('data.data')
z.close()
# (2) 解压
z = zipfile.ZipFile('file1.zip', 'r')
z.extractall(path='.')
z.close()
# 工具2 - tarfile
# 压缩
t=tarfile.open('/tmp/egon.tar','w')
t.add('/test1/a.py',arcname='a.bak')
t.add('/test1/b.py',arcname='b.bak')
t.close
# 解压
t=tarfile.open('/tmp/egon.tar','r')
t.extractall('/egon')
t.close()
序列化模块之json/pickel
# json 与pickle模块的使用
# 1.什么是序列化和反序列化 - pickle是python专用的
# 内存中的数据类型 -->序列化 (变成)-->变成特定的格式 (json格式或者pickle格式)
# 内存中的数据类型 <------(反序列化转换成) <--变成特定的格式 (json格式或者pickle格式)
# 2.为什么要序列化?
# (1) 方便存储,用于存档
# (2) 跨平台数据交互 - json
'''
python java
列表 特定的数据格式 数组
json类型 对应关系 python类型 java类型
{} - object dict Map
[] - array list/tuple Array
"string" - string str String
1234.56 - number int/float Int/Float/Double
true/false True/False # true/false
null None null
'''
# 3.如何使用序列化和反序礼化
# 做个小说明,每个平台都有自己的json工具,里面对应着不同格式在json中的样子
import json
# 1.json序列化与反序列化
# (1) 结果-序列化与反序列化 - dumps/loads - 数据类型
# 序列化 - dumps(data)
dic = {'a':1,'b':[1,2,3],'c':True,'d':123.45,'e':'eee' }
json_res=json.dumps(json_res)
print(json_res)
# 反序列化 - loads(data)
res=json.loads(json_res)
print(res)
# (2) 文件-序列化与反序列化 - dump/load - 文件句柄 f
# 序列化 - dump(数据,f)
with open('a.json','w',encoding='utf-8') as f1:
json.dump(dic, f1)
# 反序列化 - load(f)
with open('a.json','r',encoding='utf-8') as f2:
# 方式一 - loads - 读取数据
data = f2.read()
data1 = json.loads(data)
print(data1)
# 方式二 - load - 文件句柄
f2.seek(0,0) # 前面f.read()读取数据后,指针在末尾,而末尾没有数据
data2 = json.load(f2)
print(data2)
# 2.解决中文编码问题
info = {"name": "邱恩婷", "age": 19,'a':'b'}
data = json.dumps(info,ensure_ascii=False)
print(data)
import decimal
data = decimal.Decimal('0.3')
info = float(data)
data = json.dumps(info,ensure_ascii=False)
print(data)
# pickle的使用 - 把json替换成pickle就完全一样了
import pickle
res=pickle.dumps({1,2,3,4,5})
print(res,type(res))
s=pickle.loads(res)
print(s,type(s))
猴子补丁
# 一.什么是猴子补丁?
# 属性在运行时的动态替换,叫做猴子补丁(Monkey Patch)。
# 猴子补丁的核心就是用自己的代码替换所用模块的源代码,详细地如下
# 1,这个词原来为Guerrilla Patch,杂牌军、游击队,说明这部分不是原装的,在英文里guerilla发音和gorllia(猩猩)相似,再后来就写了monkey(猴子)。
# 2,还有一种解释是说由于这种方式将原来的代码弄乱了(messing with it),在英文里叫monkeying about(顽皮的),所以叫做Monkey Patch。
# 二. 猴子补丁的功能(一切皆对象)
# 1.拥有在模块运行时替换的功能, 例如: 一个函数对象赋值给另外一个函数对象(把函数原本的执行的功能给替换了)
# 补充猴子补丁 - 不影响原调用的情况下,替换模块
'''
如果我们的程序中已经基于json模块编写了大量代码了,发现有一个模块ujson比它性能更高,
但用法一样,我们肯定不会想所有的代码都换成ujson.dumps或者ujson.loads,那我们可能
会想到这么做
import ujson as json,但是这么做的需要每个文件都重新导入一下,维护成本依然很高
此时我们就可以用到猴子补丁了
只需要在入口处加上
, 只需要在入口加上:
'''
import json
import ujson
# 调用不变,替换成ujson的函数内地址
def monkey_patch_json():
json.__name__ = 'ujson'
json.dumps = ujson.dumps
json.loads = ujson.loads
# 一般放在入口处打猴子补丁,也就是执行文件
monkey_patch_json()
'''
#其实这种场景也比较多, 比如我们引用团队通用库里的一个模块, 又想丰富模块的功能, 除了继承之外也可以考虑用Monkey
Patch.采用猴子补丁之后,如果发现ujson不符合预期,那也可以快速撤掉补丁。个人感觉Monkey
Patch带了便利的同时也有搞乱源代码的风险!
'''
加密模块之hashlib
# hashlib模块的使用 - 对数据进行加密的模块
# 1、什么是哈希hash
# hash一类算法,该算法接受传入的内容,经过运算得到一串hash值
# hash值的特点:
#I 只要传入的内容一样,得到的hash值必然一样
#II 不能由hash值返解成内容
#III 不管传入的内容有多大,只要使用的hash算法不变,得到的hash值长度是一定
# 2、hash的用途
# 用途1:特点II用于密码密文传输与验证
# 用途2:特点I、III用于文件完整性校验
# 3、如何用
# 以md5算法为例
def my_md5(arg:str) ->str:
import hashlib
salt = 'abcdefghijklmnopqrstuvwxyz' # 加盐
obj = hashlib.md5() # 如果换算法,只需要该这个就行
obj.update(salt.encode('utf-8')) # 加盐
obj.update(arg.encode('utf-8'))
return obj.hexdigest()
res = my_md5('你好世界')
print(res)
# 模拟撞库 -虽然不能反解,使用常用的密码,进行拼接
cryptograph='aee949757a2e698417463d47acac93df'
import hashlib
# 制作密码字段
passwds=[
'zs3714',
'zs1313',
'zs94139413',
'zs123456',
'123456zs',
'z123s',
]
dic={}
for p in passwds:
res=hashlib.md5(p.encode('utf-8'))
dic[p]=res.hexdigest()
# 模拟撞库得到密码
for k,v in dic.items():
if v == cryptograph:
print('撞库成功,明文密码是:%s' %k)
break
获取ini文件模块之configparser
import configparser
# 指定解析文件路径
config = configparser.ConfigParser()
config.read('config.ini',encoding='utf-8')
# 0.常用
config.get('section1','k1')
# 1、获取sections
print(config.sections())
# 2、获取某一section下的所有options
print('secition1',config.options('section1'))
# 3、获取items
print('secction2',config.items('section2'))
# 4.获取具体值
print(config.get('section1','user'))
# 5.其它 - 一般来说,都是作为获取配置来用,很少去改
config.remove_section('section2')
config.remove_option('section1','k1')
print(config.has_section('section1'))
config.add_section('egon')
config.set('egon','name','egon')
config.write(open('a.cfg','w'))
数据库封装版本
# # 获取配置文件信息
import os
from configparser import ConfigParser
from urllib import parse
import pymysql
import psycopg2
import pandas as pd
from sqlalchemy import create_engine
class ConfigUtil(object):
def __init__(self,config_file):
# 读取配置文件
current_dir = os.getcwd()
self.config_path = os.path.join(current_dir,config_file)
self.config = ConfigParser()
self.config.read(self.config_path,encoding='utf8')
def getConfig(self,param1,param2):
return self.config.get(param1,param2)
class DBUtil(ConfigUtil):
# 相当于上下文,开始和销毁
def __enter__(self):
return self.getConn()
def __exit__(self, exc_type, exc_val, exc_tb):
return self.getConn().close()
def __init__(self,cofile_file='config/dbconfig.ini'):
# 读取继承类的初始化方法
super().__init__(cofile_file)
# 获取数据库配置信息
self.host = self.config.get("MYSQL",'host')
self.port = self.config.get("MYSQL", 'port')
self.database = self.config.get("MYSQL", 'database')
self.user = self.config.get("MYSQL", 'user')
# 防止乱码
self.password = parse.quote(self.config.get("MYSQL", 'password'))
def getConn(self,model):
if model == 'mysql':
# conn = pymysql.connect(host="localhost",port = 3306,database="guigushang", user="root", password="123456")
conn = create_engine('mysql+pymysql://{}:{}@{}:{}/{}'
.format(self.user, self.password, self.host, self.port, self.database))
elif model == 'pg':
# import warnings
# # 设置程序警告
# warnings.filterwarnings("ignore")
# option_str = "-c search_path=test_schame "
# conn = psycopg2.connect(host="localhost", port=5432, database="Test_DB",options=option_str)
conn = create_engine(f'postgresql+psycopg2://{self.user}:{self.password}@{self.host}:{self.port}/postgres')
# conn.execution_options({'shard_1': 'test_schame'})
else:
raise Exception('当前没有配置该数据库')
return conn
def read_data(self,sql_str,model):
conn = self.getConn(model)
df = pd.read_sql(sql_str,conn)
return df
def write_data(self,df1,tablename,writemodel,model):
conn = self.getConn(model)
# df.to_sql(name ='test4' ,con = conn1, if_exists= "append",index = False, index_label=['a','b','c','d'])
df1.to_sql(name = tablename , con = conn,if_exists= writemodel,index=False)
# 原生的sql
def run_sql(self,sql_str,model):
conn1 = self.getConn(model)
conn = conn1.connect()
conn.execute(sql_str)
命令交互模块之subprocess
# subprocess模块的使用
# 可以用来与不同的操作系统做命令交互
import subprocess
# in_str = input('请输入命令').strip()
obj = subprocess.Popen( 'ipconfig /all', # 执行命令
shell=True, # If true, the command will be executed through the shell
stdout=subprocess.PIPE, # 输出正确结果的管道
stderr=subprocess.PIPE, # 输出错误结果的管道
)
# print(obj)
# subprocess使用当前系统默认编码,得到结果为bytes类型
print(obj.stdout.read().decode('gbk')) # 读取正确结果 - window:gbk mac/linux:utf-8
print(obj.stderr.read().decode('gbk')) # 读取错误结果 - window:gbk mac/linux:utf-8
# 一个数据流与另一个数据流交互
# sh-3.2# ls /Users/egon/Desktop | grep txt$
res1=subprocess.Popen('ls /Users/jieli/Desktop',shell=True,stdout=subprocess.PIPE)
res=subprocess.Popen('grep txt$',shell=True,stdin=res1.stdout,
stdout=subprocess.PIPE)
print(res.stdout.read().decode('utf-8'))
日志模块之logging
日志的基本使用
# logging模块的使用
import logging
# 1.日志基本设置
logging.basicConfig(
# 1.日志输出位置 (1) 终端 (2) 文件
# filename='access.log', # 不指定,默认打印到终端
# 2.日志格式
format= '%(asctime)s - %(name)s - %(levelname)s - %(module)s: %(message)s',
# 3.时间格式
datefmt='%Y-%m-%d %H-%M-%S %p',
# 4.日志级别
# critical => 50 # 失败
# error => 40 #
# warning => 30 # 警告
# info => 20 # 提示
# debug => 10 #
level=10,
# 5.文件打开方式
filemode='a',
)
# 2.输出日志
logging.debug('调试debug')
logging.info('消息info')
logging.warning('警告warn')
logging.error('错误error')
logging.critical('严重critical')
# 3.对象的使用 - logger对象:负责产生日志,然后交给Filter过滤,然后交给不同的Handler输出
# (1) logger对象:负责产生日志
logger = logging.getLogger(__file__) # __file__ 可以换其它名字
# (2) Filter对象:过滤 - 不常用
# (3) Handler对象:接受logger传来的日志,然后控制输出
h1=logging.FileHandler('t1.log') # 日志存入到文件中
h2=logging.StreamHandler() # 打印到终端
# (4) Formatter对象:日志格式
formmater1=logging.Formatter('%(asctime)s - %(name)s - %(levelname)s -'
'%(module)s: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S %p',)
# (5) 为Handler对象绑定格式
h1.setFormatter(formmater1)
# (6) 将Handler添加给logger并设置日志级别
logger.addHandler(h1)
logger.setLevel(10)
日志的通用版本(可直接用)
"""
日志配置字典LOGGING_DIC
"""
# 1、定义三种日志输出格式,日志中可能用到的格式化串如下
# %(name)s Logger的名字
# %(levelno)s 数字形式的日志级别
# %(levelname)s 文本形式的日志级别
# %(pathname)s 调用日志输出函数的模块的完整路径名,可能没有
# %(filename)s 调用日志输出函数的模块的文件名
# %(module)s 调用日志输出函数的模块名
# %(funcName)s 调用日志输出函数的函数名
# %(lineno)d 调用日志输出函数的语句所在的代码行
# %(created)f 当前时间,用UNIX标准的表示时间的浮 点数表示
# %(relativeCreated)d 输出日志信息时的,自Logger创建以 来的毫秒数
# %(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒
# %(thread)d 线程ID。可能没有
# %(threadName)s 线程名。可能没有
# %(process)d 进程ID。可能没有
# %(message)s用户输出的消息
# 日志级别
CRITICAL = 50 #FATAL = CRITICAL
ERROR = 40
WARNING = 30 #WARN = WARNING
INFO = 20
DEBUG = 10
NOTSET = 0 #不设置
# 2、强调:其中的%(name)s为getlogger时指定的名字
standard_format = '%(asctime)s - %(threadName)s:%(thread)d - 日志名字:%(name)s - %(filename)s:%(lineno)d -' \
'%(levelname)s - %(message)s'
simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
test_format = '%(asctime)s] %(message)s'
# 3、日志配置字典
LOGGING_DIC = {
'version': 1,
'disable_existing_loggers': False,
# 第一部分:日志格式
'formatters': {
'standard': {
'format': standard_format
},
'simple': {
'format': simple_format
},
'test': {
'format': test_format
},
},
# 过滤用的比较少
'filters': {},
# 第二部分:handlers是日志的接收者,不同的handler会将日志输出到不同的位置
'handlers': {
#打印到终端的日志
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler', # 打印到屏幕
'formatter': 'simple'
},
# 打印到文件的日志,收集WARNING及以上的日志
'default': {
'level': 'WARNING',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件
# 'maxBytes': 1024*1024*5, # 日志大小 5M
'maxBytes': 1000,
'backupCount': 5,
'filename': 'a1.log', # os.path.join(os.path.dirname(os.path.dirname(__file__)),'log','a2.log')
'encoding': 'utf-8',
'formatter': 'standard',
},
#打印到文件的日志,收集info及以上的日志
'other': {
'level': 'INFO',
'class': 'logging.FileHandler', # 保存到文件
'filename': 'a2.log', # os.path.join(os.path.dirname(os.path.dirname(__file__)),'log','a2.log')
'encoding': 'utf-8',
'formatter': 'test',
},
},
# 第三部分:loggers是日志的产生者,产生的日志会传递给handler然后控制输出
'loggers': {
#logging.getLogger(__name__)拿到的logger配置
'kkk': {
'handlers': ['console','other'], # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕
'level': 'DEBUG', # loggers(第一层日志级别关限制)--->handlers(第二层日志级别关卡限制)
'propagate': False, # 默认为True,向上(更高level的logger)传递,通常设置为False即可,否则会一份日志向上层层传递
},
'终端提示': {
'handlers': ['console',], # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕
'level': 'DEBUG', # loggers(第一层日志级别关限制)--->handlers(第二层日志级别关卡限制)
'propagate': False, # 默认为True,向上(更高level的logger)传递,通常设置为False即可,否则会一份日志向上层层传递
},
'': {
'handlers': ['default', ], # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕
'level': 'DEBUG', # loggers(第一层日志级别关限制)--->handlers(第二层日志级别关卡限制)
'propagate': False, # 默认为True,向上(更高level的logger)传递,通常设置为False即可,否则会一份日志向上层层传递
},
},
}
# 使用
from logging import config,getLogger
config.dictConfig(LOGGING_DIC) # 获取字典对象的配置
# 根据需要使用不同的配置日志对象
logger1 = getLogger('kkk')
logger1.info('这是个日志1')
# 自定义名字 - 即 在logger 中 '' 没有写名字的那个可以任意指定
logger2 = getLogger('用户常规使用')
logger2.info('这是个日志2')
正则模块之re
# re模块的使用
# 一.正则:通过一些特殊符号去匹配文本或字符串的表达式
# 很多的语言都有这个的存在,python中使用re模块实现
# 二.正则基本知识
'''
\w - 匹配数字、字母、下划线 ; \W - 匹配非数字、字母、下划线
\d - 匹配数字 ; \D - 匹配非数字
\s - 匹配空白字符 ; \S - 匹配非空白字符
^ - 匹配字符串的开头 ; $ - 匹配字符串的末尾
. - 匹配任意字符
+ -- 匹配 1次 或 多次
* -- 匹配 0次 或 多次
? -- 匹配 0次 或 1次
{n,m} / {n,} / {n}
[a-zA-Z] --- 取出一个
[^a-zA-Z] --- 不在这个集合中的
a|b --- 匹配a或b
() --- 分组
(1) 贪心算法 - 尽可能多匹配
.*
(2) 非贪心算法 - 尽可能少匹配或不匹配
.*?
'''
# 三.re模块的使用
'''
(1) findall 以列表的形式返回
(2) search 只能返回一此匹配上的结果
(3) match 只能从开头开始匹配,有局限性
(4) split
(5) sub 替换内容
(6) compile + search/findall使用
'''
# 四.正则匹配的用法
import re
# 1.基本匹配
# (1) \w与\W - 匹配数字字符下划线
print(re.findall('\w','aAbc123_*()-=')) # ['a', 'A', 'b', 'c', '1', '2', '3', '_']
print(re.findall('\W','aAbc123_*()-= ')) # # ['*', '(', ')', '-', '=', ' ']
# (2) \s与\S - 匹配空白字符
print(re.findall('\s','aA\rbc\t\n12\f3_*()-= ')) # ['\r', '\t', '\n', '\x0c', ' ']
print(re.findall('\S','aA\rbc\t\n12\f3_*()-= ')) # ['a', 'A', 'b', 'c', '1', '2', '3', '_', '*', '(', ')', '-', '=']
# (3) \d 与 \D - 匹配数字
print(re.findall('\d','aA\rbc\t\n12\f3_*()-= ')) # ['1', '2', '3']
print(re.findall('\D','aA\rbc\t\n12\f3_*()-= ')) # ['a', 'A', '\r', 'b', 'c', '\t', '\n', '\x0c', '_', '*', '(', ')', '-', '=', ' ']
# (4) \n 与 \t
print(re.findall(r'\n','hello egon \n123')) #['\n']
print(re.findall(r'\t','hello egon\t123')) #['\n']
# (5) \A 与 \Z 只匹配到换行前字符
print(re.findall('\Ahe','hello egon 123')) #['he'],\A==>^
print(re.findall('123\Z','hello egon 123')) #['123'],\Z==>$
# 2.重复匹配 | . | * | ? | .* | .*? | + | {n,m} |
# (1) .:匹配除了\n之外任意一个字符,指定re.DOTALL之后才能匹配换行符
print(re.findall('a.b','a1b a2b a b abbbb a\nb a\tb a*b')) # ['a1b', 'a2b', 'a b', 'abb', 'a\tb', 'a*b']
print(re.findall('a.b','a1b a2b a b abbbb a\nb a\tb a*b',re.DOTALL)) # ['a1b', 'a2b', 'a b', 'abb', 'a\nb', 'a\tb', 'a*b']
# (2) *:左侧字符重复0次或无穷次,性格贪婪
print(re.findall('ab*','a ab abb abbbbbbbb bbbbbbbb')) # ['a', 'ab', 'abb', 'abbbbbbbb']
# (3) +:左侧字符重复1次或无穷次,性格贪婪
print(re.findall('ab+','a ab abb abbbbbbbb bbbbbbbb')) # ['ab', 'abb', 'abbbbbbbb']
# (4) ?:左侧字符重复0次或1次,性格贪婪
print(re.findall('ab?','a ab abb abbbbbbbb bbbbbbbb')) # ['a', 'ab', 'ab', 'ab']
# (5){n,m}:左侧字符重复n次到m次,性格贪婪
# {0,} => * | {1,} => + | {0,1} => ?
print(re.findall('ab{2,5}','a ab abb abbb abbbb abbbbbbbb bbbbbbbb')) # ['abb', 'abbb', 'abbbb', 'abbbbb']
# (6) .*默认为贪婪匹配
print(re.findall('a.*b','a1b22222222b')) #['a1b22222222b']
# (7) .*?为非贪婪匹配:推荐使用
print(re.findall('a.*?b','a1b22222222b')) #['a1b']
# 3. [] :匹配指定字符一个
# (1) 匹配指定字符一个
print(re.findall('a[0-9a-zA-Z]b','a1111111b axb a3b a1b a0b a4b a9b aXb a b a\nb',re.DOTALL)) # ['axb', 'a3b', 'a1b', 'a0b', 'a4b', 'a9b', 'aXb']
# (2) 不在指定字符中的
print(re.findall('a[^0-9a-zA-Z]b','a1111111b axb a3b a1b a0b a4b a9b aXb a b a\nb',re.DOTALL)) # ['a b', 'a\nb']
# 4. () :分组匹配 -- 匹配到符合条件时,只输出 () 里的内容
print(re.findall('(ab)+123','ababab123')) #['ab'] 匹配到末尾的ab123中的ab
print(re.findall('(?:ab)+123','ababab123')) # ['ababab123'] findall的结果不是匹配的全部内容,而是组内的内容,?:可以让结果为匹配的全部内容
# 5.贪心算法: (.*?) 解释:匹配此条件下的所有字符
print(re.findall('href="(.*?)"','<a href="http://www.baidu.com">点击</a>'))#['http://www.baidu.com']
print(re.findall('href="(?:.*?)"','<a href="http://www.baidu.com">点击</a>'))#['href="http://www.baidu.com"']
# 6. 相当于条件 或 - 0 或 1 任意一个匹配即可
print(re.findall('compan(?:y|ies)','Too many companies is my company')) # ['companies', 'company']
print(re.findall('compan(y|ies)','Too many companies is my company')) # ['ies', 'y']
# 五、re模块的用法
# 1.findall 以列表的形式返回
print(re.findall('e','alex make love') ,re.S) #['e', 'e', 'e'],返回所有满足匹配条件的结果,放在列表里
# 2.search 返回第一个匹配上的结果 - 调用group()方法得到匹配的字符串 - 没有返回None
print(re.search('love','alex make love love').group()) # love
# 3.match 只能从头开始匹配 - 有局限性
print(re.match('e','alex make love')) #None,同search,不过在字符串开始处进行匹配,完全可以用search+^代替match
res=re.match('^Hello\s(\d+)\s(\d+)\s.*Demo', 'Hello 123 456 World_This is a Regex Demo')
print(res.group()) #取所有匹配的内容 # 'Hello 123 456 World_This is a Regex Demo'
print(res.group(1)) #取匹配的第一个括号内的内容 # 123
print(res.group(2)) #去陪陪的第二个括号内的内容 # 456
# 4.split
print(re.split('[ab]','abcd')) #['', '', 'cd'],先按'a'分割得到''和'bcd',再对''和'bcd'分别按'b'分割
# 5.sub 替换内容
print('===>',re.sub('a','A','alex make love')) #===> Alex mAke love,不指定n,默认替换所有
print('===>',re.sub('a','A','alex make love',1)) #===> Alex make love
print('===>',re.sub('a','A','alex make love',2)) #===> Alex mAke love
print('===>',re.sub('^(\w+)(.*?\s)(\w+)(.*?\s)(\w+)(.*?)$',r'\5\2\3\4\1','alex make love')) #===> love make alex
print('===>',re.subn('a','A','alex make love')) #===> ('Alex mAke love', 2),结果带有总共替换的个数
#6 compile + search/findall使用
obj=re.compile('\d{2}')
print(obj.search('abc123eeee').group()) #12
print(obj.findall('abc123eeee')) #['12'],重用了obj
#总结:尽量精简,详细的如下
# 尽量使用泛匹配模式.*
# 尽量使用非贪婪模式:.*?
# 使用括号得到匹配目标:用group(n)去取得结果
# 有换行符就用re.S:修改模式
办公模块之excel/word(暂时不学)
# excel参考地址: https://www.cnblogs.com/wykang/p/17518831.html
# word参考地址 : https://www.cnblogs.com/wykang/p/17518832.html
监控模块 -之paramiko(-来自知乎参考)
# 参考地址 https://zhuanlan.zhihu.com/p/313718499
def demo1():
import paramiko
# 实例化SSHClient
ssh_client = paramiko.SSHClient()
# 自动添加策略,保存服务器的主机名和密钥信息,如果不添加,那么不再本地know_hosts文件中记录的主机将无法连接 ,此方法必须放在connect方法的前面
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 连接SSH服务端,以用户名和密码进行认证 ,调用connect方法连接服务器
ssh_client.connect(hostname='123.249.68.131', port=22, username='root', password='Kangc123')
# 打开一个Channel并执行命令 结果放到stdout中,如果有错误将放到stderr中
# stdin, stdout, stderr = ssh_client.exec_command('cd /home/')
stdin, stdout, stderr = ssh_client.exec_command('tree /home/')
# stdout 为正确输出,stderr为错误输出,同时是有1个变量有值 # 打印执行结果
print(stdout.read().decode('utf-8'))
# 关闭SSHClient连接
ssh_client.close()
def demo2():
import paramiko
# 实例化一个transport对象
tran = paramiko.Transport(('123.249.68.131', 22))
# 连接SSH服务端,使用password
tran.connect(username="root", password='Kangc123')
# # 或使用
# # 配置私人密钥文件位置
# private = paramiko.RSAKey.from_private_key_file('/root/.ssh/id_rsa')
# # 连接SSH服务端,使用pkey指定私钥
# tran.connect(username="root", pkey=private)
# 获取SFTP实例
sftp = paramiko.SFTPClient.from_transport(tran)
# 设置上传的本地/远程文件路径
local_path = "/home/1.txt"
remote_path = "/tmp/1.txt"
# 执行上传动作
sftp.put(local_path, remote_path)
# 执行下载动作
sftp.get(remote_path, local_path)
# 关闭Transport通道
tran.close()
def demo3():
# 单台主机操作
import paramiko
#############################配置信息#####################################
# 登陆参数设置
hostname = "192.168.137.100"
host_port = 22
username = "root"
password = "123456"
########################################################################
def ssh_client_con():
"""创建ssh连接,并执行shell指令"""
# 1 创建ssh_client实例
ssh_client = paramiko.SSHClient()
# 自动处理第一次连接的yes或者no的问题
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy)
# 2 连接服务器
ssh_client.connect(
port=host_port,
hostname=hostname,
username=username,
password=password
)
# 3 执行shell命令
# 构造shell指令
shell_command = "ps aux"
# 执行shell指令
stdin, stdout, stderr = ssh_client.exec_command(shell_command)
# 输出返回信息
stdout_info = stdout.read().decode('utf8')
print(stdout_info)
# 输出返回的错误信息
stderr_info = stderr.read().decode('utf8')
print(stderr_info)
def sftp_client_con():
# 1 创建transport通道
tran = paramiko.Transport((hostname, host_port))
tran.connect(username=username, password=password)
# 2 创建sftp实例
sftp = paramiko.SFTPClient.from_transport(tran)
# 3 执行上传功能
local_path = "米汤.jpg" # 本地路径
remote_path = "/home/333.jpg" # 远程路径
put_info = sftp.put(local_path, remote_path, confirm=True)
print(put_info)
print(f"上传{local_path}完成")
# 4 执行下载功能
save_path = "7.jpg" # 本地保存文件路径
sftp.get(remotepath=remote_path, localpath=save_path)
print(f'下载{save_path}完成')
# 5 关闭通道
tran.close()
# 调用函数执行功能
ssh_client_con()
sftp_client_con()
def demo4():
import paramiko
def ssh_client_con():
"""创建ssh连接,并执行shell指令"""
# 1 创建ssh_client实例
ssh_client = paramiko.SSHClient()
# 自动处理第一次连接的yes或者no的问题
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy)
# 2 连接服务器
ssh_client.connect(
port=host_port,
hostname=hostname,
username=username,
password=password
)
# 3 执行shell命令
# 构造shell指令
shell_command = """
path='/tmp'
tree ${path}
# 如果tree命令执行失败,则安装tree后再执行
if [ $? -ne 0 ]
then
yum install -y tree
tree ${path}
fi
"""
# 执行shell指令
stdin, stdout, stderr = ssh_client.exec_command(shell_command)
# 输出返回信息
stdout_info = stdout.read().decode('utf8')
print(stdout_info)
# 输出返回的错误信息
stderr_info = stderr.read().decode('utf8')
print(stderr_info)
def sftp_client_con():
# 1 创建transport通道
tran = paramiko.Transport((hostname, host_port))
tran.connect(username=username, password=password)
# 2 创建sftp实例
sftp = paramiko.SFTPClient.from_transport(tran)
# 3 执行上传功能
local_path = "米汤.jpg"
remote_path = "/home/333.jpg"
put_info = sftp.put(local_path, remote_path, confirm=True)
print(put_info)
print(f"上传{local_path}完成")
# 4 执行下载功能
save_path = "7.jpg"
sftp.get(remotepath=remote_path, localpath=save_path)
print(f'下载{save_path}完成')
# 5 关闭通道
tran.close()
# 读取主机信息
try:
with open('host_site.txt', 'r', encoding='utf8') as host_file:
for host_info in host_file:
line = host_info.strip('\n')
hostname, host_port, username, password = line.split(',')
print(f'---------------{hostname}执行结果-----------')
ssh_client_con() # 执行command并返回结果
# 本例适合批量主机处理相同的command
except FileNotFoundError as e:
print(e)
# host_site.txt 文件内容格式如下
# 主机地址 端口 用户 密码
# 192.168.137.100,22,root,123456
# 192.168.137.101,22,root,123456
# 192.168.137.102,22,root,123456
# 192.168.137.103,22,root,123456
# 192.168.137.104,22,root,123456
异常处理
异常处理 - 基本版
# 什么是异常
- 1.异常是程序发生错误的信号。程序一旦出现错误,便会产生一个异常,
- 2.若程序中没有处理它,就会抛出该异常,程序的运行也随之终止。在Python中,错误触发的异常如下
- 3.而错误分成两种:
- (1) 语法上的错误SyntaxError,这种错误应该在程序运行前就修改正确
- (2) 逻辑上的错误,常见的逻辑错误如:
# 1+’2’ - TypeError:数字类型无法与字符串类型相加
# num=int(input(">>: ")) | hello - ValueError:当字符串包含有非数字的值时,无法转成int类型
# x - NameError:引用了一个不存在的名字x
# l=['a','b'] | l[2] - IndexError:索引超出列表的限制
# dic={'name':'zs'} | dic['age'] - KeyError:引用了一个不存在的key
# class Foo: pass | Foo.x - AttributeError:引用的属性不存在
# 1/0 - ZeroDivisionError:除数不能为0
- 4.异常处理的三个特征
- (1) 异常的追踪信息
- (2) 异常的类型
- (3) 异常的内容
- 5.为何处理异常
- (1) 为了增强程序的健壮性,即便是程序运行过程中出错了,也不要终止程序
- (2) 而是捕捉异常并处理:将出错信息记录到日志内
# 如何使用?
# 万能版
try:
被检测的代码块
except NameError:
触发NameError时对应的处理逻辑
except (IndexError,TypeError):
触发IndexError,TypeError)时对应的处理逻辑
except Exception:
其他类型的异常统一用此处的逻辑处理
else:
没有异常发生时执行的代码块 - 可用来提示正常结束执行
finally:
无论有无异常发生都会执行的代码块 - 可用来回收系统资源
异常处理 - 自定义版
# 自定义错误类型 - raise
- 在不符合Python解释器的语法或逻辑规则时,即Python解释器主动触发的各种类型的异常
- 使用raise语句,来自定义触发规则或抛出自带异常
# 自带异常类型
class Student:
def __init__(self,name,age):
if not isinstance(name,str):
raise TypeError('name must be str')
if not isinstance(age,int):
raise TypeError('age must be int')
self.name=name
self.age=age
stu1=Student(4573,18) # TypeError: name must be str
stu2=Student('egon','18') # TypeError: age must be int
# 自定义异常类型
class PoolEmptyError(Exception): # 可以通过继承Exception来定义一个全新的异常
def __init__(self,value='The proxy source is exhausted'): # 可以定制初始化方法
super(PoolEmptyError,self).__init__()
self.value=value
def __str__(self): # 可以定义该方法用来定制触发异常时打印异常值的格式
return '< %s >' %self.value
class NetworkIOError(IOError): # 也可以在特定异常的基础上扩展一个相关的异常
pass
raise PoolEmptyError # __main__.PoolEmptyError: < The proxy source is exhausted >
raise NetworkIOError('连接被拒绝') # __main__.NetworkIOError: 连接被拒绝
# 断言 -asset
- Python还提供了一个断言语句assert expression,断定表达式expression成立,
- 否则触发异常AssertionError,与raise-if-not的语义相同
- 成功,程序继续执行,不成功直接报错
age='18'
# 若表达式isinstance(age,int)返回值为False则触发异常AssertionError
assert isinstance(age,int)
# 等同于
if not isinstance(age,int):
raise AssertionError
面向对象编程
面向编程介绍
对象的概念
# 一、对象的概念
'''
面向过程:
核心是"过程"二字
过程的终极奥义就是将程序流程化
过程是"流水线",用来分步骤解决问题的
面向对象:
核心是"对象"二字
对象的终极奥义就是将程序"整合"
对象是"容器",用来盛放数据与功能的集合
类也是"容器",该容器用来存放同类对象共有的数据与功能
'''
# 程序 = 数据 + 功能
# 容器 - 盛放数据与功能
# 学生的容器=学生的数据+学生的功能
# 课程的容器=课程的数据+课程的功能
类和对象
# 二、类和对象
'''
什么是类?对象具有相同数据和功能的集合
什么是对象?对类的实例化
'''
# 类和对象的关系
'''
对象1 (对象1独有的数据)
对象2 (对象2独有的数据) ---(相同的东西去类中取) ---> 类 (同类对象们相同/共有的数据与功能)
对象3 (对象3独有的数据)
1.在程序中,必须要事先定义类,然后再调用类产生对象(调用类拿到的返回值就是对象)
2.产生对象的类与对象之间存在关联,这种关联指的是:对象可以访问到类中共有的数据与功能,
- 所以类中的内容仍然是属于对象的,类只不过是一种节省空间、减少代码冗余的机制
3.面向对象编程最终的核心仍然是去使用对象。
'''
面向对象编程案例
# 三、面向对象编程
# 1.类的定义和实例化
# (1) 先定义类
# 类是对象相似数据与功能的集合体
# 所以类体中常见的是变量和函数的定义,但是类体起始可以包含任意的其它代码的
# 注意(要记住):类体代码是类定义阶段就会立即执行,会产生类的名称空间
# 以学校选课系统为例
# 需求
'''
学生的数据有:学校、名字、年龄、性别
学生的功能有:选课
学生1: 数据:学校='清北大学' 名字='111' 年龄=18 性别=男
功能:选课
学生2: 数据:学校='清北大学' 名字='333' 年龄=28 性别=男
功能:选课
学生3: 数据:学校='清北大学' 名字='111' 年龄=38 性别=女
功能:选课
'''
# 总结 - 总结出一个学生类,用来存放学生们相同的数据与功能
'''
# 学生类
相同的特征:
学校=清华大学
相同的功能:
选课
'''
class Student(object):
# 1.变量的定义
school = '清北大学'
# 总结__init__方法
# 1、会在调用类时自动触发执行,用来为对象初始化自己独有的数据
# 2、__init__内应该存放是为对象初始化属性的功能,但是是可以存放任意其他代码,想要在
# 类调用时就立刻执行的代码都可以放到该方法内
# 3、__init__方法必须返回None
def __init__(self, name, sex, age):
self.name = name
self.sex = sex
self.age = age
# 2.功能的定义
def choose(self):
print('%s is choosing a course' % self.name)
# 调用类 - 1.先产生一个空对象stu,然后将stu1
# - 2.连同调用类括号内的参数一起传给Student.__init__(stu1,’李建刚’,’男’,28)
# - 3.产生对象的名称空间,返回初始完的对象 - 可使用__dict__查看
stu1 = Student('李建刚', '男', 28)
stu2 = Student('王大力', '女', 18)
stu3 = Student('牛嗷嗷', '男', 38)
print(stu1.__dict__) # 对象名称空间的属性
# 这个时候变化
'''
对象1 (对象1独有的数据)
'name':'李建刚' | 'sex':'男' | 'age':28
对象2 (对象2独有的数据) ---(相同的东西去类中取) ---> 类 (同类对象们相同/共有的数据与功能)
'name':'王大力' | 'sex':'女' | 'age':18 'school':'清北大学' | 'choose':<function Student.choose at 0x000001FDEDB10E58>
对象3 (对象3独有的数据)
'name':'牛嗷嗷' | 'sex':'男' | 'age':38
'''
属性访问
class Student(object):
# 1.变量的定义
school = '清北大学'
# 总结__init__方法
# 1、会在调用类时自动触发执行,用来为对象初始化自己独有的数据
# 2、__init__内应该存放是为对象初始化属性的功能,但是是可以存放任意其他代码,想要在
# 类调用时就立刻执行的代码都可以放到该方法内
# 3、__init__方法必须返回None
def __init__(self, name, sex, age):
self.name = name
self.sex = sex
self.age = age
# 2.功能的定义
def choose(self):
print('%s is choosing a course' % self.name)
# 2.属性访问
# (1) 类属性与对象属性
# 类属性:数据属性 + 函数属性 - 通过__dict__ 如:Student.__dict__['school']
print(Student.__dict__)
# 对象属性:对象在实例化时,给__init__传入的参数
stu1_obj = Student('小明','男',100)
stu2_obj = Student('小红','女',18)
print(stu1_obj.__dict__)
print(stu2_obj.__dict__)
# (2) 属性查找顺序与绑定方法
# 类的数据属性 - 共享给所有对象用的,指向相同的内存地址,
print(Student.school,id(Student.school)) # 清北大学 2765815764336
print(stu1_obj.school,id(stu1_obj.school)) # 清北大学 2765815764336
print(stu2_obj.school,id(stu2_obj.school)) # 清北大学 2765815764336
# 类的函数属性 - 类本身可以调用,但类中定义的函数主要是给对象使用的,而且是绑定给对象的,
# - 虽然所有对象指向的都是相同的功能,
# - 但是绑定到不同的对象就是不同的绑定方法,(绑定的)内存地址各不相同
# 绑定方法
# 绑定方法的特殊之处在于:谁来调用绑定方法就会将谁当做第一个参数自动传入,(方法__init__也是一样的道理)
# 绑定到对象方法的这种自动传值的特征,决定了在类中定义的函数都要默认写一个参数self,self可以是任意名字,但命名为self是约定俗成的。
print(Student.choose,id(Student.choose)) # <function Student.choose at 0x00000195883D7288> 1741747483272
print(stu1_obj.choose,id(stu1_obj.choose)) # <bound method Student.choose of <__main__.Student object at 0x00000195883C4348>> 1741740131400
print(stu2_obj.choose,id(stu2_obj.choose)) # <bound method Student.choose of <__main__.Student object at 0x00000195883D8108>> 1741740131400
# 类 - 调用类的函数属性
Student.choose(stu1_obj) # 小明 is choosing a course
# 对象 - 调用类的函数属性 -
stu2_obj.choose() # 小红 is choosing a course -- 相当于 Student.choose(stu1_obj)
# 总结:共享和绑定的区别
# 共享指的是,我只给你了内存地址,但是你办法修改我
# 验证一:类修改数据属性,对象跟着变
Student.school = '华清大学' # 将默认的清北大学 改成华清大学
print(f'Student 类的school:{Student.school}') # Student 类的school华清大学
print(f'stu1_obj 对象的school:{stu1_obj.school}') # stu1_obj 对象的school华清大学
print(f'stu2_obj 对象的school:{stu2_obj.school}') # stu2_obj 对象的school华清大学
# 验证二:对象修改属性,类和其它对象不会变
stu1_obj.school = 'xx大学' # 将默认的清北大学 改成华清大学
print(f'Student 类的school:{Student.school}') # Student 类的school华清大学
print(f'stu1_obj 对象的school:{stu1_obj.school}') # stu1_obj 对象的schoolxx大学
print(f'stu2_obj 对象的school:{stu2_obj.school}') # stu2_obj 对象的school华清大学
# 绑定时指的是,我把方法给你,你自己造一个绑定的内存地址,这个东西就是你的了
stu1_obj.choose() # 小明 is choosing a course
stu2_obj.choose() # 小红 is choosing a course
封装
封装
# 一、封装的介绍:
# - 面向对象三大特性最核心的一个特性
# - 封装 -->数据和功能的整合
# 二、隐藏属性 - 私有属性 - 严格控制类外部访问
# 1.如何隐藏:在属性名前加__前缀,就会实现一个对外隐藏属性效果
# 2.注意事项:
# - (1)类外部无法直接访问双下划线开头的属性,但可以通过 _类型__属性拼接拿到,不够严格
# - (2):类内部,可以访问 __属性名 统一发生变形
# - (3):类中所有双下滑线开头的属性都会在类定义阶段、检测语法时自动变成"_类名__属性名"的形式
# - (4):变形操作只在类定义阶段发生一次,在类定义之后的赋值操作,不会变形
class Foo:
__x = 111 # 变形为 _Foo__y
def __init__(self): # 定义函数时,会检测函数语法,所以 __属性 也会变形
print(self.__x) # 内部调用可正常使用
self.__y = 222 # self._Foo__y
def __f1(self): # 变形为 _Foo__f1
print('__f1 run...')
def f2(self): # 定义函数时,会检测函数语法,所以 __属性 也会变形
self.__f1() # self._Foo__f1 - 内部调用可正常使用
# 类外部,不能访问 __属性
# print(Foo.__x) # AttributeError: type object 'Foo' has no attribute '__x'
# print(Foo.__f1) # AttributeError: type object 'Foo' has no attribute '__f1'
# 通过 _类名__属性名 访问类内部 __属性
print(Foo._Foo__x) # 111
obj = Foo() # __init__ 初始化
print(obj._Foo__x) # 111
obj.f2() # __f1 run...
# 三,开放接口
# - 定义属性就是为了使用,所有隐藏并不是目的
# - 目的:(1) 定义接口,对数据进行严格控制
# (2) 隐藏函数/方法属性:目的的是为了隔离复杂度
# 1.将数据隐藏起来 - 严格控制数据 - 一般设置get/set方法,参考java的封装类的创建
class Teacher:
def __init__(self, name, age): # 将名字和年纪都隐藏起来
self.__name = name
self.__age = age
def tell_info(self): # 对外提供访问老师信息的接口
print('姓名:%s,年龄:%s' % (self.__name, self.__age))
def set_info(self, name, age): # 对外提供设置老师信息的接口,并附加类型检查的逻辑
if not isinstance(name, str):
raise TypeError('姓名必须是字符串类型')
if not isinstance(age, int):
raise TypeError('年龄必须是整型')
self.__name = name
self.__age = age
t = Teacher('lili', 18)
# t.set_info('LiLi','18') #TypeError: 年龄必须是整型
t.tell_info() # 姓名:lili,年龄:18
# 2.将功能隐藏起来 - 隔离复杂度 -
# - 我们只需要使用开发者提供的接口就行,内部使用的函数是为了简化代码,我们不需要管
class ATM:
def __card(self): # 插卡
print('插卡')
def __auth(self): # 身份认证
print('用户认证')
def __input(self): # 输入金额
print('输入取款金额')
def __print_bill(self): # 打印小票
print('打印账单')
def __take_money(self): # 取钱
print('取款')
def withdraw(self): # 取款功能
self.__card()
self.__auth()
self.__input()
self.__print_bill()
self.__take_money()
obj = ATM()
obj.withdraw()
property
# 四、property - 保证了属性访问的一致性
# - 像操作属性一样,操作函数
class People(object):
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) # 体质指数(BMI)=体重(kg)÷身高^2(m)
# 相当于get
@property
def name(self):
return self.__name
# 相当于set
@name.setter
def name(self,value):
if not isinstance(value, str): # 在设定值之前进行类型检查
raise TypeError('%s must be str' % value)
self.__name = value # #通过类型检查后,将值value存放到真实的位置self.__NAME
# 相当于del
@name.deleter
def bmi(self):
raise PermissionError('不能删除,sb')
obj=People('lili',75,1.85)
print(obj.bmi)
继承与派生
继承
# 一、继承介绍:面向对象三大特性之一 :什么是什么
# 1.什么是继承?
# I:继承是一种创建新类的方式,新建的类可称为子类或派生类,父类又可称为基类或超类,子类会遗传父类的属性
# II:需要注意的是:python支持多继承
# 在Python中,新建的类可以继承一个或多个父类
# - 优点:子类可以同时遗传多个父类的属性,最大限度地重用代码
# - 缺点:
# 1、违背人的思维习惯:继承表达的是一种什么"是"什么的关系
# 2、代码可读性会变差
# 3、不建议使用多继承,有可能会引发可恶的菱形问题,扩展性变差,
# 如果真的涉及到一个子类不可避免地要重用多个父类的属性,应该使用Mixins
# 2. 为何要用继承?
# - 用来解决类与类之间代码冗余问题
class ParentClass1: # 定义父类
pass
class ParentClass2(object): # 定义父类
pass
class SubClass1(ParentClass1): # 单继承
pass
class SubClass2(ParentClass1, ParentClass2): # 多继承
pass
# 通过类的内置属性__bases__可以查看类继承的所有父类
# - (1) 在python2中有经典类与新式类之分
# - (2) 新式类:继承了object类的子类,以及该子类的子类子子类
# - (3) 经典类:没有继承object类的子类,以及该子类的子类子子类
# - (4) 在python3中没有继承任何类,那么会默认继承object类,所以python3中所有的类都是新式类
print(SubClass1.__bases__) # (<class '__main__.ParentClass1'>,)
print(SubClass2.__bases__) # (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
print(ParentClass1.__bases__) # (<class 'object'>,)
print(ParentClass2.__bases__) # (<class 'object'>,)
# 二、继承与抽象
# 类与类之间的继承指的是什么’是’什么的关系
# - (比如人类,猪类,猴类都是动物类)
# - 子类可以继承/遗传父类所有的属性
# - 因而继承可以用来解决类与类之间的代码重用性问题
# 以学生、老师、人类为例
class People:
school = '清华大学'
def __init__(self, name, sex, age):
self.name = name
self.sex = sex
self.age = age
def __str__(self):
return str(self.__dict__)
class Student(People):
def choose(self):
print('%s is choosing a course' % self.name)
class Teacher(People):
def teach(self):
print('%s is teaching' % self.name)
# 老师和学生都是人类,那么可以继承people这个类
# - 这里老师和学生虽然没有定义__init__方法,但是从父类中能找到__init_,所以可以正常实例化
teacher1 = Teacher('lili', 'female', 28)
print(teacher1) # {'name': 'lili', 'sex': 'female', 'age': 28}
student1 = Student('zhangsan', 'male', 18)
print(student1) # {'name': 'zhangsan', 'sex': 'male', 'age': 18}
# 三、属性查找 - 单继承
# 有了继承关系,对象在查找属性时,先从对象自己的__dict__中找,如果没有则去子类中找,然后再去父类中找
# - (很重要)遇到self 就按顺序查找:1.实例化对象类 2.子类的父类 3.子类父类的父类 4.以此递推
class Foo:
def f1(self):
print('Foo.f1')
def f2(self):
print('Foo.f2')
# self.f1()
def f3(self):
self.f1()
class Bar(Foo):
def f1(self):
print('Bar.f1')
b = Bar()
b.f2() # 子类Bar中没有,调用父类Foo中的f2()
b.f1() # 子类Bar中有,直接调用子类Bar中的f1()
b.f3() # 子类Bar中有,直接调用子类Bar中的f3() - 然后重置顺序调用 子类Bar中有,直接调用子类Bar中的f1()
# 菱形问题 - 多继承
# - B
# D A
# - C
# 如果A中有一个方法,B和/或C都重写了该方法,而D没有重写它,那么D继承的是哪个版本的方法:B的还是C的?
class A(object):
def test(self):
print('from A')
class B(A):
def test(self):
print('from B')
class C(A):
def test(self):
print('from C')
class D(B, C):
pass
obj = D()
obj.test() # 结果为:from B
# 通过mro()查看继承顺序
print(D.mro()) # [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
# mro表遵循的三条准则:
# - 1.子类会先于父类被检查
# - 2.多个父类会根据它们在列表中的顺序被检查
# - 3.如果对下一个类存在两个合法的选择,选择第一个父类
# 类和对象的属性查找顺序
# - 1.由对象发起的属性查找,会从对象自身的属性里检索,没有则会按照对象的类.mro()规定的顺序依次找下去,
# - 2.由类发起的属性查找,会按照当前类.mro()规定的顺序依次找下去,
# 深度优先和广度优先 - 关于算法就不在说明
# 经典类:按照 深度优先 顺序查找
# 新式类:按照 广度优先 顺序查找
class G(object):
def test(self):print('from G')
class E(G):
def test(self):print('from E')
class F(G):
def test(self):print('from F')
class B(E):
def test(self):print('from B')
class C(F):
def test(self):print('from C')
class D(G):
def test(self):print('from D')
class A(B,C,D):pass
obj = A()
print(A.mro())
# 新式类
obj.test() # 如上图,查找顺序为:obj->A->B->E->C->F->D->G->object
# 可依次注释上述类中的方法test来进行验证
# 经典类 - 注意请在python2.x中进行测试
# 查找顺序为:obj->A->B->E->G->C->F->D->object
Mixins机制
# 四、 Pyton Mixins机制
# 1.引出问题
# 多继承的正确打开方式:mixins机制
# mixins机制核心:就是在多继承背景下尽可能地提升多继承的可读性
# ps:让多继承满足人的思维习惯=》什么"是"什么
# 飞机和汽车都属于交通工具,但飞机会飞,汽车不会飞
# 但民航飞机和直升飞机都写飞行fly()方法,又违背了代码尽可能重用的原则
# 2,解决问题
# 所以为了解决这个问题,只需要像Java提供了接口interface功能,来实现多重继承
# Mixins机制指的是子类混合(mixin)不同类的功能
# - 注意: - (1)代表某一种功能,mixin类的命名方式一般以 Mixin, able, ible 为后缀
# - (2)必须责任单一,如果有多个功能,那就写多个Mixin类
# - (3) 类即便没有继承这个Mixin类,子类即便没有继承这个Mixin类,也照样可以工作
class Vehicle: # 交通工具
def fly(self):
'''
飞行功能相应的代码
'''
print("I am flying")
class CivilAircraft(Vehicle): # 民航飞机
pass
class Helicopter(Vehicle): # 直升飞机
pass
class Car(Vehicle): # 汽车并不会飞,但按照上述继承关系,汽车也能飞了
pass
# ---------解决 ---------
class Vehicle: # 交通工具
pass
class FlyableMixin:
def fly(self):
'''
飞行功能相应的代码
'''
print("I am flying")
class CivilAircraft(FlyableMixin, Vehicle): # 民航飞机
pass
class Helicopter(FlyableMixin, Vehicle): # 直升飞机
pass
class Car(Vehicle): # 汽车
pass
# -----------补充-----
# Java只允许接口的多重继承。接口本质上是抽象基类,具有所有抽象方法,没有数据成员。
# 与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化,继承的子类必须实现抽象基类规定的方法,
# 这样便可保证始终只有一个特定方法或属性的实现,并且不会产生歧义,因而也可以起到避免菱形问题的作用
派生
# 五、 派生与方法重用 - super()用法
# 1.引入案例 - 理解这个,后面也可以不看
# 这个的意思是,继承了LoggerMixin和Displayer后,MySubClass的super指的是的问题?
class Displayer:
def display(self, message):
print(message)
class LoggerMixin:
def log(self, message, filename='logfile.txt'):
with open(filename, 'a') as fh:
fh.write(message)
def display(self, message):
super().display(message) # super的用法请参考下一小节
self.log(message)
class MySubClass(LoggerMixin, Displayer):
def log(self, message):
super().log(message, filename='subclasslog.txt')
obj = MySubClass()
obj.display("This string will be shown and logged in subclasslog.txt")
# 属性查找的发起者是obj,所以会参照类MySubClass的MRO来检索属性
# [<class '__main__.MySubClass'>, <class '__main__.LoggerMixin'>, <class '__main__.Displayer'>, <class 'object'>]
# 1、首先会去对象obj的类MySubClass找方法display,没有则去类LoggerMixin中找,找到开始执行代码
# 2、执行LoggerMixin的第一行代码:执行super().display(message),参照MySubClass.mro(),
# - super会去下一个类即类Displayer中找,找到display,开始执行代码,打印消息"This string will be shown and logged in subclasslog.txt"
# 3、执行LoggerMixin的第二行代码:self.log(message),self是对象obj,即obj.log(message),
# - 属性查找的发起者为obj,所以会按照其类MySubClass.mro(),即MySubClass->LoggerMixin->Displayer->object的顺序查找,
# - 在MySubClass中找到方法log,开始执行super().log(message, filename='subclasslog.txt'),super会按照MySubClass.mro()查找下一个类,
# - 在类LoggerMixin中找到log方法开始执行,最终将日志写入文件subclasslog.txt
方法重用(super方法)
# 2.使用
class People:
school = '清北大学'
def __init__(self, name, sex, age):
self.name = name
self.sex = sex
self.age = age
class Teacher(People):
def __init__(self, name, sex, age, title): # 派生
self.name = name
self.sex = sex
self.age = age
self.title = title
def teach(self):
print('%s is teaching' % self.name)
obj = Teacher('lili', 'female', 28, '高级讲师') # 只会找自己类中的__init__,并不会自动调用父类的
obj.name, obj.sex, obj.age, obj.title
('lili', 'female', 28, '高级讲师')
# (1)方法一:“指名道姓”地调用某一个类的函数 - 不依赖继承
class Teacher(People):
def __init__(self, name, age, sex):
People.__init__(self, name, age, sex) # #调用的是函数,因而需要传入self
def teach(self):
def teach(self):
print('%s is teaching' % self.name)
# (2) 方法二:super() - 依赖继承 - 顺序是mro的顺序
# 调用super()会得到一个特殊的对象,该对象专门用来引用父类的属性,且严格按照MRO规定的顺序向后查找
class Teacher(People):
def __init__(self, name, sex, age, title):
super(People,self).__init__(name,age,sex)
super().__init__(name, age, sex) # 调用的是绑定方法,自动传入self
self.title = title
def teach(self):
print('%s is teaching' % self.name)
组合 - 继承 + 类当数据属性
# 组合 - 在一个类中以另外一个类的对象作为数据属性
# - class Date:pass
# - self.birth=Date(year,mon,day)
# 组合与继承都是用来解决代码的重用性问题。
# - 不同的是:继承是一种“是”的关系,
# - 比如老师是人、学生是人,当类之间有很多相同的之处,应该使用继承;
# - 而组合则是一种“有”的关系,
# - 比如老师有生日,老师有多门课程,当类之间有显著不同,并且较小的类是较大的类所需要的组件时,应该使用组合,
class Course:
def __init__(self,name,period,price):
self.name=name
self.period=period
self.price=price
def tell_info(self):
print('<%s %s %s>' %(self.name,self.period,self.price))
class Date:
def __init__(self,year,mon,day):
self.year=year
self.mon=mon
self.day=day
def tell_birth(self):
print('<%s-%s-%s>' %(self.year,self.mon,self.day))
class People:
school='清华大学'
def __init__(self,name,sex,age):
self.name=name
self.sex=sex
self.age=age
#Teacher类基于继承来重用People的代码,基于组合来重用Date类和Course类的代码
class Teacher(People): #老师是人
def __init__(self,name,sex,age,title,year,mon,day):
super().__init__(name,age,sex)
self.birth=Date(year,mon,day) #老师有生日
self.courses=[] #老师有课程,可以在实例化后,往该列表中添加Course类的对象
def teach(self):
print('%s is teaching' %self.name)
python=Course('python','3mons',3000.0)
linux=Course('linux','5mons',5000.0)
teacher1=Teacher('lili','female',28,'博士生导师',1990,3,23)
# teacher1有两门课程
teacher1.courses.append(python)
teacher1.courses.append(linux)
# 重用Date类的功能
teacher1.birth.tell_birth()
# 重用Course类的功能
for obj in teacher1.courses:
obj.tell_info()
多态性与鸭子类型
多态性
# 一、多态性 - 面向对象三大特性之一 :什么有什么
# - 好处:强了程序的灵活性和可扩展性
# 1.什么是多态:同一事物多种形态,比如动物又很多形态:人、猫、狗
# 2.为何要有多态=》多态会带来什么样的特性,多态性
# - 多态性指的是可以在不考虑对象具体类型的情况下而直接使用对象
# - 把对象的使用方法统一成一种,如人、猫、狗都会发出声音
class Animal: # 统一所有子类的方法 - 同一类事物:动物
def say(self):
print('动物基本的发声频率...',end=' ')
class People(Animal): # 动物形态之一:人
def say(self):
super().say()
print('嘤嘤嘤')
class Dog(Animal): # 动物形态之一:狗
def say(self):
super().say()
print('汪汪汪')
class Cat(Animal): # 动物形态之一:猫
def say(self):
super().say()
print('喵喵喵')
# 3.定义统一的接口,接收传入的动物对象
# - 多态性的本质在于不同的类中定义有相同的方法名
def animal_say(animal:object):
animal.say()
obj1=People()
obj2=Dog()
obj3=Cat()
obj1.say()
animal_say(obj2)
抽象类
# 二、抽象类 - abc
# - 1.多态性的本质在于不同的类中定义有相同的方法名
# - 2.可以通过父类引入抽象类的概念来硬性限制子类必须有某些方法名
import abc
class Animal(metaclass=abc.ABCMeta): # 同一子类标准
@abc.abstractmethod # # 该装饰器限制子类必须定义有一个名为say的方法
def say(self): # 抽象方法无序实现具体功能
pass
class Pig(Animal): # 但凡继承Animal的子类都必须遵守Animal绑定的标准
def say(self):
print('哼哼哼')
obj = Pig() # TypeError: Can't instantiate abstract class
# Pig with abstract methods say
鸭子类型
# 三、鸭子类型
# - 不依赖与继承,只要制造出外观和行为相同的对象即可
# - 1.虽然python支持通过对父类变成抽象类 abc完成
# - 2.但是这并不符合python的初衷
# - 3.所以引入鸭子类型 - python推崇
# - 4.鸭子类型:如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子
# 二者看起来都像文件,那么就把他当作文件一样取用,然而它们没有直接的关系
class Txt(object): # TxT类有两个与文件类型同名的方法read和write
def read(self):
print('执行txt文件读操作')
def write(self):
print('执行txt文件写操作')
class Disk(object): # Disk类也有两个与文件类型同名的方法:read和write
def read(self):
print('执行硬盘读操作')
def write(self):
print('执行硬盘写操作')
obj1 = Txt()
obj2 = Disk()
绑定方法与非绑定方法
# 一、绑定方法与非绑定方法
# 类定义的函数分为:
# - 1.绑定方法:
# - (1)绑定方法的特殊之处在于将调用者本身当做第一个参数自动传入
# - (2) 绑定到对象的对象方法 :__init__
# - 调用者是对象,自动传入的是对象
# - __init__初始化后,将类中方法绑定给对象
# - (3) 绑定到类的类方法 :@classmethod
# - 调用者类,自动传入的是类
# - 用来在__init__的基础上提供额外的初始化实例的方式
# - 2.非绑定方法:
# - (1) 没有绑定给任何人:调用者可以是类、对象,没有自动传参的效果
# - (2) 该方法不与类或对象绑定,类与对象都可以来调用它
# - (3) 静态方法:@staticmethod
import uuid
# 配置文件settings.py的内容
# HOST='127.0.0.1'
# PORT=3306
# 对象绑定 + 类的绑定
import settings
class MySQL(object):
def __init__(self,host,port): # 类实例化后,对象自动执行__init__方法
self.host = host
self.port = port
# 对象绑定 - 对象在调用这个方法,类将该方法绑定给对象
def show(self):
return f'IP地址为{self.host}:{self.port}'
# 类的绑定 - 类调用第一个参数传入类本身
# - 此方法可以用来配置固定模块,不需要对象调用即可获取
# - 用来在__init__的基础上提供额外的初始化实例的方式
@classmethod # 注意:类的绑定方法,指的是当前类,不是继承的父类的类,不能搞混
def from_conf(cls): # 从配置中读取配置进行初始化
print(cls)
return cls(settings.HOST,settings.PORT)
# 注意:
# - 1.绑定到类的方法就是专门给类用的,但其实对象也可以调用,
# - 2.只不过自动传入的第一个参数仍然是类,也就是说这种调用是没有意义的
print(MySQL.from_conf) # <bound method MySQL.from_conf of <class '__main__.MySQL'>>
conn = MySQL.from_conf() # 调用类方法,自动将类MySQL当作第一个参数传给cls
print(conn.port) # 这里我终于明白,类方法是用来给__init__做额外初始化,相当于默认值
# - 3.对象的绑定是将类中的函数绑定给不同的对象,其内存地址不同
obj1 = MySQL(settings.HOST,settings.PORT)
obj2 = MySQL(settings.HOST,settings.PORT)
print(id(MySQL.show),id(obj1.show),id(obj2.show),sep='\n')
# 输出结果:
# 1868222464648
# 1868215108680
# 1868215108680
# 二、非绑定方法 ->静态方法:
# - 没有绑定给任何人:调用者可以是类、对象,没有自动传参的效果
# - 为类中某个函数加上装饰器@staticmethod后
class MySQL(object):
def __init__(self,host,port): # 类实例化后,对象自动执行__init__方法
self.id = self.create_id()
self.host = host
self.port = port
# 对象绑定 - 对象在调用这个方法,类将该方法绑定给对象
def show(self):
return f'IP地址为{self.host}:{self.port}'
# 类的绑定 - 类调用第一个参数传入类本身
# - 此方法可以用来配置固定模块,不需要对象调用即可获取
# - 用来在__init__的基础上提供额外的初始化实例的方式
@classmethod # 注意:类的绑定方法,指的是当前类,不是继承的父类的类,不能搞混
def from_conf(cls): # 从配置中读取配置进行初始化
print(cls)
return cls(settings.HOST,settings.PORT)
# 非绑定方法 - 静态方法 - 没有参数 - 不绑定给类、对象使用
@staticmethod
def create_id():
return uuid.uuid4()
# 类或对象来调用create_id发现都是普通函数,而非绑定到谁的方法
print(MySQL.create_id) # <function MySQL.create_id at 0x000001C348BC7168>
conn=MySQL('127.0.0.1',3306)
a = conn.create_id()
print(conn.create_id) # <function MySQL.create_id at 0x00000234B74C7168>
# 总结:绑定方法与非绑定方法的使用:
# - 1.若类中需要一个功能
# - 2.该功能的实现需要引用对象 --> 对象方法 - 对象方法的引用,工厂模式
# - 3.该功能的实现需要引用类 --> 类方法 - 初始化或赋值成对象(在drf的CBV有体现) - 单例模式
# - 4.无序引用类或对象 --> 静态方法 - 单独一个功能
# 案例- 类绑定方法实现单例 - 单例 - 确保某一个类中只能有一个实例存在
class People:
instance = False # 使用类属性,做一个标识
def __init__(self, name):
self.name = name
@classmethod # 类绑定方法
def get_signleton_obj(cls, *args, **kwargs):
# print(cls)
# print(cls.instance) # instance 实例
if not cls.instance: # 如果instance为假,则创建一个实例
cls.instance = cls(*args, **kwargs)
# 调用People类,产生一个实例,并赋给instance属性
cls.instance.__init__(*args, **kwargs) # 让调用者可以更改实例的属性
return cls.instance
def send_email(self):
print(f'{self.name}发送了邮箱~')
p1 = People.get_signleton_obj('jack')
p1.send_email()
p2 = People.get_signleton_obj('tom')
p2.send_email()
print(p1 is p2) # is比较的是两个对象的内存地址,如果相同,则使用的是同一个实例
反射与内置方法
反射
# 一、什么是反射?
# - 1.程序运行过程中可以"动态"获取对象的信息,并动态调用对象的功能
# - (1) 对于任意一个类,都可以知道这个类的所有属性和方法
# - (2) 对于任意一个对象,都能都调用他的任意方法和属性
class People:
country = '中国'
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
def say(self):
print('<%s:%s>' % (self.name, self.age))
# 实现反射机制的步骤
# 1.先通过dir,获取类或对象的有哪些属性
obj = People('zhangsan', 18, 'male')
print(dir(obj)) # 列表中查看到的属性全为字符串
# 2.可以通过字符串反射到真正的属性上,得到属性值
print(obj.__dict__)
print(obj.__dict__[dir(obj)[-2]])
# 四个内置函数的使用
""" 通过字符串操作类对象 或者 模块中的相关成员的操作 """
"""
#hasattr() 检测对象/类是否有指定的成员
#getattr() 获取对象/类成员的值
#setattr() 设置对象/类成员的值
#delattr() 删除对象/类成员的值
"""
# (1) hasattr() 检测对象/类是否有指定的成员
res1 = hasattr(obj,'country')
res2 = hasattr(People,'addr')
print(res1,res2)
# (2) getattr() 获取对象/类成员的值
# - 如果获取的值不存在,可以设置第三个参数,防止报错
# - 通过类进行反射 (反射出来的是普通方法)
# - 通过对象进行反射 (反射出来的是绑定方法)
res1 = getattr(obj,'say')
res2 = getattr(People,'name',res1)
print(res1(),res2)
# (3) setattr() 设置对象/类成员的值
setattr(obj,'func1',res1)
setattr(People,'addr','nanjing')
print(obj.func1)
print(People.addr)
# (4) delattr() 删除对象/类成员
delattr(obj,'func1',)
delattr(People,'addr',)
# print(obj.func1) # 报错 - AttributeError
# print(People.addr) # 报错 - AttributeError
# 案例 - 参考django-CBV
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
class CBV(object):
@classmethod
def func(cls):
print(cls)
@classmethod
def dispatch(cls, request, *args, **kwargs):
self = cls(**kwargs)
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
class Children(CBV):
def get(cls):
print('aaaaa')
Children.func() # <class '__main__.Children'>
内置方法
# 魔法方法
# 参考知乎 :https://zhuanlan.zhihu.com/p/329962624
# - Python的Class机制内置了很多特殊的方法来帮助使用者高度定制自己的类
# - 这些内置方法都是以双下划线开头和结尾的,会在满足某种条件时自动触发
'''
__init__:类实例化会触发
__str__:打印对象会触发
__call__:对象()触发,类也是对象 类(),类的实例化过程调用元类的__call__
__new__:在类实例化会触发,它比__init__早(造出裸体的人,__init__穿衣服)
__del__:del 对象,对象回收的时候触发
__setattr__,__getattr__:(.拦截方法),当对象.属性--》赋值会调用setattr,如果是取值会调用getattr
__getitem__,__setitem__:([]拦截)
__enter__和__exit__ 上下文管理器
'''
# __str__
# - __str__方法会在对象被打印时自动触发,
# - print功能打印的就是它的返回值
# - 该方法必须返回字符串类型
class People:
def __init__(self,name,age):
self.name = name
self.age = age
# 对象被打印时自动触发
def __str__(self):
# 返回类型必须是字符串
return '<Name:%s Age:%s>' %(self.name,self.age)
obj = People('zhangsan',18)
print(obj)
# __del__
# - 会在对象被删除时自动触发
# - Python的垃圾回收机制会自动清理python程序的资源
# - 但是,不能对系统资源回收,如系统发开文件,网络连接,数据库连接等
import pymysql
class MySQL:
def __init__(self,*args,**kwargs):
self.conn = pymysql.connect(
user="root",
password="123456",
host="localhost",
database="test",
port=0,
charset="utf8") # 发起网络连接,需要占用系统资源
def __del__(self):
self.conn.close() # 关闭网络连接,回收系统资源
def query(self):
cursor = self.conn.cursor(pymysql.cursors.DictCursor)
cursor.execute('select * from account')
res = cursor.fetchall()
return res
obj = MySQL('zhangsan',18)
res = obj.query()
print(res)
# 魔法方法之上下文管理 __enter__ __exit__
import pymysql
class MySQL:
def __enter__(self):
return self.getConn()
def __exit__(self, exc_type, exc_val, exc_tb):
return self.getConn().close()
def __init__(self,*args,**kwargs):
pass
def getConn(self):
return pymysql.connect(
user="root",
password="123456",
host="localhost",
database="test",
port=0,
charset="utf8") # 发起网络连接,需要占用系统资源
def query(self):
cursor = self.getConn().cursor(pymysql.cursors.DictCursor)
cursor.execute('select * from account')
res = cursor.fetchall()
return res
with MySQL.getConn(MySQL) as conn:
cur = conn.cursor()
cur.execute('select * from account')
res = cur.fetchall()
print(res)
# 关于属性操作 - __getitem__,__setitem__:([]拦截) 》赋值会调用__setitem__,如果是取值会调用__getitem__
# __setattr__,__getattr__:(.拦截方法),当对象.属性--》赋值会调用__setattr__,如果是取值会调用__getattr__
# 对象.属性名 ----->__setattr__,__getattr__
# 对象['属性名'] ------>__getitem__,__setitem__
# 特别说明,两种方式都是成对出现,且一个函数一般使用一组,不然会递归调用
class Person1:
def __init__(self,name):
self.name=name
def __setitem__(self, key, value):
print("对象['属性名'] = xxx,赋值会触发")
setattr(self,key,value) #反射
def __getitem__(self, item):
print("对象['属性名'] ,取值会触发")
return getattr(self,item) # 反射取值
class Person2:
def __init__(self, name):
self.name = name
# def __setattr__(self, key, value):
# print("对象.属性 = xxx ,赋值会触发")
# self[key] = value
# def __getattr__(self, item):
# print("对象.属性,取值会触发")
# return self[item]
obj1 = Person1('zhangsan')
obj1['name'] = 'lisi'
print(obj1['name'])
obj2 = Person2('张三')
obj2.name = '李四'
print(obj2.name) # TypeError
元类(难理解,记住创建流程即可)
# 一、什么是元类?
# - 一切源自一句话: python中一切皆为对象。
# - 元类就是用来实例化产生类的类
# - 关系: |元类type| ---> 实例化 ---> |类(People)| ---> 实例化 --->|对象(obj)|
class People:
def __init__(self,name,age):
self.name=name
self.age=age
def say(self):
print('%s:%s' %(self.name,self.name))
# 所有的对象都是实例化或者说是调用类得到的(调用类的过程称为类的实例化)
# - 对象是调用类(或者说实例化)得到的
# - 类本身也是对象,也就说,类也需要调用一个类得到,而这个类就叫做元类
# - 查看内置元类
# - (1)type是内置的元类
# - (2)我们用class关键字定义的所有类 + 内置的类都是由元类type实例化产生
obj = People('张三',18)
# People = type() # class创建的类是有元类type实例化得到的
print(type(obj)) # <class '__main__.People'>
print(type(People)) # <class 'type'>
print(type(int)) # <class 'type'>
# 二、class关键字创造类People的步骤
# - 1.我们用关键字定义的类本身 和内置的类 都是对象
# - 2.负责产生该对象的类称之为元类(元类可以简称类的类),内置的元类是type
# 3.一个类由三大组成部分(三大特征):
# (1) 类名
class_name = "People"
# (2) 类的基类
class_bases = (object,)
# (3)类的名称空间class_dic - 通过执行类体代码得到的
class_dic={}
class_body="""
def __init__(self,name,age):
self.name=name
self.age=age
def say(self):
print('%s:%s' %(self.name,self.name))
"""
# exec的用法
# - 三个参数
# - (1) 参数一:包含一系列python代码的字符串
# - (2) 参数二:全局作用域(字典形式),如果不指定,默认为globals()
# - (3) #参数三:局部作用域(字典形式),如果不指定,默认为locals()
# - 可以把exec命令的执行当作是一个函数的执行,会将执行期间产生的名字存放到局部名称空间中
# g={'x':1,'y':2}
# l={}
# exec('''
# global x,z
# x=100
# z=200
# m=300
# ''',g,l)
# print(g) #{'x': 100, 'y': 2,'z':200,......}
# print(l) #{'m': 300}
g = {}
exec(class_body,g,class_dic) # 参数一:执行代码体 参数二:全局变量可为None 参数三:局部变量
# print(g) # 全局变量 - 内置和不是函数中的变量
print(class_dic) # 执行完后,可以拿到全局变量和局部变量
# (4) 调用元类 - 类名 + 基类 + 名称空间(执行类的代码体获得的名字)
People = type(class_name,class_bases,class_dic)
print(People)
# 三、如何自定义元类来控制类的产生(创建)
# - 1.一个类如果没有声明自己的元类,默认他的元类就是type
# - 2.除了使用内置的元类type,我们可以通过继承type来自定义元类
# - 3.然后使用metaclass关键字参数为一个类指定元类
# 执行过程:
# - 1 People =Mymeta('People',(object),{...})
# - 2 调用Mymeta就是type.__call__ 做以下三件事
# - (1) 调用Mymeta这个类内的__new__方法 --> 产生一个空对象=>People
# - (2) 调用Mymeta这个类内的__init__(*arg,**kwargs)方法 --> 完成对象的初始化操作
# - (3) 返回初始化好对象
class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
def __init__(self,class_name,class_bases,class_dic):
print('我是__init__方法')
print(class_name,class_bases,class_dic)
# 重用父类的功能 - type元类
# super(Mymeta,self).__init__(class_name,class_bases,class_dic)
super().__init__(class_name,class_bases,class_dic)
if class_name.islower():
raise TypeError('类名%s请修改为驼峰体' % class_name)
if '__doc__' not in class_dic or len(class_dic['__doc__'].strip(' \n')) == 0:
raise TypeError('类中必须有文档注释,并且文档注释不能为空')
def __new__(cls, *args, **kwargs):
# 造Mymeta的对象 - cls
print('我是__new__方法')
print((cls,args,kwargs))
# return super().__new__(cls, *args, **kwargs)
return type.__new__(cls, *args, **kwargs)
class People(metaclass=Mymeta):
'''
类People的文档注释
'''
def __init__(self,name,age):
self.name=name
self.age=age
def say(self):
print('%s:%s' %(self.name,self.name))
print('-----------------------------------------')
# 只要是调用类,那么会一次调用
# 1、类内的__new__
# 2、类内的__init__
# 四、自定义元类控制类的调用
# __call__ 知识储备
class Foo:
def __init__(self,x,y):
self.x=x
self.y=y
def __call__(self,*args,**kwargs):
print('call方法',args,kwargs)
return 123
def func(self):
print('hello world')
#1、要想让obj这个对象变成一个可调用的对象,需要在该对象的类中定义一个方法__call__方法,该方法会在调用对象时自动触发
#2、调用obj的返回值就是__call__方法的返回值
obj=Foo(111,222)
print(obj) # <__main__.Foo object at 0x000001B71FB63C88>
obj.func() # hello world
res = obj(1,2,3,a=4,b=5,c=6) # call方法 (1, 2, 3) {'a': 4, 'b': 5, 'c': 6}
obj.func() # hello world
print(res) # 123
print('===============================')
# 综述:
# 调用一个对象,就是触发对象所在类的__call__方法的执行
# 若把People当作一个对象的话,那么再People这个对象中也必然存在一个__call__方法
# 应用:如果想让一个对象可以加括号调用,需要在该对象的类中添加一个方法__call__
# 总结:
# 对象()->类内的__call__
# 类()->自定义元类内的__call__
# 自定义元类()->内置元类__call__
class Mymeta(type): # 只有继承了type类的类才是元类
def __call__(self, *args, **kwargs): #self=<class '__main__.People'>
# 1、调用__new__产生一个空对象obj
# - Mymeta.__call__函数内会先调用People内的__new__
# - 此处的self是类People,必须传参,代表创建一个SPeople的对象obj
obj = self.__new__(self, *args, **kwargs)
# 2、调用__init__初始化空对象obj
# Mymeta.__call__函数内会调用People内的__init__
self.__init__(obj,*args,**kwargs)
print('people对象的属性:', obj.__dict__)
obj.__dict__['xxxxx'] = 11111
# 3、返回初始化好的对象obj
# Mymeta.__call__函数内会返回一个初始化好的对象
return obj
# 类的产生
# People=Mymeta()=> type.__call__=>干了3件事
# 1、type.__call__函数内会先调用Mymeta内的__new__
# 2、type.__call__函数内会调用Mymeta内的__init__
# 3、type.__call__函数内会返回一个初始化好的对象
class People(metaclass=Mymeta):
def __init__(self,name,age):
self.name=name
self.age=age
def say(self):
print('%s:%s' %(self.name,self.name))
def __new__(cls, *args, **kwargs):
print('People中的__new__方法')
# 产生真正的对象
# return object.__new__(cls)
return super().__new__(cls)
print('*******************************************')
# 类的调用
# obj=People('egon',18) =》Mymeta.__call__=》干了3件事
# 1、Mymeta.__call__函数内会先调用People内的__new__
# 2、Mymeta.__call__函数内会调用People内的__init__
# 3、Mymeta.__call__函数内会返回一个初始化好的对象
obj1=People('zhangsan',18)
obj2=People('li',28)
print(type(obj1))
# print(obj1.__dict__)
# print(obj2.__dict__)
# 总结:
# class Mymeta(type)
# class People(metaclass=Mymeta)
# People = Mymeta() # type.__call__ __new__ + __init__ + return class_obj
# obj = People() # Mymeta.__call__ __new__ + __init__ + return class_obj
# 对象通过类调用,去找被调用的类里面的__call__
# 对象创建, __new__ + __init__ + return class_obj
元类的应用
# 应用1:在元类中控制把自定义类的数据属性都变成大写
class Mymeta(type):
def __new__(cls,name,bases,attrs):
updata_attrs={}
for k,v in attrs.items():
if not callable(v) and not k.startswith('__'):
updata_attrs[k.upper()] = v
else:
updata_attrs[k] = v
return type.__new__(cls,name,bases,updata_attrs)
class Chinese(metaclass=Mymeta):
country='China'
tag='Legend of the Dragon' #龙的传人
def walk(self):
print('%s is walking' %self.name)
print(Chinese.__dict__)
# 2、在元类中控制自定义的类无需__init__方法
'''
1.元类帮其完成创建对象,以及初始化操作;
2.要求实例化时传参必须为关键字形式,否则抛出异常TypeError: must use keyword argument
3.key作为用户自定义类产生对象的属性,且所有属性变成大写
'''
class Mymeta(type):
def __call__(self, *args, **kwargs):
if args:
raise TypeError('must use keyword argument for key function')
obj = object.__new__(self)
for k, v in kwargs.items():
obj.__dict__[k.upper()] = v
return obj
class Chinese(metaclass=Mymeta):
country='China'
tag='Legend of the Dragon' #龙的传人
def walk(self):
print('%s is walking' %self.name)
print(Chinese.__dict__)
# 3、在元类中控制自定义的类产生的对象相关的属性全部为隐藏属性
class Mymeta(type):
def __init__(self,class_name,class_bases,class_dic):
super(Mymeta, self).__init__(class_name, class_bases, class_dic)
def __call__(self, *args, **kwargs):
#控制Foo的调用过程,即Foo对象的产生过程
obj = self.__new__(self)
self.__init__(obj,*args,**kwargs)
obj.__dict__ = {'_%s__%s' % (self.__name__, k): v for k, v in obj.__dict__.items()}
return obj
class Foo(object,metaclass=Mymeta): # Foo=Mymeta(...)
def __init__(self, name, age,sex):
self.name=name
self.age=age
self.sex=sex
obj=Foo('lili',18,'male')
print(obj.__dict__)
属性查找
class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
n=444
def __call__(self, *args, **kwargs): #self=<class '__main__.StanfordTeacher'>
obj=self.__new__(self)
self.__init__(obj,*args,**kwargs)
return obj
class Bar(object):
n=333
class Foo(Bar):
n=222
class StanfordTeacher(Foo,metaclass=Mymeta):
n=111
school='Stanford'
def __init__(self,name,age):
self.name=name
self.age=age
def say(self):
print('%s says welcome to the Stanford to learn Python' %self.name)
print(StanfordTeacher.n) #自下而上依次注释各个类中的n=xxx,然后重新运行程序,发现n的查找顺序为StanfordTeacher->Foo->Bar->object->Mymeta->type
#查找顺序:
#1、先对象层:StanfordTeacher->Foo->Bar->object
#2、然后元类层:Mymeta->type
设计模式
单例模式
# 单例:即单个实例,指的是同一个类实例化多次的结果指向同一个对象,用于节省内存空间
# 如果我们从配置文件中读取配置来进行实例化,在配置相同的情况下,就没必要重复产生对象浪费内存了
#settings.py文件内容如下
HOST='1.1.1.1'
PORT=3306
# 1.模块实现单例 - 从另一个模块调用函数和数据,原理是导入模块只在第一次加载执行
# a.py
import settings
def singleton(cls):
if not cls.__instance:
cls.__instance=cls(settings.HOST,settings.PORT)
return cls.__instance
# b.py
# from a import Mysql
#方式一:定义一个类方法实现单例模式
import settings
class Mysql:
__instance = None
def __init__(self,host,port):
self.host = host
self.port = port
@classmethod
def singleton(cls):
if not cls.__instance:
cls.__instance = cls(settings.HOST,settings.PORT)
# print(cls.__instance)
return cls.__instance
print('方法一')
obj1=Mysql('1.1.1.2',3306)
obj2=Mysql('1.1.1.3',3307)
print(obj1 is obj2) #False
obj3=Mysql.singleton()
obj4=Mysql.singleton()
print(obj3 is obj4) #True
#方式二:定制元类实现单例模式
class Mymeta(type):
# 定义类Mysql时就触发 - Mysql = Mymeta()
def __init__(self,name,bases,dic):
# 事先先从配置文件中取配置来造一个Mysql的实例出来
# self.__instance = self.__new__(self)
# # # 初始化对象
# self.__init__(self.__instance, settings.HOST, settings.PORT)
# 上述两步可以合成下面一步
self.__instance=super().__call__(settings.HOST, settings.PORT)
super().__init__(name, bases, dic)
# 在Mysql(xxxx)触发
def __call__(self, *args, **kwargs):
if args or kwargs:
obj = self.__new__(self)
self.__init__(obj,*args, **kwargs)
return obj
return self.__instance
class Mysql(metaclass=Mymeta):
def __init__(self,host,port):
self.host=host
self.port=port
print('方法二')
obj1=Mysql() # 没有传值则默认从配置文件中读配置来实例化,所有的实例应该指向一个内存地址
obj2=Mysql()
obj3=Mysql()
print(obj1 is obj2 is obj3)
obj4=Mysql('1.1.1.4',3307)
#方式三:定义一个装饰器实现单例模式
import settings
def singleton(cls): #cls=Mysql
_instance=cls(settings.HOST,settings.PORT)
def wrapper(*args,**kwargs):
if args or kwargs:
obj=cls(*args,**kwargs)
return obj
return _instance
return wrapper
@singleton # Mysql=singleton(Mysql)
class Mysql:
def __init__(self,host,port):
self.host=host
self.port=port
print('方法三')
obj1=Mysql()
obj2=Mysql()
obj3=Mysql()
print(obj1 is obj2 is obj3) #True
obj4=Mysql('1.1.1.3',3307)
obj5=Mysql('1.1.1.4',3308)
print(obj3 is obj4) #False
工厂模式
工厂模式
# - 1、简单工厂 - 多个产品,通过工厂根据类型调用 - 静态方法
# - 2.工厂模式 - 多个子工厂,重写同一个工厂的抽象方法
# - 3.抽象工厂 - 抽象工厂有多个抽象方法,不同工厂再去继承抽象工厂
# 工厂模式
# - 1.简单工厂 - 静态方法 - 判断类型
# 定义一系列水果
class Apple(object):
"""苹果"""
def __repr__(self):
return "苹果"
class Banana(object):
"""香蕉"""
def __repr__(self):
return "香蕉"
class Orange(object):
"""橘子"""
def __repr__(self):
return "橘子"
class FactorySimple(object):
"""简单工厂模式"""
@staticmethod
def get_fruit(fruit_name):
if 'a' == fruit_name:
return Apple()
elif 'b' == fruit_name:
return Banana()
elif 'o' == fruit_name:
return Orange()
else:
return '没有这种水果'
if __name__ == '__main__':
# 分别获取3种水果
# 输入参数,通过简单工厂,返回对应的实例
instance_apple = FactorySimple.get_fruit('a')
instance_banana = FactorySimple.get_fruit('b')
instance_orange = FactorySimple.get_fruit('o')
# - 2.工厂方法 - 将创建对象的工作让相应的工厂子类去实现
import abc
# from factory.fruit import *
class AbstractFactory(object):
"""抽象工厂"""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def get_fruit(self):
pass
class AppleFactory(AbstractFactory):
"""生产苹果"""
def get_fruit(self):
return Apple()
class BananaFactory(AbstractFactory):
"""生产香蕉"""
def get_fruit(self):
return Banana()
class OrangeFactory(AbstractFactory):
"""生产橘子"""
def get_fruit(self):
return Orange()
if __name__ == '__main__':
# 每个工厂负责生产自己的产品也避免了我们在新增产品时需要修改工厂的代码,而只要增加相应的工厂即可
instance_apple = AppleFactory().get_fruit()
instance_banana = BananaFactory().get_fruit()
instance_orange = OrangeFactory().get_fruit()
print(instance_apple)
print(instance_banana)
print(instance_orange)
# - 3.抽象工厂 - 如果一个工厂要生产多个产品
class MaoXW_CC(object):
"""川菜-毛血旺"""
def __str__(self):
return "川菜-毛血旺"
class XiaoCR_CC(object):
"""川菜-小炒肉"""
def __str__(self):
return "川菜-小炒肉"
class MaoXW_XC(object):
"""湘菜-毛血旺"""
def __str__(self):
return "湘菜-毛血旺"
class XiaoCR_XC(object):
"""湘菜-小炒肉"""
def __str__(self):
return "湘菜-小炒肉"
class AbstractFactory(object):
"""
抽象工厂
既可以生产毛血旺,也可以生成小炒肉
"""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def product_maoxw(self):
pass
@abc.abstractmethod
def product_xiaocr(self):
pass
class CCFactory(AbstractFactory):
"""川菜馆"""
def product_maoxw(self):
return MaoXW_CC()
def product_xiaocr(self):
return XiaoCR_CC()
class XCFactory(AbstractFactory):
"""湘菜馆"""
def product_maoxw(self):
return MaoXW_XC()
def product_xiaocr(self):
return XiaoCR_XC()
if __name__ == '__main__':
# 川菜炒两个菜,分别是:毛血旺和小炒肉
maoxw_cc = CCFactory().product_maoxw()
xiaocr_cc = CCFactory().product_xiaocr()
print(maoxw_cc, xiaocr_cc)
maoxw_xc = XCFactory().product_maoxw()
xiaocr_xc = XCFactory().product_xiaocr()
print(maoxw_xc, xiaocr_xc)