描述符和property内建函数

时间:2023-03-08 16:02:47

首先我们搞清楚__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= ''
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__中寻找。

我们执行下面的程序:

 class simpleDescriptor(object):
def __get__(self, obj, type=None):
return 'getting' def __set__(self, obj, val):
return 'setting' class A(object):
foo = simpleDescriptor() print(A.__dict__)
print(A.foo)
a = A()
print(a.foo) #执行描述器里面的__get__
a.foo = 13 #执行描述器里面的__set__
print(a.__dict__)
print(a.foo)
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__。

但是当我们执行

 class simpleDescriptor(object):
def __get__(self, obj, type=None):
return 'getting' def __set__(self, obj, val):
return 'setting' class A(object):
foo = simpleDescriptor() print(A.__dict__)
A.foo=3 #这个表示类属性的赋值
print(A.__dict__)
print(A.foo)
a = A()
print(a.foo)
a.foo = 13
print(a.__dict__)
print(a.foo)
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__的在书籍里被称为非数据描述符,不好理解,我可以这么理解,看下面这个程序:

 class simpleDescriptor(object):
def __get__(self, obj, type=None):
return 'getting' class A(object):
foo = simpleDescriptor() print(A.__dict__)
print(A.foo)
a = A()
print(a.foo)
a.foo = 13
print(a.__dict__)
print(a.foo)
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__方法。

也可以这么用:

 class Movie(object):
def __init__(self, title, rating, runtime, budget, gross):
self._budget = None self.title = title
self.rating = rating
self.runtime = runtime
self.gross = gross
self.budget = budget @property
def budget(self):
return self._budget @budget.setter
def budget(self, value):
if value < 0:
raise ValueError("Negative value not allowed: %s" % value)
self._budget = value def profit(self):
return self.gross - self.budget m = Movie('Casablanca', 97, 102, 964000, 1300000) #calls budget.setter(96400)
print (m.budget) # calls m.budget(), returns result
try:
m.budget = -100 # calls budget.setter(-100), and raises ValueError
except ValueError:
print ("Woops. Not allowed")

第11行的意思表示 budget =property(budget)

第第15行的意思表示 budget=property(fget=budget)

结果:

964000
Woops. Not allowed

3.property函数和描述符的比较

因为描述符是在单独的类中描述的,而且有传递实例和实例的类型,所以可以统一命名,property函数无法统一命名。

 from weakref import WeakKeyDictionary

 class NonNegative(object):
"""A descriptor that forbids negative values"""
def __init__(self, default):
self.default = default
self.data = WeakKeyDictionary() def __get__(self, instance, owner):
# we get here when someone calls x.d, and d is a NonNegative instance
# instance = x
# owner = type(x)
return self.data.get(instance, self.default) def __set__(self, instance, value):
# we get here when someone calls x.d = val, and d is a NonNegative instance
# instance = x
# value = val
if value < 0:
raise ValueError("Negative value not allowed: %s" % value)
self.data[instance] = value class Movie(object): #always put descriptors at the class-level
rating = NonNegative(0)
runtime = NonNegative(0)
budget = NonNegative(0)
gross = NonNegative(0) def __init__(self, title, rating, runtime, budget, gross):
self.title = title
self.rating = rating
self.runtime = runtime
self.budget = budget
self.gross = gross def profit(self):
return self.gross - self.budget m = Movie('Casablanca', 97, 102, 964000, 1300000)
print(m.budget) # calls Movie.budget.__get__(m, Movie)
m.rating = 100 # calls Movie.budget.__set__(m, 100)
try:
m.rating = -100 # calls Movie.budget.__set__(m, -100)
except ValueError:
print ("Woops, negative value")

结果是:

964000
Woops, negative value

  具体可以见下面:http://www.geekfan.net/7862/   (这个文章太牛逼了,初学者可以看十遍,关于描述符就很懂了)