描述符与使用描述符自定义property属性

先上代码:

 1 import math
 2 class Circle:
 3     def __init__(self, radius):
 4         self.radius = radius
 5 
 6     def area(self):
 7         return math.pi*self.radius*self.radius
 8 
 9 c = Circle(2)
10 print(c.area())

我们定义了一个Circle类,其中初始化方法接收一个参数,作为半径,以及一个计算圆面积的方法area。

我们可以通过描述符来代理实例中的radius属性同时增加对radius的合法性校验。

 1 import math
 2 import numbers
 3 
 4 class Typed:
 5     def __init__(self, typed):
 6         self.typed = typed
 7     def __get__(self, instance, owner):
 8         print("__get__")
 9         return self.data
10 
11     def __set__(self, instance, value):
12         print("__set__")
13         if not isinstance(value, self.typed): #如果不是指定的类型,抛出TypeError异常
14             raise TypeError("%s must be %s type" %(value, self.typed))
15         self.data = value
16 
17 
18 class Circle:
19     radius = Typed(numbers.Real) #radius是实数类
20     def __init__(self, radius):
21         self.radius = radius
22 
23     def area(self):
24         return math.pi*self.radius*self.radius
25 
26 c = Circle(2)
27 print(c.__dict__)
28 print(c.radius)
29 print(c.area())
30 
31 d = Circle("foo") #TypeError异常

使用@property可以把方法转变成属性

1 class Circle:
2         xxx
3     @property
4         def area(self):
5         return math.pi*self.radius*self.radius
6 
7 c = Circle(2)
8 print(c.area)

下面我们尝试自己实现python自带的property一样的功能。

首先property应该是一个可调用对象,函数也好,类也好,接收一个参数。我们不妨假设是一个类,这个类接收一个参数,返回的结果是该类的实例。那么就应该有如下伪代码:

 1 class 属性:
 2     def __init__(self, func):
 3         xxx
 4     ...
 5 
 6 class Circle:
 7     radius = Typed(numbers.Real) #radius是实数类
 8     def __init__(self, radius):
 9         self.radius = radius
10     @属性
11     def area(self):
12         return math.pi*self.radius*self.radius
13 
14 c = Circle(2)
15 print(c.__dict__)
16 print(c.radius)
17 print(c.area)

实现起来并不难,python代码如下:

 1 class Property:
 2     def __init__(self, func):
 3         self.func = func
 4     
 5 
 6 class Circle:
 7     radius = Typed(numbers.Real) #radius是实数类
 8     def __init__(self, radius):
 9         self.radius = radius
10     @Property
11     def area(self):
12         return math.pi*self.radius*self.radius
13 
14 c = Circle(2)
15 print(c.__dict__)
16 print(c.radius)
17 #print(c.area())#不能这么调用了
18 print(c.area.func(c))

但是这里存在一个问题:

@Property

def area(self):xxx

等价于area = Property(area),那么area就变成Property的实例,而且是Circle的类属性,那么要计算面积我们得如下调用:

c.area.func(c)或者Circle.area.func(c)。

但是我们的目标是想要如下调用:

c.area

很明显,这样达不到我们的目的,还需要改进。

我们来思考一下:c.area访问的是属性,不管这个属性是实例属性、类属性,但是这两个属性都不会调用计算面积的方法,那么还有一个利器:就是描述符。

利用描述符,我们可以达到通过成员访问符(.)来执行函数(__get__方法)的目的。

我们把Property声明为描述符类,area是Property实例,代码如下:

 1 import math
 2 import numbers
 3 
 4 class Typed:
 5     def __init__(self, typed):
 6         self.typed = typed
 7     def __get__(self, instance, owner):
 8         print("__get__")
 9         return self.data
10 
11     def __set__(self, instance, value):
12         print("__set__")
13         if not isinstance(value, self.typed): #如果不是指定的类型,抛出TypeError异常
14             raise TypeError("%s must be %s type" %(value, self.typed))
15         self.data = value
16 
17 class Property:
18     def __init__(self, func):
19         self.func = func
20     def __get__(self, instance, owner):
21         print("Property __get__")
22         return self.func(instance)
23 
24     def __set__(self, instance, value):
25         raise AttributeError("not allow assign value")
26 
27 class Circle:
28     radius = Typed(numbers.Real) #radius是实数类
29     def __init__(self, radius):
30         self.radius = radius
31     @Property
32     def area(self):
33         return math.pi*self.radius*self.radius
34 
35 c = Circle(3.3)
36 print(c.__dict__)
37 print(c.radius)
38 #print(c.area())#不能这么调用了
39 print(c.area)
40 c.area = 9 #异常

OK,大功告成。

在这里有几点需要说明:

我们定义了__set__方法,抛出了AttributeError异常,表示不允许赋值操作,Property是数据描述符,因为即实现__set__方法又实现了__get__方法,描述符会覆盖实例属性,如果没有__set__方法,那么Property是非数据描述符,实例属性会覆盖类属性。

 1 class Property:
 2     def __init__(self, func):
 3         self.func = func
 4     def __get__(self, instance, owner):
 5         print("Property __get__")
 6         return self.func(instance)
 7 
 8 #    def __set__(self, instance, value):
 9 #        raise AttributeError("not allow assign value")
10 
11 class Circle:
12     radius = Typed(numbers.Real) #radius是实数类
13     def __init__(self, radius):
14         self.radius = radius
15     @Property
16     def area(self):
17         return math.pi*self.radius*self.radius
18 
19 c = Circle(3.3)
20 print(c.__dict__)
21 print(c.radius)
22 #print(c.area())#不能这么调用了
23 print(c.area)
24 c.area = 9 #正常,实例属性赋值
25 print(c.area)
26 print(c.__dict__)

猜你喜欢

转载自www.cnblogs.com/forwardfeilds/p/10515913.html