Python 新式类的一些高级特性(描述符)

通用特性:

Python 2.2 起类型和类获得了统一,标准的数据类型开始能够子类化,不过也所以,本来的转换函数如今都变成了工厂函数。如 int() 、list() 等,他们的行为变成了生成一个类型对象的实例,虽然用起来还像个函数似的。这带来的一个小的便利之处是,对变量的类型判断有了更好的实现方法:python

>>> type(123) is int
True
>>> isinstance(123,int)
True
>>> isinstance(123.0,(int,float))
True

这里须要注意的是:isinstance() 函数对于子类的实例也会返回 True,因此若是想精确断定,请使用 is 。shell

__slots__类属性:

每一个类对象中都有个 __dict__ 属性,他使用字典来存储这个对象内全部(可写的)属性。安全

__slots__ 是一个序列类型的对象,他能够是列表、元组,或其余可迭代对象。当类属性中定义了 __slots__ 时,__dict__ 会被排斥而不存在。__slots__ 里面存储的也是类的属性,他和 __dict__ 的区别在于:函数

  • 不使用字典,从而更加节省内存
  • 禁止给类的实例动态增长 __slots__ 中没有定义的属性,从而提供某种意义上的安全(运行时修改 __slots__ 也没用)
class C(object):
    __slots__=['a']
	
>>> c=C()
>>> c.a=1
>>> c.b=0
Traceback (most recent call last):
  File "<pyshell#196>", line 1, in <module>
    c.b=0
AttributeError: 'C' object has no attribute 'b'

注:__slots__ 也能够是一个字符串,若是这样定义的话这个类就只容许有这一个属性。ui

特殊方法 __getattribute__()

在上一篇的“受权”中曾提到过一个 __getattr__() 特殊方法,它仅在找不到属性时被调用。而这里的 __getattribute__() 则老是被调用。当两者同时存在时,除非 __getattribute__() 明确调用后者,或其引起了 AttributeError,不然 __getattr__() 不会被调用。3d

__getattribute__() 在搜寻属性时有以下优先级:代理

  1. 类属性
  2. 数据描述符
  3. 实例属性
  4. 非数据描述符
  5. 默认的 __getattr__()

这个顺序的具体应用和描述符的解释在后面:code

描述符(descriptor):

前面提到的 __getattr__() 和 __getattribute__() 均可以用来将对对象的属性访问重定向到其余地方。而描述符的做用则是,自定义“对象的属性访问”行为自己。描述符“描述”的是对象的属性——将对属性的访问(get)、赋值(set)和删除(delete)行为代理起来,由三个函数来从新实现。若是只设置了 getter,则这是一个只读属性。所以,描述符其实是一个实现了 __get__(), __set__(), __delete__() 这三个特殊方法(描述符协议)的类,并被赋值给某个对象的属性。orm

  • object.__get__(self,instance,owner)
  • object.__set__(self,instance,value)
  • object.__delete__(self,instance)

上面的参数里,self 都是指描述符自己,owner 是包含描述符为某个属性的那个对象,instance 是 owner 的实例。这些参数由解释器自动传递,没有就传 None。函数体由用户自定义,此处能够妥善使用三个参数(self, instance, owner)。因为这里的描述符是 owner 的类属性,因此若是你使用 self.xxx 来存储属性值的话,这个属性也会被存成类属性,即你全部的实例都将访问同一个描述符:对象

class Descriptor(object):

    def __init__(self):
        self.d_name = ''

    def __get__(self, instance, owner):
        return self.d_name

    def __set__(self, instance, name):
        self.d_name = name.title()

    def __delete__(self, instance):
        del self.d_name

class Person(object):
    name = Descriptor()

这里咱们使用描述符的方式,是保存一个字符串,并把首字母大写,实际描述符能够作得更多。运行以下:

>>> a = Person()
>>> a.name = 'adam'
>>> a.name
'Adam'
>>> b = Person()
>>> b.name
'Adam'

这就是问题所在,若是想把描述符用做实例属性而不是类属性的话,使用 instance 参数代替 self 就行了。

property()

除了上面这种使用类来实现描述符的方式外,Python 还提供了一个 property() 内建函数。本函数接受【fget(), fset(), fdel(), doc】三个函数和一个文档字符串做为参数,并返回一个 property 对象(描述符)。描述符协议由那三个函数参数来实现:

property(fget=None,fset=None,fdel=None,doc=None)

fget(self), fset(self,val), fdel(self) 这三个函数都默认接受 self 参数,这指的是实现 property 属性的类的实例,fset 还多接受一个 val 参数。因此通常来讲,property 用来实现实例属性,除非你刻意使用 type(self) 这样的语句来访问类属性:

class Person(object):
    def fget(self):
        return self.val
    def fset(self,val):
        self.val = val
    name = property(fget,fset,doc='its a name property.')

运行以下:

>>> a = Person()
>>> a.name = 'John'
>>> a.name
'John'
>>> Person.name.__doc__
'its a name property.'
>>> a.val
'John'

这里没办法在实例里如‘a.name.__doc__’般引用文档字符串是由于‘a.name’会直接调用 fget() 。另外这种在类里面定义三个函数的方法有可能带来污染命名空间的困扰,所以在 ASPN上曾有人提出过一种解决方法,仍是上面那个 Person 的例子:

class Person(object):
    def name():        
        def fget(self):
            return self.val
        def fset(self,val):
            self.val = val    
        return locals()
		
    name = property(**name())

运行结果和上面的同样。或者还有一种方法,就是使用 property 提供的装饰器:

class Person(object):
    @property
    def name(self):
        return self.val

    @name.setter
    def name(self,val):
        self.val = val

    @name.deleter
    def name(self):
        del self.val

这里第一个被 @property 装饰的是 fget 函数,随后使用 name 的 setter 和 deleter 方法来装饰 fset 和 fdel。其实 property.getter() 方法也是有的,不过先定义 getter 比较方便(由于是第一个参数,没必要使用关键字参数来传),并且 getter 实现的机会比另两个更多。来看一下装饰器的 getter 和 setter:

>>> Person.name.setter
<built-in method setter of property object at 0x0000000009E4D5E8>
>>> Person.name.getter
<built-in method getter of property object at 0x0000000009E4D5E8>
相关文章
相关标签/搜索