当前位置: 首页 > 后端技术 > Python

Python在什么情况下会生成pyc文件?

时间:2023-03-25 23:15:42 Python

会员名不允许重复这部分我的第一个想法是在dict中控制key。但是这种方式不好,__dict__的范围很大,它包含了类的所有属性和方法。不仅仅是枚举名称空间。我在源代码中发现枚举使用了另一种方法。通过preparemagic方法可以返回一个类似字典的实例,在这个实例中可以使用preparemagic方法自定义命名空间,并且限制这个空间中的成员名称不允许重复。#自己现实class_Dict(dict):def__setitem__(self,key,value):ifkeyinself:raiseTypeError('Attemptedtoreusekey:%r'%key)super().__setitem__(key,value)classMyMeta(type):@classmethoddef__prepare__(metacls,name,bases):d=_Dict()returndclassEnum(metaclass=MyMeta):passclassColor(Enum):red=1red=1#TypeError:尝试重用密钥:'red'再看Enum模块的具体实现:class_EnumDict(dict):def__init__(self):super().__init__()self._member_names=[]...def__setitem__(self,key,value):...elifkeyinself._member_names:#描述符覆盖枚举?raiseTypeError('Attemptedtoreusekey:%r'%key)...self._member_names.append(key)super().__setitem__(key,value)classEnumMeta(type):@classmethoddef__prepare__(metacls,cls),基数):enum_dict=_EnumDict()...returnenum_dictclassEnum(metaclass=EnumMeta):...模块中的_EnumDict创建了一个_member_names列表来存储成员名称,因为命名空间中的成员并不是所有的都是枚举成员,比如str__、__new等。魔术方法不是,所以这里的setitem需要做一些过滤:def__setitem__(self,key,value):if_is_sunder(key):#下划线的开头和结尾,比如_order__raiseValueError('_names_arereservedforfutureEnumuse')elif_is_dunder(key):#以双下划线结束,例如__new__ifkey=='__order__':key='_order_'elifkeyinself._member_names:#duplicatedefinedkeyraiseTypeError('Attemptedtoreusekey:%r'%key)elifnot_is_descriptor(value):#valueisnotadescriptorself._member_names.append(key)self._last_values.append(value)super().__setitem__(key,value)模块考虑会更全面.每个成员都有一个name属性和一个value属性在上面的代码中,Color.red的值为1。在eumu模块中,在定义的枚举类中,每个成员都有一个name和属性值;如果你细心,你会发现Color.red是Color的一个实例。这种情况是如何实现的?它仍然是用元类完成的,并在新的元类中实现。具体思路是先创建目标类,然后为每个成员创建相同的类,然后通过setattr的方式将后续类作为属性添加到目标类中,伪代码如下:def__new__(metacls,cls,bases,classdict):__new__=cls.__new__#创建一个枚举类enum_class=super().__new__()#每个成员都是cls的一个实例,通过setattr注入到目标类中,名字,值在cls.members中。items():member=super().__new__()member.name=namemember.value=valuesetattr(enum_class,name,member)returnenum_class看一个工作演示:class_Dict(dict):def__init__(self):super().__init__()self._member_names=[]def__setitem__(self,key,value):ifkeyinself:raiseTypeError('Attemptedtoreusekey:%r'%key)ifnotkey.startswith(“_”):self._member_names.append(key)super().__setitem__(key,value)classMyMeta(type):@classmethoddef__prepare__(metacls,name,bases):d=_Dict()returnddef__new__(元cls,cls,bases,classdict):__new__=bases[0].__new__ifbaseselseobject.__new__#创建一个枚举类enum_class=super().__new__(metacls,cls,bases,classdict)#在classdict._member_names中为member_name创建一个成员:value=classdict[member_name]enum_member=__new__(enum_class)enum_member.name=member_nameenum_member.value=valuesetattr(enum_class,member_name,enum_member)returnenum_classclassMyEnum(metaclass=MyMeta):passclass颜色(MyEnum):red=1blue=2def__str__(self):return"%s.%s"%(self.__class__.__name__,self.name)print(Color.red)#Color.redprint(Color.red.name)#redprint(Color.red.value)#1enum模块同样的思路是让每个成员都有name和value属性(代码我就不贴了)EnumMeta.__new__是这个模块的重点,几乎所有的枚举特性都是在这个函数中实现的。当成员值相同时,第二个成员是第一个成员的别名。从这一节开始,不再使用自己实现的类的描述,而是拆解enum模块的代码来说明其实现。从模块的使用特点可以知道,如果成员值相同,则后者将是前者的别名:fromenumimportEnumclassColor(Enum):red=1_red=1print(Color.redisColor._red)#True从这里可以得到知道red和_red是同一个对象。如何实现?元类会为枚举类创建一个member_map属性,用于存储成员名称与成员的映射关系。如果发现创建的成员的值已经在映射关系中,则将其替换为映射表中的对象:classEnumMeta(type):def__new__(metacls,cls,bases,classdict):...#创建我们的新枚举类型enum_class=super().__new__(metacls,cls,bases,classdict)enum_class._member_names_=[]#namesindefinitionorderenum_class._member_map_=OrderedDict()#name->valuemapformember_nameinclassdict._member_names:enum_member=__new__(enum_class)#如果已经定义了另一个具有相同值的成员,#新成员将成为现有成员的别名。forname,canonical_memberinenum_class._member_map_.items():ifcanonical_member._value_==enum_member._value_:enum_member=canonical_member#replacebreakelse:#别名不出现在成员名称中(仅在__members__).enum_class._member_names_.append(member_name)#添加新成员到_member_names_enum_class._member_map_[member_name]=enum_member...从代码来看,即使成员值相同,也会被创建first对象,但是后面创建的对象很快就会被垃圾回收(我觉得这里还有优化的空间)。通过与member_map映射表对比,用来创建member值的member替换了follow-up,但是两者的成员名称都在member_map中,比如例子中的red和_red都在字典中,但是他们指向同一个对象。属性member_names只会记录第一个,这会和枚举的迭代有关。成员可以通过成员值获取print(Color['red'])#Color.red通过成员名获取print(Color(1))#Color.red通过成员值获取枚举类中的成员都是单例模式。元类创建的枚举类也维护着值和成员的映射关系。value2member_map:classEnumMeta(type):def__new__(metacls,cls,bases,classdict):...#创建我们的新枚举类型enum_class=super().__new__(metacls,cls,bases,classdict)enum_class._value2member_map_={}formember_nameinclassdict._member_names:value=enum_members[member_name]enum_member=__new__(enum_class)enum_class._value2member_mapenum_member=...然后在Enum的new中返回单例:classEnum(metaclass=EnumMeta):def__new__(cls,value):iftype(value)iscls:returnvalue#Trytogetfrom_value2member_map_try:ifvalueincls._value2member_map_:returncls._value2member_map_[value]exceptTypeError:#getformemberfrom_member_map_映射incls._member_map_.values():ifmember._value_==value:returnmemberraiseValueError("%risnotavalid%s"%(value,cls.__name__))迭代方式遍历成员枚举类支持遍历成员以迭代的方式,按照定义的顺序,如果有成员有重复的值,则只获取第一个重复的成员。对于重复的成员值,只获取第一个成员,属性member_names只会记录第一个:_member_names_)这样总结enum模块的核心特性,几乎都是通过元类黑魔法实现的。对于成员,不能比较大小,但可以进行相等比较。相反,没有必要谈论它。其实这是它继承自object的方式,是一个不需要做的“特性”。总之enum模块比较独立,代码量也不多。想了解元类编程可以看看,课本式教学,还有单例模式等等,值得一读。以上就是本次分享的全部内容。想了解更多python知识,请前往公众号:Python编程学习圈,发“J”免费领取,每日干货分享