1.析构方法【__del__】
1)当申请一个对象,并且用完之后,不需要自己去释放,python会自动的去释放
class A:pass a = A() print(a) del a #删除对象(包括对象的所有引用) print(a) #删除户打印就会出错 输出: <__main__.A object at 0x00000266AB8A5C88> NameError: name 'a' is not defined
2)当通过del删除对象是,内部调用的就是__del__析构方法(先执行__del__,再删除对象)
class A: def __del__(self): #析构方法内部,虽然没有任何的操作,但是仍然删除了对象 print("析构方法执行") a = A() del a print(a) 输出: 析构方法执行 NameError: name 'a' is not defined
3)作用:去归还/释放一些在创建对象的时候借用的资源(这些资源python解释器无法自动回收,如打开的文件等)
若对象不涉及到操作系统的一些资源或者网络连接等,python解释器都会自动回收
class File: def __init__(self,file_path): self.f = open(file_path) def read(self): self.f.read(1024) def __del__(self): #当程序结束时,要去释放打开的系统资源 self.f.close() f = File('文件名') f.read()
注:不管是主动还是被动,这个f对象总会被清理掉,被清理掉就触发__del__方法,触发这个方法就会归还操作系统的文件资源
4)__del__析构方法执行的时间
①在手动【del 对象】的时候执行;由程序员触发
②python解释器的垃圾回收机制,回收这个对象所占的内存的时候执行;由python解释器触发
补充:python解释器在内部就能做的事:申请一块内存空间(内存空间由操作系统分配),而在申请的这一块内存空间中的所有事,都是由python解释器来管理的
python是没有权限直接操控硬盘的文件,必须借助操作系统,才能操作文件(即python给操作系统发一条指令,告诉操作系统,要打开某个文件,此时操作系统给与python一个权限,操作系统替python打开文件,并给python一个文件操作符[文件句柄]),要想在程序中使用这个文件操作父,必须给这个文件操作符分配一块内存,然后通过变量f指向这个文件操作符的地址)所以正常情况下都需要在关闭文件描述符之前,都需要close一个文件
【del 对象】释放的是python解释器存储的内存
【f.close】释放的是操作系统打开的文件资源
总结:某个对象借用了操作系统的资源,还要通过析构方法归还回去(包括文件资源、网络资源等),如果不归还,有时会引发巨大错误
补充:对于开发文件的方式,【with open() as f】虽然可以自动打开和关闭一个文件,凡是不如【f.open()、f.close()】靠谱,因为如果使用with的打开文件的过程中,程序出现了异常等,那么获取到的资源就得不到释放
总结:在清除一个对象在内存中的使用的时候,会触发这个对象所在类中的析构方法
2.item系列方法
item系类的方法和对象使用中括号,即【对象[]】访问值有联系、
1)__getitem__方法用于对象[]获取值
class A: def __getitem__(self, item): print("getitem方法执行。。。",item) return "AAAAAA" a = A() print(a['b']) 输出: getitem方法执行。。。 b AAAAAA
2)__setitem__方法用于对象[]改变值(改变值打印其实还是__setitem__中的内容,两个语法本身是没有联系的,获取值和改变值调用的是两个不同的方法)
class A: def __getitem__(self, item): print("getitem方法执行。。。",item) return "AAAAAA" def __setitem__(self, key, value): print(key,value) a = A() print(a['b']) a['b'] = '阿狸' #通过对象[key] = 'value'设置值时,自动调用__setitem__方法 print(a['b']) 输出: getitem方法执行。。。 b AAAAAA b 阿狸 getitem方法执行。。。 b AAAAAA
#让两个方法产生联系(即通过getitem获取的值为setitem改变的值)
class A: def __getitem__(self, item): return getattr(self,item) def __setitem__(self, key, value): #setarrt(self,'k1','v1')等价self.k1='v1' setattr(self,key,value) a = A() a['b'] = 'Value' print(a['b']) 输出: Value
注:在一些内置的模块中,有一些特殊的方法,要求对象必须实现__getitem__、__seritem__才能使用
3)__delitem__用于删除对象[]的值(和析构方法不同,如果在__delitem__不进行任何操作,是删除不了值得)
class A: def __getitem__(self, item): return getattr(self,item) def __setitem__(self, key, value): setattr(self,key,value) def __delitem__(self, key): delattr(self,key) a = A() a['b'] = 'Value' del a['b'] print(a['b']) 报错: AttributeError: 'A' object has no attribute 'b'
#通过操作一个自定义得对象,操纵了对象中得某个列表(使得操作更加简便),即实现了通过操作对象,操作了对象中得某个数据类型
class A: def __init__(self,lst): self.lst =lst def __getitem__(self, item): return self.lst[item] def __setitem__(self, key, value): self.lst[key] = value def __delitem__(self, key): self.lst.pop(key) a =A(['111','222','aaa','bbb']) print(a.lst[0]) #普通获取值 print(a[0]) #通过getitem方法获取值 a[3] = '阿狸' #通过setitem改变值 print(a.lst) del a[2] #通过delitem删除值 输出: 111 111 ['111', '222', 'aaa', '阿狸'] ['111', '222', '阿狸']
3.__hash__方法
补充:底层数据结构基于hash值寻址得优化操作
HASH是一个算法,能够把某一个要存在内存里的值通过一系列计算生成一个唯一的值(hash算法保证不同值得hash结果是不一样的)
对同一个值在多次执行python代码的时候hash值是不同的;但是对同一个值,在同一次执行python代码的时候hash值永远不变(执行一次run,算一次执行)
print(hash("abc")) print(hash("abc")) print(hash("abc")) print(hash("abc")) 输出: -4026252806826000686 -4026252806826000686 -4026252806826000686 -4026252806826000686
2)字典的寻址:字典的寻址要比列表的寻址块,因为在内部使用了hash的机制
对于一个字典{'key':'value'},先对 key做一个hash【hash(key)】计算得出一个hash值,然后把字典中kei对应得value值直接放置在之前计算出得hash值对应的内存地址处,当下次通过【dic[key]】获取key对应的值时,直接对key进行一个【hash(key)】值得计算,然后将这个值和字典中key算出得hash值做比较,相等时直接获取key得hash值所在内存得值
3)set集合
集合去重时也利用的hash算法,对集合中的每一个元素进行hash计算,当有重复的值时,它们的hash值是一样的,那么就会把值存在同一个内存地址(两个值存在了一个内存地址中,相当于进行了覆盖),实现了去重。
通过hash的结果找到一块内存地址,只要这块内存地址上没有数据,就说明之前没有重复的数据;但是如果这块地址上有一个数据存在了,才判断这个值和将要存储的值是否一样,如果一样,就进行覆盖去重,如果 不一样,二次寻址给这个值换个地方存储
注:对于hash算法,如果两个字符串,通过【hash()】计算出的hash值是一样的,那么还会再次判断两个字符串是否相等【A == B】,如果不相等,再开辟一块内存空间去存放另一个值(进行二次寻址),如果相等,就覆盖。(即先算hash,再判断值)
python描述的整数值是有限的,但是可hash得值是无限的,所以无法保证每一个字符串不同得字符串都有一个不同得hash值,基于这种情况,只能先判断hash,再判断值(先判断值是否相等,再判断hash,虽然结果准确,但是慢)
4.__eq__方法
1)__eq__方法和==是相关的
2)如果两个实例化的对象的值是相等的,就返回True,否则返回False
class A: def __init__(self,name,age): self.name = name self.age = age def __eq__(self, other): if self.name == other.name and self.age ==other.age: return True else: return False a1 = A("阿狸",20) a2 = A("阿狸",20) a3 = A("史迪仔",6) print(a1 == a2) print(a1 == a3) 输出: True False
5.面试题:
1)有一个类,对象的属性有:姓名、性别、年龄、部门,有一个场景:早期的时候有一个员工管理系统,所有员工的姓名、性别、年龄都是存起来的,但是有些员工内部转岗了,此时将员工的姓名、性别、年龄、新部门又存了一遍(即相当于同一个人,他之前在研发部有一条信息,但是转岗到运维部后,还有一条信息);需求:如果有1000个员工,要求对这1000个员工进行去重,如果几个员工对象的姓名和性别相同,表示这是一个人,此时对这些员工去重(如果有一个人存储了2条信息,去重后还有999条员工信息)
class Employee: def __init__(self,name,age,sex,partment): self.name = name self.age = age self.sex = sex self.partment = partment def __hash__(self): #自定义的hash return hash('%s%s' %(self.name,self.sex)) def __eq__(self, other): #只有hash值相同的情况下,才会执行__eq__方法 if self.name == other.name and self.sex ==self.sex: return True employ_lst = [] #生成1000个对象 for i in range(200): employ_lst.append(Employee("希维尔",18,'女',"战争学院")) for i in range(200): employ_lst.append(Employee("拉克丝",20,'女',"德玛西亚")) for i in range(200): employ_lst.append(Employee("维鲁斯",25,'男',"艾欧尼亚")) for i in range(200): employ_lst.append(Employee("劫",19,'男',"均衡教派")) for i in range(200): employ_lst.append(Employee("德莱文",23,'男',"诺克萨斯")) employ_set = set(employ_lst) #set去重时,就是对集合中的每一个元素,做了一个hash计算,此时通过set去重,调用了__hash__方法 for person in employ_set: print(person.__dict__) #对于上面程序,使用set集合去重,一定会调用__hash__方法,再通过hash值是否一样 判断是否调用__eq__方法,如果程序中没有自定义hash方法,程序也不会报错,因为在object类中也有一个内置的hash方法 输出: {'name': '拉克丝', 'sex': '女', 'age': 20, 'partment': '德玛西亚'} {'name': '德莱文', 'sex': '男', 'age': 23, 'partment': '诺克萨斯'} {'name': '希维尔', 'sex': '女', 'age': 18, 'partment': '战争学院'} {'name': '维鲁斯', 'sex': '男', 'age': 25, 'partment': '艾欧尼亚'} {'name': '劫', 'sex': '男', 'age': 19, 'partment': '均衡教派'}
总结:set的去重机制:先调用__hash__,再调用__eq__;eq不是每次都触发,只有hash值相等的时候才触发
6.模块
0)模块分类
①内置模块:安装python解释器时,跟着安装的一些方法,存放在python安装目录下的lib目录
②自定义模块:自己写的功能,如果是一个通用的功能,可当成一个模块
③第三方模块/扩展模块:没在安装python解释器的时候安装的那些功能;存放在python安装目录下的lib/site-packages目录;如爬虫、Django、flask框架等
1) 什么是模块:
①有的功能开发者自己无法完成,需要借助已经实现的函数、类来完成这些功能;即自己无法实现的功能,都由别人实现了;
②例如和操作系统打交道、和时间打交道、在1000个数中取随机数、压缩一个文件、和网络通信等,自己无法实现,都由别人实现,但是如果把这些功能都放在一个文件,不管运行什么程序,都会将所有文件加载到内存,会浪费很大内存
③所以模块就是将别人写好的一组功能进行分类管理、达到节省内存并且给用户提供了更多的功能(一般为文件夹、py文件或者C语言编译好的一些编译文件)
2)为什么要有模块:对功能进行分类管理、达到节省内存并且给用户提供了更多的功能
3)模块的导入和使用
①语法
import 模块名 #要导入一个py文件的名字,但是不加.py后缀名
②import这个模块就相当于执行了这个模块所在的py文件
--------------------------------------- moudle_name.py print("我是一个模块") ------------------------------------------ import moudle_name 输出: 我是一个模块
注:一个模块不会被重复导入
4)使用模块
①模块的名字必须要满足变量的命名规范,一般情况下,模块都是小写字母开头的名字
my_moudle.py模块 def login(): print("这是一个登陆函数") name = "阿狸" 主程序: import my_moudle my_moudle.login() print(my_moudle.name)
②导入一个模块时,发生了那些事
1' 先找到my_moudle这个模块
2' 创建一个属于my_moudle的内存空间
3' 执行这个my_moudle,把里面的变量等存入这个命名空间(模块有自己独立的命名空间)
4' 将这个模块所在的命名空间建立一个和my_module之间的引用关系
5)模块的重命名
①语法
import 模块名 as 新名字
②使用
import my_moudle as m #模块的重命名 m.login() my_moudle.login() #此时再通过原来的模块名就会出错 输出: 这是一个登陆函数 NameError: name 'my_moudle' is not defined
注:不是把模块名改了,而是把内存中引用模块的变量名改了
6)模块导入注意事项
①可以在一行导入多个模块
import os,sys
②PEP8规范中,模块不应该在同一行导入
③所有的模块导入都应该尽量放在这个文件的开头
④模块的导入也是有顺序的:先导入内置模块,再倒入第三方模块,最后导入自定义模块