描述符和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/   (这个文章太牛逼了,初学者可以看十遍,关于描述符就很懂了)

posted on 2017-01-16 14:39  wzxds02  阅读(197)  评论(0编辑  收藏  举报

导航