16.3 Python descriptor-property(特性)

Python property

转载请标明出处(http://blog.csdn.net/lis_12/article/details/53469589).

properties,methods, static methods,class methods, and super()都是基于描述符实现的。

建造描述符对象需要一个描述符类,如果只需要一个描述符对象就定义一个类的话,感觉有点坑啊,Python这么机智,肯定有建造描述符对象的简便方法…就是property啦。

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 and del 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 the voltage() 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, and deleter 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, and fdel corresponding to the constructor arguments.

property的简单用例

​ 通常,访问实例或类的属性时,将会返回属性所存储的值。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就是将函数调用伪装成对属性的访问。那么如何利用property将函数调用伪装成对属性的访问呢?

创建property对象的方法:

  1. 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.")
  2. 装饰器;

    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就是建造描述符的一个简便方法,它基于描述符实现,在属性访问时也会自动触发相应的描述符方法。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)

code

  1. 测试特性为类属性还是实例属性

    class Foo(object):
       @property
       def x(self):
           return 1
    f = Foo()
    print f.__dict__   #{}
    print Foo.__dict__ #特性x为类属性,不是实例属性
    '''{'x': <property object at 0x0000000003436688>,
    '__dict__': <attribute '__dict__' of 'Foo' objects>, 
    '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None}'''

  2. 验证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模拟实现。

  3. 只读属性

    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为只读属性。

  4. 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': <property object>, 'x': <property object>...},新建了一个property对象..
    print f.y          #get,0,y.fget == x.fget 
    f.y = 2            #set,2
  5. 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

参考网址

  1. https://docs.python.org/2/howto/descriptor.html#definition-and-introduction
  2. http://www.geekfan.net/7862/
  3. http://blog.csdn.net/lis_12/article/details/53453665
  4. https://docs.python.org/2/library/functions.html#property
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值