详解Python中的动态属性和特性
导语:本文章记录了本人在学习Python基础之元编程篇的重点知识及个人心得,打算入门Python的朋友们可以来一起学习并交流。
一、利用动态属性处理JSON数据源
属性:在Python中,数据的属性和处理数据的方法统称属性。
元编程:用元类进行编程,元类→类→对象,元类比类更抽象,生成类的类。
1、使用动态属性访问JSON类数据
第一版:利用json.load(fp)审查数据
fromurllib.requestimporturlopen importwarnings importos importjson URL='http://www.oreilly.com/pub/sc/osconfeed' JSON='data/osconfeed.json' defload(): ifnotos.path.exists(JSON): msg='downloading{}to{}'.format(URL,JSON) warnings.warn(msg)#如果需要下载就发出提醒。 withurlopen(URL)asremote,open(JSON,'wb')aslocal:#在with语句中使用两个上下文管理器分别用于读取和保存远程文件。 local.write(remote.read()) withopen(JSON)asfp: returnjson.load(fp)#json.load函数解析JSON文件,返回Python原生对象。
第二版:使用动态属性访问JSON类数据
第一版查阅深层数据的格式比较冗长,例如feed'Schedule'40,我们希望在读取属性上采用feed.Schedule.events[40].name这类方式来改进。并且第二版的类能递归,自动处理嵌套的映射和列表。
fromcollectionsimportabc classFronenJSON(): def__init__(self,mapping): self.__data=dict(mapping)#创建副本,同时确保处理的是字典。 def__getattr__(self,name):#仅当没有指定名称的属性才调用__getattr__方法。 ifhasattr(self,name): returngetattr(self.__data,name) else: returnFronenJSON.build(self.__data[name]) @classmethod def__build__(cls,obj): ifisinstance(obj,abc.Mapping):#判断obj是否是映射。 returncls(obj)#创建FrozenJSON对象。 elifisinstance(obj,abc.MutableSequence): return[cls.build(item)foriteminobj]#递归调用.build()方法,构建一个列表。 else:#既不是字典也不是列表,则返回元素本身。 returnobj
分析:FronenJSON类的关键是__getattr__方法。仅当无法使用常规的方式获取属性(即在实例、类或超类中找不到指定的属性),解释器才会调用特殊的__getattr__方法。
2、处理无效属性名
在Python中,由于关键字被保留,名称为关键字的属性是无效的。因此需要对第二版中的__init__进行改进:
def__init__(self,mapping): self.__data={} forkey,valueinmapping.items(): ifkeyword.iskeyword(key): key+='_'#与Python关键字重复的key在尾部加上下划线。 self.__data[key]=value
3、使用特殊方法__new__
第三版:使用__new__构造方法把一个类转换成一个灵活的对象工厂函数。
fromcollectionsimportabc classFronenJSON(): def__new__(cls,arg):#__new__是类方法,第一个参数是类本身cls。 ifisinstance(arg,abc.Mapping): returnsuper().__new__(cls)#委托给超类object基类的__new__方法处理。 elifisinstance(arg,abc.MutableSequence):#余下方法与原先的build方法一致。 return[cls(item)foriteminarg] else: returnarg def__init__(self,mapping): self.__data={} forkey,valueinmapping.items(): ifkeyword.iskeyword(key): key+='_' self.__data[key]=value def__getattr__(self,name): ifhasattr(self,name): returngetattr(self.__data,name) else: returnFronenJSON(self.__data[name])
二、特性
1、类属性、实例属性、私有属性与特性
类属性:类属性在__init__()外初始化,属于类所有,所有实例共享一个属性。
调用方法:类属性在内部用classname.类属性名调用,外部既可以用classname.类属性名又可以用instancename.类属性名来调用。
实例属性:实例属性属于各个实例所有,互不干扰。
私有属性:
- 单下划线_开头:只是告诉别人这是私有属性,外部依然可以访问更改。
- 双下划线__开头:外部不可通过instancename.propertyname来访问或者更改,实际将其转化为了_classname__propertyname。
特性:是用于管理实例属性的类属性。
特性用途:经常用于把公开的属性变成使用读值方法和设值方法管理的属性,且在不影响客户端代码的前提下实施业务规则。
注意:
- 不要对实例属性和类属性使用相同的名字。否则实例属性会遮盖类属性,发生难以发现的错误。
- 实例属性不会遮盖类特性,但类特性会遮盖实例属性。
这是因为obj.attr不会从实例obj开始寻找attr,而是从obj.__class__开始;而且仅当类中没有名为attr的特性时,Python才会在实例中寻找attr。
简言之,就遮盖层级而言,类特性>实例属性>类属性。
2、使用特性验证属性
使用特性可以验证实例属性的有效性,同时能够根据已知属性和属性之间的关系式调整其他属性,避免硬编码。
案例:假设某商店经营坚果、杂粮等多种有机食物,每位顾客的订单会包含店中的一系列商品,我们需要根据客户的订单计算出总价。
分析:我们不希望顾客订单的商品重量为非正数,需要借助@property装饰器实现值的获取与设置,从而验证实例属性的有效性。代码如下:
classLineItem(): def__init__(self,description,weight,price): self.description=description self.weight=weight self.price=price defsubtotal(self): returnself.weight*self.price @property#读值。 defweight(self): returnself.__weight#真正的值存储在私有属性中。 @weight.setter defweight(self,value): ifvalue>0: self.__weight=value#有效值存入私有属性中。 else: raiseValueError('Valuemustbe>0')#对于无效的值抛出ValueError。
Tips:当我们需要设置只读属性时,只使用@property,无需使用@func.setter。
原理解析:为了更好地理解@property装饰器的原理,我们写一版效果相同但没使用装饰器的代码。
classLineItem: def__init__(self,description,weight,price): self.description=description self.weight=weight self.price=price defsubtotal(self): returnself.weight*self.price defget_weight(self):#普通读值方法。 returnself.__weight defset_weight(self,value):#普通设值方法。 ifvalue>0: self.__weight=value else: raiseValueError('valuemustbe>0') weight=property(get_weight,set_weight)#构建property对象,赋值给公开的类特性。
property构造方法的完整签名:
property(fget=None,fset=None,fdel=None,doc=None)
3、特性工厂函数
抽象定义特性的方式有两种,一是使用特性工厂函数,二是使用描述符类。
下面我们用特性工厂函数来完成上文中提到的订单结算案例:
defquantity(storage_name): defqty_getter(instance):#instance指的是要把属性存储其中的LineItem实例。 returninstance.__dict__[storage_name]#引用闭包中的自由变量storage_name,值直接从instance.__dict__中获取,以便跳过特性,防止无限递归。 defqty_setter(instance,value): ifvalue>0: instance.__dict__[storage_name]=value#同理存储,跳过特性。 else: raiseValueError('valuemustbe>0') returnproperty(qty_getter,qty_setter)#构建自定义特性对象并返回。 classLineItem: weight=quantity('weight')#将自定义特性weight定义为类属性。 price=quantity('price')#同上。 def__init__(self,description,weight,price): self.description=description self.weight=weight#此处特性已经激活,可验证值的有效性。 self.price=price defsubtotal(self): returnself.weight*self.price#此处利用特性获取实例中存储的值。
4、使用特性删除属性
classBlackKnight: def__init__(self): self.members=['anarm','anotherarm', 'aleg','anotherleg'] self.phrases=["'Tisbutascratch.", "It'sjustafleshwound.", "I'minvincible!", "Allright,we'llcallitadraw."] @property defmember(self): print('nextmemberis:') returnself.members[0] @member.deleter defmember(self): text='BLACKKNIGHT(loses{})\n--{}' print(text.format(self.members.pop(0),self.phrases.pop(0)))
删除属性只需在主程序中发出指令:delobj.attr
三、处理属性的重要属性和函数
1、特殊属性
- __class__:对象所属类的引用(即obj.__class__和type(obj)的作用相同)。Python中的某些特殊方法比如__getattr__,只在对象的类中寻找,而不在实例中寻找。
- __dict__:一个映射,存储对象或类的可写属性。
- __slots__:类可以定义这个属性,限制实例有哪些属性。
2、内置函数
- dir([object]):列出对象的大多数属性。
- getattr(object,name[,default]):从object对象中获取name字符串对应的属性。获取的属性可能来自对象所属的类或超类。
- hasattr(object,name):若object对象中存在指定的属性,或者能以某种方式(如继承)通过object对象获取指定的属性,返回True。
- setattr(object,name,value):把object对象指定属性的值设为value,前提是object对象能接受那个值。这个函数可能会创建一个新属性,或者覆盖现有的属性。
- var([object]):返回object对象的__dict__属性。
3、特殊方法
- __delattr__(self,name):只要使用del语句删除属性,就会调用这个方法。
- __dir__(self):把对象传给dir函数时调用,列出属性。
- __getattr__(self,name):仅当获取指定的属性失败,搜索过obj,Class和超类之后调用。
- __getattribute__(self,name):尝试获取指定的属性时总会调用这个方法。不过寻找的属性是特殊属性或特殊方法时除外。为了防止无限递归,__getattribute__方法的实现要使用super().__getattribute__(obj,name)。
- __setattr__(self,name,value):尝试设置指定的属性时总会调用这个方法。点号和setattr内置函数会触发这个方法。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。