200字范文,内容丰富有趣,生活中的好帮手!
200字范文 > 面向对象常用魔法方法(内置方法)合集 超级无敌宇宙详细

面向对象常用魔法方法(内置方法)合集 超级无敌宇宙详细

时间:2022-06-07 05:31:32

相关推荐

面向对象常用魔法方法(内置方法)合集  超级无敌宇宙详细

引入

众所周知,方法是需要调用执行的,而魔法方法则不一样,他无需你的调用,在特定的时候会自己执行, 例如我们之前所学的__init__, 在示例对象 ([类名]+()) 的时候触发执行它

1.什么是内置方法

定义在类的内部, 以双下滑线开头__, 以双下滑线__结尾的方法特点: 在某种情况下自动触发执行

2.为什么使用内置方法

为了高度定制化我们使用的类或者实例

一.点拦截方法__getattr__,__setattr__,__delattr__

__setattr__: 在 [对象].[属性] = [值]设置属性值的时候就会触发它的执行__getattr__: 在 [对象].[属性]获取属性不存在的时候会触发它的执行__delattr__: 在 del [对象].[属性]删除属性的时候会触发它的执行

class Panda:def __init__(self, name):self.name = namedef __getattr__(self, item):print('通过点的方式找属性没有找到, 于是触发了我')def __setattr__(self, key, value):print('你通过点的方式修改属性值,触发了我')# 📛self.key=value# 📛如此设置值得时候(又通过点的方式设置), 于是又触发__setattr__的执行, 又开始设置,无限递归了self.__dict__[key]=value # 可以通过操作对象字典 key = value 方式设置def __delattr__(self, item):print('通过点的方式删除属性,触发了我')# 📛del self.item # 与上面一样, 无限递归了self.__dict__.pop(item) # 同样通过操作对象字典的方式去删属性# 🔰实例对象的初始化方法其实也进行了设置属性值的操作P = Panda("hangd") # 你通过点的方式修改属性值,触发了我# 🔰获取一个不存在的属性P.age# 通过点的方式找属性没有找到, 于是触发了我# 🔰设置属性值P.age = 99# 你通过点的方式修改属性值,触发了我print(P.__dict__) # {'name': 'hangd', 'age': 99}# 🔰删除属性del P.name# 通过点的方式删除属性,触发了我print(P.__dict__) # {'age': 99}

应用小示例 : 重写字典, 实现字典可以通过点.取值赋值, 也可以[]取值赋值

class MyDict(dict):def __setattr__(self, key, value):self[key] = valuedef __getattr__(self, item):return self[item]DD = MyDict()print(DD) # {}# 🔰点取值赋值DD.name = "shawn"print(DD.name) # shawnprint(DD) # {'name': 'shawn'}# 🔰[] 取值赋值DD["age"] = 20print(DD["age"]) # 20print(DD) # {'name': 'shawn', 'age': 20}

二. __getattribute__

1. 先看看 : __getattr__

点 . 属性没找到触发

class Bar:def __getattr__(self, item):print("没找到,触发了我")bb = Bar()bb.name # 没找到,触发了我

2.__getattribute__

点 . 属性无论找没找到都触发

class Bar:def __init__(self,name):self.name = namedef __getattribute__(self, item):print(f"无论找没找到,都触发了我-->{item}")bb = Bar("shawn")bb.name # 无论找没找到,都触发了我-->namebb.age # 无论找没找到,都触发了我-->age

3.两者同时存在

🍔两者同时存在class Bar:def __init__(self,name):self.name = namedef __getattr__(self, item):print("没找到,触发了我")def __getattribute__(self, item):print(f"无论找没找到,都触发了我-->{item}")bb = Bar("shawn")bb.name # 无论找没找到,都触发了我-->namebb.age # 无论找没找到,都触发了我-->age🍔设置异常class Bar:def __init__(self,name):self.name = namedef __getattr__(self, item):print("没找到,触发了我")def __getattribute__(self, item):print(f"无论找没找到,都触发了我-->{item}")raise AttributeError('让小弟接管') # 设置异常,直接交给__getattr__bb = Bar("shawn")bb.name'''无论找没找到,都触发了我-->name没找到,触发了我'''bb.age'''无论找没找到,都触发了我-->age没找到,触发了我'''

