首页 > 代码库 > Python——管理属性(1)

Python——管理属性(1)

管理属性
这里将展开介绍前面提到的【属性拦截】技术。包含下面内容:

【1】__getattr__和__setattr__方法。把没有定义的属性获取和全部的属性赋值指向通用的处理器方法
【2】__getattribute__方法,把全部属性获取都指向一个泛型处理器
【3】property内置函数。把特定属性訪问定位到get和set处理器函数,也叫做特性(property)
【4】描写叙述符协议,把特定属性訪问定位到具有随意get和set处理器方法的类的实例

最后两种方法适用于特定属性。而前面两种方法足够通用。

本篇先介绍后面两种方法。

===============================================================================

特性property
特性协议同意我们把一个特定属性的get和set操作指向我们所提供的函数或方法,使得我们可以插入在属性訪问的时候自己主动执行的代码,拦截属性删除。

通过property内置函数来创建特性并将其分配给类属性。就像方法函数一样。一个特性管理一个单个的、特定的属性。虽然他不能广泛地捕获全部的属性訪问。但它同意我们控制訪问和赋值操作,而且同意我们自由地把一个属性从简单的数据改变为一个计算,而不会影响已有的代码。



特性和描写叙述符有非常大的关系。它们基本上是描写叙述符的一种受限制的形式。

我们能够通过把一个内置函数的结果赋给一个类属性来创建一个特性:

attrbute = property(fget, fset, fdel, doc)
这个内置函数的參数都没必要的,而且假设没有传递參数的话,全部都取默认值None,那么这种操作是不受支持的。
当使用它们的时候。我们向fget传递一个函数来拦截属性訪问。给fset传递一个函数进行赋值,而且给fdel传递一个函数进行属性删除;doc參数接收该属性的一个文档字符串,假设想要的话。

fget返回计算过的属性值,而且fset和fdel不会返回数据,返回None。

------------------------------------------------------------------------------------------------------------------------------------------

第一个样例
例如以下的类使用了一个特性来记录对一个名为name的属性的方法。实际存储的数据名为_name。以便不会和特性搞混了:

class Person:
    def __init__(self,name):
        self._name = name
    def getName(self):
        print('fetch...')
        return self._name
    def setName(self,value):
        print('change...')
        self._name = value
    def delName(self):
        print('remove...')
        del self._name
    name = property(getName,setName,delName,'name property docs')

bob = Person('Bob Smith')
print(bob.name)
bob.name = 'Robert Smith'
print(bob.name)
del bob.name

print('-'*20)
sue = Person('Sue Jones')
print(sue.name)
print(Person.name.__doc__)
这个特定的特性所做的事情并不多——它仅仅是拦截并跟踪了一个属性。两个实例继承了该特性,就好像它们是附加到其类的另外两个属性一样。

然而,捕获了它们的属性訪问:

fetch...
Bob Smith
change...
fetch...
Robert Smith
remove...
--------------------
fetch...
Sue Jones
name property docs
就像全部的类属性一样,实例和较低的子类都继承特性。假设我们把样例改动为例如以下所看到的:
class Super:
    def __init__(self,name):
        self._name = name
    def getName(self):
        print('fetch...')
        return self._name
    def setName(self,value):
        print('change...')
        self._name = value
    def delName(self):
        print('remove...')
        del self._name
    name = property(getName,setName,delName,'name property docs')

class Person(Super):
    pass

bob = Person('Bob Smith')
print(bob.name)
bob.name = 'Robert Smith'
print(bob.name)
del bob.name

print('-'*20)
sue = Person('Sue Jones')
print(sue.name)
print(Person.name.__doc__)
输出是同样的。Person子类从Super继承了name特性,而且bob实例从Person获取了它。

------------------------------------------------------------------------------------------------------------------------------------------

计算的属性
上述样例仅仅是简单了跟踪了属性訪问。然而,通常特性做的很多其它——比如。当获取属性的时候。动态地计算属性的值。看下述样例:

class PropSquare:
    def __init__(self,start):
        self.value = http://www.mamicode.com/start>这个样例定义了一个X属性,而且将其当做静态数据一样訪问,但实际执行的代码在获取该属性的时候计算了它的值。

------------------------------------------------------------------------------------------------------------------------------------------

使用装饰器编写特性
函数装饰器的语法是:

@decorator
def func(args):...
Python会自己主动将其翻译成对等的形式。把函数名又一次绑定到可调用的decorator的返回结果上:
def func(args):...
func = decorator(func)
因为这一映射,证实了内置函数property能够充当一个装饰器,来定义一个函数,当获取一个属性的时候自己主动执行该函数:
class Person:
	@property
	def name(self):...
执行的时候,装饰的方法自己主动传递给property内置函数的第一个參数(即fget)。这事实上仅仅是创建一个特性并手动绑定属性名的一种替代语法:
class Person:
	def name(self):...
	name = property(name)
