Property是什么?

Property是Python中内置的装饰器,它的核心作用是将类的"方法调用",伪装成"属性访问"

为什么要用Property?

要回答“为什么要用property”这个问题,首先我们先不用property,看没有它行不行

我们创建一个名为Student的类,并基于这个类创建一个实例

class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score

xm = Student("xiaoming", 90)
print(xm.name) # xiaoming
print(xm.score) # 90
xm.score = 999
print(xm.name) # xiaoming
print(xm.score) # 999

我们创建了xm这个实例,并进行了初始化。我们输出namescore这两个属性的值,没有任何问题

但是当我们对score进行修改后再输出,发现此时的分数可以被设置成999,这显然是不合理的,所以我们要对属性的设置进行条件限制

那我们第一时间,一定会想到往类中添加额外的方法,在方法中添加限制条件,像下面这样:

class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score

    def get_score(self):
        return self.score

    def set_score(self, value):
        if 100 >= value >= 0:
            self.score = value
        else:
            raise ValueError("分数必须在0-100之间")


xm = Student("xiaoming", 90)
print(xm.name) # xiaoming
print(xm.score) # 90
xm.set_score(999)  # ValueError: 分数必须在0-100之间
print(xm.name) 
print(xm.score)

添加方法后,确实实现了对score这个属性的设置添加了限制条件,但是也会带来两个问题

第一个问题就是我们必须要用set_score这样后添加的方法来对score进行设置,如果仍然使用.这种方式就绕过了条件限制

class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score

    def get_score(self):
        return self.score

    def set_score(self, value):
        if 100 >= value >= 0:
            self.score = value
        else:
            raise ValueError("分数必须在0-100之间")


xm = Student("xiaoming", 90)
print(xm.name) # xiaoming
print(xm.score) # 90
xm.score = 999 
print(xm.name) # xiaoming
print(xm.score) # 999 

第二个问题就是我们需要将所有之前用.来设置属性的语句,全都修改成用set_score的方式。如果像上面这种例子一样,只修改一条语句,那当然没问题。可如果这个类有成百上千个实例,有成千上万条语句需要修改呢?一条条修改显然是不现实的

那么有没有一种方法,可以不修改用.来设置属性的方式,还能做到对属性设置添加条件限制呢?

答案就是添加property装饰器

使用Property

我们用property对上面的代码进行修改

class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if 0 <= value <= 100:
            self._score = value
        else:
            raise ValueError("abnormal score")


xm = Student("xiaoming", 90)
print(xm.name) # xiaoming
print(xm.score) # 90
xm.score = 999 # ValueError: abnormal score
print(xm.name) 
print(xm.score) 

@property就是声明一个装饰器,而装饰的就是score这个属性,接下来定义的score方法,其实就是我们要装饰的属性名。这个方法名,一定一定要与你装饰的属性名一致。这个方法其实就代表了我们运行实例.属性所返回的结果,也就是getter这个“读取协议”

而接下来声明的@score.setter就是对装饰属性进行设置的条件,该方法中的语句就代表了运行实例.属性=...所经过的过程,也就是setter这个“写入协议”

当我们用装饰器修改完之后就可以发现,我们用.方法对属性进行设置,也可以像之前调用方法那样拦截到错误

在我们使用property装饰器时,需要注意,我们需要用被保护的变量,也就是_score来作为返回值或进行修改,一定不能用score。原因和property的运行逻辑有关,我们后面会深入讲解

另外就是在我们使用property时,初始化即__init__()中,尽量不要用self._score = score,我们可以看一下这两者的区别

使用_score:

class Student:
    def __init__(self, name, score):
        self.name = name
        self._score = score # 初始化时使用受保护的变量

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if 0 <= value <= 100:
            self._score = value
        else:
            raise ValueError("abnormal score")


xm = Student("xiaoming", 999) # 在初始化时拦截不到问题
print(xm.score) # 999

使用score

class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score # 初始化时不使用受保护的变量

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if 0 <= value <= 100:
            self._score = value
        else:
            raise ValueError("abnormal score")


xm = Student("xiaoming", 999) # 在初始化时拦截到问题
print(xm.score) # ValueError: abnormal score

两者的区别也是由Property的运行逻辑所导致的,接下来我们就深入了解一下property是如何运行的

Property的运行逻辑

还是用上面的代码举例

class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score 

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if 0 <= value <= 100:
            self._score = value
        else:
            raise ValueError("abnormal score")


xm = Student("xiaoming", 90) 
print(xm.score) 
xm.score = 999
print(xm.score)

当我们用xm = Student("xiaoming", 90)来创建实例时,xm.name接收了"xiaoming"这个值。而在对xm.score进行赋值时,python检测到score是我们装饰过的属性,并且我们设置了setter这个“写入协议”,所以此时self.score = 90就变成了score(self, 90),90这个值就进入了判定,最后赋值给了_score,所以在我们执行print(xm.score)时,python也同样会检测到score是被装饰的属性,从而用getter这个“读取协议”返回_score的值,也就是90

后面对xm.score进行赋值和输出也是一样的逻辑

我们可以理解为,当使用装饰器后,score这个属性就变成了一个读取-写入-删除的“逻辑”

所以,当我们理解property真正的运行逻辑后,我们就能解释刚才我们遇到的问题

当我们在初始化中直接使用_score时,我们相当于绕开了setter这个检测机制,在输出时就是_score的值。而当我们再次对score进行设置,python在检测到装饰器后,传入的值才能进入检测机制

如果我们在装饰器中不用_score而用score,那么程序就会进入死循环

结语

如果文章中有什么错误或者可以优化的地方,欢迎大家指正,感谢!

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:alixiixcom@163.com