Python-属性控制与描述符

原文地址:http://hi.baidu.com/_yuan0518/blog/item/803d8fd24e30fc20970a160c.html

Python-属性控制与描述符

1.__dict__与__slots__
字典位于类与实例的“心脏”。__dict__属性跟踪所有实例属性。举例来说,你有一个实例inst,它有一个属性foo,那使用inst.foo 来访问它与使用inst.__dict__['foo']来访问是一致的。class AClass():     A = 0         def __init__(self):         self.x = 'x'         self.y = 'y'         self.z = 'z'  inst = AClass()  print AClass.__dict__ print inst.__dict__  inst.w = 'w' print inst.__dict__ 

输出:
{'A': 0, '__module__': '__main__', '__doc__': None, '__init__': <function __init__ at 0xb736ad4c>}
{'y': 'y', 'x': 'x', 'z': 'z'}
{'y': 'y', 'x': 'x', 'z': 'z', 'w': 'w'}

字典会占据大量内存,如果你有一个属性数量很少的类,但有很多实例,那么正好是这种情况。为内存上的考虑,可以使用__slots__属性来替代__dict__。基本上,__slots__是一个类变量,由一序列型对象组成,由所有合法标识构成的实例属性的集合来表示。它可以是一个列表,元组或可迭代对象。也可以是标识实例能拥有的唯一的属性的简单字符串。带__slots__属性的类定义不会存在__dict__,且任何试图创建一个其名不在__slots__中的名字的实例属性都将导致AttributeError 异常:

class SlottedClass(object):      A = 0      __slots__ = ('x', 'y', 'z')                def __init__(self):          self.x = 'x'          self.y = 'y'          self.z = 'z'    inst = SlottedClass()  print SlottedClass.__dict__ 

输出:
{'A': 0, '__module__': '__main__', '__slots__': ('x', 'y', 'z'), 'y': <member 'y' of 'SlottedClass' objects>, 'x': <member 'x' of 'SlottedClass' objects>, 'z': <member 'z' of 'SlottedClass' objects>, '__doc__': None, '__init__': <function __init__ at 0xb752417c>}

print inst.__dict__ 

输出:
Traceback (most recent call last):
  File "/home/zhangjun/workspace/try/src/try.py", line 15, in <module>
    print inst.__dict__
AttributeError: 'SlottedClass' object has no attribute '__dict__'

inst.w = 'w' 

输出:
Traceback (most recent call last):
  File "/home/zhangjun/workspace/try/src/try.py", line 15, in <module>
    inst.w = 'w'
AttributeError: 'SlottedClass' object has no attribute 'w'

这种特性的主要目的是节约内存。其副作用是某种类型的"安全",它能防止用户随心所欲的动态增加实例属性。

2.__getattr__()与__getattribute__()
Python 类有一个名为__getattr__()的特殊方法,它仅当属性不在实例的__dict__或它的类的__dict__,或者祖先类的__dict__中找到时,才被调用。__getattribute__()使用起来类似__getattr__(),不同之处在于,当属性被访问时,它就一直都可以被调用,而不局限于不能找到的情况。

如果类同时定义了__getattribute__()及__getattr__()方法,除非明确从__getattribute__()里调用,或__getattribute__()引发了AttributeError 异常,否则后者不会被调用。

3.描述符__get__(),__set__()和__delete__()
描述符是Python 新式类中的关键点之一。它为对象属性提供强大的API。你可以认为描述符是表示对象属性的一个代理。当需要属性时,可通过描述符(如果有)或者采用常规方式(句点属性标识法)来访问它。严格来说, 描述符实际上可以是任何(新式) 类, 这种类至少实现了三个特殊方法__get__(),__set__()及__delete__()中的一个,这三个特殊方法充当描述符协议的作用。__get__(),__set__()及__delete__()的原型,如下:
def __get__(self, obj, typ=None) ==> value
def __set__(self, obj, val) ==> None
def __delete__(self, obj) ==> None

整个描述符系统的心脏是__getattribute__(),因为对每个属性的实例都会调用到这个特殊的方法。举例来说,给定类X 和实例x, x.foo 由__getattribute__()转化成:

type(x).__dict__['foo'].__get__(x, type(x))

如果类调用了__get__()方法,那么None 将作为对象被传入(对于实例, 传入的是self):

X.__dict__['foo'].__get__(None, X)

最后,如果super()被调用了,比如,给定Y 为X 的子类,然后用super(Y,obj).foo 在obj.__class__.__mro__中紧接类Y 沿着继承树来查找类X,然后调用:

X.__dict__['foo'].__get__(obj, X)

__getattribute__()方法查询属性的优先级:
类属性
数据描述符   #由__get__(),__set__()及__delete__()定义
实例属性    #由示例的__dict__跟踪
非数据描述符  #函数定义
默认为__getattr__()

示例一展示__get__与__set__的调用过程:

class PropertyClass(object):      def __get__(self, obj, typ=None):          print 'call __get__, obj as %r, type as %r' % (obj, typ)           def __set__(self, obj, val):          print 'call __set__, obj as %r, val as %r' % (obj, val)    class MyClass(object):      foo = PropertyClass()    inst = MyClass()  inst.foo = 'x'  inst.foo 

输出:
call __set__, obj as <__main__.MyClass object at 0xb733a84c>, val as 'x'
call __get__, obj as <__main__.MyClass object at 0xb733a84c>, type as <class '__main__.MyClass'>