实际上,property对象也有getter、setter和deleter方法。这些方法指定对应的特性訪问器方法而且返回特性自身的一个副本。

看下述样例:

class Person:
    def __init__(self, name):
        self._name = name

    @property
    def name(self):
        "name property docs"
        print('fetch...')
        return self._name

    @name.setter
    def name(self,value):
        print('change...')
        self._name = value

    @name.deleter
    def name(self):
        print('remove...')
        del self._name

bob = Person('Bob Smith')
print(bob.name)
bob.name = 'Robert Smith'
print(bob.name)
del bob.name

print('-'*20)
sue = Person('Sue Jones')
print(sue.name)
print(Person.name.__doc__)
这段代码等同于上文写的第一个样例,在这个样例中,装饰仅仅是编写特性的一种替代方法。输出结果是同样的:
fetch...
Bob Smith
change...
fetch...
Robert Smith
remove...
--------------------
fetch...
Sue Jones
name property docs
和property手动赋值的结果相比,这个样例中,使用装饰器来编写特性仅仅须要3行额外的代码。所以使用装饰器更加方便。

===============================================================================

描写叙述符
描写叙述符提供了拦截属性訪问的一种替代方法,它们与特性有非常大关系。实际上,特性是描写叙述符的一种——从技术上讲,property内置函数仅仅是创建一个特定类型的描写叙述符的一种简化方式。而这样的描写叙述符在属性訪问时执行方法函数。

和特性一样,描写叙述符也管理一个单个的、特定的属性。



描写叙述符作为独立的类创建,而且针对想要拦截的属性訪问操作提供特定命名的訪问器方法——当以对应的方式訪问分配给描写叙述符类实例的属性时。描写叙述符类中的获取、设置和删除等方法自己主动执行:

class Descriptor:
	"docstring goes here"
	def __get__(self, instance, owener):... # Return attr value
	def __set__(self, instance, value):...  # Return noting(None)
	def __del__(self,instance):...		# Return noting(None)
带有不论什么这些方法的类都能够看做是描写叙述符。而且当它们的一个实例分配给还有一个类的属性的时候,它们的这些方法是特殊的——当訪问属性的时候,会自己主动调用它们。假设这些方法中的不论什么一个空缺,通常意味着不支持对应类型的訪问。

然而,和特性不同,省略一个__set__意味着同意这个名字在一个实例中又一次定义,因此,要使得一个属性是仅仅读的。我们必须定义__set__来捕获赋值并引发一个异常。

------------------------------------------------------------------------------------------------------------------------------------------

描写叙述符方法參数

__get__、__set__、__del__三种描写叙述符方法都传递了描写叙述符实例(self)以及描写叙述符实例所附加的客户类的实例。__get__訪问方法还额外地接收一个owner參数。指定了描写叙述符实例要附加到的类。

当中insatance參数要么是訪问的属性所属的实例(用于instance.attr),要么当所訪问的属性直接属于类的时候是None(用于class.attr)。

前者通常针对实例訪问计算一个值。假设描写叙述符对象訪问是受支持的,后者通常返回self。


比如,在以下的样例中,当获取X.attr的时候,Python自己主动执行Descriptor类的__get__方法,Subject.attr类属性分配给该方法:

>>> class Descriptor(object):
	def __get__(self,instance,owner):
		print(self,instance,owner,sep='\n')

		
>>> class Subject:
	attr = Descriptor()

	
>>> X = Subject()
>>> X.attr
<__main__.Descriptor object at 0x032C0F70>
<__main__.Subject object at 0x032C0FF0>
<class '__main__.Subject'>

>>> Subject.attr
<__main__.Descriptor object at 0x032C0F70>
None
<class '__main__.Subject'>
注意在第一个属性获取中自己主动传递到__get__方法中的參数,当获取X.attr的时候。就好像发生了例如以下转换:
X.attr --> Descriptor.__get__(Subject.attr, X, Subject)
当描写叙述符的实例參数为None的时候,该描写叙述符知道将直接訪问它。

------------------------------------------------------------------------------------------------------------------------------------------

仅仅读描写叙述符
例如以下所看到的,使用特性忽略set方法时,就无法对该属性赋值了。

这样能够让属性成为仅仅读的。

>>> class A :
	def __init__(self,name):
		self.name = name

	def getName(self):
		return self.name
	name = property(getName)

	
>>> a = A(1)
Traceback (most recent call last):
  File "<pyshell#25>", line 1, in <module>
    a = A(1)
  File "<pyshell#23>", line 3, in __init__
    self.name = name
AttributeError: can't set attribute
可是。描写叙述符和特性不同,省略__set__方法不足以让属性称为仅仅读的,由于描写叙述符名称能够赋给一个实例。在以下的样例中,对X.a的属性赋值在实例对象X中存储了a,由此,隐藏了存储在类C中的描写叙述符:

