先上代码:
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__)
原文:https://www.cnblogs.com/forwardfeilds/p/10515913.html