转载请标明出处(http://blog.csdn.net/lis_12/article/details/53469589).
properties
,methods
, static methods
,class methods
, and super()
都是基于描述符实现的。
建造描述符对象需要一个描述符类,如果只需要一个描述符对象就定义一个类的话,感觉有点坑啊,Python这么机智,肯定有建造描述符对象的简便方法…就是property
啦。
class
property
([fget[, fset[, fdel[, doc]]]])Return a property attribute for new-style classes (classes that derive from
object
).fget is a function for getting an attribute value. fset is a function for setting an attribute value. fdel is a function for deleting an attribute value. And doc creates a docstring for the attribute.
A typical use is to define a managed attribute
x
:
class C(object): def __init__(self): self._x = None def getx(self): return self._x def setx(self, value): self._x = value def delx(self): del self._x x = property(getx, setx, delx, "I'm the 'x' property.")
If c is an instance of C,
c.x
will invoke the getter,c.x = value
will invoke the setter anddel c.x
the deleter.If given, doc will be the docstring of the property attribute. Otherwise, the property will copy fget‘s docstring (if it exists). This makes it possible to create read-only properties easily using
property()
as a decorator:
class Parrot(object): def __init__(self): self._voltage = 100000 @property def voltage(self): """Get the current voltage.""" return self._voltage
The
@property
decorator turns thevoltage()
method into a “getter” for a read-only attribute with the same name, and it sets the docstring for voltage to “Get the current voltage.”A property object has
getter
,setter
, anddeleter
methods usable as decorators that create a copy of the property with the corresponding accessor function set to the decorated function. This is best explained with an example:
class C(object): def __init__(self): self._x = None @property def x(self): """I'm the 'x' property.""" return self._x @x.setter def x(self, value): self._x = value @x.deleter def x(self): del self._x
This code is exactly equivalent to the first example. Be sure to give the additional functions the same name as the original property (
x
in this case.)The returned property object also has the attributes
fget
,fset
, andfdel
corresponding to the constructor arguments.
通常,访问实例或类的属性时,将会返回属性所存储的值。property(特性)
是一种特殊属性,在访问它时才会计算它的值。举个简单例子…
class Circle(object):
def __init__(self,val = 0):
self.r = val
@property
def area(self):
print 'area = ',
return self.r ** 2 * 3.14
c = Circle(2)
print c.area #area = 12.56,啥时候调用,啥时候才计算值,像不像赶作业,啥时候交,啥时候才写...
c.r = 10
print c.area #area = 314.0
@property装饰器
支持以简单属性的形式访问后面的方法,无需添加额外的()来调用该方法。对象使用者很难发现正在计算一个属性,除非出现了异常- -….
定义一个类时,尽可能保证编程接口的统一,如果没有property
,将会以简单的属性形式(如c.r
)访问对象的某些属性,而其他属性又以方法(如c.area())的形式访问,方法还需要费时去了解,也可能会带来不必要的混淆,property
完美的解决了该问题。
其实,property
就是将函数调用伪装成对属性的访问。那么如何利用property
将函数调用伪装成对属性的访问呢?
创建property对象的方法:
x = property(fget = None, fset = None, fdel = None, doc = None)
;
class Foo1(object):
def __init__(self):
self.__x = 0
def getx(self):
return self.__x
def setx(self, value):
self.__x = value
def delx(self):
del self.__x
x = property(getx, setx, delx, "I'm the 'x' property.")
装饰器;
class Foo2(object):
def __init__(self):
self.__x = 0
@property
def x(self):
"I am the 'x' property."
return self.__x
@x.setter #关联set操作
def x(self, value):#方法名要与特性x的名字一致,不然会生成新的property对象,不懂的的话继续往下看,看了property的模拟实现就知道原因了,如果还是不懂的话,见code里的第四个例子.
self.__x = value
@x.deleter #关联del操作
def x(self): #方法名需要与特性x的名字一致,不然会生成新的property对象
del self.__x
注:Foo1,Foo2
中的x
是一样的,只不过定义的形式不同。
其实property
就是建造描述符的一个简便方法,它基于描述符实现,在属性访问时也会自动触发相应的描述符方法。property()
是怎么实现的呢? 以下为Python的模拟实现:
class Property(object):
"Emulate PyProperty_Type() in Objects/descrobject.c"
def __init__(self, fget = None, fset = None, fdel = None, doc = None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
def __get__(self, obj, objtype = None):
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj)
def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__) #修改了fget方法
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__) #修改了fset方法
def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__) #修改了fdel方法
从上述代码可知,property
基本上就是一个描述符类嘛,只不过多了getter(),setter(),deleter()
三个方法。
__get__(),__set__(),__delete__()
分别去调用self.fget, self.fset, self.fdel
对应的方法,描述符方法相当于一个中转站,通过描述符方法调用自定义方法。getter(),setter(),deleter()
用于生成新的property对象。
调用描述符对象,a为定义了三个描述符方法的描述符,并且a为类属性
print t.a →
a.__get__(t, type(t))
t.a = v →
a.__set__(t, v)
del t.a →
a.__delete__(t)
调用property
对象,a为property对象
print t.a →
a.__get__(t, type(t))
→ a.fget(t)t.a = v →
a.__set__(t, value)
→ a.fset(t,value)del t.a →
a.__delete__(t)
→ a.fdel(t)
测试特性为类属性还是实例属性
class Foo(object):
@property
def x(self):
return 1
f = Foo()
print f.__dict__ #{}
print Foo.__dict__ #特性x为类属性,不是实例属性
'''{'x': ,
'__dict__': ,
'__module__': '__main__', '__weakref__': , '__doc__': None}'''
验证property的调用方式
class Foo(object):
def __init__(self):
self._x = 1
@property
def x(self):
print 'get',
return self._x
@x.setter #调用x.setter,返回新的 property 对象
def x(self,val):
print 'set'
self._x = val
@x.deleter
def x(self):
print 'del'
del self._x
f = Foo()
'''正常调用'''
print f._x #1
print f.x #get 1
f.x = 2 #set
print f._x #2
print f.x #get 2
'''利用描述符方法调用特性x'''
print Foo.__dict__['x'].__get__(f) #get 2,与f.x是等价的
print Foo.__dict__['x'].__get__(f,type(f)) #get 2,与f.x是等价的
print f.__getattribute__('x') #get 2,x对象定义了__get__方法,所以调用Foo.__dict__['x'].__get__(f,type(f)),与f.x也是等价的...
'''利用描述符方法给特性赋值'''
Foo.__dict__['x'].__set__(f,3) #set,与f.x = 3等价
print f.x #get 3,x的值改变了...
'''利用fget,fset'''
print Foo.x.fget(f) #get 3,与f.x等价
Foo.x.fset(f,4) #set,与f.x = 4等价
print f.x #get 4
f1 = Foo()
print f1.x,f.x #get 1 get 3
'''虽然特性x为类属性,但是不同实例之间的特性值是互不影响的,因为在这个property对象中get/set/delete的是实例自身的_x属性(实例属性),肯定不一样啊...(有点像废话啊)'''
如果看不懂上述代码的话,建议看下描述符和property的Python模拟实现。
只读属性
class Foo(object):
def __init__(self):
self.__x = 1
@property #x = property(x),这样就创建了一个只读属性
def x(self):
print 'get',
return self.__x
f = Foo()
print f.x #get 1
f.x = 2 #AttributeError: can't set attribute,因为f.set = None
f._Foo__x = 2#但是修改了__x,x的值还是会变的
print f.x #get 2
因为f.x
没有给fset
赋值,fset
为None,在调用__set__
时会触发异常,所以f.x
为只读属性。
set,get,del操作的关联
根据Python的模拟实现可知,
@property:新建一个property对象;
@property def x(self): pass #等价于 x = property(fget = x)
@x.getter:基于特性x创建一个新的property对象,新特性的fget与x.fget不同,其余和特性x一样;
@x.getter def y(self): pass #等价于 y = property(y, x.fset, x.fdel, x.__doc__)
@x.setter:基于特性x创建一个新的property对象,新特性的fset与x.fset不同,其余和特性x一样;
@x.setter def y(self,val): pass #等价于 y = property(x.fget, y, x.fdel, x.__doc__)
@x.deleter:基于特性x创建一个新的property对象,新特性的fdel与x.fdel不同,其余和特性x一样;
@x.deleter def y(self): pass #等价于 y = property(x.fget, x.fset, y, x.__doc__)
综上所述,所谓的操作关联其实就是不断新建特性…
验证
class Foo(object):
def __init__(self):
self.__x = 0
@property #等价于x = property(fget = x)
def x(self):
print 'get',
return self.__x
@x.setter #等价于y = property(x.fget,y,x.fdel,x.__doc__)
def y(self, value):
print 'set',value
self.__x = value
f = Foo()
print f.x #get 0
print f.__dict__ #{'_Foo__x': 0}
print Foo.__dict__ #{'y': , 'x': ...},新建了一个property对象..
print f.y #get,0,y.fget == x.fget
f.y = 2 #set,2
fset参数个数问题
正常情况
class Foo(object):
def __init__(self):
self._x = 1
@property
def x(self):
return self._x
@x.setter
def x(self,val):#参数要为两个,self代表实例,val为'='左侧的数值
self._x = val
f = Foo()
f.x = 10
print f.x #10
x.setter装饰的函数,参数要为两个,不然会出现错误。
以下为错误使用的情况。
参数大于两个
class Foo(object):
def __init__(self):
self._x = 1
@property
def x(self):
return self._x
@x.setter
def x(self,val,val1):#三个参数
self._x = val
f = Foo()
f.x = 10 #error,TypeError: x() takes exactly 3 arguments (2 given),不能正常使用
一个参数
class Foo(object):
def __init__(self):
self._x = 1
@property
def x(self):
return self._x
@x.setter
def x(self):#一个参数
self._x = val
f = Foo()
f.x = 10 #TypeError: x() takes exactly 1 argument (2 given)
print f.x