[对象] . [属性]的调用顺序 : 先执行 __getattribute__—>去类的名称空间找—>__getattr__(本质是去对象自己的名称空间找)[对象] . [属性]的查找顺序 : 对象自己**—>—>父类—>**父类

4.总结

__getattribute__方法优先级比__getattr__高没有重写__getattribute__的情况下, 默认使用的是父类的__getattribute__方法只有在使用默认__getattribute__方法中找不到对应的属性时,才会调用__getattr__如果是对不存在的属性做处理,尽量把逻辑写在__getattr__方法中如果非得重写__getattribute__方法,需要注意两点: 第一是避免.操作带来的死循环第二是不要遗忘父类的__getattribute__方法在子类中起的作用

三.item系列__getitem__,__setitem__,__delitem__

__getitem__: 通过中括号取值, 触发它的执行

__setitem__: 通过中括号赋值, 触发它的执行

__delitem__: 通过中括号删值, 触发它的执行

class Person:def __getitem__(self, item):print('取值操作,触发了执行')def __setitem__(self, key, value):print('赋值操作,触发了执行')def __delitem__(self, key):print('删值操作,触发了执行')p=Person()p['name'] = "派小星" # 赋值操作,触发了执行p['name'] # 取值操作,触发了执行del p["name"] # 删值操作,触发了执行

小示例 : 在不继承字典类的情况下, 自定义一个字典, 支持[ ]取值赋值

class Mydict:def __init__(self,**kwargs):# 🔰方案一# for key,value in kwargs.items():#setattr(self,key,value)# 🔰方案三# for key,value in kwargs.items():#self.__dict__[key]=value# 🔰方案二self.__dict__.update(kwargs)def __getitem__(self, item):# 🔰方案一# return self.__dict__[item]# 🔰方案二return getattr(self,item)def __setitem__(self, key, value):# 🔰方案一# setattr(self,key,value)# 🔰方案二self.__dict__[key]=valuedef __delitem__(self, key):del self.__dict__[key]dd = Mydict()print(dd.__dict__) # {}dd["name"] = "shawn"print(dd.__dict__) # shawnprint(dd["name"])# shawn

四. __format__

自定义格式化字符串

1.format( )函数

前面 字符串类型的内置方法 已经详细的介绍了format( )函数的玩法, 下面简单回顾一下

🍉直接传变量名res="my name is {name} my age is {age}".format(age=18,name="shawn")print(res) #my name is shawn my age is 18🍉不放任何值, 让其按位置自动传值res="my name is {} my age is {}".format(18,"shawn")print(res) #my name is 18 my age is shawn🍉通过索引传值res="my name is {0}{0}{0} my age is {1}{0}".format(18,"shawn")print(res) #my name is 181818 my age is shawn18

2.__format__方法

其实我们使用format( )函数的时候触发的就是__format__方法

class For:def __format__(self, format_spec):print(type(format_spec)) # 看看这个参数是什么类型print(f"使用format()函数,触发了我-->({format_spec})")return "aaa" # 必须返回一个字符串(不然报错),可以是任意的字符串(后面可以使用此特性来操作自定义)F = For()🔰当第二个参数不传入时默认为空format(F)'''输出<class 'str'>使用format()函数,触发了我-->()'''🔰传参类型 "str"format(F,"你好")'''输出<class 'str'>使用format()函数,触发了我-->(你好)

3.小示例 : 制作一个输出日期类, 实例的时候传入年,月,日, 可以自定义格式

class Date:__format_dic = {"1": "{obj.year}-{obj.mon}-{obj.day}","2": "{obj.year}:{obj.mon}:{obj.day}","3": "{obj.year}/{obj.mon}/{obj.day}"}def __init__(self,Year,Mon,Day):self.year = Yearself.mon = Monself.day = Daydef __format__(self, format_spec):if not format_spec or not format_spec in self.__format_dic: # 如果格式编号为空或者不在自定义字典里面f = self.__format_dic["1"] # 那我们就给一个默认的格式else:f = self.__format_dic[format_spec] # 否则就使用你传入的格式return f.format(obj=self) # 使用 "obj=self" 赋值的方法避免递归调用mm = Date(,12,28)print(format(mm,"1"))# -12-28print(format(mm,"2"))# :12:28print(format(mm,"3"))# /12/28print(format(mm,)) # -12-28 (空, 选择默认)print(format(mm,"3iop")) # -12-28 (不存在, 选择默认)

五.析构方法__del__

