python学习21天----析构、item、hash、eq、模块

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
View Code

2)当通过del删除对象是,内部调用的就是__del__析构方法(先执行__del__,再删除对象)

class A:
    def __del__(self):      #析构方法内部,虽然没有任何的操作,但是仍然删除了对象
        print("析构方法执行")
a = A()
del a
print(a)
输出:
析构方法执行
NameError: name 'a' is not defined
View Code

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()
View Code

注:不管是主动还是被动,这个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
View Code

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
View Code

#让两个方法产生联系(即通过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
View Code

注:在一些内置的模块中,有一些特殊的方法,要求对象必须实现__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'
View Code

#通过操作一个自定义得对象,操纵了对象中得某个列表(使得操作更加简便),即实现了通过操作对象,操作了对象中得某个数据类型

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', '阿狸']
View Code

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
View Code

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
View Code

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': '均衡教派'}
View Code

总结: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
输出:
我是一个模块
View Code

注:一个模块不会被重复导入

4)使用模块

①模块的名字必须要满足变量的命名规范,一般情况下,模块都是小写字母开头的名字

my_moudle.py模块
def login():
    print("这是一个登陆函数")
name = "阿狸"
主程序:
import my_moudle
my_moudle.login()
print(my_moudle.name)
View Code

②导入一个模块时,发生了那些事

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
View Code

注:不是把模块名改了,而是把内存中引用模块的变量名改了

6)模块导入注意事项

①可以在一行导入多个模块

import os,sys

②PEP8规范中,模块不应该在同一行导入

③所有的模块导入都应该尽量放在这个文件的开头

④模块的导入也是有顺序的:先导入内置模块,再倒入第三方模块,最后导入自定义模块

原文地址:https://www.cnblogs.com/piaolaipiaoqu/p/13917263.html