示例二展示数据描述符与示例属性的优先级关系,当设置实例的__dict__后,使用点操作符访问时,仍然无法拿到__dict__中赋的值,因为首先会访问数据描述符:

class PropertyClass(object):        def __get__(self, obj, typ=None):          print 'call __get__, obj as %r, type as %r' % (obj, typ)           def __set__(self, obj, val):          print 'call __set__, obj as %r, val as %r' % (obj, val)    class MyClass(object):      foo = PropertyClass()    inst = MyClass()  inst.foo = 'bar'  print 'print inst.foo as %r' % inst.foo    inst.__dict__['foo'] = 'no-bar'  print "print inst.foo as %r but has inst.__dict__['foo'] as %r" % (inst.foo, inst.__dict__['foo'])  

输出:
call __set__, obj as <__main__.MyClass object at 0xb752f92c>, val as 'bar'
call __get__, obj as <__main__.MyClass object at 0xb752f92c>, type as <class '__main__.MyClass'>
print inst.foo as None
call __get__, obj as <__main__.MyClass object at 0xb752f92c>, type as <class '__main__.MyClass'>
print inst.foo as None but has inst.__dict__['foo'] as 'no-bar'

示例三展示实例属性与非数据描述符的优先级关系,当设置实例的__dict__后,就覆盖了原有的非数据描述符定义:

def callFoo():      print 'call by function callFoo()'    class MyClass(object):      def foo(self):          print 'call by function self.foo()'    inst = MyClass()  print inst.__dict__  inst.foo()    inst.foo = callFoo  print inst.__dict__  inst.foo() 

输出:
{}
call by function self.foo()
{'foo': <function callFoo at 0xb7511d4c>}
call by function callFoo()

4.property()内建函数
property()内建函数有四个参数,它们是 :
property(fget=None, fset=None, fdel=None, doc=None)
property()的一般用法是,将它写在一个类定义中,property()接受一些传进来的函数作为参数。你不必写一个描述符类,并在其中定义你要调用的这些方法。只要把你写的函数全部传递给property()就可以了:

from math import pi    def get_pi(dummy):      return pi    class PI(object):      pi = property(get_pi, doc='Constant "pi"')    inst = PI()  print inst.pi  print PI.pi.__doc__ 

输出:
3.14159265359
Constant "pi"

下面例子展示类如何建立一个只读属性,它只在初始化时被赋值一次,,用逐位异或操作符将它隐藏起来:

class ProtectAndHideX(object):      def __init__(self, x):          assert isinstance(x, int), '"x" must be an integer!'          self.__x = ~x               def get_x(self):          return ~self.__x           x = property(get_x) 

>>> inst = ProtectAndHideX('foo')
Traceback (most recent call last):
  File "/home/zhangjun/workspace/try/src/try.py", line 13, in <module>
    inst = ProtectAndHideX('foo')
  File "/home/zhangjun/workspace/try/src/try.py", line 5, in __init__
    assert isinstance(x, int), '"x" must be an integer!'
AssertionError: "x" must be an integer!

>>> inst = ProtectAndHideX(10)
>>> print 'inst.x =', inst.x
inst.x = 10
>>> inst.x = 20
Traceback (most recent call last):
  File "/home/zhangjun/workspace/try/src/try.py", line 15, in <module>
    inst.x = 20
AttributeError: can't set attribute

下面是另一个关于setter 的例子:

class HideX(object):      def __init__(self, x):          self.x = x               def get_x(self):          return ~self.__x           def set_x(self, x):          assert isinstance(x, int), '"x" must be an integer!'          self.__x = ~x           x = property(get_x, set_x) 

>>> inst = HideX('foo')
Traceback (most recent call last):
  File "/home/zhangjun/workspace/try/src/try.py", line 16, in <module>
    inst = HideX('foo')
  File "/home/zhangjun/workspace/try/src/try.py", line 5, in __init__
    self.x = x
  File "/home/zhangjun/workspace/try/src/try.py", line 11, in set_x
    assert isinstance(x, int), '"x" must be an integer!'
AssertionError: "x" must be an integer!

>>> inst = HideX(10)
>>> print 'inst.x =', inst.x
inst.x = 10
>>> inst.x = 20
>>> print 'inst.x =', inst.x
inst.x = 20

这里其实可以用inst.set_x(30)来set属性,如何控制代码只能通过点操作符进行属性赋值:

class HideX(object):      def __init__(self, x):          self.x = x           def x():          def fget(self):              return ~self.__x               def fset(self, x):              assert isinstance(x, int), '"x" must be an integer!'              self.__x = ~x               return locals()      x = property(**x()) 

>>> inst = HideX(10)
>>> 'inst.x =', inst.x
inst.x = 10
>>> inst.x = 20
>>> 'inst.x =', inst.x
inst.x = 20
>>> inst.set_x(30)
Traceback (most recent call last):
  File "/home/zhangjun/workspace/try/src/try.py", line 22, in <module>
    inst.set_x(30)
AttributeError: 'HideX' object has no attribute 'set_x'

这里两点明显不同:(1) 类的名字空间更加简洁,只有 ['__doc__','__init__', '__module__', 'x'], (2), 用户不能再通过inst.set_x(40) 给属性赋值,必须使用init.x = 30。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值