前面我们学了__init__这个构造方法, 在实例对象的时候自动触发, 也叫初始化, 而析构方式是在执行del对象的时候, 也就是在对象被清理之前自动触发__del__的执行, 那么我们就可以在这方法里面进行一些操作

class Test:def __del__(self):print("删除对象, 触发了我")T = Test()del T # 删除对象, 触发了我

1.__del__的使用场景

一般用来进行回收系统资源的操作如果一个对象仅仅占用应用程序(用户级)的资源时, 删除对象时, 垃圾回收机制会自动回收该对象所占用的应用程序资源, 即用户态内存

class A:def __del__(self):print("---触发del---")a = A()del aprint("---程序结尾---")'''输出---触发del------程序结尾---'''

当一个程序运行完毕时, 也会自动触发GC, 进而GC触发__del__

class A:def __del__(self):print("---触发del---")a = A()print("---程序结尾---")'''输出---程序结尾------触发del---'''

当对象涉及到申请了操作系统的资源, 比如 open打开了文件, 或者与网络连接等, GC无法派上用场, 那么就需要我们重写一下__del__的功能

🔰重写示例 : 对象中打开了一个文件class Open:def __init__(self):self.f = open("test26.py","rt",encoding="utf-8") # 打开了一个文件def __del__(self):self.f.close() # 触发执行的时候, 关闭文件(系统资源的回收)print("删除对象之前我关闭了文件,哈哈")O = Open()del O# 删除对象之前我关闭了文件,哈哈

六.注释文档__doc__

返回类的注释信息

class Test:'''这是用来测试__doc__的注释信息信息信息'''...T = Test()print(T.__doc__)'''输出这是用来测试__doc__的注释信息信息信息'''

该属性无法被继承

class Foo:'我是描述信息'passclass Bar(Foo):passF = Foo()print(F.__doc__) # 我是描述信息B = Bar()print(B.__doc__) # None

七. 描述符 __get__, __set__, __delete__

1.什么是描述符

描述符的本质就是一个新式类, 在这个新式类中至少实现了__get__(),__set__(),__delete__()中的一个就称为描述符, 也被称为描述符协议

__get__(self,inatance,owener): 当访问一个属性的时候触发__set__(self,instance,value): 为一个属性赋值时触发__delete__(self,instance): 使用 del 删除一个属性的时候触发

定义一个描述符

class MyDecriptor:def __get__(self, instance, owner):print('触发get')def __set__(self, instance, value):print('触发set')def __delete__(self, instance):print('触发delete')🔰以下方法是描述符的实例进行的操作, 并不会触发上面三种方法,不要搞混了f1=MyDecriptor()f1.name='shawn'print(f1.name)del f1.name

设置简单代理, 定义成另一个类的类属性

🌙描述符一class Str:def __get__(self, instance, owner):print('--->触发 Str_get')def __set__(self, instance, value):print('--->触发 Str_set')def __delete__(self, instance):print('--->触发 Str_Delete')🌙描述符二class Int:def __get__(self, instance, owner):print('--->触发 Int_get')def __set__(self, instance, value):print('--->触发 Int_set')def __delete__(self, instance):print('--->触发 Int_Delete')🌙普通类class Person:name = Str() # 将name给Str代理 (也可以说name是描述符对象)age = Int() # 将age给Int代理 (也可以说age是描述符对象)def __init__(self, name, age):self.name = name # 这里赋值就会触发描述符__set__方法self.age = age⭐实例对象P1 = Person("派大星",22)# --->触发 Str_set# --->触发 Int_set⭐查找属性print(P1.name)# --->触发 Str_get# None⭐查找属性print(P1.age)# --->触发 Int_get# None⭐设置属性P1.name = "海绵宝宝" # --->触发 Str_set⭐查找属性print(P1.name)# --->触发 Str_get# None

查看类与对象属性字典

print(P1.__dict__) # {} 为空print(Person.__dict__)'''从中取出了两个(name,age)'name': <__main__.Str object at 0x00000267479E7AC8>,'age': <__main__.Int object at 0x00000267479E7B08>'''

小结只要类属性被描述符类代理了, 以后使用 [对象] . [属性], 就会触发描述符类的__set__, __get__, __delete__方法并且被代理的属性只在类名称空间有, 对象名称空间就没有该属性了

2.描述符是做什么的

描述符的作用是用来代理另外一个类的属性的, 并且必须把描述符定义成这个类的类属性,不能定义到构造函数中

class Str:def __get__(self):passclass Duck:name = Str() # 正确 : name被Str代理def __init__(self):self.name = Str() # 错误 : 只能代理类属性,不能代理对象属性

代理属性小示例: 代理属性, 并限制传入数据的类型

🌙数据描述符class Str:def __init__(self, agencyName, agencyType):''':param agencyName: 被代理的属性名字:param agencyType: 被代理的属性类型'''self.name = agencyNameself.type = agencyType# 将属性存在描述符的 __dict__ 里面, 也可也直接放到"P1"对象属性字典中去, 看自己需求self.date_dict = self.__dict__def __get__(self, instance, owner):return self.date_dict[instance]def __set__(self, instance, value):# 在这里可以进行属性类型的判断if isinstance(value, self.type):self.date_dict[instance] = valueelse:print("类型不正确")def __delete__(self, instance):del self.date_dict[instance]🌙普通类class Panda:name = Str("name", str) # 将描述符实例当做属性保存age = Str("age", int)def __init__(self, name, age):self.name = nameself.age = ageP1 = Panda("派大星", 18)print(P1.__dict__) # {} 空的,因为全被代理了print(P1.name)# 派大星print(P1.age) # 18P1.name = "海绵宝宝"P1.age = 22print(P1.name)# 海绵宝宝print(P1.age) # 22print(P1.__dict__) # {}# 类型错误时P1.name = 2222# 类型不正确P1.age = "3333"# 类型不正确

print(Panda.__dict__["name"].__dict__)# {'name': 'name', 'type': <class 'str'>, 'date_dict': {...}, <__main__.Panda object at 0x000001DBE96F7A88>: '海绵宝宝'}

由上面示例我们可以知道, 一个类中被代理的属性将会保存在代理类(也就是描述符)的属性字典中, 而描述符对象又被当做属性保存在类的属性字典中

将进行过类型判断的属性放入到"P1"对象属性字典中, 方法一样, 只需要将"self"改成"instance"

class Str:def __init__(self,agencyName, agencyType):self.name = agencyNameself.type = vagencyTypedef __get__(self, instance, owner):return instance.__dict__.get(self.name)def __set__(self, instance, value):if not isinstance(value,self.type):print("类型不符合")returnelse:instance.__dict__[self.name] = valuereturndef __delete__(self, instance):instance.__dict__.pop(self.name)

3.描述符的种类与属性查找优先级

通常的只要定义了__get__和 另一个或两个方法的类, 我们就称之为数据描述符

如果一个类只定义了__get__方法 (没有修改数据的功能) , 我们称之为非数据描述符

属性查找优先级:

无论有没有都会先触发__getattribute__类属性数据描述符 (如果数据描述符中重写了__getattribute__可能会导致无法调用描述符)实例属性 (对象属性)非数据描述符找不到触发__getattr__()的执行

示例 : 类属性>数据描述符

🌙数据描述符class Str:def __get__(self, instance, owner):print('--->触发 Str_get')def __set__(self, instance, value):print('--->触发 Str_set')def __delete__(self, instance):print('--->触发 Str_Delete')🌙普通类class Person:name = Str()def __init__(self,name):self.name = namePerson.name = "派大星" # 类的属性p = Person("name") # --->触发 Str_setprint(p.name) # 派大星🔰由上面的实验可知, 最先找的是类属性

示例 : 数据描述符>实例属性

🌙数据描述符class Str:def __get__(self, instance, owner):print('--->触发 Str_get')def __set__(self, instance, value):print('--->触发 Str_set')def __delete__(self, instance):print('--->触发 Str_Delete')🌙普通类class Person:name = Str() # name是描述符对象def __init__(self,name,age):self.name = nameself.age = ageP1 = Person("海绵哥哥",99) # --->触发 Str_setP1.name = "海绵爸爸" # --->触发 Str_setP1.name # --->触发 Str_getdel P1.name# --->触发 Str_Deleteprint(P1.__dict__) # {'age': 99} (查看对象属性字典,没有name属性)print(Person.__dict__) # ['name'] (查看类属性字典,存在name,这个name就是描述符对象)

示例 : 示例属性>非数据描述符

