class MyClass(object):
class_attr = 1
MyClass.__dict__
mappingproxy({'__module__': '__main__', 'class_attr': 1, '__dict__': <attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': None})
每一个类都有一个__dict__
属性,其中包含的是它的所有属性,又称为类属性。
留意类属性的最启一个元素,可以看到代码中定义的属性在其中的体现。
my_instance=MyClass()
my_instance.__dict__
{}
除了与类相关的类属性之外,每一个实例也有相应的属性表(__dict__
),称为实例属性。
当通过实例访问一个属性时,它肯先会尝试在实例属性中查找,如果找不到,则会到类属性中查找。
my_instance.class_attr
1
可以看到实例 my_instance
可以访问类属性 class_attr
。但与读操作有所不同,如果通过实例增加一个属性,
只能改变此实例的属性,对类属性而言并没有丝毫变化。这从下面的代码中可以得到印证。
my_instance.inst_attr='china'
my_instance.__dict__
{'inst_attr': 'china'}
MyClass.__dict__
mappingproxy({'__module__': '__main__', 'class_attr': 1, '__dict__': <attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': None})
那么,能不能给类增加一个属性呢?答案是,能,也不能。说能,是因为每一个 class
也是一个对象,
动态地增减对象的属性与方法正是Python这种动态语言的特性,自然是支持的。
MyClass.class_attr2= 100
my_instance.class_attr2
100
说不能,是因为在Python中,内罝类型和用户定义的类型是有分别的,内置类型并不能够随意地为它增加属性或方法。
# str.new_attr=1
至此,应当理解了,当通过"."操作符访问一个属性时,如果访问的是实例属性,
与直接通过__dict__
属性获取相应的元素是一样的;而如果访问的是类属性,
则并不相同:"."操作符封装了对两种不同属性进行査找的细节。
my_instance.__dict__['inst_attr']
'china'
# my_instance.__dict__['class_attr2']
不过,这里要讲的并不止于此,"."操作符封装了对实例属性和类属性査找的细节, 只讲了一半事实,还有—部分隐而未谈,那就是描述符机制。
# MyClass.__dict__['inst_attr']
# MyClass.inst_attr
现在已经知道访问类属性时,通过__dict__
访问和使用操作符访问是一样的,
但如果是方法,却又不是如此了。
class MyClass(object):
def my_method(self):
print('my_method')
MyClass.__dict__['my_method']
<function __main__.MyClass.my_method(self)>
MyClass.my_method
<function __main__.MyClass.my_method(self)>
甚至它们的类型都不一样!
type(MyClass.my_method)
function
type(MyClass.__dict__['my_method'])
function
这其中作怪的就是描述符了。当通过"."操作符访问时, Python的名字査找并不是之前说的先在实例属性中査找,然后再在类属性中査找那么简单, 实际上,根据通过实例访问 属性和根据类访问属性的不同,有以下两种情况:
一种是通过实例访问,比如代码obj.x,如果x是一个描述符,
那么__getattribute__()
会返回 type(obj).__dict_['x'].__get__(obj,type(obj))
结果,
即: type(obj)
获取 obj
的类型; type(obj).__dict__['x']
返回的是一个描述符,
这里有一个试探和判断的过程;最后调用这个描述符的__get__()
方法。
另一种是通过类访问的情况,比如代码 cls.x
,
则会被_getattribute_()
转换为cls.__dict__['x'].__get__(None,cls)
。
至此,就能够明白 MyClass.__dict__['my_method']
返回的是 function
而不是 instancemethod
了,
原因是没有调用它的__get__()
方法。是否如此呢?怎么验证一下?可以尝试手动调用 __get__()
。
t= f.__get__(None,MyClass)
t
<unbound method MyClass.my_method>
type(t)
<type 'instancemethod'>
MyClass.my_method
<function __main__.MyClass.my_method(self)>
a =MyClass()
a.my_method
<bound method MyClass.my_method of <__main__.MyClass object at 0x7fef381d2990>>
上面例子输出的不同,其实来自于对描述符的__get__()
的调用参数的不同,
当以 obj.x
的形式访问时,调用参数是__get__(obj,type(obj))
;
而以 cls.x
的形式访问时,调用参数是__get__(None,type(obj))
,
这可以通过未绑定方法的 im_self
属性为 None
得到印证。
print(MyClass.my_method.im_self)
None
a.my_method.im_self
<__main__.MyClass object at 0x10277a490>
除此之外,所有对属性、方法进行修饰的方案往往都用到了描述符,
比如 classmethod
、 staticmethod
和 property
等。在这里,
给出 property
的参考实现作为本节的结束,更深入的应用可以进一步参考Python源码中的其他用法。
class Property(object):
"Emulate PyProperty_Type() in Object/descrobject.c"
def __init__(self,fget=None,fset=None,fdel=None,doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
def __get__(self,obj,objtype=None):
if obj is None:
return self
if self.fget in None:
raise AttributeError("unreadable attribute")
return self.fget(obj)
def __set__(self,obj,value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj,value)
def __delete__(self,obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj)