Property

上一次,我们简单介绍了一下Descriptor,但是由于Descriptor默认是类属性,如果我们需要更进一步的控制,就是针对每个属性都能够使用__get__, __set__进行访问的话,就需要property来协助了。

Property Introduction

property就是一个特殊的Descriptor,当我们对这个属性进行访问时,将会自动转化为对实例属性的访问(如果调用时提供了实例)。先来看一个例子:

class Foo(object):
    def set_x(self, val):
        print '%s %d' % ('set_x', val)
        self._x = val

    def get_x(self):
        print '%s' % ('get_x')
        return self._x

    x = property(get_x, set_x)

f = Foo()
f.x = 1
print f.x

输出结果将是

 set_x 1
 get_x
 1

property是一个内建函数,该函数的原型是

 property([fget[, fset[, fdel[, doc]]]]) 

我们只提供了fget和fset,因为这是最常用的。

我们可以使用decorator来创建只读的属性,以下是python manual中的一个例子

class Parrot(object):
    def __init__(self):
        self._voltage = 100000


    @property
    def voltage(self):
        """Get the current voltage."""
        return self._voltage

可以提供一个名字为voltage的函数,用decorator装饰后,就变成只读属性了,这样也就不需要显式地使用 voltage = property(get_voltage)来创建property了。

回想一下,我们如果创建一个属性为 a 的非数据描述符的时候,我们还是可以对 a 进行赋值,这是因为,python把值放在了实例的__dict__中。但是这里当我们试图修改voltage时,将会出现如下错误:

 >>> p.voltage = 1
 Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
 AttributeError: can't set attribute
 
Implement property with Python

显然,property返回的描述符是一个数据描述符,并且当我们只提供了get函数的时候,__set__将会抛出AttributeError异常。我们用python来简单实现一下:

class PyProperty(object):
    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc:
            self.__class__.__doc__ = doc
        elif fget.__doc__:
            self.__class__.__doc__ = fget.__doc__
    
    def __set__(self, instance, val):
        if self.fset:
            self.fset(instance, val)
        else:
            raise AttributeError("Can't set attribute")

    def __get__(self, instance, owner):
        if owner:
            if self.fget:
                return self.fget(instance)
            else:
                raise AttributeError("unreadable attribute")
        else:
            return self

    def __delete__(self, instance):
        if self.fdel:
            self.fdel(instance)
        else:
            raise AttributeError("Can't delete attribute")

class Foo3(object):
    def get_x(self):
        print 'get_x'
        return self._x

    def set_x(self, v):
        print 'set_x'
        self._x = v

    def del_x(self):
        print 'del_x'

    x = property(get_x, set_x, del_x)

f = Foo3()
f.x = 1
print f.x
del f.x

print Foo3.x.__doc__

这里,输出将是:

set_x
get_x
1
del_x
Foo3.x

这里输出了正确的结果。

Class scope name space

每次我们要添加一个property的时候,就必须提供getter, setter等函数,但是如果直接在class中存放,很快类中函数将会遍布这些小函数,有一个解决方是利用函数来做name space,把getter, setter等函数都放入一个函数中,代码如下:

class Foo4(object):
    def x():
        # name must be same as property's parameters
        def fget(self):
            print 'get_x'
            return self._x

        def fset(self, v):
            print 'set_x'
            self._x = v
        
        return locals()

    x = property(**x())

f = Foo4()
f.x = 1
print f.x

这里,我们使用了一个函数x在其中定义嵌套函数fget, fset。之后我们利用locals函数返回 x 中局部名称,也就是fget, fset。locals返回一个dictionary,所以我们使用** unpacking operator,将dictionary unpacking后,传递给property。这里getter, setter的函数名字很重要,必须是fget和fset,也就是必须同property定义时的named parameters的名字一样,因为我们传递的是named arguments。

Other Note

细心的人可能已经注意到,我们在set_x, get_x等函数中,都是用了_x,而不是x,原因是因为如果使用x,那么我们就会陷入到递归调用的境地。当我们从外界请求属性x,此时调用了get_x,而get_x又再一次请求x,这样就递归了,所以我们通常取一个其他名字来代替属性的名字。

references

[1] 《Core python programming》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值