python学习笔记 第九章 回顾补充2

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:\aaa\bbb\ccc  以/开头的路径,默认在当前盘符下
os.path.abspath(r'bbb/ccc')  #D:\aaa\bbb\ccc
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]) + '\n')
  f.write(json.dumps([4, 5, 6]) + '\n')
  
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用\t匹配,enter回车用\n匹配

正则表达式中能帮助我们表示匹配内容的字符都是正则中的元字符

元字符 -- 匹配内容的规则

\W 非数字字母下划线

\D 非数字

\S 非空白

[\d\D] [\w\W] [\s\S] 匹配所有

. 表示匹配除了换行符的的所有

[^\d] 匹配所有的非数字

^ 匹配一个字符串的开始

$ 匹配一个字符串的结尾

()分组

a表达式|b表达式 匹配a或者匹配b表达式中的内容,如果匹配a成功了,就不会继续匹配b

记忆元字符:都是表示能匹配到哪些内容,一个元字符匹配一个字符

  • \d \w \s \t \n \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:\test')

#硬盘信息
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:\test')

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当中一个模块的导入不会导入多次
#只要在模块里面创建了一个实例
posted @ 2021-03-26 14:33  wrrr  阅读(47)  评论(0编辑  收藏  举报