>>> class D:
	def __get__(*args):
		print('get')

		
>>> class C:
	a = D()

	
>>> X = C()
>>> X.a
get
>>> C.a
get
>>> X.a = 99
>>> X.a
99
>>> list(X.__dict__.keys())
['a']
>>> Y = C()
>>> Y.a
get
>>> C.a
get
这就是Python中全部的实例属性赋值工作的方式。而且它同意在它们的实例中类选择性地覆盖类级默认值。


要让基于描写叙述符的属性成为可读的,捕获描写叙述符类中的赋值并引发一个异常来阻止属性赋值——当要赋值的属性是一个描写叙述符的时候,Python有效地绕过了常规实例层级的赋值行为,而且把操作指向描写叙述符对象:

>>> class D:
	def __get__(*args):
		print('get')
	def __set__(*args):
		raise AttributeError('cannot set')

	
>>> class C:
	a = D()

	
>>> X = C()
>>> X.a
get
>>> X.a = 99
Traceback (most recent call last):
  File "<pyshell#35>", line 1, in <module>
    X.a = 99
  File "<pyshell#29>", line 5, in __set__
    raise AttributeError('cannot set')
AttributeError: cannot set
------------------------------------------------------------------------------------------------------------------------------------------

第一个演示样例
如今让我们来改动前面为特性编写的第一个样例。例如以下代码定义了一个描写叙述符,来拦截对其客户类中的名为name的一个属性的訪问。

其方法使用它们的instance參数来訪问主体实例中的状态信息:

class Name:
    "name descriptor docs"
    def __get__(self,instance,owner):
        print('fetch...')
        return instance._name
    def __set__(self,instance,value):
        print('change...')
        instance._name = value
    def __delete__(self,instance):
        print('remove...')
        del instance._name

class Person:
    def __init__(self,name):
        self._name = name
    name = Name()

bob = Person('Bob Smith')
print(bob.name)
bob.name = 'Robert Smith'
print(bob.name)
del bob.name

print('-'*20)
sue = Person('Sue Jones')
print(sue.name)
print(Name.__doc__)
执行结果例如以下,和之前的一模一样:
fetch...
Bob Smith
change...
fetch...
Robert Smith
remove...
--------------------
fetch...
Sue Jones
name descriptor docs
当描写叙述符的__get__方法执行的时候。它传递了3个对象来定义其上下文:
【1】self是Name的实例
【2】instance是Person类实例
【3】owner是Person类实例
还和特性演示样例中类似。描写叙述符实例是一个类属性。而且因此由客户类和不论什么子类的全部实例继承。

假设我们把演示样例中的Person类改动为例如以下的样子,脚本的输出是同样的:

...
class Super:
	def __init__(self,name):
		self._name = name
	name = Name()

class Person(Super):
	pass
...
当然,这个样例也仅仅是追踪了属性的訪问。
------------------------------------------------------------------------------------------------------------------------------------------

计算属性
相同的,也能够用来在每次获取属性的时候计算它们的值,例如以下所看到的:

class DescSquare:
    def __init__(self,start):
        self.value = http://www.mamicode.com/start>执行结果例如以下:

9
16
1024
------------------------------------------------------------------------------------------------------------------------------------------

在描写叙述符中使用状态信息
在上述两个样例中,你可能会发现它们从不同的地方获取信息——第一个样例使用存储在客户实例中的数据,第二个样例使用附加到描写叙述符对象本身的数据。实际上,描写叙述符能够使用实例状态和描写叙述符状态,或者二者的不论什么组合:
【1】描写叙述符状态用来管理内部用于描写叙述符工作的数据
【2】实例状态记录了和客户类相关的信息。以及可能由客户类创建的信息
------------------------------------------------------------------------------------------------------------------------------------------

特性和描写叙述符是怎样相关的
特性和描写叙述符有非常强的相关性——property内置函数仅仅是创建描写叙述符的一种方便方式。

既然已经知道了二者是怎样工作的,我们能够使用例如以下的一个描写叙述符类来模拟property内置函数:

class Property:
    def __init__(self,fget=None,fset=None,fdel=None,doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        self.__doc__ = doc

    def __get__(self,instance,instancetype=None):
        if instance is None:
            return self
        if self.fget is None:
            raise AttributeError("can't get attribute")
        return self.fget(instance)

    def __set__(self,instance,value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(instance,value)

    def __delete__(self,instance):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(instance)

class Person:
    def getName(self):...
    def setName(self,value):...
    name = Property(getName,setName)
这个Property类捕获了带有描写叙述符协议的属性訪问,而且把请求定位到创建类的时候在描写叙述符状态中传入和保存的函数或方法。


Python——管理属性(1)