python的属性管理

2020-01-27  本文已影响0人  天命_风流

python对于类的属性的管理有四种方式:特性、描述符、'__getattr__' 和 '__getattribute__'。使用这些方法可以让你对一个类的属性实现更多操作。

特性(property)

形式:

name = property( getName, setName, delName, 'name doc' )

用例:

    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): ... #删除属性

为了理解这几个参数,有这样一个实例:


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 __中也很常见,对此,我们使用两种方法避免:

  1. 使用object :object是python中所有类的父类,所有类都是在继承了object类的基础上进行的。你可以使用object.__setattr __(self, name, value)(__getattribute __类似)将属性设置向超类传递。这种使用方法我在之后" __getattribute __ "的代码中会特别标注。
  2. 使用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)

用例:

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 __这样的函数重载运算符。

上一篇 下一篇

猜你喜欢

热点阅读