python学习笔记 day28 面向对象的进阶
1.__getitem__() __setitem__() __delitem__() 都是通过操作字典类似的方式来获得属性对应的值;
首先需要明确一点,self.name=name 后面的name是我们传的参数,也即一个确定的值,前面的是我们自己定义的对象属性--变量,然后__dict__()中存放的就是 左边变量名的字符串形式作为字典的key,字典的值就是该属性对应的值;
__getitme__(self,'属性名') 返回属性对应的值;当obj['要查看的属性名']会调用该方法;
class Student(): def __init__(self,name,age): self.name=name self.age=age def __getitem__(self,key): # __getitem__()会接受一个参数item 作为要查询的key 然后去字典__dict__()中去找 if hasattr(self,key): # 如果对象有属性key,这里的key刚好是字符串形式的属性比如'name'刚好可以利用反射hasattr查看字符串类型的变量 return self.__dict__[key] # 返回__dict__字典中对应于该key的值 student=Student("璇璇",22) print(student.__dict__) # 查看对象student字典中包含的键值对(注意键的形式是对象属性加引号) print(student['name']) # 获得属性为name 也就是__dict__中键为'name'对应的值
运行结果:
__setitem__(self,'属性名',属性值),当obj['要设置的属性名']=属性值 会调用该方法;
class Student(): def __init__(self,name,age): self.name=name self.age=age def __getitem__(self,key): if hasattr(self,key): return self.__dict__[key] def __setitem__(self,key,value): # 为对象设置新的属性,(只不过这里的属性其实是加了引号的,因为__dict__中的key其实就是字符串形式的属性名) self.__dict__[key]=value student=Student('xuanxuan',22) print(student.__dict__) print(student['name']) # obj['对象名']这种方式就会自动调用__getitem__()方法,获得传进去的字符串类型的属性对应的属性值 #print(student.name) # 以前我们调用name属性对应的值 是根据对象名.属性名调用的 print(student['age']) student['sex']='女' # obj['新的属性名']=属性对应的值 就会自动调用__setitem__()方法,然后会存到字典中__dict__ print(student.__dict__) # 可以打印此时字典中的键值对,发现就是多了一个'sex'属性 print(student['sex']) # 因为刚才使用__setitem__设置了新属性,所以现在可以直接查看该属性对应的值
运行结果:
__delitem__(self,'要删掉的属性名') del obj['要删掉的属性名']会自动调用该方法;只不过这里传的都是属性名的字符串形式,因为__dict__字典中的key就是属性名的字符串形式;
class Student(): def __init__(self,name,age): self.name=name self.age=age def __getitem__(self,key): if hasattr(self,key): return self.__dict__[key] def __setitem__(self,key,value): self.__dict__[key]=value def __delitem__(self,key): del self.__dict__[key] # 从字典__dict__删掉属性key student=Student('xuanxuan',22) print(student.__dict__) print(student['name']) # 自动调用__getitem__()方法 print(student['age']) student['sex']='女' # 自动调用__setitem__()方法 print(student.__dict__) del student['age'] # 自动调用__delitem__()方法 print(student.__dict__) # 发现字典__dict__中已经没有age属性了
运行结果:
__delattr__(self,key) 当del obj.属性名(这里的属性名不再是加引号的字符串格式了)就会自动调用__delattr__()方法;
上面的__delitem__(self,key) 只是当用户obj['要删掉的属性名'](这里的属性名是加上引号的,也就是字符串格式的)才会调用__delitem__()方法;
之前我们还讲过反射 hasattr(obj,‘属性名’) getattr(obj,‘属性名’) delattr(obj,‘属性名’) 这里的属性名或者对象的方法都必须是加上引号的,也就是属性名或者方法名的字符串格式; 这些反射需要用的内置函数是需要在外部调用时使用,而__delattr__()是在类中是实现的,当调用 del obj.属性名会触发的类方法;
class Student(): def __init__(self,name,age): self.name=name self.age=age def __getitem__(self,key): if hasattr(self,key): return self.__dict__[key] def __setitem__(self,key,value): self.__dict__[key]=value def __delitem__(self,key): del self.__dict__[key] def __delattr__(self,key): self.__dict__.pop(key) student=Student('xuanxuan',22) print(student.__dict__) print(student['name']) # 会调用__getitem__()方法 ,属性名需要加引号 student['sex']='女' # 会调用__setitem__()方法 设置新属性 属性名需要加引号 print(student.__dict__) del student['age'] # 调用__delitem__()方法删掉age属性 属性名需要加引号 print(student.__dict__) # 此时就没有age属性了 del student.sex # 调用__delattr__()方法,删掉sex属性,属性名不需要加引号 print(student.__dict__) # 此时字典中只剩下name属性 print(Student.__dict__) # 这是类的字典,存的是类属性,方法这些
运行结果:
2. __new__()方法
类被创建时就会先执行一些在类中全局作用域中的东西,比如私有属性,类属性,这些,这是类被创建的时候就会被执行的,当类实例化之后首先其实先执行__new__方法(一般我们创建类不会重写__new__方法,所以都是去object父类中去调用__new__()方法),__new__方法会返回一个实例化的对象其实也就是self ,把这个实例化的对象传给__init__()方法,其实之前也说过self对象是在实例化对象之后会自动创建的对象,在执行__init__()初始化方法之前就被创建了,就是因为实例化一个对象,会先执行__new__()方法,得到一个self对象,然后去执行初始化方法__init__()
import time class A(): print("我是定义类的时候就被执行了") time.sleep(3) # 其实这个时候你就会发现,在创建类时就会首先执行这里 def __new__(cls,*args,**kwargs): # 一旦实例化对象就会首先执行__new__()方法,返回一个实例化的对象,这个返回的结果就传给了__init__()的self print("我在__new__方法中,一旦实例化对象就最先被执行的") return object.__new__(A,*args,**kwargs) # 调用父类的__new__()方法 def __init__(self): # 执行完__new__()之后会得到一个实例化对象,传给这里的__init__()中参数self print("我是__init__()方法中,实例化对象时执行完__new__()方法之后才会被执行") a=A()
运行结果:
通过重写__new__()实现一个单例模式
先来看一个例子,不重写__new__()方法时,当我们对一个类实例化出多个对象时,看一下多个对象的内存地址(不一样):
class A(): print("我是没有重写__new__()方法") def __init__(self): print("上面那个print()语句不管对类A实例化多少次,都只是会执行一次,因为它是在定义类的时候就会被执行,而__init__()方法中的这个print()是每次创建一个对象才会被执行") a1=A() a2=A() print(a1) # 直接打印对象 其实去调用__str__()方法,本类未实现就去调用父类的,效果就是返回该对象的内存地址 print(a2)
运行结果:
可以发现正常情况下(在没有对类重写__new__()方法之前)每当进行一次实例化就会创建一个对象,申请一个新的内存空间,所以打印多个实例化的对象得到的内存地址也是不一样的)
所谓单例模式就是限制一个类自始自终只能实例化一个对象,对这个类进行多次调用,操作的对象也都是地一次实例化创建的那个对象,操作的都是同一个对象;针对这种情况就可以在__new__()方法中动手脚:
思路就是,最开始实例化一个对象时,调用父类的__new__()方法,为对象分配一个内存空间,当接下来再实例化对象时,就直接返回第一次实例化的对象就行,不再调用父类的__new__()方法(再创建一个内存空间,返回一个新的实例化对象),而是用原来那个创建好的实例化对象:
class A(): __instance=None # 注意这个私有属性是在创建类的时候就被执行了,而不是每次实例化一个对象就重新执行一次; def __new__(cls,*args,**kwargs): print("我是__new__()方法,在实例化对象时,最先被执行,结果返回一个实例化对象给__init__()方法的参数self") if cls.__instance: # 如果之前已经有这个类的对象了,就不会再去调用父类的__new__()方法,重新开一个内存空间,创建一个新对象了 return cls.__instance else: cls.__instance=object.__new__(A,*args,*kwargs) # 第一次实例化对象时,cls.__instance还是None 所以会先去调用父类object的__new__()方法,返回一个实例化的对象 return cls.__instance # 把实例化的对象返回给__init__()方法的self def __init__(self): print("我在__init__()方法中,实例化一个对象时先去执行__new__()方法,返回一个实例化对象也就是这里的self") a1=A() a2=A() # 由于重写了__new__()方法,当第一次实例化一个对象a之后,cls.__instance 已经不是None了 而是对象a; # 所以当后续进行实例化时,也不再去调用父类object的__new__()方法,再创建一个新的实例化对象了,而是用第一次实例化的对象那个 print(a1) print(a2) # 其实调用__str__()方法,但是本类未实现,就去调用父类的,功能就是返回两个对象的内存地址,发现两者内存地址都一样
运行结果:
对单例模式的类多次实例化得到的对象自始至终都是操作的同一个对象~
class Student(): __instance=None # flag最开始创建类的时候执行的,也就是只有第一次实例化对象时cls.__instance才是None 此时会去调用父类的__new__()方法,创建一个对象 # 再去实例化对象时都是直接使用第一次实例化的对象,所以自从这个类被创建,自始自终都只创建了一个对象,都是只操作同一个对象 def __init__(self,name,age): self.name=name self.age=age def __new__(cls,*args,**kwargs): if cls.__instance==None: cls.__instance=object.__new__(Student) return cls.__instance else: return cls.__instance s1=Student('xuanxuan',22) s2=Student('璇璇',23) # 其实操作的都是同一个对象 print(s1.name,s1.age) print(s2.name,s2.age)
运行结果:
3. __eq__() ; 当obj==obj时 就会自动调用__eq__()方法
在本类中不去重写__eq__()就会调用父类的__eq__() 完成的功能就是比较这两个对象的内存地址,但是当我们在本类中重写__eq__()方法时就可以自己定制obj==obj2时依据什么样得原则比较了~
不去重写__eq__()方法:执行父类的方法,比较两个对象的内存地址:
class Student(): def __init__(self,name,age): self.name=name self.age=age s1=Student('xuanxuan',22) s2=Student('璇璇',23) print(s1) print(s2) # 可以查看一下两个实例化对象的内存地址 print(s1==s2) # 当执行obj1==obj2时会自动调用__eq__()方法,由于本类并没有实现,所以去调用父类的__eq__()方法,就是直接比较的时内存地址 # 这很明显两个实例化的对象内存地址不一样,是因为不是单例模式,并没有重写__new__()方法,每次实例化都会产生一个新的实例化对象
运行结果:
重写本类的__eq__()方法,可以按照自己定制的规则来比较两个对象:(比如我们比较Obj1==obj2时想让当两个对象的属性相同时 认为obj1==obj2成立)
class Student(): def __init__(self,name,age): self.name=name self.age=age def __eq__(self,other): # 当obj1==obj2时会自动调用__eq__()方法 if self.__dict__==other.__dict__: # 重写的__eq__()方法就是根据当两个属性的__dict__对象属性一样时,默认两个对象obj1==obj2是True return True s1=Student('xuanxuan',22) s2=Student('xuanxuan',22) # 两个对象的属性相同 print(s1==s2) # obj1==obj2会自动执行__eq__()方法,根据两个对象属性是否完全一样来比较
运行结果:
4. __hash__() 当调用hash()时 会自动执行__hash__()方法,,根据对象的地址来得到hash值
不可变数据类型是可哈希的,返回的就是对象的哈希值(其实类似地址)
当我们不去重写__hash__()方法时,执行hash(obj)会返回两个对象的hash值(其实就是两个对象的地址):
class A(): def __init__(self,name,age): self.name=name self.age=age a1=A('璇璇',22) a2=A('璇璇',22) print(a1) # 打印两个对象的地址 print(a2) print(hash(a1)) print(hash(a2)) # 可以看出两个对象的地址不同,hash值也不一样,因为hash()就是根据对象的地址来获取hash值的
运行结果:
当我们重写__hash__()方法时,就可以根据自己的需求去定制:
class A(): def __init__(self,name,age): self.name=name self.age=age def __hash__(self): print("这里重写了__hash__()方法") return hash(self.age+self.name) # 调用hash()方法 a1=A('xuanxuan','22') a2=A('xuanxuan','22') print(hash(a1)) print(hash(a2))
运行结果: