找回密码
 会员注册
查看: 31|回复: 0

Python魔法之旅-魔法方法(07)

[复制链接]

2万

主题

0

回帖

6万

积分

超级版主

积分
69864
发表于 2024-9-10 06:28:31 | 显示全部楼层 |阅读模式
目录一、概述1、定义2、作用二、应用场景1、构造和析构2、操作符重载3、字符串和表示4、容器管理5、可调用对象6、上下文管理7、属性访问和描述符8、迭代器和生成器9、数值类型10、复制和序列化11、自定义元类行为12、自定义类行为13、类型检查和转换14、自定义异常三、学习方法1、理解基础2、查阅文档3、编写示例4、实践应用5、阅读他人代码6、参加社区讨论7、持续学习8、练习与总结9、注意兼容性10、避免过度使用四、魔法方法23、__getattribute__方法23-1、语法23-2、参数23-3、功能23-4、返回值23-5、说明23-6、用法24、__getitem__方法24-1、语法24-2、参数24-3、功能24-4、返回值24-5、说明24-6、用法25、__getnewargs__方法25-1、语法25-2、参数25-3、功能25-4、返回值25-5、说明25-6、用法五、推荐阅读1、Python筑基之旅2、Python函数之旅3、Python算法之旅4、博客个人主页一、概述1、定义        魔法方法(MagicMethods/SpecialMethods,也称特殊方法或双下划线方法)是Python中一类具有特殊命名规则的方法,它们的名称通常以双下划线(`__`)开头和结尾。        魔法方法用于在特定情况下自动被Python解释器调用,而不需要显式地调用它们,它们提供了一种机制,让你可以定义自定义类时具有与内置类型相似的行为。2、作用        魔法方法允许开发者重载Python中的一些内置操作或函数的行为,从而为自定义的类添加特殊的功能。二、应用场景1、构造和析构1-1、__init__(self,[args...]):在创建对象时初始化属性。1-2、__new__(cls,[args...]):在创建对象时控制实例的创建过程(通常与元类一起使用)。1-3、__del__(self):在对象被销毁前执行清理操作,如关闭文件或释放资源。2、操作符重载2-1、__add__(self,other)、__sub__(self,other)、__mul__(self,other)等:自定义对象之间的算术运算。2-2、__eq__(self,other)、__ne__(self,other)、__lt__(self,other)等:定义对象之间的比较操作。3、字符串和表示3-1、__str__(self):定义对象的字符串表示,常用于print()函数。3-2、__repr__(self):定义对象的官方字符串表示,用于repr()函数和交互式解释器。4、容器管理4-1、__getitem__(self,key)、__setitem__(self,key,value)、__delitem__(self,key):用于实现类似列表或字典的索引访问、设置和删除操作。4-2、__len__(self):返回对象的长度或元素个数。5、可调用对象5-1、__call__(self,[args...]):允许对象像函数一样被调用。6、上下文管理6-1、__enter__(self)、__exit__(self,exc_type,exc_val,exc_tb):用于实现上下文管理器,如with语句中的对象。7、属性访问和描述符7-1、__getattr__,__setattr__,__delattr__:这些方法允许对象在访问或修改不存在的属性时执行自定义操作。7-2、描述符(Descriptors)是实现了__get__,__set__,和__delete__方法的对象,它们可以控制对另一个对象属性的访问。8、迭代器和生成器8-1、__iter__和__next__:这些方法允许对象支持迭代操作,如使用for循环遍历对象。8-2、__aiter__,__anext__:这些是异步迭代器的魔法方法,用于支持异步迭代。9、数值类型9-1、__int__(self)、__float__(self)、__complex__(self):定义对象到数值类型的转换。9-2、__index__(self):定义对象用于切片时的整数转换。10、复制和序列化10-1、__copy__和__deepcopy__:允许对象支持浅复制和深复制操作。10-2、__getstate__和__setstate__:用于自定义对象的序列化和反序列化过程。11、自定义元类行为11-1、__metaclass__(Python2)或元类本身(Python3):允许自定义类的创建过程,如动态创建类、修改类的定义等。12、自定义类行为12-1、__init__和__new__:用于初始化对象或控制对象的创建过程。12-2、__init_subclass__:在子类被创建时调用,允许在子类中执行一些额外的操作。13、类型检查和转换13-1、__instancecheck__和__subclasscheck__:用于自定义isinstance()和issubclass()函数的行为。14、自定义异常14-1、你可以通过继承内置的Exception类来创建自定义的异常类,并定义其特定的行为。三、学习方法        要学好Python的魔法方法,你可以遵循以下方法及步骤:1、理解基础        首先确保你对Python的基本语法、数据类型、类和对象等概念有深入的理解,这些是理解魔法方法的基础。2、查阅文档        仔细阅读Python官方文档中关于魔法方法的部分,文档会详细解释每个魔法方法的作用、参数和返回值。你可以通过访问Python的官方网站或使用help()函数在Python解释器中查看文档。3、编写示例        为每个魔法方法编写简单的示例代码,以便更好地理解其用法和效果,通过实际编写和运行代码,你可以更直观地感受到魔法方法如何改变对象的行为。4、实践应用        在实际项目中尝试使用魔法方法。如,你可以创建一个自定义的集合类,使用__getitem__、__setitem__和__delitem__方法来实现索引操作。只有通过实践应用,你才能更深入地理解魔法方法的用途和重要性。5、阅读他人代码        阅读开源项目或他人编写的代码,特别是那些使用了魔法方法的代码,这可以帮助你学习如何在实际项目中使用魔法方法。通过分析他人代码中的魔法方法使用方式,你可以学习到一些新的技巧和最佳实践。6、参加社区讨论        参与Python社区的讨论,与其他开发者交流关于魔法方法的使用经验和技巧,在社区中提问或回答关于魔法方法的问题,这可以帮助你更深入地理解魔法方法并发现新的应用场景。7、持续学习        ython语言和其生态系统不断发展,新的魔法方法和功能可能会不断被引入,保持对Python社区的关注,及时学习新的魔法方法和最佳实践。8、练习与总结        多做练习,通过编写各种使用魔法方法的代码来巩固你的理解,定期总结你学到的知识和经验,形成自己的知识体系。9、注意兼容性        在使用魔法方法时,要注意不同Python版本之间的兼容性差异,确保你的代码在不同版本的Python中都能正常工作。10、避免过度使用        虽然魔法方法非常强大,但过度使用可能会导致代码难以理解和维护,在编写代码时,要权衡使用魔法方法的利弊,避免滥用。        总之,学好Python的魔法方法需要不断地学习、实践和总结,只有通过不断地练习和积累经验,你才能更好地掌握这些强大的工具,并在实际项目中灵活运用它们。四、魔法方法23、__getattribute__方法23-1、语法__getattribute__(self,name,/)Returngetattr(self,name)23-2、参数23-2-1、self(必须):一个对实例对象本身的引用,在类的所有方法中都会自动传递。 23-2-2、name(必须):一个字符串,表示你尝试访问的属性的名称。23-2-3、/(可选):这是从Python3.8开始引入的参数注解语法,它表示这个方法不接受任何位置参数(positional-onlyparameters)之后的关键字参数(keywordarguments)。23-3、功能    用于拦截对对象属性的访问。23-4、返回值        返回值是被访问属性的值,这可以是任何类型的值,包括整数、浮点数、字符串、列表、字典等或者甚至是另一个对象。23-5、说明    如果 __getattribute__ 方法没有返回任何值(即没有return语句),那么它实际上会返回None,但这通常是不希望的,因为它可能会掩盖其他潜在的问题。        由于__getattribute__方法会拦截所有属性访问,包括对象自身的属性和继承自基类的属性,因此在使用时需要特别小心,以避免无限递归或其他意外行为。23-6、用法#023、__getattribute__方法:#1、基本访问控制classAccessControl:def__init__(self,data):self._data=datadef__getattribute__(self,name):ifname=='_data':raiseAttributeError("Directaccessto_dataisnotallowed")returnsuper().__getattribute__(name)if__name__=='__main__':ac=AccessControl({'secret':'value'})#ac._data#这会引发AttributeErrorirectaccessto_dataisnotallowed#2、属性惰性加载classLazyLoad:def__init__(self):self._loaded=Falsedefload_data(self):print("Loadingdata...")self._data="Loadeddata"self._loaded=Truedef__getattribute__(self,name):ifname=='_data'andnotself._loaded:self.load_data()returnsuper().__getattribute__(name)if__name__=='__main__':ll=LazyLoad()print(ll._data)#第一次会加载数据并输出print(ll._data)#第二次不会再次加载#3、属性访问记录classAccessLogger:def__init__(self):self._access_log=[]self._methods={}#用于存储占位符方法的字典def__getattr__(self,name):ifnamenotinself._methods:#创建一个新的占位符方法,并存储到字典中defplaceholder(*args,**kwargs):self._access_log.append(name)raiseAttributeError(f"AccessLoggerhasnoattributeormethod'{name}'")self._methods[name]=placeholderreturnself._methods[name]deflog(self):returnself._access_logif__name__=='__main__':al=AccessLogger()try:al.method1()#这会触发占位符方法并记录'method1'al.method2()#这会触发占位符方法并记录'method2'exceptAttributeErrorase:print(e)#输出:AccessLoggerhasnoattributeormethod'method1'print(e)#输出:AccessLoggerhasnoattributeormethod'method2'print(al.log())#输出['method1']#4、只读属性classReadOnly:def__init__(self,value):self._value=valuedef__getattribute__(self,name):ifname=='_value':returnsuper().__getattribute__(name)ifname.startswith('read_'):returnsuper().__getattribute__(name)ifname=='value':raiseAttributeError("valueisread-only")defread_value(self):returnself._valueif__name__=='__main__':ro=ReadOnly(10)print(ro.read_value())#输出10#ro.value=20#这会引发AttributeError#5、动态属性classDynamicProps:def__getattribute__(self,name):ifname=='dynamic_prop':returnf"Thisisa{name}withvaluegeneratedonthefly."returnsuper().__getattribute__(name)if__name__=='__main__':dp=DynamicProps()print(dp.dynamic_prop)#输出"Thisisadynamic_propwithvaluegeneratedonthefly."#6、属性验证classValidated:def__setattr__(self,name,value):ifname=='value'andnotisinstance(value,int):raiseValueError("valuemustbeaninteger")super().__setattr__(name,value)def__getattribute__(self,name):attr=super().__getattribute__(name)ifname=='value'andnotisinstance(attr,int):raiseAttributeError("valuehasbeencorrupted")returnattrif__name__=='__main__':v=Validated()v.value=10#v.value="ten"#这会引发ValueError:valuemustbeaninteger#7、属性别名classAlias:def__init__(self,data):self._data=datadef__getattribute__(self,name):ifname=='alias_data':returnsuper().__getattribute__('_data')returnsuper().__getattribute__(name)if__name__=='__main__':a=Alias('somedata')print(a.alias_data)#输出'somedata'#8、条件性访问classConditionalAccess:def__init__(self,data,condition):self._data=dataself._access_condition=conditiondef__getattribute__(self,name):#调用内置的__getattribute__方法来避免无限递归#但我们先检查是否是我们想要控制的属性ifname=='_data'andnotobject.__getattribute__(self,'_access_condition'):raiseAttributeError("Accessto_dataisnotallowedundercurrentcondition")#对于其他属性,正常返回returnobject.__getattribute__(self,name)@propertydefaccess_condition(self):returnobject.__getattribute__(self,'_access_condition')@access_condition.setterdefaccess_condition(self,value)bject.__setattr__(self,'_access_condition',value)if__name__=='__main__':ca=ConditionalAccess('sensitivedata',False)#尝试访问_data会引发AttributeErrortry:print(ca._data)exceptAttributeErrorase:print(e)#允许访问_dataca.access_condition=Trueprint(ca._data)#现在可以访问_data,因为access_condition为True'运行运行24、__getitem__方法24-1、语法__getitem__(self,key,/)returnself.__getitem__(key)self[key]24-2、参数24-2-1、self(必须):一个对实例对象本身的引用,在类的所有方法中都会自动传递。 24-2-2、key(必须):一个用于索引或切片对象的值。24-2-3、/(可选):这是从Python3.8开始引入的参数注解语法,它表示这个方法不接受任何位置参数(positional-onlyparameters)之后的关键字参数(keywordarguments)。24-3、功能        用于实现对象的索引和切片功能。24-4、返回值        返回被索引或切片访问的元素的值。24-5、说明        如果key是整数,则执行索引访问;如果key是slice对象,则执行切片访问。对于无效的索引(例如,超出范围的整数索引或不支持的索引类型),__getitem__方法应该抛出相应的异常。24-6、用法#024、__getitem__方法:#1、简单的列表包装类classMyList:def__init__(self,data):self.data=datadef__getitem__(self,index):returnself.data[index]if__name__=='__main__':my_list=MyList([1,2,3,4])print(my_list[1])#输出2#2、字典的键访问classMyDict:def__init__(self,data):self.data=datadef__getitem__(self,key):returnself.data[key]if__name__=='__main__':my_dict=MyDict({'a':1,'b':2})print(my_dict['a'])#输出1#3、字符串索引(仅支持正索引)classMyString:def__init__(self,string):self.string=stringdef__getitem__(self,index):ifindexself.end_date:raiseIndexError("Indexoutofrange")returnself.current_date.date()if__name__=='__main__':start=datetime(2024,3,13)end=datetime(2024,5,31)date_range=DateRange(start,end)print(date_range[2].strftime('%Y-%m-%d'))#输出'2024-03-15'#注意:这个示例中的__getitem__改变了内部状态,通常不建议这样做,除非有明确的需求。#8、自定义字典,通过属性名访问值classAttributeDict:def__init__(self,*args,**kwargs):self.__dict__.update(*args,**kwargs)def__getitem__(self,key):returngetattr(self,key)if__name__=='__main__':attr_dict=AttributeDict(a=1,b=2)print(attr_dict['a'])#输出1#9、自定义树形结构,通过路径访问节点classTreeNode:def__init__(self,value,children=None):self.value=valueself.children=childrenifchildrenisnotNoneelse{}def__getitem__(self,key):ifkeyinself.children:returnself.children[key]raiseKeyError(f"Nochildnodewithkey:{key}")if__name__=='__main__':#示例树形结构root=TreeNode("root",{"child1":TreeNode("child1"),"child2":TreeNode("child2",{"grandchild":TreeNode("grandchild")})})#访问节点print(root["child2"]["grandchild"].value)#输出'grandchild'#10、自定义文件读取,按块(chunk)索引classChunkedFileReader:def__init__(self,filename,chunk_size):self.filename=filenameself.chunk_size=chunk_sizeself.file_handle=open(filename,'rb')def__getitem__(self,index):self.file_handle.seek(index*self.chunk_size)data=self.file_handle.read(self.chunk_size)ifnotdata:raiseIndexError("Indexoutofrange")returndatadef__del__(self):self.file_handle.close()if__name__=='__main__':chunked_reader=ChunkedFileReader('example.bin',1024)#每个块1024字节print(chunked_reader[0].hex())#输出第一个块的内容的十六进制表示#11、自定义颜色查找表(通过颜色名访问RGB值)classColorLookup:def__init__(self,colors):self.colors=colorsdef__getitem__(self,key):returnself.colors.get(key,"Unknowncolor")if__name__=='__main__':colors=ColorLookup({"red"255,0,0),"green"0,255,0),"blue"0,0,255)})print(colors['red'])#输出(255,0,0)print(colors['purple'])#输出'Unknowncolor'#12、自定义二维数组(类似NumPy数组,但简化版)classSimple2DArray:def__init__(self,data):self.data=datadef__getitem__(self,index):ifisinstance(index,int):return[row[index]forrowinself.data]elifisinstance(index,tuple)andlen(index)==2:row,col=indexreturnself.data[row][col]else:raiseIndexError("Invalidindex")if__name__=='__main__':array_2d=Simple2DArray([[3,5,6],[8,10,11],[7,8,12]])print(array_2d[1])#输出[5,10,8]print(array_2d[1,2])#输出1125、__getnewargs__方法25-1、语法__getnewargs__(self,/)25-2、参数25-2-1、self(必须):一个对实例对象本身的引用,在类的所有方法中都会自动传递。 25-2-2、/(可选):这是从Python3.8开始引入的参数注解语法,它表示这个方法不接受任何位置参数(positional-onlyparameters)之后的关键字参数(keywordarguments)。25-3、功能        用于支持pickle模块的自定义序列化。25-4、返回值        返回一个元组,该元组中的元素将作为参数传递给对象的__new__方法来重新创建对象的一个新实例。25-5、说明        如果对象不需要额外的参数来重新创建(即,它可以通过默认构造函数重新创建),那么__getnewargs__可以简单地返回一个空元组。25-6、用法#025、__getnewargs__方法:#1、自定义整数范围importpickleclassIntRange:def__init__(self,start,end):self.start=startself.end=enddef__getnewargs__(self):return(self.start,self.end)if__name__=='__main__':range_obj=IntRange(1,10)pickled=pickle.dumps(range_obj)unpickled=pickle.loads(pickled)print(unpickled.start,unpickled.end)#输出110#2、自定义颜色类importpickleclassColor:def__init__(self,r,g,b):self.r=rself.g=gself.b=bdef__getnewargs__(self):return(self.r,self.g,self.b)if__name__=='__main__':color_obj=Color(255,0,0)pickled=pickle.dumps(color_obj)unpickled=pickle.loads(pickled)print(unpickled.r,unpickled.g,unpickled.b)#输出25500#3、自定义坐标点importpickleclassPoint:def__init__(self,x,y):self.x=xself.y=ydef__getnewargs__(self):return(self.x,self.y)if__name__=='__main__':point_obj=Point(10,20)pickled=pickle.dumps(point_obj)unpickled=pickle.loads(pickled)print(unpickled.x,unpickled.y)#输出1020#4、自定义复数类importpickleclassComplexNumber:def__init__(self,real,imag):self.real=realself.imag=imagdef__getnewargs__(self):return(self.real,self.imag)if__name__=='__main__':complex_obj=ComplexNumber(3,4)pickled=pickle.dumps(complex_obj)unpickled=pickle.loads(pickled)print(unpickled.real,unpickled.imag)#输出34#5、自定义日期类importpicklefromdatetimeimportdateclassCustomDate:def__init__(self,year,month,day):self.date=date(year,month,day)def__getnewargs__(self):return(self.date.year,self.date.month,self.date.day)if__name__=='__main__':date_obj=CustomDate(2024,3,13)pickled=pickle.dumps(date_obj)unpickled=pickle.loads(pickled)print(unpickled.date)#输出类似'2024-03-13'的日期#6、自定义分数类importpicklefromfractionsimportFractionclassCustomFraction:def__init__(self,numerator,denominator):self.fraction=Fraction(numerator,denominator)def__getnewargs__(self):return(self.fraction.numerator,self.fraction.denominator)if__name__=='__main__':fraction_obj=CustomFraction(1,3)pickled=pickle.dumps(fraction_obj)unpickled=pickle.loads(pickled)print(unpickled.fraction)#输出1/3#7、自定义带版本的类importpickleclassVersionedClass:def__init__(self,data,version):self.data=dataself.version=versiondef__getnewargs__(self):return(self.data,self.version)def__getstate__(self):#如果需要,可以覆盖此方法以保存额外的状态returnself.__dict__def__setstate__(self,state):#如果需要,可以覆盖此方法以在反序列化时设置状态self.__dict__.update(state)if__name__=='__main__':versioned_obj=VersionedClass("exampledata",1)pickled=pickle.dumps(versioned_obj)unpickled=pickle.loads(pickled)print(unpickled.data,unpickled.version)#输出exampledata1#8、自定义具有动态属性的类importpickleclassDynamicProperties:def__init__(self,**kwargs):self.__dict__.update(kwargs)def__getnewargs__(self):#因为属性是动态的,我们可能需要将它们序列化为一个字典return(self.__dict__,)def__getstate__(self):#返回一个表示对象状态的字典returnself.__dict__def__setstate__(self,state):#设置对象状态self.__dict__.update(state)if__name__=='__main__':dynamic_obj=DynamicProperties(name="Myelsa",age=18,city="Guangzhou")pickled=pickle.dumps(dynamic_obj)unpickled=pickle.loads(pickled)print(unpickled.name,unpickled.age,unpickled.city)#输出Myelsa18Guangzhou#9、自定义带时间戳的日志条目importpicklefromdatetimeimportdatetimeclassLogEntry:def__init__(self,message,timestamp=None):self.message=messageself.timestamp=timestampordatetime.now()def__getnewargs__(self):#假设我们想要重新创建日志条目时保留原始的时间戳return(self.message,self.timestamp)if__name__=='__main__':log_entry=LogEntry("Systemstarted")pickled=pickle.dumps(log_entry)unpickled=pickle.loads(pickled)print(unpickled.message,unpickled.timestamp)#输出类似"Systemstarted2024-05-3123:29:16.357606"#10.自定义用户账户类(带密码哈希)importpicklefromhashlibimportsha256classUserAccount:def__init__(self,username,password):self.username=usernameself.password_hash=sha256(password.encode()).hexdigest()def__getnewargs__(self):#注意:出于安全考虑,我们不会直接序列化密码哈希用于反序列化#这里仅作为示例,通常不会这样做return(self.username,self.password_hash)#注意:在真实应用中,密码不应以明文形式存储或传输if__name__=='__main__':user=UserAccount("Myelsa","mypassword")#通常,我们不会序列化/反序列化此类对象,因为这涉及安全问题#但为了示例,我们仍然这样做pickled=pickle.dumps(user)unpickled=pickle.loads(pickled)print(unpickled.username,unpickled.password_hash)#输出类似"Myelsa"和密码哈希值89e01536ac207279409d4de1e5253e01f4a1769e696db0d6062ca9b8f56767c8#11、自定义文件路径和打开模式importpickleclassFilePath:def__init__(self,path,mode):self.path=pathself.mode=modedef__getnewargs__(self):return(self.path,self.mode)defopen(self):returnopen(self.path,self.mode)if__name__=='__main__':file_path=FilePath("test.txt","r")pickled=pickle.dumps(file_path)unpickled=pickle.loads(pickled)withunpickled.open()asf:print(f.read())#假设文件存在且可读#12、自定义带有自定义属性的矩形importpickleclassRectangle:def__init__(self,width,height,color="red"):self.width=widthself.height=heightself.color=colordef__getnewargs__(self):return(self.width,self.height,self.color)if__name__=='__main__':rectangle=Rectangle(10,5,"blue")pickled=pickle.dumps(rectangle)unpickled=pickle.loads(pickled)print(unpickled.width,unpickled.height,unpickled.color)#输出105blue五、推荐阅读1、Python筑基之旅2、Python函数之旅3、Python算法之旅4、博客个人主页
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2025-1-7 07:10 , Processed in 1.962985 second(s), 26 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表