描述符和property内建函数
首先我们搞清楚__getattr__ ,__get__ 和 __getattribute__ 作用的不同点。
- __getattr__在授权中会用到。
- __getattribute__ 当要访问属性时,就会一开始被调用,你可以定义,也可以不定义(默认)。
class B(): def __init__(self,x): self.x = x self.y =200 def __getattribute__(self, item): if item == 'x': return 'xxx' else: return object.__getattribute__(self,item) def __getattr__(self, item): return 'defautvalue' def say(self): print('this is B') b=B(100) print(b.x) #查找顺序__getattribute__, __dict__, 父类的__dict__,__getattr__ b.say()
输出结果:
xxx this is B
同时它也是描述符系统的心脏。
- __get__是描述符中的一个属性(下面会具体讲)
1.描述符的用法
就是将某种特殊类型的类的实例指派给另一个类的属性(注意:这里是类属性,而不是对象属性)。而这种特殊类型的类就是实现了__get__,__set__,__delete__的新式类(即继承object)。
属性就是描述符,定义的描述符就是可以被重复使用的属性。当你需要属性时候,可以通过点属性表示号访问(通过默认的__get__,__set__,__delete__函数),如果是描述符,则需要通过描述符的类的属性来访问
属性和定义的描述符不同之处在于,描述符需要指定的类中重新编辑__get__,__set__,__delete__函数。
下面定义一个描述符,obj表示实例,下面就是c1,;type表示实例的类型,下面就表示C1。
class Dev(): def __get__(self,obj,type=None): print('get',self,obj,type) pass def __set__(self,obj,val): print('set',self,obj,val) pass class C1(): foo = Dev() #将某种特殊类型的类的实例指派给另一个类的属性 c1=C1() c1.foo= '3' print(c1.foo) print(C1.foo)
输出结果:
set <__main__.Dev object at 0x0053F3F0> <__main__.C1 object at 0x0053F350> 3 get <__main__.Dev object at 0x0053F3F0> <__main__.C1 object at 0x0053F350> <class '__main__.C1'> None get <__main__.Dev object at 0x0053F3F0> None <class '__main__.C1'> None
给定类X和实例x,以及属性fool,
x.fool将会被__getattribute__转化为:
type(x).__dict__['foo'].__get__(x,type(x)) 。 x.fool= val(如果x中没有fool属性) 会被转化成:type(x).__dict__['foo'].__set__(x,val)
当访问X.fool(如果X中没有fool值)转化为:X.__dict__['fool'].__get__(None,X) 。 X.fool=val会直接赋值,可以理解为调用了默认的__set__。
在python中__getattribute__如果还没有找到,就到__getattr__中寻找。
我们执行下面的程序:
1 class simpleDescriptor(object): 2 def __get__(self, obj, type=None): 3 return 'getting' 4 5 def __set__(self, obj, val): 6 return 'setting' 7 8 class A(object): 9 foo = simpleDescriptor() 10 11 print(A.__dict__) 12 print(A.foo) 13 a = A() 14 print(a.foo) #执行描述器里面的__get__ 15 a.foo = 13 #执行描述器里面的__set__ 16 print(a.__dict__) 17 print(a.foo) 18 print(A.foo)
得到结果:
{'__module__': '__main__', 'foo': <__main__.simpleDescriptor object at 0x0217F370>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None} getting getting {} getting getting
第12行,因为foo为描述器,转换为X.__dict__['fool'].__get__(None,X) ,调用描述器里面的__get__。
但是当我们执行
1 class simpleDescriptor(object): 2 def __get__(self, obj, type=None): 3 return 'getting' 4 5 def __set__(self, obj, val): 6 return 'setting' 7 8 class A(object): 9 foo = simpleDescriptor() 10 11 print(A.__dict__) 12 A.foo=3 #这个表示类属性的赋值 13 print(A.__dict__) 14 print(A.foo) 15 a = A() 16 print(a.foo) 17 a.foo = 13 18 print(a.__dict__) 19 print(a.foo) 20 print(A.foo)
得到结果
{'__module__': '__main__', 'foo': <__main__.simpleDescriptor object at 0x0043A4F0>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None} {'__module__': '__main__', 'foo': 3, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None} 3 3 {'foo': 13} 13 3
执行第12行,因为有类属性'foo'了,所以是直接赋值。
因为第12行,A.__dict__['foo']已经不是一个描述器了。当我们执行第17行a.foo=13 的时候,因为x.__dict__[‘fool’]中没有,转化成A.__dict__['foo'].__set__(a,val),因为不是描述器了,所以不根据描述器中的__set__赋值。
第16行,因为x.__dict__[‘fool’]中没有,所以转化成X.__dict__['fool'].__get__(None,X),以及不是描述器,所以不用描述器里面的__get__。
第19行,有实例属性'foo'了,可以直接显示。
所以很多时候取决于,类属性foo在类的__dict__存储中是不是被表明为描述器。
没有__set__的在书籍里被称为非数据描述符,不好理解,我可以这么理解,看下面这个程序:
1 class simpleDescriptor(object): 2 def __get__(self, obj, type=None): 3 return 'getting' 4 5 class A(object): 6 foo = simpleDescriptor() 7 8 print(A.__dict__) 9 print(A.foo) 10 a = A() 11 print(a.foo) 12 a.foo = 13 13 print(a.__dict__) 14 print(a.foo) 15 print(A.foo)
执行结果为:
{'__module__': '__main__', 'foo': <__main__.simpleDescriptor object at 0x005C0210>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None} getting getting {'foo': 13} 13 getting
第12行执行以后,因为实例属性中没有,所以要转化成A.__dict__['foo'].__set__(a,val),因为没有描述器中没有__set__,所以就会调用默认的给赋值。
到第13行的时候,因为被赋值,所以实例属性中有,可以直接被调用。
2.property函数的用法
property是一种有用的特殊的描述符。一般写在类定义中。property()一般形式为property(fget,fset,fdel,doc)
class HideX(): def __init__(self, x): self.x = x def get_x(self): print('call get_x') return self._x #!!! def set_x(self, val): self._x = val print('call set_x ') x = property(get_x, set_x) inst = HideX(10) # 就触发了set_x函数 print(inst.x) # 触发了get_x函数 inst.x = 20 # 调用的是property属性x,call set_x print(inst.x) #
相当于把描述符中的__set__方法放到了类中来描述。当你调用inst.x, 其实是调用x中的__get__方法,也就是property.get_x()函数。结果为:
call set_x call get_x 10 call set_x call get_x 20
x是描述符,第一行的Hidex(10)调用了self.x=10,就相当于赋值,会调用__set__方法。
第二行,相当于显示,调用__get__方法。
也可以这么用:
1 class Movie(object): 2 def __init__(self, title, rating, runtime, budget, gross): 3 self._budget = None 4 5 self.title = title 6 self.rating = rating 7 self.runtime = runtime 8 self.gross = gross 9 self.budget = budget 10 11 @property 12 def budget(self): 13 return self._budget 14 15 @budget.setter 16 def budget(self, value): 17 if value < 0: 18 raise ValueError("Negative value not allowed: %s" % value) 19 self._budget = value 20 21 def profit(self): 22 return self.gross - self.budget 23 24 m = Movie('Casablanca', 97, 102, 964000, 1300000) #calls budget.setter(96400) 25 print (m.budget) # calls m.budget(), returns result 26 try: 27 m.budget = -100 # calls budget.setter(-100), and raises ValueError 28 except ValueError: 29 print ("Woops. Not allowed")
第11行的意思表示 budget =property(budget)
第第15行的意思表示 budget=property(fget=budget)
结果:
964000
Woops. Not allowed
3.property函数和描述符的比较
因为描述符是在单独的类中描述的,而且有传递实例和实例的类型,所以可以统一命名,property函数无法统一命名。
1 from weakref import WeakKeyDictionary 2 3 class NonNegative(object): 4 """A descriptor that forbids negative values""" 5 def __init__(self, default): 6 self.default = default 7 self.data = WeakKeyDictionary() 8 9 def __get__(self, instance, owner): 10 # we get here when someone calls x.d, and d is a NonNegative instance 11 # instance = x 12 # owner = type(x) 13 return self.data.get(instance, self.default) 14 15 def __set__(self, instance, value): 16 # we get here when someone calls x.d = val, and d is a NonNegative instance 17 # instance = x 18 # value = val 19 if value < 0: 20 raise ValueError("Negative value not allowed: %s" % value) 21 self.data[instance] = value 22 23 class Movie(object): 24 25 #always put descriptors at the class-level 26 rating = NonNegative(0) 27 runtime = NonNegative(0) 28 budget = NonNegative(0) 29 gross = NonNegative(0) 30 31 def __init__(self, title, rating, runtime, budget, gross): 32 self.title = title 33 self.rating = rating 34 self.runtime = runtime 35 self.budget = budget 36 self.gross = gross 37 38 def profit(self): 39 return self.gross - self.budget 40 41 m = Movie('Casablanca', 97, 102, 964000, 1300000) 42 print(m.budget) # calls Movie.budget.__get__(m, Movie) 43 m.rating = 100 # calls Movie.budget.__set__(m, 100) 44 try: 45 m.rating = -100 # calls Movie.budget.__set__(m, -100) 46 except ValueError: 47 print ("Woops, negative value")
结果是:
964000 Woops, negative value
具体可以见下面:http://www.geekfan.net/7862/ (这个文章太牛逼了,初学者可以看十遍,关于描述符就很懂了)