python的属性管理
python对于类的属性的管理有四种方式:特性、描述符、'__getattr__' 和 '__getattribute__'。使用这些方法可以让你对一个类的属性实现更多操作。
特性(property)
形式:
name = property( getName, setName, delName, 'name doc' )
- 实现对name属性的管理
- property有四个参数分别实现类四种功能:获取属性、设置属性、删除属性 和 属性的文档。
用例:
class CardHolder:
acctlen = 8
retireage = 59.5
def __init__(self, acct, name, age, addr):
self.acct = acct
self.name = name
self.age = age
self.addr = addr
def getName(self):
return self.__name
def setName(self, value):
value = value.lower().replace(' ', '-')
self.__name = value
name = property(getName, setName) # 对name属性:转化为__name,将字符转为小写,并且将空格转换成'-'
def getAge(self):
return self.__age
def setAge(self, value):
if value < 0 or value > 150:
raise ValueError('invalid age')
else:
self.__age = value
age = property(getAge, setAge) # 对age:判断是否处于0-150之间
def getAcct(self):
return self.__acct[:-3] + '***'
def setAcct(self, value):
value = value.replace('-', '')
if len(value) != self.acctlen:
raise TypeError('invald acct number')
else:
self.__acct = value
acct = property(getAcct, setAcct) # 对acct:将'-'去除,规定acct长度为8,输出时将后三位转为 ***
def remainGet(self):
return self.retireage - self.age
remain = property(remainGet)
if __name__ == '__main__':
bob = CardHolder('1234-5678', 'Bob Smith', 40, '123 main st')
print(bob.acct, bob.name, bob.age, bob.remain, bob.addr, sep=' / ')
bob.name = 'Bob Q. Smith'
bob.age = 50
bob.acct = '23-45-67-89'
print(bob.acct, bob.name, bob.age, bob.remain, bob.addr, sep=' / ')
sue = CardHolder('5678-12-34', 'Sue Jones', 35, '124 main st')
print(sue.acct, sue.name, sue.age, sue.remain, sue.addr, sep=' / ')
try:
sue.age = 200
except:
print('Bad age for Sue')
try:
sue.remain = 5
except:
print("Can't set sue.remain")
try:
sue.acct = '1234567'
except:
print('Bad acct for Sue')
执行结果:
12345*** / bob-smith / 40 / 19.5 / 123 main st
23456*** / bob-q.-smith / 50 / 9.5 / 123 main st
56781*** / sue-jones / 35 / 24.5 / 124 main st
Bad age for Sue
Can't set sue.remain
Bad acct for Sue
描述符(descriptor)
上面提到的特性是描述符的特殊形式,使用描述符和特性也有许多相似之处,描述符使用一个类实现对属性的管理,形式:
class Descriptor:
"docstring goes here" #设置属性文档
def __get__(self, instance, owner) : ... #获取属性
def __set__(self, instance, value) : ... #设置属性
def __delete__(self, instance): ... #删除属性
- self :代表Descriptor类本身
- instance:代表调用这个描述符的类的实例,如果这个属性是类属性,则会返回None
- owner:代表实例附加到的类
为了理解这几个参数,有这样一个实例:
image.png
- 描述符可以嵌套在需要描述的类内,也可以自身独立,在下面给出的例子中就是嵌套的
用例:
class CardHolder:
acctlen = 8
retireage = 59.5
def __init__(self, acct, name, age, addr):
self.acct = acct
self.name = name
self.age = age
self.addr = addr
class Name:
def __get__(self, instance, owner):
return self.name # 使用描述符的存储空间,而不是CardHolder的
def __set__(self, instance, value): # 在这里你可以使用instance,但是要注意将属性名改成_name,否则会循环
value = value.lower().replace(' ', '-')
self.name = value
name = Name()
class Age:
def __get__(self, instance, owner):
return self.age
def __set__(self, instance, value):
if value < 0 or value > 150:
raise ValueError('invalid age')
else:
self.age = value
age = Age()
class Acct:
def __get__(self, instance, owner):
return self.acct[:-3] + '***'
def __set__(self, instance, value):
value = value.replace('-', '')
if len(value) != instance.acctlen: # 在这里使用了instance的属性
raise TypeError('invald acct number')
else:
self.acct = value
acct = Acct()
class Remain:
def __get__(self, instance, owner):
return instance.retireage - instance.age # 使用instance.age会触发Age.__get__
def __set__(self, instance, value):
raise TypeError('cannot set remain')
remain = Remain()
if __name__ == '__main__':
bob = CardHolder('1234-5678', 'Bob Smith', 40, '123 main st')
print(bob.acct, bob.name, bob.age, bob.remain, bob.addr, sep=' / ')
bob.name = 'Bob Q. Smith'
bob.age = 50
bob.acct = '23-45-67-89'
print(bob.acct, bob.name, bob.age, bob.remain, bob.addr, sep=' / ')
sue = CardHolder('5678-12-34', 'Sue Jones', 35, '124 main st')
print(sue.acct, sue.name, sue.age, sue.remain, sue.addr, sep=' / ')
try:
sue.age = 200
except:
print('Bad age for Sue')
try:
sue.remain = 5
except:
print("Can't set sue.remain")
try:
sue.acct = '1234567'
except:
print('Bad acct for Sue')
12345*** / bob-smith / 40 / 19.5 / 123 main st
23456*** / bob-q.-smith / 50 / 9.5 / 123 main st
56781*** / sue-jones / 35 / 24.5 / 124 main st
Bad age for Sue
Can't set sue.remain
Bad acct for Sue
特性和描述符通常被用于管理某个特定的属性,我们使用这种方法可以更加深度地定制一个属性,以此实现更加丰富的功能。额外说一下,形如描述符的形式,在一个类中使用另一个类完成自身的功能,这种形式称为委托。
下面我们会使用__getattr__ 和 __getattribute__对类属性进行广泛的捕捉和管理。
__getattr__(捕捉所有未定义的get请求)
使用__getattr__可以对所有未定义的属性获取请求进行捕捉。通常与之搭配的还有__setattr__,它可以捕捉所有属性的设置请求。
形式:
def __getattr__(self, name)
def __setattr__(self, name, value)
__setattr __ 会捕捉所有的属性设置,这就导致你可能掉进循环设置的陷阱,这种情况在之后要使用的__getattribute __中也很常见,对此,我们使用两种方法避免:
- 使用object :object是python中所有类的父类,所有类都是在继承了object类的基础上进行的。你可以使用object.__setattr __(self, name, value)(__getattribute __类似)将属性设置向超类传递。这种使用方法我在之后" __getattribute __ "的代码中会特别标注。
- 使用self.__dict __[key] = value 的形式实现,注意,这种方式在可以在__setattr __中使用,__getattribute __中不要使用
用例:
class CardHolder:
acctlen = 8
retireage = 59.5
def __init__(self, acct, name, age, addr):
self.acct = acct
self.name = name
self.age = age
self.addr = addr
def __getattr__(self, item):
if item == 'acct': # 请求acct,注意在__setattr__中,保存的内容为 _acct
return self._acct[:-3] + '***'
elif item == 'remain':
return self.retireage - self.age
else: # 其它未定义的属性将会抛出异常
raise AttributeError(item)
def __setattr__(self, key, value):
if key == 'name':
value = value.lower().replace(' ', '-')
elif key == 'age':
if value < 0 or value > 150:
raise ValueError('invalid age')
elif key == 'acct': # 注意这里将key 改成了 _acct,否则__getattr__将无法捕捉acct
key = '_acct'
value = value.replace('-', '')
if len(value) != self.acctlen:
raise TypeError('invald acct number')
elif key == 'remain':
raise TypeError('cannot set remain')
self.__dict__[key] = value # 将属性保存,不要使用self.key = value 这种代码,会引起循环
if __name__ == '__main__':
bob = CardHolder('1234-5678', 'Bob Smith', 40, '123 main st')
print(bob.acct, bob.name, bob.age, bob.remain, bob.addr, sep=' / ')
bob.name = 'Bob Q. Smith'
bob.age = 50
bob.acct = '23-45-67-89'
print(bob.acct, bob.name, bob.age, bob.remain, bob.addr, sep=' / ')
sue = CardHolder('5678-12-34', 'Sue Jones', 35, '124 main st')
print(sue.acct, sue.name, sue.age, sue.remain, sue.addr, sep=' / ')
try:
sue.age = 200
except:
print('Bad age for Sue')
try:
sue.remain = 5
except:
print("Can't set sue.remain")
try:
sue.acct = '1234567'
except:
print('Bad acct for Sue')
12345*** / bob-smith / 40 / 19.5 / 123 main st
23456*** / bob-q.-smith / 50 / 9.5 / 123 main st
56781*** / sue-jones / 35 / 24.5 / 124 main st
Bad age for Sue
Can't set sue.remain
Bad acct for Sue
__getattribute __(捕捉所有的属性请求)
形式:
def __getattribute__(self, name)
- 请注意循环可能带来的问题,下面的例子使用了object.__getattribute __解决这个问题。
用例:
class CardHolder:
acctlen = 8
retireage = 59.5
def __init__(self, acct, name, age, addr):
self.acct = acct
self.name = name
self.age = age
self.addr = addr
def __getattribute__(self, item):
superget = object.__getattribute__ # __getattribute__会捕捉所有属性,使用object(所有类的基类)破除循环
if item == 'acct':
return superget(self, 'acct')[:-3] + '***'
elif item == 'remain':
return superget(self, 'retireage') - superget(self, 'age')
else:
return superget(self, item)
def __setattr__(self, key, value):
if key == 'name':
value = value.lower().replace(' ', '-')
elif key == 'age':
if value < 0 or value > 150:
raise ValueError('invalid age')
elif key == 'acct':
value = value.replace('-', '')
if len(value) != self.acctlen:
raise TypeError('invald acct number')
elif key == 'remain':
raise TypeError('cannot set remain')
self.__dict__[key] = value
if __name__ == '__main__':
bob = CardHolder('1234-5678', 'Bob Smith', 40, '123 main st')
print(bob.acct, bob.name, bob.age, bob.remain, bob.addr, sep=' / ')
bob.name = 'Bob Q. Smith'
bob.age = 50
bob.acct = '23-45-67-89'
print(bob.acct, bob.name, bob.age, bob.remain, bob.addr, sep=' / ')
sue = CardHolder('5678-12-34', 'Sue Jones', 35, '124 main st')
print(sue.acct, sue.name, sue.age, sue.remain, sue.addr, sep=' / ')
try:
sue.age = 200
except:
print('Bad age for Sue')
try:
sue.remain = 5
except:
print("Can't set sue.remain")
try:
sue.acct = '1234567'
except:
print('Bad acct for Sue')
12345*** / bob-smith / 40 / 19.5 / 123 main st
23456*** / bob-q.-smith / 50 / 9.5 / 123 main st
56781*** / sue-jones / 35 / 24.5 / 124 main st
Bad age for Sue
Can't set sue.remain
Bad acct for Sue
注意,__getattr __ 和 __getattribute __ 不会捕捉内置运算符,也就是说你依然可以使用 __add __这样的函数重载运算符。