🔰以下比较并没有实际意义, 只是便于对描述符的理解🌙非数据描述符class Str:def __get__(self, instance, owner):print('--->触发 Str_get')def __delete__(self, instance):print('--->触发 Str_Delete')🌙普通类class Person:name = Str() # name是描述符对象def __init__(self,name,age):self.name = nameself.age = ageP1 = Person("海绵妈妈",88) # 抛出异常 : "AttributeError" 属性中没有__set__方法

八. __call__

触发条件 : [对象] + ( ) 就触发__call__的执行

class Person:def __init__(self,name):self.name = namedef __call__(self, *args, **kwargs):print(args)print(kwargs)print(self.name + "触发了__call__")P1 = Person("派大星")P1() # ()# {}# 派大星触发了__call__P1(1,2,3,4,name="shawn")# (1, 2, 3, 4)# {'name': 'shawn'}# 派大星触发了__call__

一切皆对象, Person类也是一个对象, Persion+( ) 触发的是生成Persion类的类里的__call__

而Persion类生成的对象+( ) 触发的才是Persion类的__call__

九. __init__ 和 __new__

1.先看代码 :

class Person:def __init__(self,name):print("触发__init__")self.name = namedef __new__(cls, *args, **kwargs):print("触发__new__")# obj = object.__new__(cls) # 这个是直接使用 object 实例出来的对象obj = super(Person,cls).__new__(cls) # 这个是通过当前类的父类实例出来的对象return objp = Person("shawn")# 触发__new__# 触发__init__print(p.name) # shawn

在一个类中如果没有重写__new__()方法, 那么就会使用父类中的__new__()方法, 如果父类中没有, 则会一直追溯到object中的__new__()方法

如果在类中重写了__new__()方法, 我们可以通过return出一个实例, 该实例可以直接是object实例出来的对象, 也可以是当前类的父类实例出来的对象 (看上面代码)

return当前类的对象就会自动触发__init__()方法, 该实例是通过cls参数来控制是当前类的实例, 如果是别的类的实例或者其他任意东西则不会触发__init__()的执行

无法触发__init__示例 :

🐞当"return"的不是当前类的实例,是其它的一些任意数据class Persion:def __init__(self,name):print("触发__init__")self.nam = namedef __new__(cls, *args, **kwargs):print("触发__new__")obj = super(Person,cls).__new__(cls)return 1111p = Person("shawn") # 触发__new__ (没有触发__init__的执行)print(p.name) # 抛出异常 : "AttributeError" 没有这个属性🐞当"return"的不是当前类的实例,是其它类的实例(这里我们让Person继承Str,然后return出Str的实例)class Str:'''Str_obj'''class Person(Str):def __init__(self,name):print("触发__init__")self.name = namedef __new__(cls, *args, **kwargs):print("触发__new__")obj = super(Str,Str).__new__(Str)print(obj.__doc__) # Str_objreturn objp = Person("shawn") # 触发__new__ (同样只是触发__new__,并没有触发__init__方法)print(p.name) # 同样抛出异常 : "AttributeError" 没有这个属性

2.使用方法比较

__new__()用于创建实例,所以该方法是在实例创建之前被调用,它是类级别的方法,是个静态方法

__init__()用于初始化实例,所以该方法是在实例对象创建后被调用,它是实例级别的方法,用于设置对象属性的一些初始值

由此可知,__new__()__init__()之前被调用。如果__new__()创建的是当前类的实例,会自动调用__init__()函数

3.传入参数比较

__new__()至少有一个参数 cls, 代表当前类, 此参数在实例化的时候由 Python 解释器自动识别__init__()至少有一个参数 self, 就是__new__()返回的实例,__init__()__new__()的基础上完成一些初始化的操作

4.返回值的比较

__new__()必须要有返回值, 返回实例对象__init__()不需要返回值

十. __str__ 和 __repr__

1.__str__

触发条件 : print([对象]) 的时候触发__str__的执行

class Person:def __init__(self,name):self.name = namedef __str__(self):print("打印对象时触发了__str__")return f"对象名字 : {self.name}" # 🔰返回值必须是一个字符串, 否则报错P1 = Person("shawn")P2 = Person("乔治")print(P1)# 打印对象时触发了__str__# 对象名字 : shawnprint(P2)# 打印对象时触发了__str__# 对象名字 : 乔治print(P2.name) # 乔治

返回一个看起来字典格式的字符串示例

🔰示例一:class Person:def __init__(self,name,age):self.name = nameself.age = agedef __str__(self):return f"{self.__dict__}"P1 = Person("木之本樱",88)print(P1) # {'name': '木之本樱', 'age': 88}🔰示例二:import jsonclass Person:def __init__(self,name,age):self.name = nameself.age = agedef __str__(self):return json.dumps(self.__dict__)P1 = Person("shawn",22)print(P1) # {"name": "shawn", "age": 22}

2.__repr__

触发条件 : 在命令窗口中直接写 [对象] 时触发__repr__的执行

>>> class Person:...def __init__(self, name):... self.name = name...def __repr__(self):... print("命令窗口直接写对象触发了__repr__")... return f"名字 : {self.name}" # 🔰返回值也必须是一个字符串, 否则报错...>>> P1 = Person("佩奇")>>> P1命令窗口直接写对象触发了__repr__名字 : 佩奇>>> P1.name'佩奇'

十一. __module__ 和 __class__

1.__module__

可以查看当前操作的对象在哪个模块

🐞文件 "test.py" 内容class Foo:def __init__(self,name):self.name = name

🐞当前执行文件内容from test import Fooobj = Foo("shawn")print(obj.__module__) # test (test 来自模块)class Bar:def __init__(self,name):self.name = nameB = Bar("野猪")print(B.__module__) # __main__ (属于当前文件)

2.__class__

可以查看当前操作的对象所属的类

from test import Fooobj = Foo("shawn")print(obj.__class__) # <class 'test.Foo'>print(obj.__class__.__name__) # Fooclass Bar:def __init__(self,name):self.name = nameB = Bar("野猪")print(B.__class__) # <class '__main__.Bar'>print(B.__class__.__name__) # Bar

十二. __slots__ 和 __dict__

1.__slots__

默认我们可以给class实例绑定任何属性和方法,这就是动态语言的灵活性如果我们想要限制class的属性怎么办?比如,只允许对Persion实例添加nameage属性。为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class能添加的属性

slots是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)

class Str:# __slots__ = "name"# __slots__ = ["name","age"]__slots__ = ("name","age") # 对象只拥有 name,age 属性def __init__(self,name,age):self.name = nameself.age = age# self.sex = sexp1 = Str("shawn",22)print(p1.__slots__)# ('name', 'age')print(p1.name,p1.age) # shawn 22p1.friend = "水冰月" # 报错 : "AttributeError" (friend没有放到slots中)

一旦在类中使用了slots属性, 实例出来的对象将不会有自己的dict, 所有的对象,都共享使用类名称空间中的属性, 在此之前每生成一个对象, 都会申请一份内存空间, 对象越多, 占用的空间就越多, 使用了slots后, 对象越多,越节省内存

共享类的名称空间属性示例

class Str:__slots__ = ["name","age"]def __init__(self,name,age):self.name = nameself.age = agep1 = Str("大雄",22)p2 = Str("静香",90)print(p1.name,p1.age) # 大雄 22print(p2.name,p2.age) # 静香 90print(p1.__slots__) # ['name', 'age']print(p2.__slots__) # ['name', 'age']print(p1.__dict__)# 报错 : "AttributeError" 没有该属性print(p1.__dict__)# 报错 : "AttributeError" 没有该属性print(Str.__dict__)'''输出{'__module__': '__main__', '__slots__': ['name', 'age'], '__init__': <function Str.__init__ at 0x0000025344BEC828>, \'age': <member 'age' of 'Str' objects>, 'name': <member 'name' of 'Str' objects>, '__doc__': None}'''

当改变类的 name 和 age 时

Str.name = "小玉"Str.age = 77print(p1.name,p1.age) # 小玉 77print(p2.name,p2.age) # 小玉 77# 🔰发现所有对象的属性都发生了改变

总结 :第一 :slots属性是类属性, 可以用来限制实例对象能拥有的属性, 并且不再有自己的名称空间

第二 : 对象属性字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用slots取代实例的dict

2.__dict__

dict在前面的许多地方都使用到了dict这个属性, 它用来查看一个对象或类的名称空间属性,也可以说属性字典

class Person:def __init__(self,name,age):self.name = nameself.age = ageP1 = Person("夏尔凡多姆海恩",1250)print(P1.__dict__) # {'name': '夏尔凡多姆海恩', 'age': 1250}print(Person.__dict__)'''{'__module__': '__main__', '__init__': <function Person.__init__ at 0x0000021CDDDAC4C8>, '__dict__': <attribute '__dict__' of 'Person' objects>,\'__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}'''

