Python笔记(4)类__属性与描述符
部分参考自:http://www.geekfan.net/7862/
新式类与经典类
2和3不一样,3都是新式类。
新式类和经典类的区别:
class A: #classic class """this is class A""" pass __slots__=('x','y') def test(self): # classic class test """this is A.test()""" print "A class" class B(object): #new class """this is class B""" __slots__=('x','y') pass def test(self): # new class test """this is B.test()""" print "B class" if __name__ == '__main__': a=A() b=B() print dir(a) print dir(b)
['__doc__', '__module__', '__slots__', 'test'] ['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'test', 'x', 'y']
新式类要指明父类,上面代码class B 声明他的父类为object。
python是动态语言,可以动态的添加属性。
>>> a.x = 1 >>> a <__main__.A instance at 0x05BBB620> >>> a.x 1
__slots__槽,属性限制了实例b只能添加x,y属性,a是经典类,可以继续添加,但是b是新式类不能继续添加。
>>> a.z = 2 >>> a.z 2 >>> b.z = 2 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'B' object has no attribute 'z'
>>> help(a) Help on instance of A in module __main__: class A | this is class A | | Methods defined here: | | test(self) | this is A.test() >>> help(b) Help on B in module __main__ object: class B(__builtin__.object) | this is class B | | Methods defined here: | | test(self) | this is B.test() | | ---------------------------------------------------------------------- | Data descriptors defined here: | | x | | y
B类由于是新式类 __slots__起作用了,尽量使用新式类,因为这样python2,3中都能跑。
属性和封装
实例类型
__init__ 双下划线是特殊方法,__init__定义实例属性,owner,country是实例属性,country是类属性。
调用的时候,比如类属性和实例属性名字一样,调用实例属性。如果没有实例属性,则去寻找是不是存在类属性。
class Car(object): country = u'中国' def __init__(self,owner=None): self.owner = owner self.country = "china" if __name__ == '__main__': a = Car(u'小张') print a.country a.country = u'美国' print a.country print "--------------------------" del a.country print a.country
>>> china 美国 -------------------------- 中国
私有属性
私有属性只在函数内部可见。通过get,set方法对其赋值更改。
在变量前加两个下划线__ 可以间接访问,只加一个下划线_模块私有化。变量前后各两个下划线__是系统自带的属性。
class Car(object): def __init__(self,owner=None): self.__owner = owner def getOwner(self): return self.__owner def setOwner(self, value): self.__owner = value if __name__ == '__main__': a = Car(u'黑板客') print a.getOwner()
>>> a.owner Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Car' object has no attribute 'owner' >>> a.getOwner() u'\u9ed1\u677f\u5ba2' >>> dir(a) ['_Car__owner', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'getOwner', 'setOwner'] >>> a._Car__owner u'\u9ed1\u677f\u5ba2'
描述符
装饰器描述符
@property @xx.setter @xx.deleter
用@property装饰器指定一个getter方法,用@owner.setter装饰器指定了一个setter方法。当我们这么做的时候,访问owner属性,python就会自动调用相应的getter/setter方法。这样当我们要判断一个值的时候,如果放到__init__里,他只能在出初始化的时候判断,而放到setter里,每次set的时候都会判断。
可以把get,set方法变成属性访问。
class Car(object): def __init__(self,owner=None): self._owner = owner @property def owner(self): return self._owner @owner.setter def owner(self, value): self._owner = value @owner.deleter def owner(self): self._owner = None if __name__ == '__main__': a = Car(u'你大爷') print a.owner del a.owner print a.owner
你大爷 None
这样一个owner get,set,del要定义三个,如果有别的属性,则又需要三个,这样会产生冗余,重复代码。
__getattr__, __setattr__, __delattr__
__getattr__ 在变量的__dict__和_class__.__dict__中没有找到属性,就会调用__getattr__,如果有的话,就直接调用__dict__中的值了。
__setattr__ 变量赋值
__delattr__ 删除变量
class Car(object): country = u'中国' #__slots__=('length','width','height','owner','__dict__') def __init__(self, length, width, height, owner=None): self.owner = owner self.length = length self.width = width self.height = height def __getattr__(self,name): print "__getattr__",name return self.__dict__.get(name,None) def __setattr__(self,name,value): print "__setattr__",name if name!='owner': assert value>0, name+" must larger than 0" self.__dict__[name]=value def __delattr__(self,name): print "__delattr__",name if name=='owner': self.__dict__[name]=None if __name__ == '__main__': a = Car(1.2,1.4,1.5,u'二大爷')
输出:
__setattr__ owner __setattr__ length __setattr__ width __setattr__ height
把__slots__加上之后,因为可以访问__setattr__所以还是可以任意的加属性而不会报错,要使得slots有效果,得在__setattar__里面修改代码:
def __getattr__(self,name): print "__getattr__",name assert name in self.__slots__, "Not have this attribute "+name return self.__dict__.get(name,None) def __setattr__(self,name,value): print "__setattr__",name assert name in self.__slots__, "Not have this attribute "+name if name!='owner': assert value>0, name+" must larger than 0" self.__dict__[name]=value def __delattr__(self,name): print "__delattr__",name assert name in self.__slots__, "Not have this attribute "+name if name=='owner':
类描述符
描述符可以用作类的属性,数据描述符__get__,__set__,__del__。
class PositiveNum(object): def __init__(self): self.default = 1 self.data = {} def __get__(self, instance, owner): # instance = x # owner = type(x) print "__get__",instance,owner return self.data.get(instance, self.default) def __set__(self, instance, value): # instance = x print "__set__",instance,value try: assert int(value)>0 self.data[instance] = value except AssertionError: print "ERROR: "+str(value)+" is not positive number." except: print "ERROR: "+str(value)+" is not number value." def __delete__(self,instance): print "__delete__",instance del self.data[instance] class Car(object): country = u'中国' length = PositiveNum() width = PositiveNum() height = PositiveNum() __slots__=('owner','length','width','height') def __init__(self, length, width, height, owner=None): self.owner = owner self.length = length self.width = width self.height = height if __name__ == '__main__': a = Car(1.2,1.4,1.5,u'黑板客') b = Car(2.2,2.4,2.5,u'小明') print a.length a.length=1
当解释器遇到print a.length时,它会把length当作一个带有__get__方法的描述符,调用a.length.__get__方法并将方法的返回值打印,这和上面的property相似。__get__接收两个参数:instance 实例对象,这里就是a.length中的a,另一个是实例的类型Car。在一些文档中,Car被称作描述符的所有者(owner)。如果需要访问Car.length,python将会调用Car.length.__get__(None,Car)。可以看到第一个参数要么是实例,要么是None。
当解释器看到a.length = 1时,Python识别出length是一个带__set__方法的描述符,于是就调用a.length.__set__(a,100),第一个参数instance是实例,第二个是赋值。
删除时Car.length.__delete__(a)。
每个PositiveNum维护着一个字典,其中保存着所有者实例和对应数据的映射关系。调用a.length时,__get__方法会找出与a相关的数据,并发挥结果,如果不存在就返回一个默认值。__set__采用的方式相同,但是会包含额外的非法检查。
描述符作用与类的层次上,每一个类的实例都共享同一个描述符。所以不同的实例对象不得不手动的管理不同的状态,需要显示的将参数精确的传递给__get__,__set__以及__delete__方法。
如果将PositiveNum中的 data = {}去掉,由于描述符是基于类层面的,他们会共享同一个类属性,这就是使用字典的原因。__get__,__set__参数也指明哪一个实例,以实例为字典的key。
错误示例:
class PositiveNum(object): def __init__(self,value): self.val = value def __get__(self, instance, owner): # instance = a,b # owner = Car print "__get__",instance,owner return self.val def __set__(self, instance, value): # instance = a,b print "__set__",instance,value try: assert int(value)>0 self.val = value except AssertionError: print "ERROR: "+str(value)+" is not positive number." except: print "ERROR: "+str(value)+" is not number value." def __delete__(self,instance): print "__delete__",instance self.val = None #def __getattribute__(self,name): #print self, name class Car(object): country = u'中国' length = PositiveNum(0) width = PositiveNum(0) height = PositiveNum(0) #__slots__=('owner','length','width','height') def __init__(self, length, width, height, owner=None): self.owner = owner self.length = length self.width = width self.height = height if __name__ == '__main__': a = Car(1.2,1.4,1.5,u'黑板客') b = Car(2.2,2.4,2.5,u'小明')
a.length __get__ <__main__.Car object at 0x098E61B0> <class '__main__.Car'> Out[39]: 2.2 b.length __get__ <__main__.Car object at 0x098E6230> <class '__main__.Car'> Out[40]: 2.2
虽然a定义的1.2,但由于与b公用一个类属性,所以也变成了2.2。
__getter__,__setter__和类描述符都可以去掉重复的臃肿,实现内部代码的简洁。