在交互式环境中输入:
1 >>> class A: 2 a=0 3 def __init__(self): 4 self.a=10 5 self.b=100 6 7 8 >>> a=A() 9 >>> a.a10 1011 >>> a.b12 10013 >>> A.a14 015 >>> A.b16 Traceback (most recent call last):17 File "<pyshell#10>", line 1, in <module>18 A.b19 AttributeError: type object 'A' has no attribute 'b'20 >>>
如下图:
还是在交互式环境中:
1 >>> class A: 2 a=0 3 def __init__(self): 4 self.a=10 5 self.b=100 6 7 8 >>> a=A() 9 >>> getattr(a,'a')#用getattr()函数获取实例a中a属性的值10 1011 >>> setattr(a,'a',20)#设置实例a中a属性的值为2012 >>> getattr(a,'a')#用getattr()函数获取实例a中a属性的值13 2014 >>> hasattr(a,'b')#测试实例a中是否包含属性b15 True
图片展示:
这种反射机制的用字符串来操作类的属性和方法的三个函数并不常用。编写框架等特殊项目是采用到。
1 class Washer: 2 3 def __init__(self,water=10,scour=2): 4 self._water=water #不想让用户直接访问实例变量,可以标志成私有 5 self.scour=scour 6 #属性包装,将water属性包装成方法,用户使用water时实际是访问的方法 7 @property 8 def water(self):#如果用户使用 实例.water相当于访问这个方法,而不是真的访问属性 9 return self._water10 11 def set_water(self,water):12 self.water=water 13 14 def set_scour(self,scour):15 self.scour=scour 16 17 def add_water(self):18 print('Add water:',self.water)19 20 def add_scour(self):21 print('Add scour:',self.scour)22 23 def start_wash(self):24 self.add_water()25 self.add_scour()26 print('Start wash...')27 28 if __name__=='__main__':29 w=Washer()30 #w.start_wash()31 print(w.water)# 可以像访问属性一样访问方法
但这时用户仍然可以通过w._water来访问实例属性,封装的不好,也不会自动检查数据是不是浮点型,不好。
怎么解决?
用@属性.setter
1 class Washer: 2 3 def __init__(self,water=10,scour=2): 4 self._water=water #不想让用户直接访问实例变量,可以标志成私有 5 self.scour=scour 6 #属性包装,将water属性包装成方法,用户使用water时实际是访问的方法 7 @property 8 def water(self):#如果用户使用 实例.water相当于访问这个方法,而不是真的访问属性 9 return self._water10 11 @water.setter #新添加代码12 def water(self,water):13 if 0<water<=500:14 self._water=water15 else:16 print('set Failure!')17 18 def set_water(self,water):19 self.water=water 20 21 def set_scour(self,scour):22 self.scour=scour 23 24 def add_water(self):25 print('Add water:',self.water)26 27 def add_scour(self):28 print('Add scour:',self.scour)29 30 def start_wash(self):31 self.add_water()32 self.add_scour()33 print('Start wash...')34 35 if __name__=='__main__':36 w=Washer()37 print(w.water)# 可以像访问属性一样访问方法38 #w._water=20 #为了不让用户这样直接给实例属性赋值,用下面的语句39 w.water=123 #可以像给属性赋值一样给方法赋值,并检测范围40 print(w.water)# 可以像访问属性一样访问方法
结果:
另外,很好奇@water.setter这个water到底是个什么东西,发现可以理解成一个新的实例属性,跟构造函数的形参没有关系。比如下图
再比如:
结果仍为:
同样可以包装一个删除变量,@water.delete
-------------------------------------
这块代码表示water这个变量可以重写,
------------------------------------------
上面这块代码表示water属性可以读取。
---------------------------------------------
最后一个用法是用属性装饰器@property来新定义一个虚拟的属性。
1 class Washer: 2 3 def __init__(self,water=10,scour=2): 4 self._water=water #不想让用户直接访问实例变量,可以标志成私有 5 self.scour=scour 6 self.year=2000#这是生产日期 7 #属性包装,将water属性包装成方法,用户使用water时实际是访问的方法 8 @property 9 def water1(self):#如果用户使用 实例.water相当于访问这个方法,而不是真的访问属性10 return self._water11 12 @water1.setter13 def water1(self,water):14 if 0<water<=500:15 self._water=water16 else:17 print('set Failure!')18 @property 19 def total_year(self): #定义一个虚拟实例属性,这个属性其实是一个方法,但是可以按照属性来用 20 return 2017-self.year21 22 def set_water(self,water):23 self.water=water 24 25 def set_scour(self,scour):26 self.scour=scour 27 28 def add_water(self):29 print('Add water:',self.water)30 31 def add_scour(self):32 print('Add scour:',self.scour)33 34 def start_wash(self):35 self.add_water()36 self.add_scour()37 print('Start wash...')38 39 if __name__=='__main__':40 w=Washer()41 print(w.water1)# 可以像访问属性一样访问方法42 #w._water=20 #为了不让用户这样直接给实例属性赋值,用下面的语句43 w.water1=12344 print(w.water1)# 可以像访问属性一样访问方法45 print(w.total_year)46 47
运行结果:
描述符
描述符的意义是避免重复写具有相同限定属性的实例属性的定义代码。比如下面的例子:
1 class NonNeg:#数据描述符 2 def __init__(self,default=0):#构造方法 3 self.default=default#一个实例属性 4 def __get__(self,instance,owner):#协议方法 5 return self.default 6 def __set__(self,instance,val):#协议方法 7 if val>0: 8 self.default=val 9 else:10 print('The value must be NonNegative!')11 def __delete__(self,instance):#协议方法12 pass13 class Movie:14 rating=NonNeg()#描述符类NonNeg作另一个类Movie的属性,rating是Movie的类属性。15 score=NonNeg()16 17 if __name__=='__main__':18 m=Movie()19 print('rating:',m.rating)20 print('score:',m.score)#输出默认值default21 m.rating=80#使用__set__协议方法22 print('rating:',m.rating)#使用到 __get__协议方法23 m.score=-324 print('score:',m.score)
输出结果:
---------------------------------------
下面说明所有的 类成员函数都是非数据描述符。
在这个交互式环境中可以看出pr这个类方法仅有__get__协议,三个不全,所以是非数据描述符。
----------------------------------------------------------------
同名的实例属性和非数据描述符(以类方法为例)同时出现时,访问的优先级是什么?
再看:
为啥结果还不一样了?再做一遍老师的例子:
重新打开idel之后重新写了一遍:
总结如下:
在交互式环境中,
若在类内实例方法中定义与此方法名想同的实例变量pr,则在类外实例化此类后,实例.pr 首先访问的是此实例变量,实例.pr() 肯定访问的是类内实例方法。若再类外实例中定义一个 实例.pr=20,则再访问 实例.pr时则访问的是刚定义的实例属性 实例.pr=20。
若在类内没有定义与类方法同名的实例属性,则实例.pr访问的是类内的实例方法,若又在类实例化后实例下定义同名的的实例属性pr,则 实例.pr访问的刚定义的。。。
感觉好混乱:若访问过t.pr()再访问t.pr,t.pr就为10了,若没有访问过t.pr()直接访问t.pr,这个就先访问的是method Tst.pr of <__main__.Tst object,也就是一个方法了。
1 class Tst: 2 def pr(self): 3 self.pr=10 4 print('Tst') 5 t1=Tst() 6 t1.pr()#输出Tst 7 t1.pr#啥都没有输出 8 print(t1.pr)#输出10 9 print('下面实例化后不访问t.pr()直接访问t.pr:')10 t2=Tst()11 t2.pr#啥都没输出12 print(t2.pr)#输出了bound method Tst.pr of <__main__.Tst object
但后来在实例下新定义的同名实例属性会覆盖原先类中定义的实例方法。优先级知道了吧。
扩展:
1 class Tst: 2 def __init__(self,default=1): 3 self.water=default 4 def __call__(self): 5 print('包含call函数的类,他的实例可以直接当做函数使用。') 6 def info(self): 7 print("pass") 8 9 t=Tst()10 t()
当调用t()时只调用类中__call__函数。
--------------------------------------------
解答如下:
1 class surfaceNum:#定义一个描述类 2 def __init__(self,default=1): 3 self.number=default 4 def __get__(self,instance,owner):#参数instance和owner暂时没有用到,只有self是固定名参数 5 return self.number 6 def __set__(self,instance,val):#参数instance暂时没有用到 7 if 0<val<7 and isinstance(val,int)==True: 8 self.number=val 9 Box.info_num(self)#Box类还没有创建,故不能引用Box.infor_num,哈哈,能创建啊10 else:11 print('please set the correct surface number!')12 def __delete__(self,instance):#协议方法13 pass14 15 class Box:#定义一个类名为Box,类名后不必有括号,类包含类属性和类方法,这个类没有定义类属性16 '''这是一个计算体积的类'''#这是这个类的__doc__属性,执行类后就可以在交互界面输入Box.__doc__查看这行说明文字了17 openstate=018 number=surfaceNum()19 def __init__(self):#这是类的构造函数,当实例化Box后会自动调用这个__init__方法20 self.length=0.0 #这是实例属性,在类内访问用self.length,在类外访问用 实例名.length21 self.width=0.022 self.height=0.023 self._color='red' 24 self.__valum=0.0#双下换线开头的变量表示私有变量,所以他为私有实例属性,只能在类内访问到25 26 @property27 def color(self):28 return self._color29 @color.setter30 def color(self,color):31 self._color=color32 33 def set_color(self,color):34 self._color=color 35 36 def computevalum(self):#定义了一个类方法。37 self.__valum=self.length*self.width*self.height38 print('长度=',self.length,'宽度=',self.width,'高度=',self.height,'valum=',self.__valum)39 40 def info_color(self):41 #self.set_color(self._color)#在类中,函数调用函数的方式42 print('Box的颜色为',self._color)43 44 def open_box(self):45 if Box.openstate==0:46 print('打开了Box')47 Box.openstate=148 else:49 print('Box已经打开了,不能重复打开')50 def info_num(self):51 #self.set_color(self._color)#在类中,函数调用函数的方式52 53 print('Box面上的数字为',Box.number)54 #定义 __call__ 函数,输出体积55 def __call__(self):56 self.__valum=self.length*self.width*self.height57 print('长度=',self.length,'宽度=',self.width,'高度=',self.height,'调用自身computa()输出:valum=',self.__valum)58 59 60 61 62 if __name__=='__main__': 63 computa=Box() #实例化Box类64 computa.number =265 computa.info_num()66 computa.length=167 computa.width=268 computa.height=369 computa.computevalum()70 computa()#实例名函数调用__call__函数直接输出体积71 computa.set_color ('yellow')72 computa.info_color()73 computa.open_box()74 computa.color='green'75 computa.info_color()76 print('')77 78 computb=Box()#实例化Box类79 computb.length=280 computb.width=281 computb.height=382 computb.computevalum()83 computb.set_color ('black')84 computb.info_color()85 computb.open_box()
这个题目是上节课题目的拔高,上节课题目及解答见链接:
以上是深入类的属性介绍与使用的详细内容。更多信息请关注PHP中文网其他相关文章!