十三. __all__

前面模块介绍章节其实已经介绍过了__all__的使用, 它用来在使用from [模块名] import *的时候, 针对这个*来限制能够使用的属性

🐞文件 "test.py" 内容__all__ = ["Foo","Bar"] # 如此只能使用 Foo,Bar 两个属性class Foo:def __init__(self):print("Foo")class Bar:def __init__(self):print("Bar")class Abc:def __init__(self):print("Abc")

🐞当前执行文件内容from test import * f = Foo() # Foob = Bar() # BarA = Abc() # 报错 : "NameError" 这个名字没有找到

十四. __iter__ 和 __next__

前面迭代器那一章节我们讲解了可迭代对象和迭代器对象的特点以及For循环的原理 :

可迭代对象 (iterable) : 在python中,但凡内置有__iter__方法的对象,都是可迭代的对象迭代器对象 (iterator) : 内置有__next____iter__方法的对象, 就是迭代器对象, 可迭代对象执行__iter__得到的返回值就是迭代器对象For循环原理请到上面链接查看 (重点)

接下来我们来给一个类重写__iter__以及__next__方法, 让其可以迭代, 也可以被for循环

class Foo:def __init__(self,start,end):self.__start = start-1self.__end=enddef __iter__(self):return selfdef __next__(self):self.__start+=1if self.__start>=self.__end:raise StopIteration('超出值') # 抛出一个异常,For循环会检测异常自动结束循环return self.__startfor i in Foo(1,10):print(i) # 1 2 3 4 5 6 7 8 9

在函数学习的那一章节, 我们通过函数实现了MyRange, 现在我们试试使用类来实现

class MyRange:def __init__(self,start,stop,step=1):self.__start = start-1self.__stop = stopself.__step = stepdef __iter__(self):return selfdef __next__(self):self.__start += self.__stepif self.__start >= self.__stop:raise StopIteration("超出值") # 抛出异常return self.__startfor i in MyRange(0,10,2):print(i) # 1 3 5 7 9

应用小示例 : 产生一个小于某值的斐波那契数列

class Fib:def __init__(self,count):self.__start = 0self.__stop = 1self.__count = countdef __iter__(self):return selfdef __next__(self):self.__start,self.__stop = self.__stop,self.__start+self.__stopif self.__start >= self.__count:raise StopIteration("超出") # 抛出异常return self.__startfor i in Fib(50):print(i) # 1 1 2 3 5 8 13 21 34

补充链式调用知识

链式调用是设计程序的模式, 在Java 和 js 中被广泛应用链式调用格式 : A.方法.方法.方法.方法优势和好处 : 代码量大大减少, 逻辑集中清晰明了, 且易于查看和修改

class Person:def __init__(self,name,age,sex):self.name= nameself.age = ageself.sex = sexdef print_name(self):print(self.name)return self # 返回对象本身def print_age(self):print(self.age)return self # 返回对象本身def print_sex(self):print(self.sex)return self # 返回对象本身P1 = Person("市丸银",45,"man")P1.print_name() # 市丸银P1.print_age() # 45P1.print_sex() # manP1.print_name().print_age().print_sex() # 市丸银 45 man

十五. __len__

计算对象的长度,return是多少,len([对象])的结果就是多少

触发条件 : 使用len([对象])时触发, 返回值必须为整数

🐞"return"多少就是多少class Person:def __len__(self):return 11111 # 只能返回整数, 不然报错 : "TypeError" 类型错误p = Person()print(len(p)) # 11111🐞class Person:def __init__(self,name,age):self.name = nameself.age = agedef __len__(self):return len(self.__dict__)p = Person("我爱罗",5)print(len(p)) # 2p.sex = "man"print(len(p)) # 3

十六. __hash__

触发条件 : 使用hash([对象])函数的时候触发, 里面返回什么值就是什么值, 返回值必须是整数

class Str:def __hash__(self):return 1231 # 只能返回整数, 不然报错 : "TypeError" 类型错p = Str()print(hash(p)) # 1231

可变类型 -----> 不可hash类型不可变类型 -----> 可hash类型

print(hash(132))# 132print(hash(1.1))# 230584300921369601print(hash((4,5))) # 3713084879518070856print(hash([11,23,4,4])) # 报错print(hash({"age":12})) # 报错

