Python学习笔记 第九章 回顾补充2
1.模块
什么是模块:本质就是py文件,封装语句的最小单位
自定义模块:时机上就是定义.py可以包含:变量定义,可执行语句,for循环,函数定义等等,统称为模块的成员
为什么要有模块:
- 拿来主义,提高开发效率
- 便于管理维护
- 什么是脚本
- 脚本就是py文件,长期保存代码的文件
模块的分类
- 内置模块,大约有200种,python解释器自带的模块,如time, os, sys, hashlib
- 第三方模块6000多种,一些大牛写的,非常好用,pip install安装, 如request , django, flask
- 自定义模块,自己写一个py文件
1.1模块的导入
模块的运行方式:直接用解释器运行,或者pycharm
模块方式:被其他的模块带入,为导入他的模块提供资源(变量,函数定义,类定义)
第一次import导入模块执行三件事情,会将这个模块里面的所有代码加载到内存当中,只要你的程序没有结束,接下来你在用多少次,他会优先在内存中寻找有没有此模块,如果已经加载到内存,就不需要重复加载
- 在内存中创建一个以模块名命名的名称空间
- 执行此名称空间所有的可执行代码(p y文件中所有的变量与值的对应关系加载到这个名称空间)
- 通过模块名. 的方法。调用此模块的内容(变量、函数名、类名)
被导入模块有独立的名称空间
#a.py
name = 'aaa'
def func():
global name
name = 'ccc'
#b.py
import a
name = 'bbb'
a.func() #引用a.py中的func函数,会自动创建一个命名空间,是模块独有的
print(name) #bbb 本文件中的name
print(a.name) #ccc a.py中的name
注意:
#a.py
def func(): #<function 1111>
print(555)
#b.py
from a import func
def func(): #<function 1111>
print(666)
func() #555 <function 1111> 执行的是模块里面的函数
#再定义一个相同的函数,不是在本文件中创建一个新的函数,而是创建一个变量func,但是func指向还是a.py中的func的内存地址
#因为模块中有,便会一直引用
#a.py
def func():
global name
name = 'aaa'
#b.py
from a import func
name = 'bbb'
print(name) #bbb
func() # name = 'aaa'
print(name) #bbb
from a import func
print(name) #aaa 此时将b.py中的name覆盖掉
#python提供一种方法,检查是否是当前运行的脚本__name__
#判断该模块是属于开发阶段,还是使用阶段
#在脚本方式运行的时候,__name__是固定的字符串__main__
#在以模块方式被导入的时候,__name__就是本模块的名字
if __name__ == '__main__':
run()
import sys
print(sys.path) #查看
#添加路径
sys.path.append(r'D:app') #绝对路径
#相对路径
#__file__ 当前文件所在的绝对路径
import os
#使用os模块获取父路径
ret = os.path.dirname(__file__)
sys.path.append(ret + '/app')
系统导入模块的路径:
- 内存中,如果之前成功导入过某个模块,直接使用已经存在的模块
- 内置路径中:安装路径下:lib
- python path : import 时寻找模块的路径
- sys.path 是一个路径的列表 (动态修改sys.path不会影响到python的其他模块)
如果上面都找不到,就会报错
导入模块的多种方式:
-
import xxx:导入一个模块的所有成员
-
import aaa, bbb:一次性导入多个模块,不推荐这种写法,分开写
-
from xxx import a: 从某个模块导入某个成员
-
from xxx import a, b, c:从某个模块中导入多个成员
-
from xxx import *:从模块中导入所有成员
#__all__可以控制被导入成员 #my_module.py __all__ = ['a', 'b'] #控制成员被外界使用 a = 1 b = 2 c = 3 #run.py from my_module import * #此时只能导入__all__中的成员, __all__只针对这种方式的导入生效,其他方式都不生效 #使用如下方式可以绕过__all__的限制 import my_module
注意:import xxx和from xxx import *的区别
- 第一种方式在使用其中成员时,必须使用模块名作为前缀,不容易产生命名冲突
- 第二种方式在使用成员时,不用使用模块名作为前缀,直接使用成员名即可,容易产生命名冲突,在后定义的成员生效(覆盖)
解决名称冲突的方式:
-
该用import xxx这种方式导入
-
自己避免使用同名
-
使用别名解决冲突
#给成员起别名,避免名称冲突 as --> alias from xxx imoprt age as a #给模块取名,目的简化书写 import my_module as m
#一般情况下把项目的父路径加入到sys.path中即可
import os, sys
sys.path.append(os.path.dirname(__file__))
相对路径:包含了点号的一个相对路径
.表示的是当前的路径
..表示的是父路径
...表示的是父路径的父路径
1.2常用模块
1.2.1random
此模块提供了随机数获取的方法:
- random.random获取[0.0, 1.0]范围内的浮点数
- random.randint(a, b) 获取[a, b]范围内的整数
- random.uniform(a, b)获取[a, b]范围内的浮点数
- random.shuffle(x):将参数指定的数据元素打乱,参数必须是可变的数据类型,如列表
- random.sample(x, k):从x中随机抽取k个数据,组成一个列表返回
1.2.2time和datetime
import time
#获取时间戳:从1970 1 1 00:00:00 到现在经过的秒数
print(time.time())
#时间对象转换成时间戳
print(time.mktime(time.localtime()))
#获取格式化时间对象,由9个字段组成
print(time.gmtime()) #GMT
#time.struct_time(tm_year=2021, tm_mon=3, tm_mday=20, tm_hour=6, tm_min=47, tm_sec=58, tm_wday=5, tm_yday=79, tm_isdst=0)
print(time.localtime()) #当地之间
#格式化时间对象和字符串之间的转化
s = time.strftime('%Y %m %d %H:%M:%S')
print(s)
#把时间字符串转换成时间对象
time_obj = time.strptime('2020', '%Y') #没有设置的会根据字段的默认值进行设置,如月份和日期会设置为1
#暂停当前程序
time.sleep(5) #睡眠5秒
import datetime
#date类
d = datetime.date(2010, 10, 10) #2010-10-10
#获取date对象的各个属性
print(d.year, d.month, d.day)
#time类
t = datetime.time(10, 30, 30) #10:30:30
print(t.hour, t.minute, t,second)
#datetime
dt = datetime.datetime(2020, 10, 10, 10, 10, 10)
print(dt.year, dt.month, dt.day, dt.hour, dt.minite, dt.second)
print(dt) #2020-10-10 10:10:10
#timedelta 时间的变化量
#datetime.timedelta(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0)
td = datetime.timedelta(days=1)
#参与数学运算
#创建时间对象
d = datetime.date(2020, 10, 10)
res = d + td
print(res) #2020-10-11
#时间的计算量计算是否会产生进位
t = datetime.datetime(2020, 10, 10, 10, 10, 59)
td = datetime.timedelta(seconds = 3)
res = t + td
print(res) #2020-10-10 10:11:02 #type(res):datetime.datetime
#和时间段进行运算的结果,类型和另一个操作数保持一致
1.2.3os
#文件操作的相关
import os
os.remove('a.txt')
os.rename('a.txt', 'b.txt')
#删除目录,必须是空目录
os.removedirs('aa')
#shutil可以删除带内容的目录
import shutil
shutil.rmtree('aa')
#和路径相关的操作,被封装到另一个字模块中:os.path
os.path.dirname(r'd:/aaa/bbb/ccc/a.txt') # d:/aaa/bbb/ccc/ 不判断路径是否存在
#获取文件名:
os.path.basename(r'd:/aaa/bbb/ccc/a.txt') # a.txt
#将路径中的路径名和文件名切分开,返回类型是元组
os.path.split(r'd:/aaa/bbb/ccc/a.txt') # ('d:/aaa/bbb/ccc', 'a.txt') 前面的是路径,后面是文件名
#拼接路径
os.path.join('d:\', 'aaa', 'bbb', 'ccc')
#绝对路径
os.path.abspath(r'/aaa/bbb/ccc') #D:aaabbccc 以/开头的路径,默认在当前盘符下
os.path.abspath(r'bbb/ccc') #D:aaabbccc
os.path.isabs('a.txt') #False 不是以盘符为开头,不是绝对路径
os.path.isdir('d:/a.txt') #是否是目录
os.path.isfile('d:/a.txt') #是否是文件
os.path.islink('d:/a.txt') #是否是快捷方式
1.2.4sys
#获取命令行方式运行的脚本后面的参数
import sys
print('脚本名', sys.argv[0])
print('第一个参数', sys.argv[1])
print('第二个参数', sys.argv[2])
print(type(sys.argv[1])) #str
#解释器寻找模块的路径
sys.path
#已经加载的模块
sys.modules
1.2.5json
已经成为一种简单的数据交换格式
磁盘是一个线性数据,数据之间没有引用关系
结构化数据----》拆分(序列化过程)serialization -----> ..... ------>组装(反序列化过程)deserialization --->结构化数据
序列化:将内存中的数据,转换成字符串,用以保存在文件或者通过网络传输
反序列化:从文件中,网络中获取的数据,转换成内存中原来的数据类型
json:将数据转换成字符串,用于存储或者网络传输
t -> text
b -> binary
a -> append
w -> write
r -> read
#默认是rt
import json
s = json.dumps([1, 2, 3])
print(type(s)) #str
s = jsoon.dumps((1, 2, 3)) #元组可以被序列化,但是序列化后变成列表
#将json结果写入到文件当中
with open('a.txt', mode='at', encoding='utf-8') as f:
json.dump([1, 2, 3], f)
#反序列化
json.loads(s) #还原成原来的数据类型, 注意元组经过序列化是列表,返回还是列表
#从文件中反序列化
with open('a.txt', mode='rt', encoding='utf-8') as f:
ret = json.load(f)
#json文件通常是一次性写,一次性读,使用另一种方式,可以实现多次写,多次读
#把需要序列化的对象,通过多次序列化的方式,用文件的write方法,把多次序列化的json字符串写入到文件中
with open('a.txt', mode='at', encoding='utf-8') as f:
f.write(json.dumps([1, 2, 3]) + '
')
f.write(json.dumps([4, 5, 6]) + '
')
with open('a.txt', mode='rt', encoding='utf-8') as f:
json.loads(f.readline().strip())
json.loads(f.readline().strip())
for x in f:
json.loads(x.strip())
python | json |
---|---|
dict | object |
list, tuple | array |
str | String |
int, float, int -& float-derived Enums | number |
True | true |
False | false |
None | null |
1.2.6pickle
将python中所有的数据类型转换成字符串,序列化过程
将字符串转换成python数据类型,反序列化过程
import pickle
bys = pickle.dumps([1, 2, 3])
print(type(bys)) #bytes
bys = pickle.dumps((1, 2, 3))
ret = pickle.loads(bys)
print(type(ret)) #tuple 保存了元组的类型
#json当中set不能使用,pickle则是所有的数据类型都可以序列化
bys = pickle.dumps(set('abc'))
res = pickle.loads(bys)
print(type(res)) #set
#读写文件 pickle常用的是一次读和一次写,不然读的时候次数不对会报错
with open('b.txt', mode='wb') as f:
pickle.dump([1, 2, 3], f)
#pickle.dump([1, 2, 3], f)
#pickle.dump([1, 2, 3], f)
with open('b.txt', mode='rb') as f:
pickle.load(f)
"""
for x in range(3):
ret = pickle.load(f)
print(type(ret)) #list
"""
json和pickle的比较
- json不是所有的数据类型都可以序列化,不能多次用同一个文件序列化,但是json数据可以跨语言
- pickle数据所有python数据都可以序列化,可以多次对同一个文件序列化,只能用在python中
1.2.7hashlib模块
封装一些用于加密的类
md5(), ...
加密的特点:用于判断和验证,而非解密
特点:
- 把一个大的数据,切分成不同块,分别对不同的块进行加密,再汇总的结果和直接对整体数据加密的结果是一致的
- 单向加密,不可逆
- 原始数据的一点小变化,将导致结果的非常大的差异,‘雪崩’
"""
md5算法
给一个数据加密的三大步骤
1.获取一个加密对象
2.使用加密对象的update,进行加密,update方法可以调用多次
3.通常通过hexdigest获取获取到加密结果,或者digest(看不懂)
"""
import hashlib
#获取一个加密对象
m = hashlib.md5()
#使用加密对象的update,进行加密
m.update(b'abc中文'.encode('utf-8'))
#通过hexdigest获取加密结果
res = m.hexdigest() # 结果是str类型
#给一个数据加密
#验证:用另一个数据加密的的结果和第一次加密的结果对比
#如果结果相同,那么原文相同
#不同加密算法:实际就是加密结果的长度不同
s = hashlib.sha224()
s.update(b'abc')
#在创建加密对象的时候可以指定参数,称之为salt(在update的时候添加也一样)
m = hashlib.md5(b'abc')
1.2.8collections
namedtuple :命名元组
counter :计数器
defaultdict():默认值字典
import collections
Rectangle = collections.namedtuple('rectangle_class', ['length', 'width'])
obj = Rectangle(10, 5)
#通过属性访问元组的元素
print(obj.length) #10
print(obj.width) #5
#通过索引的方式访问元素
print(obj[0]) #10
d = {k:v for k, v in [(1, 2), (2, 3)]}
dic = collections.defaultdict(int, name='apple', age=10)
print(dic['bana']) # 0 不存在的键,返回默认值为int(),将{'bana':0}添加到字典里面
print(dic['name']) # apple
#自定义函数充当第一个参数,要求是不能有参数
def func():
return 'hello'
adic = collections.defaultdict(func, name='apple', age=10)
print(adic['addr']) #hello
print(adic) #{'name':'apple', 'age':10, 'addr':'hello'} 将返回值作为值添加到字典中
#Counter计数器
c = collections.Counter('abcab') #Counter({'a':2, 'b':2, 'c':1})
print(c.most_common(2)) #取最常出现的前两名 [('a', 2), ('b', 2)]
1.2.9re
有了re模块以后,就可以在python当中进行操作正则表达式
什么是正则表达式:
-
一套规则 进行匹配字符串
-
从一个大文件中找到所有符合规则的内容,能够高效的从一大段文字中找到符合规则的内容 --日志分析 爬虫
-
检测一个输入的字符串是否合法。 --web开发 表单验证
- 用户输入一个内容的时候,我们要提前进行检测
- 能够提高程序的效率并且减轻服务器的压力
正则规则
- 所有的规则中的字符刚好匹配到字符串中的内容
- 字符组 [ ] 描述的是一个位置上出现的所有可能性,只匹配一个字符
- [abc] 匹配a或者b或者c
- [0-9] 匹配一个数字,通过ascii进行范围的匹配
- [a-zA-Z] 大小写的英文字母
- 接收范围,可以描述多个范围,连着写就可以了
- [0-9] -> d 表示匹配任意一位数字 digit
- w 匹配所有的数字、字母、下划线 [0-9a-zA-Z_]. word
- s匹配空格、tab、回撤,空格匹配所有的空白,tab用 匹配,enter回车用 匹配
正则表达式中能帮助我们表示匹配内容的字符都是正则中的元字符
元字符 -- 匹配内容的规则
W 非数字字母下划线
D 非数字
S 非空白
[dD] [wW] [sS] 匹配所有
. 表示匹配除了换行符的的所有
[^d] 匹配所有的非数字
^ 匹配一个字符串的开始
$ 匹配一个字符串的结尾
()分组
a表达式|b表达式 匹配a或者匹配b表达式中的内容,如果匹配a成功了,就不会继续匹配b
记忆元字符:都是表示能匹配到哪些内容,一个元字符匹配一个字符
- d w s D W S
- [] [^] .
- ^ $
- | ( )
量词:必须跟在元字符后面,只约束前面的元字符
{n} 表示匹配n次
{n, } 表示匹配至少n次
{n, m} 表示至少匹配n次,至多m次
? 表示匹配0次或者1次 {0, 1}
+表示匹配1次或者多次 {1,}
*表示匹配任意次,0次或者多次 {0, }
贪婪匹配,在范围允许的情况下,尽可能多的匹配
非贪婪(惰性匹配),元字符 量词 ?表示匹配 最多使用的是.*?x表示匹配任意字符, 任意多次数,但是一旦遇到x就停下来
转义符
原本有特殊意义的字符,到了表达他本身的意义的时候,需要转义
有一些特殊意义的内容,放到字符组中,会取消它的特殊意义,如[.()*+?]在字符组中取消它的意义,[a反斜杠-c] -在字符组中表示范围,如果不希望它表示范围,需要转义,或者放在字符组的最前面|最后面
import re
ret = re.findall('d+', '1900dasd1290') #findall还是按照完整的正则进行匹配,只是显示括号里面匹配的内容
print(ret)
res = re.search('d+', '1900sdfa1200') #search 还是按照完整的正则进行匹配,显示也匹配到的第一个内容,
#但是我们可以通过group方法传参数来获取具体文组中的内容
print(res) #找不到返回空
if res:
print(res.group()) #返回匹配的字符
#变量.group() 结果和变量.group(0)的结果一致
#变量.group(n)的形式来制定获取第n个分组中匹配到的内容
#为什么search中不需要分组优先,而在findall中需要
#加上括号 是为了对真正需要的内容进行提取
ret = re.findall('<w+>(w+)</w+>', '<h1>dafasf</h1>')
print(ret) # dafasf
#对于search
#可以想拿哪个分组的,就拿哪个分组的
#为什么要用分组,以及findall的分组优先到底有什么好处
exp = '2-3*(5+6)'
ret = re.findall('(d-d).*?|(d+d).*?', '2-3*(5+6)')
#如果我们要查找的内容在一个复杂的环境中
#我们要叉的内容没有一个突出的,与众不同的特点,甚至会和不要的杂乱的数据混合在一起
#这个时候我们就需要把使用的数据都统计出来,然后对这个数据进行赛选,把我们真正需要的数据对应的正则表达式用()圈起来
#想要取消分组的优先,可以使用(?:)
#应用
import re
import requests
ret = requests.get('https://gz.lianjia.com/zufang/')
text = ret.content.decode('utf-8')
#print(text)
"""
<p class="content__list--item--des">
<a target="_blank" href="/zufang/tianhe/">天河</a>-<a href="/zufang/tianhegongyuan/" target="_blank">天河公园</a>-<a title="华港花园" href="/zufang/c2111103316590/" target="_blank">华港花园</a>
<i>/</i>
16㎡
<i>/</i>北 <i>/</i>
4室1厅2卫 <span class="hide">
<i>/</i>
中楼层 (25层)
</span>
</p>
"""
msg = re.findall('<a title=".*?" href="/zufang/.*?/" target="_blank">(.*?)</a>', text)
print(msg)
#['碧桂园星港国际', '骏逸苑', '蓝色康园', '漾晴居', '万松园', '保利香雪山花园', '丽江花园玉树别院', '银星花园', '南沙碧桂园', '保利中环广场', '远洋天骄广场', '保利西子湾', '员村西街7号大院', '龙光峰景华庭', '天河山庄', '丽江花园丽波楼', '宝铼雅居', '保利中航城一期', '南沙时代', '碧桂园星港国际', '丽江花园玉树别院', '瑞东花园', '保利心语花园', '盈嘉花园', '番禺招商金山谷意库', '骏逸苑', '宝铼雅居', '悦涛雅苑', '广州融创文旅城A1区', '都市兰亭花园']
import re
ret = re.split('d+', 'aaaa000iii') #['aaaa', '000', 'iii']
ret = re.split('d(d)d', 'aaa666bbb') #['aaa', '6', 'bbb']
#sub替换
ret = re.sub('d+', 'a', 'aaa123bbb123', 1) #'aaaabbb123' 只替换几次
ret = re.subn('d+', 'a', 'aaa123bbb123') #('aaaabbba', 2) 替换2次
#match 从头开始找 即人为在开头加^
#match规定这个字符号必须是什么样的
#search用来寻找这个字符串是不是含有满足条件的
#假如一个正则表达式需要被使用多次,节省多次去解析同一个正则表达式的时间
ret = re.compile('d+')
ret.findall('aaabbb')
ret.search('aaabbb')
#finditer 节省空间
ret = re.finditer('d+', 'aaa123bbb')
for i in ret:
print(i.group())
#既节省时间也节省空间
ret = re.compile('d+') #如果没有重复使用同一个正则表达式,也不能节省时间
msg = ret.finditer('aaa123bbb')
for i in msg:
print(i.group())
#给分组取名字
ret = re.search('d+(?P<name>.*?)', '123aaa')
print(ret.group('name')) #aaa
#分组命名的引用
exp = '<abb>asdas<abb>a414asdf<abd>'
ret = re.search('<(?P<tag>w+)>.*?</(?P=tag)>', exp)
ret = re.search(r'<(w+)>.*?</1>', exp) #1表示的是第一个分组,加上r表示去除1在python中的特殊用法
print(r'1')
print('\1')
#删除列表中的空字符
alist = ['a', 'b', '', 'c', 'a', '', '']
ret = filter(lambda n:n, ret)
print(list[ret])
#匹配日期
#2018-12-31
'[1-9]d{3}-(1[0-2]|0?[1-9])-([12]d|3[01]|0?[1-9])'
1.2.10shutil模块
import shutil
#拷贝文件
shutil.copy2('xxx.txt', 'xxx_bk.txt')
#拷贝目录
shutil.copytree('原目录', '新目录', ignore = shutil.ignore_patterns('__init__.py')) #拷贝文件目录及文件忽略init方法
#删除目录
shutil.rmtree('D:project')
#移动文件
shutil.move('D:project', 'D: est')
#硬盘信息
total, used, free = shutil.dosk_usage('.')
print('当前磁盘共:%i GB, 已使用:%i GB, 剩余:%i GB') %(total / 1073741824, used / 1073741824, used / 1073741824)
#压缩文件
shutil.make_archive('outer.zip', 'zip', 'D:project') #参数1:压缩后的文件名, 参数2:压缩类型, 参数3:原文件名
#解压
shutil.unpack_archive('outer.zip', r'D: est')
1.2.11logging
为什么要写log
- log是为了排错的
- log用来做数据分析,以日志记录用户行为
import logging
#输出内容都是有等级的,默认处理warning级别以上的所有信息
logging.debug('debug message') #调试
logging.info('info message') #信息
logging.warning('warning message') #警告
logging.error('error message') #错误
logging.critical('critical message') #批判性的
#输出到文件,且设置信息的等级
logging.basic_Config(
format='%(asctime)s - %(name)s - %(levelname)s [%(lineno)d]- %(module)s - %(message)s',
detefmt='%Y-%m-%d %H:%M:S %p',
#level=logging.DEBUG #level=10 #在此级别以上才会写入日志
filename='tmp.log' #输出日志文件,是追加的模式
)
#同时向文件和屏幕输出和乱码
fh = logging.FileHandler('tmp.log', encoding='utf-8')
sh = logging.StreamHandler()
logging.basic_Config(
format='%(asctime)s - %(name)s - %(levelname)s [%(lineno)d]- %(module)s - %(message)s',
detefmt='%Y-%m-%d %H:%M:S %p',
level=logging.DEBUG #level=10 #在此级别以上才会写入日志
handlers=[fh, sh]
)
#日志的切分
import time
from logging import handlers
#按照大小做切割
rh = handlers.RotatingFileHandler('myapp.log', maxBytes=1024*1024, backupCount=5)
#按照时间进行切割
#例如下面的是按5秒对xx.log文件进行切分,每隔一段时间会出现一个文件
rh = handlers.TimedRotatingFileHandler(filename='xx.log', when='s', interval=5, encoding='utf-8')
fh = logging.FileHandler('tmp.log', encoding='utf-8')
sh = logging.StreamHandler()
logging.basic_Config(
format='%(asctime)s - %(name)s - %(levelname)s [%(lineno)d]- %(module)s - %(message)s',
detefmt='%Y-%m-%d %H:%M:S %p',
level=logging.DEBUG #level=10 #在此级别以上才会写入日志
handlers=[fh, sh]
)
for i in range(1, 10000):
time.sleep()
logging.error('KeyboardInterrupt error %s'%str(i))
#主动抛出异常
raise ValueError
raise NotImplementedError('提示信息--你没有按照要求实现函数')
2.递归
递归最多的层数规定为1000层:为了节省内存空间,不要让用户无限使用内存空间
1.递归要尽量控制次数,如果需要多层递归才能解决问题,不适合用递归解决
2.循环和递归的关系
- 递归不是万能的
- 递归比起循环来说更占用内存
修改递归的最大深度
import sys
sys.setrecursionlimit(10000)
#1.阶乘
def recursion(n):
if n == 1: return 1
return n * recursion(n - 1)
res = recursion(4)
print(res)
#2.os模块:查看一个文件夹下的所有文件,这个文件夹下面还有文件夹,不能用walk
import os
def show_file(path):
name_lst = os.listdir(path)
for name in name_lst:
abs_path = os.path.join(path, name)
if os.path.isfile(abs_path): print(name)
if os.path.isdir(abs_path):show_file(abs_path)
#3.计算一个文件夹下的所有文件的大小,这个文件夹下面还有文件夹,不能用walk
import os
def dir_size(path):
size = 0
name_lst = os.listdir(path)
for name in name_lst:
abs_path = os.path.join(path, name)
if os.path.isfile(abs_path):
size += os.path.getsize(abs_path)
if os.path.isdir(abs_path):
size += dir_size(abs_path)
return size
#4.计算斐波那契数列
#5.三级菜单 可能是n级
def menu_func(menu):
flag = True
while flag:
for name in menu:
print(name)
key = input('>>>').strip()
if menu.get(key):
dic = menu[key]
ret = menu_func(dic)
flag = ret
elif key.upper() == 'B':
return True
elif key.upper() == 'Q':
return False
3.面向对象
面向过程:想要一个结果,写代码,实现计算结果
面向对象开发:有哪些角色 角色的属性和技能 两个角色之间如何交互的
先来定义模子,用来描述一类事物
具有相同的属性和动作
class Person:
def __init__(self, name): #初始化方法
self.name = name
def eat(self, food): #自定义方法
print('%s吃%s'%(self.name, food))
#实例化一个对象,会开辟一块内存空间
obj = Person('tony') #会自动调用类中的__init__方法,把空间的内存地址作为self参数传递到函数内部
#所有的这个对象需要使用的属性都要和self关联起来,执行完init的逻辑之后,self变量会自动的被返回到调用处(实例化发生的地方)
#类和对象之间的关系
#类是一个大范围,是一个模子,约束了事物有哪些属性,但是不能约束具体的值
#对象是一个具体的内容,是模子的产物,遵循类的约束,同时给属性赋值
#类有一个空间,存储的是定义在class中的所有名字
#每一个对象又用于自己的空间,通过对象名.__dict__就可以查看这个对象的属性和值
print(obj.name) #print(obj.__dict__['name'])
obj.eat('apple') #使用对象的方法
类中的变量是静态变量
对象终端额变量只属于对象本身,每个对象有属于自己的空间来存储对象的变量
当使用对象名区调用某一个属性的时候,会优先在自己的空间寻找,找不到再去对应的类中寻找
如果自己没有就引用类的,如果类也没有就会报错
对于类来说,类中的变量所有的对象都是可以读取的,并且读取的是同一份变量
类中的静态变量的用户
如果一个变量是所有的对象共享的值,那么这个变量应该被定义成静态变量
所有和静态变量相关的增删改查都应该使用类名来处理
组合:类中的属性可以是另一个类
3.1继承
继承语法
class A:
pass
class B(A):
pass
#B继承A类,B是子类
#子类可以使用父类中的方法,属性
先开辟空间,空间里有一个类指针-》指向A
调用init方法,如果在B里面找不到init方法,到A中找
父类和子类方法的选择:
- 子类的对象如果去调用方法,永远优先调用自己的
- 如果自己有,用自己的
- 自己没有,用父类的
- 如果自己有还想用父类的,直接在子类方法中调用父类的方法:父类.方法名(self)
#思考下面输出什么
class Foo:
def __init__(self):
self.func() #在每一个self调用func的时候,我们不看这句话是在哪里执行的,只看self是谁
def func(self):
print('in foo')
class Son(self):
def func(self):
print('in son')
Son() #in son
多继承:好几个爹
有一些语言不支持多继承 java
python语言的特点:可以在面向对象中支持多继承
class B:
def func(self): print('in B')
class A:
def func(self): print('in A')
class C(A, B):
pass
C.func() #in A A写在前面,从左到右去找
单继承:
- 调子类:子类自己有的时候
- 调父类:子类自己没有的时候
- 调子类和父类的:子类父类都有,在子类中调用父类的
多继承:一个类有多个父类,在调用父类方法的时候,按照继承顺序,先继承的就先寻找
object类,是一个类祖宗
#查找父类
B.__bases__ <class '__main__.A'>
#FunctionType: 函数
#MethodType:方法
A.func #函数
a = A()
a.func #方法
特殊的类属性:
__name__ #类的名字
__doc__ #类的文档字符串
__base__ #类的第一个父亲
__bases__ #类的所有父类构成的元组
__dict__ #类的字典属性
__class__ #实例对应的类
加载代码的过程,需要先加载父类,所以父类写在前面
思考的角度,总是先把子类都写完,发现重复的代码,再把重复的代码放到父类中
python3 所有的类都继承object类,都是新式类
python2中不继承object类的都是经典类,继承object的是新式类
继承的逻辑:走到下一个点的时候,下一点即可以从深度走,也可以从广度走,总是先走广度,再走深度
-> B
D -> A
-> C
新式类:D -> C -> B ->A
经典类:D -> C -> A ->C
在经典类中,都是深度优先,总是一条路走不通了,才会换一条路,走过的点不会再走
在新式类中,遵循C3算法:
算法的内容:
如果一个类出现在从左到右所有顺序的左侧,并且没有在其他位置出现,那么先提出来的作为继承顺序中的一个
或一个类出现在从左到右顺序的最左侧,并没有在其他顺序中出现,那么先提出来作为继承顺序中的一个
如果从左到右第一个顺序中的第一个类出现在后面且不是第一个,那么不能提取,顺序向后寻找其他顺序符合上述条件的类
F(D, E) = c3(D(B) + E(C))
F = [DBAO] + [ECAO]
FD = [BAO] + [ECAO]
FDB = [AO] + [ECAO]
FDBE = [AO] + [CAO]
FDBEC = [AO] + [AO]
FDBECAO
D.mro() #只在新式类中,会告诉你c3继承的顺序
抽象类:是一个开发的规范,约束所有的子类必须实现一些和它同名的方法
#由父类定制一个规范,让子类继承后一定要实现
class Payment:
def pay(self, money):
raise NotImplementedError('请在子类中重写同名pay方法')
#实现抽象类的另一种方式,约束力强,依赖abc模块,模块改变的时候就要改一下代码
from abc import ABCMeta, abstractmethod
class Payment(metaclass=ABCMeta):
@abstractmethod
def pay(self, money):
raise NotImplementedError('请在子类中重写同名pay方法')
#第一种方式实现的时候,只有在类实例化调用pay方法的时候才会抛出异常
#第二种方法只要实例化,就会抛出异常
3.2多态
什么是多态:
借助java说明一下
class Payment: pass
class Wechat(Payment):
def func(self): pass
class Apple(Payment):
def func(self): pass
#在java中
def pay(Wechat obj, int money):
obj.func()
def pay(Apple obj, int money):
obj.func()
#为了实现多态,使得Wechat类和Apple都调用一个方法,继承一个Payment类作为爷爷
def pay(Payment obj, int money):
obj.func()
一个类型表现出来的多种状态
支付表现出的微信支付和苹果支付这两种状态
在java情况下,一个参数必须制定类型
所以想要两个类型的对象都可以传,那么必须要让这两个类继承一个父类,制定类型的时候使用父类来指定
规一化设计:
class A:
def func(self):pass
class B:
def func(self):pass
def 函数名(obj):
obj.func()
多态:
- 什么是多态:一个类表现出的多种形态,实际上是通过继承来完成的,父类表现出多个子类的形态
- 在java中,为了保证多个子类对象可以传递,那么需要子类对象继承同一个对象
- python中,处处是多态
鸭子类型 ----》 可哈希类型 迭代器类型 可迭代类型 with上下文管理的类型(enter exit)
py中一个类可以是很多类的鸭子模型
如tuple元组类,是可哈希的,又不依靠继承哈希类来判定是不是可哈希类型
元组类是可哈希类型的鸭子模型
是可迭代的,不是依靠继承迭代类来判定是不是迭代类型,内部实现iter
元组类是可迭代类型的鸭子模型
- 子类继承父类,我们说子类也是父类这个类型的
- 在python中,一个类是不是属于一个类型的,不仅仅可以通过继承来完成,还可以是不继承,但是如果这个类满足了某些类型的特征条件,我们就说它长得像这个类型,那么他就是这个类型的鸭子模型
#super方法:是按照mro方法顺序来寻找当前类的下一个类
class A:
def func(self):
print('A')
class B(A):
def func(self):
super().func()
print('B')
class C(B):
def func(self):
super().func()
print('C')
C.func() #A B C
class A:
def func(self):
print('A')
class B(A):
def func(self):
super().func()
print('B')
class C(A):
def func(self):
super().func()
print('C')
class D(C, B):
def func(self):
super().func()
print('D')
#mro D - > C - > B - > A
D.func() # A B C D
#在python中不要传递参数,自动就会寻找当前类的mro顺序的下一个类的同名方法
#py2的新式类中,需要我们主动传递参数super(子类的名字,子类的对象).函数名()这样才能调用这个子类的mro顺序的下一个类的同名方法
#py2的经典类中,并不支持使用super来找下一个类
#在单继承中,super就是找父类
3.3封装
封装:就是把属性或者方法封装起来
广义:把属性和方法封装起来,外面不能直接调用,要通过类的名字来调用
狭义:把属性和方法藏起来,外部不能直接调用,只能在内部偷偷调用
#给一个名字前面加上__的时候,这个名字变成一个私有的实力变量/私有的对象属性
#所有的私有内容或者名字都不能在类的外部直接调用,只能在类的内部使用了
class User:
def __init__(self, name, password):
self.usr = name
self.__pwd = password
def get_pwd(self): #表示拥护不能改只能看 私有+某个get方法实现
return self.__pwd
def change_pwd(self): #表示用户必须要调用外面自定义的修改方式来进行变量的修改
pass
import hashlib
class User:
def __init__(self, name, passwd):
self.usr = name
self.__pwd = passwd #私有的实例变量
def __get_md5(self): #私有的绑定方法
md5 = hashlib.md5(self.usr.encode('utf-8'))
md5.update(self.__pwd.encode('utf-8'))
def get_pwd(self):
return slef.__get_md5()
obj = User('apple', '123')
print(obj.get_pwd())
所有的私有话都是为了让用户不在外部调用类中的某个名字
如果完成私有化 那么 这个类的封装度也就越高 封装度越高各种属性和方法的安全性也越高 但是代码越复杂
加了双下划线的名字为甚么不能从类的外部调用了
class User:
__country = 'China'
def func(self):
print(self.__country) #在类的内部使用的时候,自动把当前这句话所在的类的名字拼在私有变量前完成变形
print(User._User__country)
#在类的外部根本不能定义私有概念
私有的内容能不能被子类使用呢
#不能被子类使用
class Foo(object):
def __init__(self):
self.__func()
deef __func(self):
print('in Foo')
class Son(Foo):
def __func(self):
print('in func')
Son() #in Foo
在其他语言中的数据级别都有哪些
public 公有的 类内类外都能使用 父类子类都能使用
protect 保护的 类外不能使用 类内能使用 父类子类都能用 #python不支持
private 私有的 类外不能用 本类内部使用 父类和子类都不能用
3.4类中的三个装饰器(内置函数)
能定义到类中的内容
- 静态变量 是个所有对象共享的变量 由对象或者类调用 但是不能重新赋值
- 绑定方法 是个自带self参数的函数 由对象调用
- 类方法 是个自带cls参数的函数 由对象或者类调用
- 静态方法 啥都不带的普通函数 由类或者对象调用
- property属性 是个伪装成属性的方法 由对象调用但是不加括号
@property 属性
作用:把方法伪装成一个属性,在调用这个方法的时候不需要加()即可直接得到返回值
限制:装饰的这个方法不能有参数
#变量的属性和方法
#属性: 圆的半径、圆的面积
#方法: 登陆、注册
from math import pi
class Circle:
def __init__(self):
self.r = r
@property
def area(self):
return pi * self.r**2
c1 = Circle(5)
print(c1.area) #不用加括号
#应用场景二:和私有的属性合作
class User:
def __init__(self, name, pwd):
self.usr = name
self.__pwd = pwd
@property
def pwd(slef):
return self.__pwd
obj = User('apple', '123')
print(apple.pwd) #可以获取到pwd,但是不能修改
#property进阶
class Goods:
discount = 0.8
def __init__(self, name, origin_price):
self.name = name
self.__price = origin_price
@property
def price(self):
return self.__price * self.discount
@price.setter
def price(self, new_value):
print('使用我了%s'%(new_value))
if isinstance(new_value, int):
self.__price = new_value
@price.deleter
def price(self):
print('del')
apple = Goods('apple', 5)
print(apple.price) #调用的是@property装饰的price
apple.price = 10 #调用的是被setter装饰的price
del apple.price. #del 执行@property下的方法
@classmethod
class Goods:
__discount = 0.8
def __init__(self):
self.__price = 5
self.price = self.__price * self.__discount
@classmethod
def change(cls, new_discount): #把一个对象绑定的方法修改成一个类方法,cls指的是类的名字,而不是对象的名字
cls.__discount = new_discount
#定义了一个方法,默认传self,但是这个self没被使用
#@classmethod 把一个对象绑定的方法修改成一个类方法
#第一,在方法中仍然可以使用类中的静态变量
#第二,可以不用实例化对象,就直接用类名在外部调用这个方法
#什么时候用classmethod
#1.定义一个方法,默认传self,但是self没被使用
#2.并且你在这个方法里使用类当前的类名,或者你准备使用这个类的内存空间中的名字的时候
@staticmethod
类中不会用到self和cls
class User:
@staticmethod
def login(a, b):
print('login', a, b)
反射
a = A()
if hasattr(a, 'sex'): #是否有这个对象
if callable(getattr(a, 'func')): #判断能不能调用或者执行的
getattr(a, 'func')() #获取
3.5双下划线方法
#__call__: 对象() 需要调用类中的__call__()方法
class A:
def __call__(self, *args, **kwargs):
print('_________')
obj = A()
print(callable(obj))
obj() #对象加括号调用__call__方法, 打印出_________
#__len__: 实现len(对象) 这个方法 需要在类中加入__len__()方法
#__new__: 构造方法
#实例化的时先创建一块对象空间,有一个指针能指向类-->__new__
class A:
def __new__(cls, *args, **kwargs):
o = super().__new__(cls)
#o = object.__new__(cls)
print('new')
def __init__(self):
print('init')
#__str__: 可以直接打印对象的信息,而不打印内存地址地址
class Course:
def __str__(self):
return '课程'
obj = Course()
print(obj) #课程
#当我们打印一个对象 用%s进行字符串拼接 或者str(对象)总是调用这个对象的__str__方法
#如果找不到__str__,就调用__repr__方法
#__repr__不仅是__str__的替代品,还有自己的功能
#%r进行字符串拼接 或者用repr(对象)的时候总是调用这个方法
4.设计模式
单例模式:
一个类从头到尾,只会创建一次self空间
为甚么要有,用来做什么:创建一个对象需要的空间的
class Baby:
__instance = None
def __new__(cls, *args, **kwargs):
if cls.__instance is None:
cls.__instance = super().__new__(cls)
return cls.__instance
def __init__(self, cloth, pants):
self.cloth = cloth
self.pants = pants
b1 = Baby('blue cloth', 'yellow pants')
b2 = Baby('green cloth', 'white pants')
print(b1) # green cloth white pants
print(b2) # green cloth white pants
#第二种单例模式
from a.py import baby
#在python当中一个模块的导入不会导入多次
#只要在模块里面创建了一个实例