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

通用特性:

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

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

这里需要注意的是:isinstance() 函数对于子类的实例也会返回 True,所以如果想精确判定,请使用 is 。

__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__ 也可以是一个字符串,如果这样定义的话这个类就只允许有这一个属性。

特殊方法 __getattribute__()

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

__getattribute__() 在搜寻属性时有如下优先级:

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

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

描述符(descriptor):

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

  • 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>

你可能感兴趣的:(python,描述符)