如果一个对象不可hash, 我们可以通过重写__hash__方法让它变得可hash

class Foo:def __init__(self):self.name= "黑子"self.l=[1,2,3,]def __hash__(self):return hash(self.name)f=Foo()print(hash(f)) # -3176404254760418957

十七. __eq__

触发条件 : 在两个对象进行==比较值的时候触发__eq__()的执行, 在该方法内部可以自定义比较规则

print(12 == [1,2,3]) # Falseprint("w" == (2,3,5,)) # Falsel1 = [1,2,3]l2 = [1,2,3]print(l1 == l2) # True (只比较值, 不比较ID)

自定义规则示例

class Str:def __init__(self,name):self.name = namedef __eq__(self, other): # self(本身对象),other(另一个对象)if self.name == other.name: # 只要名字相同,我就认为相同return Trueelse:return Falsep1 = Str("黑子")p2 = Str("黑子")p3 = Str("白子")print(p1 == p2) # Trueprint(p1 == p3) # False

十八. 上下文管理协议 __enter__ 和 __exit__

1.什么是上下文管理协议

上下文管理协议就是with语句, 为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__()__exit__()方法

with对象,触发对象的__enter__的执行在with同一级别写代码, 脱离了with,就会执行__exit__

class Open:def __enter__(self):print("----->enter执行了")return "enter的返回值"def __exit__(self, exc_type, exc_val, exc_tb):print("----->exit执行了")with Open() as f: # 触发 __enter__print(f)print("---->结束") # 触发 __exit__'''----->enter执行了enter的返回值----->exit执行了----->结束'''

2.exit 的三个参数

exc_type :异常类型exc_val :异常信息exc_tb :追溯信息

with语句中代码块出现异常,则with后的代码都无法执行

class Open:def __enter__(self):print("----->enter执行了")return selfdef __exit__(self, exc_type, exc_val, exc_tb):print("异常类型--->",exc_type)print("异常信息--->",exc_val)print("追溯信息--->",exc_tb)print("----->exit执行了")with Open() as f:print(f)raise AttributeError("这里设置一个异常,则下面代码不会再运行")print("---->结束") # 这一行无法运行'''----->enter执行了<__main__.Open object at 0x0000022E31429D08>异常类型---> <class 'AttributeError'>异常信息---> 这里设置一个异常,则下面代码不会再运行追溯信息---> <traceback object at 0x0000022E3142F0C8>----->exit执行了(抛出异常) : AttributeError: 这里设置一个异常,则下面代码不会再运行'''

3.为什么要使用上下文管理器

可以自动的操作(创建/获取/释放)资源,如文件操作、数据库连接无需在使用try...execept...去处理异常

如果__exit__()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行

class Open:def __enter__(self):print("----->enter执行了")return selfdef __exit__(self, exc_type, exc_val, exc_tb):print("异常类型--->",exc_type)print("异常信息--->",exc_val)print("追溯信息--->",exc_tb)print("----->exit执行了")return Truewith Open() as f:print(f)raise AttributeError("这里设置一个异常,但上面exit的return是True, 所以该异常被忽略")print("---->结束") # 这一行正常运行'''----->enter执行了<__main__.Open object at 0x000001D6CC399D08>异常类型---> <class 'AttributeError'>异常信息---> 这里设置一个异常,则下面代码不会再运行追溯信息---> <traceback object at 0x000001D6CC39F0C8>----->exit执行了---->结束Process finished with exit code 0'''

4.自定义一个open,可以进行文件操作

class Open:def __init__(self,path,mode="rt",encoding="utf-8"):# 拿到一个open对象,那么我们就可以借用open对象的方法,当然你也可以重写方法read(),write()等self.f = open(path,mode,encoding=encoding) def __enter__(self):return self.f # 返回open对象 (作用就是为了可以使用它的方法)def __exit__(self, exc_type, exc_val, exc_tb):if exc_type: # 如果不为空,说明with上下文内部出错误了print(exc_val) # 打印一下错误信息print("错误已被忽略") # 提示一下不会结束程序return True# return True 就是为了忽略错误self.f.close() # 当with内部没有错误, 正常结束时进行资源清理工作(关闭文件等)with Open("test.py","rt",encoding="utf-8") as f:res = f.read()print(res)

5.总结

使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预

在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在exit中定制自动释放资源的机制,你无须再去关注这个问题

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。