Dict
每个实例和类都有相应的__dict__属性,这个属性是一个dictonary。
对于实例,存放实例相关的实例属性等。
对于类,存放类定义中的类属性,函数对象等。
# old-style class class A: def __init__(self): self.a = 1 def f1(self): pass def f2(self): pass # new-style class class B(object): def __init__(self): self.a = 1 def f1(self): pass def f2(self): pass
new style class指的是继承自object的类,而old style class则不继承自object. 这两种类python的处理方式有所不同。
我们先来看A.__dict__和B.__dict__的区别。
# A.__dict__ {'f1': <function f1 at 0x02785330>, '__module__': '__main__', 'f2': <function f2 at 0x02785570>, '__init__': <function __init__ at 0x02785530>, '__doc__': None} # B.__dict__ {'f1': <function f1 at <0x027854B0>, '__module__': '__main__', 'f2': <function f2 at 0x027855B0>, '__dict__': <attribute '__dict__' of 'B' objects>, '__weakref__': <attribute '__weakref__' of 'B' objects>, '__doc__': None, '__init__': <function __init__ at 0x027853F0>}
我们可以通过dir函数来获取类和实例的属性,dir将会提供更加详细的信息。对于实例,不仅给出实例的属性,而且还给出类的属性(包括类的成员属性,成员函数定义)。
另外一个函数vars,只给出实例的属性。
Member Attribute
The time before descriptor
当我们经由构造函数__init__来对一个成员进行初始化的时候,python会把一个成员变量存放在__dict__中。我们可以由如下代码验证
>>> class A(object): pass ... >>> a = A() >>> print vars(a) {} >>> a.foo = 1 >>> print a.__dict__ {'foo': 1}
经过a.foo赋值后,__dict__中多出了一项,这个就是我们通过直接添加成员变量所致。
NOTE:
python的class是对外部开放的,没有必要直接在__init__中初始化成员。这就是动态语言的好处。
What is descriptor
在现在的python中,函数,成员变量等都是用descriptor来实现的。
descriptor分为
数据描述符的定义是拥有__set__、__get__接口的类,而非数据描述符是指只实现了__get__方法。
另外一个可选实现的接口是__delete__。
descriptor可以用来封装对一个属性的访问。
我们来看一个加入descriptor对于一个实例的影响。
以下是descriptor的定义,descriptor是类的静态属性
class AttrFoo(object): def __set__(self, instance, value): print 'set AttrFoo' self.value = value def __get__(self, instance, type=None): print 'get AttrFoo' return self.value class Foo(object): a = AttrFoo()
来看一下访问代码
>>> f = Foo() >>> f.a = 1 set AttrFoo >>> f.a get AttrFoo 1 >>> print f.__dict__ {}
可以看到,加入了descriptor后,python会先调用我们的set, get函数,最后再对属性做处理。这样我们就有了更多的控制权。__dict__在赋值后也没有改变。
如果我们将descriptor a改为一个非数据描述符,并且对其进行赋值会怎么样呢?
>>> del AttrFoo.__set__ # delete the __set__ from AttrFoo.__dict__ >>> f.a = 1 >>> f.__dict__ {'a': 1}
这里我们直接通过del将__set__函数删除,使其成为一个非数据描述符,也可以使用delattr来删除,这个调用是等价的。
在成为非数据描述符后,我们给f.a = 1赋值成功,此时直接在__dict__加入了一项。
这里,我们给出一段python的伪代码,用来描述整个过程:
# 以下代码摘自《Expert python programming》,page77 # 1- looking for definition if hasattr(MyClass, 'attribute'): attribute = MyClass.attribute AttributeClass = attribute.__class__ # 2 - does attribute definition has a setter ? if hasattr(AttributeClass, '__set__'): # let's use it AttributeClass.__set__(attribute, instance, value) return # 3 - regular way instance.__dict__['attribute'] = value
对于读取一个属性,将会有所不同,如果一个属性是非数据描述符,那么将会先从__dict__中获取,如果没有找到,则再去调用非数据描述符的__get__函数。
class AttrFoo2(object): def __get__(self, inst, type=None): return 1 class Foo2(object): a = AttrFoo2()
测试代码如下:
>>> f = Foo2() >>> f.a 1 >>> f.__dict__['a'] = 2 >>> f.a 2
当我们在f.__dict__中加入了a属性后,python就直接从__dict__中获取该值了。
读取一个属性的伪代码如下:
1 # when read an attribute from a class instance 2 3 # check if there's a data descriptor named 'attribute' 4 if hasattr(MyClass, 'attribute'): 5 klsAttr = MyClass.attribute.__class__ 6 if hasattr(klsAttr, '__set__'): 7 if hasattr(klsAttr, '__get__'): 8 return klsAttr.__get__(attribute, instance, value) 9 else: 10 return attribute 11 12 # we check against the __dict__ of the instance 13 if hasattr(obj, 'attribute'): 14 return obj.__dict__['attribute'] 15 16 if hasattr(klsAttr, '__get__'): 17 return klsAttr.__get__(attribute, instance, value) 18 19 raise AttributeError('cannot find "attribute"')
Descriptor Precedence
这里我们给出不同的descriptor和其他一些重要的属性之间的优先级关系。
数据描述符 > 实例属性 > 非数据描述符
Descriptor是类属性,也就是说,每个类只有这样一个Descriptor,所以Descriptor适合用来做类级别的事。比如可以用来做为一个Cache。
下次我们要讲Property,而Property是作为实例的Descriptor使用的。
reference: