基本概念
-
实例(instance)的属性查找
对于一个类的实例a,当访问a.x属性时,查找顺序是:首先查看x是否是实例a的属性(a.dict[‘x’])–>查找实例a对应的类的属性(type(a).dict[‘x’])–>继续查找类继承的基类的属性除了(metaclasses)
-
描述器协议
用到3个魔术方法: get()、set()、delete()
方法使用格式:
obj.get(self, instance, owner)
obj.set(self, instance, value)
obj.delete(self, instance)
self: 指当前类的实例本身
instance: 指owner的实例
owner: 指当前实例作为属性的所属类
当任何一个类实现了这三个魔术放发的其中一个就叫做描述器
典型的描述器分类:
a) data-descriptor : both get and set 都被定义
当一个entry被定义为描述器,此时对于a.m ==a.get(m)//a.m=dd == a.set(dd).b) non-data-descriptor : only __get__被定义
此时以__get__函数是最低优先级 -
例子
data_descriptor
class A:
def __init__(self):
self.a1='a1'
self.data=None
print("2--A init")
def __set__(self,instance,value):
print('3--set',self,'-',instance,'-',value)
self.data=value
def __get__(self,instance,owner):
print('4--get',self,'-',instance,'-',owner)
return self.data
class B:
x=A() #将X作为一个描述器,此时x属于CLASS的属性
y='b.b'
def __init__(self):
print('1--B')
self.x='b.x' #此时将调用__set__函数,x属于CLASS 的属性,不属于实例的属性
self.y='b.y'
self.z='b.z'
m=B()
print(m.y)
print(m.x)#此时将调用__get__函数
print(m.__dict__)
output:based on python 3.7
2--A init
1--B
3--set <__main__.A object at 0x0000025634C8DDA0> - <__main__.B object at 0x0000025634C7E5F8> - b.x
b.y
4--get <__main__.A object at 0x0000025634C8DDA0> - <__main__.B object at 0x0000025634C7E5F8> - <class '__main__.B'>
b.x
{'y': 'b.y', 'z': 'b.z'}
non-descriptor
class A:
def __init__(self):
self.a1='a1'
self.data=None
print("2--A init")
"""
def __set__(self,instance,value):
print('3--set',self,'-',instance,'-',value)
self.data=value
"""
def __get__(self,instance,owner):
print('4--get',self,'-',instance,'-',owner)
return self.data
class B:
x=A() #将X作为一个描述器,此时x属于CLASS的属性
y='b.b'
#x='b.m'
def __init__(self):
print('1--B')
self.x='b.x' #此时x属于实例的属性---因为此时描述器是个non-data,描述器的优先级最低
self.y='b.y'
self.z='b.z'
m=B()
print(m.y)
print(m.x) #打印 b.x 按照正常实例属性来搜索,如果找不到则调用__get__
print(m.__dict__)
output:
2--A init
1--B
b.y
b.x
{'x': 'b.x', 'y': 'b.y', 'z': 'b.z'}
class A:
def __init__(self):
self.a1='a1'
self.data=None
print("2--A init")
"""
def __set__(self,instance,value):
print('3--set',self,'-',instance,'-',value)
self.data=value
"""
def __get__(self,instance,owner):
print('4--get',self,'-',instance,'-',owner)
return self.data
class B():
x=A()
y='b.b'
#x='b.m'
def __init__(self):
print('1--B')
#self.x='b.x'
self.y='b.y'
self.z='b.z'
m=B()
print(m.y)
print(m.x)
print(m.__dict__)
output:
2--A init
1--B
b.y
4--get <__main__.A object at 0x000002218019ED30> - <__main__.B object at 0x000002218018E588> - <class '__main__.B'>
None
{'y': 'b.y', 'z': 'b.z'}
- 使用property 创建的是data-descriptor 描述器
- [property 用法1 ]
通过@property装饰器来构建描述器,即使用@property 将函数XXXX设置为__get__,通过XXXX.setter设置__set__
a) 函数名字和需要设置为描述器的实例属性相同,此时属性名字需要使用私有,即添加_/__
b) 描述器的函数也变为属性,此时当描述器方法的名字和需要使用描述器的属性名字一样时(即使用self.score)运行程序会出现无限循环造成栈溢出。
通过这种方式,对于python的解释器看到的其实两个属性:self._score 和self.score,但是当你调用self._score时,其实是在直接调用实例的属性(_score是实例属性,不是描述器),当调用a.score(score是描述器方法)时,其实调用的是函数。
通过这种封装,使用者需要记住的是score (因为在score 函数中是对_score 的操作)
class A(object):
def __init__(self,name,score):
self.name=name
self._score=score
@property
def score(self): #相当于为__get__
print("getting score here")
return self._score
@score.setter
def score(self,value): #相当于__set__
print("setting score here")
self._score =value
#score=property(getscore,setscore)
b=100
a=A('smith',100)
print(a.name)
print(a.score) #调用score描述器 函数
a.score=90 #调用score 描述器函数
print(a._score)#调用实例的属性,因为_score不是描述器
print(a.score)#调用score描述器函数
output:
smith
getting score here
100
setting score here
90
getting score here
90
-
[ property 用法2]
property(fget=None, fset=None, fdel=None, doc=None) -> property attribute
a)此时结果和1相同,只是写法不一样。
class A(object):
def __init__(self,name,score):
self.name=name
self._score=score
#@property
def getscore(self):
print("getting score here")
return self._score
#@score.setter
def setscore(self,value):
print("setting score here")
self._score =value
score=property(getscore,setscore)
b=100
a=A('smith',100)
print(a.name)
print(a.score)
#print(a._score)
#print(a.__dict__)
#a.name='bob'
a.score=90
print(a._score)
print(a.score)
output:
smith
getting score here
100
setting score here
90
getting score here
90
- 当使用property创建data-descriptor时,只有__get__被定义(即对于1–@property,对于2.x=property(get)),此时对于描述器属性是只读的,不能改变值。可以使用魔术方式构建同样的只读的描述器(定义__get__和__set__,但是在__set__中不修改值,使用raise AttributeError(“unreadable attribute”)异常处理方法)
- 当然你可以使用@property将某个类内的方法转化为属性(当访问此属性时是通过某个公式或者逻辑计算而得)
import math
class Power:
def __init__(self,r=0):
self.r=r
@property
def r_power(self):
return self.r**2
@property
def r_sqrt(self):
return self.r**0.5
d=Power(4)
print(d.__dict__)
print(d.r_power) #此时两个类内函数r_power/r_sqrt 变成了属性,不是函数或者方法,调用的时候不能用 d.r_power()
print(d.r_sqrt)
print(d.__dict__)
print(Power.__dict__)
output:
{‘r’: 4}
16
2.0
{‘r’: 4}
{‘module’: ‘main’, ‘init’: <function Power.init at 0x00000213104ABE18>, ‘r_power’: <property object at 0x00000213104575E8>, ‘r_sqrt’: <property object at 0x00000213104A1958>, ‘dict’: <attribute ‘dict’ of ‘Power’ objects>, ‘weakref’: <attribute ‘weakref’ of ‘Power’ objects>, ‘doc’: None}
以上都可以参考python doc