内置方法进阶
内置方法的进阶详解:
今天的博客将进行更多内置方法的详解,包含了__new__,__del__,__len__,__hash__,__eq__,还有item系列。
__new__:构造方法
网上经常有一个笑话“程序员可以自己new一个对象”,到底new有什么作用呢?
__new__又称为构造方法,通过学习__init__()方法,我们知道了初始化一个实例需要经历的几个步骤中的第一步就是在内存空间中开辟一个对象空间,这个对象空间是__init__方法开辟的么?其实不是,在init之前,实例化对象的第一步就是通过__new__方法创建一个对象空间。
python代码无法做到创建空间,创建空间交给了object.new()
class Foo: def __init__(self): #初始化方法 print('执行了init方法') def __new__(cls, *args, **kwargs): #构造方法,构造了对象的空间 print('执行了new') return object.__new__(cls) #或者super().__new__() #调用了object类中的__new__方法 obj = Foo() >>> 执行了new 执行了init方法
从上面的例子,我们可以更加清晰地得到初始化一个对象所经历的过程:
(1)实例化Foo类,应该先执行其__new__方法。
(2)但是Foo类中没有定义__new__方法,所以到object祖宗类中执行object的__new__方法。
回顾调用父类的两种方法:
object.__new__(cls)
super().__new__(cls)
(3)此时在内存中开辟一块对象空间。
(4)才执行init初始化方法,把地址传给self,给对象封装属性。
总结:构造方法__new__和初始化方法__init__之间的区别?
形象的说,类好比一个人类型的模板,__new__构造方法就是捏小人的过程(捏出来的每一个小人都是一样的),__init__初始化方法好比给小人穿衣服的过程(为对象封装属性),一定是先有__new__方法,后有__init__。
拓展知识:
浅谈一下设计模式,设计模式的概念来自于java这么语言,用来规范代码的编写。常用的设计模式大概有23中,现在我们来浅谈一下其中之一:单例模式。
单例模式:一个类只有一个实例的时候,这种就叫做单例模式。
# 单例模式:一个类只有一个实例。
# 思考:如果使得一个类只能实例化一次,也就是这要控制只能开辟一次空间,就能实现单例模式。
class Foo:
__instance = None #通过设置一个标志位,用来使得只能运行一次new方法
def __init__(self):pass
def __new__(cls, *args, **kwargs):
if cls.__instance is None:
cls.__instance = object.__new__(cls)
return cls.__instance
obj1 = Foo()
obj2 = Foo()
print(obj1)
print(obj2)
>>>
<__main__.Foo object at 0x000001948D887F60>
<__main__.Foo object at 0x000001948D887F60>
__del__:析构方法
在删除某个类的对象的时候,就会触发这个双下方法,再删除这个对象。即"del obj"执行这个删除对象的代码的时候,触发__del__析构方法。
运用场景:关闭文件,关闭网络连接,关闭数据库等。
class Foo: def __init__(self,age): self.age = age self.file = open('file','w',encoding='utf-8') #打开一个文件 def write(self): self.file.write('fjdkfjdkf') def __del__(self): #析构方法 self.file.close() #关闭文件。 print('执行了__del__析构方法') obj = Foo(23) obj.write() del obj
__len__
计算对象的长度,如果类中定义了__len__方法,那么在对象中就能用内置函数len(obj),就会自动调用__len__方法。
class Foo():
def __init__(self):
self.lst = []
def __len__(self):
print('调用了__len__方法,计算长度')
return len(self.lst)
f = Foo()
f.lst.append('lalal')
print(len(f)) #自动调用了__len__双下方法
>>>
调用了__len__方法,计算长度
1
__eq__
__eq__双下方法,用来自定义判断两个对象是否相等,默认object类中中的eq方法是以内存地址作为判断标准,在运行"obj1 == obj2"时,就会自动触发__eq__双下方法。
# 默认object类中的__eq__方法以内存地址作为判断标准
class Staff:
def __init__(self,name,sex):
self.name = name
self.sex = sex
alex = Staff('alex','不详')
alex2 = Staff('alex','不详')
print(alex == alex2) # obj1 == obj2自动调用__eq__方法
print(id(alex))
print(id(alex2))
>>>
False
2694349160400
2694349178696
# 自定义__eq__方法
class Staff:
def __init__(self,name,sex):
self.name = name
self.sex = sex
def __eq__(self, other): #__eq__方法接受两个参数,一个是self,一个是其他对象
return True
alex = Staff('alex','不详')
alex2 = Staff('alex','不详')
print(alex == alex2) #相当于执行了alex.__eq__(alex2)
>>>
True
__hash__
哈希是一种算法,把一个对象转换为一串数字。
(1)对于相同的值在同一次程序的运行中是不会发生变化的。
(2)对于不同的值在一次程序的运行中总是不同的。
拓展小知识:
一.字典类型寻址快的原因:
Q1:字典在内存中是如何存储的?
Q2:为什么字典的key必须是可哈希的类型?
Answer:字典通过对key转换得到的hash值直接找到内存中值的地址,一次过程key的hash值一直是一样的,所以快速。
二.set集合的去重机制
(1)必须通过哈希,因为在一次过程中,相同的值得到的哈希是一样的,如果有重复就不再添加。
(2)但是hash算法也不是完全的靠谱,为了避免极微小机率出现两个值得hash值一样,就会比较两个值是否相等,再进行二次寻址。
#通过hash()会直接调用类中的__hash__方法 class Foo(): pass obj1 = Foo() obj2 = Foo() print(hash(obj1)) print(hash(obj2)) >>> -9223371857590216732 179264559080
''' 员工类: 姓名 年龄 性别 岗位 假设员工转岗位了 姓名 年龄(变化了) 性别 岗位(变化了) 要求: 100个员工,去掉重复的 员工的姓名 性别是相同的,认为是同一个员工 ''' class Staff: def __init__(self,name,age,gender,dep): self.name = name self.age = age self.gender = gender self.dep = dep def __hash__(self): return hash(self.name + self.gender) def __eq__(self, other): if self.name == other.name and self.gender == other.gender: return True name_list = ['alex','boss_jin','wusir','egon'] obj_lst = [] for i in range(100): name = name_list[i%4] obj =Staff(name,i,'男','python') obj_lst.append(obj) ret = set(obj_lst) print(ret)