在 Python 中,数据的属性和处理数据的方法统称属性(attribute)。其实,方法只是可调用的属性。除了这二者之外,我们还可以创建特性(property),在不改变类接口的前提下,使用存取方法(即读值方法和设值方法)修改数据属性

property

property可以把一个实例方法变成其同名属性,以支持.号访问,它亦可标记设置限制,加以规范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class A:
def __init__(self):
self._t1=0
@property
def t1(self):
print('getter')
return self._t1

@t1.setter
def t1(self,value):
print('setter')
self._t1 = value


a = A()
a.t1=123
print(a.t1)
###
setter
getter
123

也可以老式写法
class A:
def __init__(self):
self._t1=0

def t1_getter(self):
print('getter')
return self._t1

def t1_setter(self,value):
print('setter')
self._t1 = value
t1 = property(t1_getter,t1_setter)

除了特性,Python 还提供了丰富的 API,用于控制属性的访问权限,以及实现动态属性。使用点号访问属性时(如 obj.attr),Python 解释器会调用特殊的方法(如 __getattr____setattr__)计算属性。用户自己定义的类可以通过 __getattr__ 方法实现“虚拟属性”,当访问不存在的属性时(如 obj.no_such_attribute),即时计算属性的值。

attr

__dict__

  一个映射,存储对象或类的可写属性。有 __dict__ 属性的对象,任何时候都能随意设置新属性。如果类有 __slots__ 属性,它的实例可能没有 __dict__ 属性。

__slots__

  类可以定义这个这属性,限制实例能有哪些属性。__slots__ 属性的值是一个字符串组成的元组,指明允许有的属性。如果 __slots__ 中没有 __dict__,那么该类的实例没有 __dict__ 属性,实例只允许有指定名称的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class A:
__slots__ = ['a','b']

a = A()
a.a = 1
a.b = 2
a.c = 3
###
AttributeError: 'A' object has no attribute 'c'

class A:
__slots__ = ('__dict__')

a = A()
a.a = 1
a.b = 2
a.c = 3
###
不报错

一些处理属性的内置函数

dir([object])

  列出对象的大多数属性。官方文档说,dir 函数的目的是交互式使用,因此没有提供完整的属性列表,只列出一组“重要的”属性名。dir 函数能审查有或没有__dict__ 属性的对象。dir 函数不会列出 __dict__ 属性本身,但会列出其中的键。dir 函数也不会列出类的几个特殊属性,例如 __mro____bases____name__。如果没有指定可选的 object 参数,dir 函数会列出当前作用域中的名称。

getattr(object, name[, default])

  从 object 对象中获取 name 字符串对应的属性。获取的属性可能来自对象所属的类或超类。如果没有指定的属性,getattr 函数抛出 AttributeError 异常,或者返回 default 参数的值(如果设定了这个参数的话)。

hasattr(object, name)

  如果 object 对象中存在指定的属性,或者能以某种方式(例如继承)通过 object 对象获取指定的属性,返回True官方文档说道:“这个函数的实现方法是调用 getattr(object, name)函数,看看是否抛出 AttributeError 异常。  

setattr(object, name, value)

  把 object 对象指定属性的值设为 value,前提是 object 对象能接受那个值。这个函数可能会创建一个新属性,或者覆盖现有的属性。

vars([object])

  返回 object 对象的 __dict__ 属性;如果实例所属的类定义了 __slots__ 属性,实例没有 __dict__ 属性,那么 vars 函数不能处理那个实例(相反,dir 函数能处理这样的实例)。如果没有指定参数,那么 vars() 函数的作用与 locals() 函数一样:返回表示本地作用域的字典。   

处理属性的特殊方法

  使用点号或内置的 getattrhasattrsetattr 函数存取属性都会触发相应的特殊方法。但是,直接通过实例的 __dict__ 属性读写属性不会触发这些特殊方法——如果需要,通常会使用这种方式跳过特殊方法。

__getattribute__(obj, 'attr')

  obj.attrgetattr(obj, 'attr', 42) 都会触发 Class.__getattribute__(obj, 'attr') 方法。

__delattr__(self, name)

  只要使用 del 语句删除属性,就会调用这个方法。例如,del obj.attr 语句触发 Class.__delattr__(obj, 'attr') 方法。

__dir__(self)

  把对象传给 dir 函数时调用,列出属性。例如,dir(obj) 触发 Class.__dir__(obj) 方法。

__getattr__(self, name)

  仅当获取指定的属性失败,搜索过 objClass 和超类之后调用。表达式 obj.no_such_attr、getattr(obj, 'no_such_attr')hasattr(obj, 'no_such_attr') 可能会触发 Class.__getattr__(obj, 'no_such_attr') 方法,但是,仅当在 objClass 和超类中找不到指定的属性时才会触发。

__getattribute__(self, name)

  尝试获取指定的属性时总会调用这个方法,不过,寻找的属性是特殊属性或特殊方法时除外。点号与 getattrhasattr 内置函数会触发这个方法。调用 __getattribute__ 方法且抛出 AttributeError 异常时,才会调用 __getattr__ 方法。为了在获取 obj 实例的属性时不导致无限递归,__getattribute__ 方法的实现要使用 super().__getattribute__(obj, name)

__setattr__(self, name, value)

  尝试设置指定的属性时总会调用这个方法。点号和 setattr 内置函数会触发这个方法。例如,obj.attr = 42setattr(obj, 'attr', 42) 都会触发 Class.__setattr__(obj, ‘attr’, 42) 方法

__getattribute____setattr__ 不管怎样都会调用

查找属性时,如obj.attr,如果Python发现这个属性attr有个__get__方法,Python会调用attr__get__方法,返回__get__方法的返回值,而不是返回attr(这一句话并不准确,我只是希望你能对descriptor有个初步的概念)。

Python中iterator(怎么扯到Iterator了?)是实现了iterator协议的对象,也就是说它实现了下面两个方法__iter__next()。类似的,descriptor也是实现了某些特定方法的对象。descriptor的特定方法是__get__,__set____delete__,其中__set____delete__方法是可选的。iterator必须依附某个对象而存在(由对象的__iter__方法返回),descriptor也必须依附对象,作为对象的一个属性,它而不能单独存在。还有一点,descriptor必须存在于类的__dict__中,这句话的意思是只有在类的__dict__中找到属性,Python才会去看看它有没有__get__等方法,对一个在实例的__dict__中找到的属性,Python根本不理会它有没有__get__等方法,直接返回属性本身。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Test(object):
def __init__(self):
self._value = {}

def __get__(self, instance, instance_type):
print("call __get__")
return self._value.get(instance, 0)

def __set__(self, instance, value):
self._value[instance] = value

class Another0(object):
def __init__(self):
self.test = Test()

class Another1(object):
test = Test()

if __name__ == "__main__":
print(Another0().test)
print('-'*20)
print(Another1.test)
print('-'*20)
print(Another1().test)
###
<__main__.Test object at 0x107bc8860>
--------------------
call __get__
0
--------------------
call __get__
0