Python类和对象-property

1 篇文章 0 订阅
1 篇文章 0 订阅

1. @property

1.1. 介绍

对于类对象,在绑定属性的时候,如果直接把属性暴露出去,虽然使用起来简单,但是没有办法检查设置的参数;此时可以通过实现一些方法来完成对属性值的校验,但是此时使用起来会发现比较复杂。Python内置的@property装饰器就是负责把一个方法变成属性调用的。

1.2. 基本用途

  • 对属性值进行校验(通常是对属性进行赋值操作的时候进行一些参数值校验)
  • 定义只读属性。
  • 定义只写属性(比如设置password为A,然后程序内部通过加密将password加密为B然后保存到password_hash,此时设置password为只写属性,即无法获取password的明文)

下面以一个定义只写属性的例子来说明:

1.2.1. 定义只写属性

一个比较常用的场景就是密码加密,比如下面的例子(需要了解werkzeug中的生成密码和校验密码的函数):

  1. 设置密码,此时会调用User类中的password方法,从而将密码加密,并保存到password_hash中,而password是一个只写属性,不过可以读取加密之后的密文。
  2. 如果尝试读取password,此时会抛出一个错误。
  3. 此时可以读取加密之后的密文password_hash。
  4. 可以使用verify_password函数来验证密码。
# Werkzeug=2.1.2
# python=3.8
from werkzeug.security import generate_password_hash, check_password_hash

class User:
    @property
    def password(self):
        raise AttributeError('password is not readable attribute')

    @password.setter
    def password(self, password):
        # generate_password_hash会根据输入的参数将其加密,得到一个加密之后的hash值。
        self.password_hash = generate_password_hash(password)

    def verify_password(self, password):
        # check_password_hash会对比加密之后的hash和加密之前的密码,从而判断密码是否正确。
        return check_password_hash(self.password_hash, password)

if __name__ == '__main__':
    user = User()
    user.password = "123"                
    print(user.password)        # AttributeError: password is not readable attribute
    print(user.password_hash)            # pbkdf2:sha256:260000$UDSvk3c1TsaXCqdX$85eb85fe7258e7d7519ccd504ae36fab91e7419e3a4f63233a0fe7fb91703834
    print(user.verify_password("123"))  # True

1.3. 注意事项

1.3.1. 注意属性的方法名和实例变量不要重名

如下,此时调用p.first_name的时候就会报错,报错的原因是执行p.first_name时,首先会转化为方法调用,然后执行Person类中的first_name()方法,在该方法中,执行return self.first_name的时候,又被看做是self的属性,从而再次转化为方法调用,从而造成递归,最终导致栈溢出,报错RecursionError。(递归栈溢出Python3.7.3 中默认递归深度为1000,若超过此递归深度将抛出异常)

# python=3.8
class Person:
    # birth = "abc"

    @property
    def first_name(self):
        return self.first_name

if __name__ == '__main__':
    p = Person()       
    print(p.first_name)         # RecursionError: maximum recursion depth exceeded

1.4. 将方法转化为属性的3种方式

1.4.1. 使用@property装饰器

使用@property装饰器将方法作为属性来访问。支持setter和deleter。注意,需要首先使用@property将方法定义为属性,之后才可以定义该属性的setter和deleter装饰器。

比如下面的例子:

  1. 注意,初始化__init__方法中没有直接对_first_name初始化,而是对first_name初始化,但是对first_name初始化的时候,此时相当于对first_name进行赋值操作,即会调用@first_name.setter函数,从而对_first_name进行了赋值。
  2. 这里使用property的主要作用是设置属性的时候进行类型检查
class Person:
    def __init__(self, first_name):
        self.first_name = first_name

    @property
    def first_name(self):
        return self._first_name

    @first_name.setter
    def first_name(self, value):
        if not isinstance(value, str):
            raise TypeError("Expected a string")
        self._first_name = value

    @first_name.deleter
    def first_name(self):
        raise AttributeError("Can't delete attribute")

if __name__ == '__main__':
    p = Person("Tom")       # 初始化的时候,相当于执行了对first_name属性的赋值操作,从而会自动执行setter函数
    print(p.first_name)     # Tom

1.4.2. 将已经存在的方法定义为property-方式1

除了上述提到的@property的方法,还可以使用property类将已经存在方法绑定到类的成员属性上,property类函数的说明如下所示:

class property([fget[, fset[, fdel[, doc]]]])
参数
    fget -- 获取属性值的函数
    fset -- 设置属性值的函数
    fdel -- 删除属性值函数
    doc -- 属性描述信息
返回值
    返回新式类属性。

以具体的例子说明:

  1. 可以定义3个方法,并传递给property中,property的三个参数值分别为fget、fset和fdel,从而将这3个方法(get_first_name、set_first_name、del_first_name)附加到类属性first_name中。
  2. 当调用print(p.first_name)时,此时不会去字典表__dict__中查找key为first_name的值,而是会调用Person.first_name.fget,即相当于执行了get_first_name()方法。比较Person.first_name.fget的id和Person类中的get_first_name函数的id,可以发现是一样的。
class Person():
    def __init__(self, first_name):
        self.set_first_name(first_name)

    def get_first_name(self):
        return self._first_name

    def set_first_name(self, value):
        if not isinstance(value, str):
            raise TypeError("Expected a string")
        self._first_name = value

    def del_first_name(self):
        raise AttributeError("Can't delete attribute")

    # 将已有的方法绑定到property上。property的参数分别是fget fset和fdel。
    first_name = property(get_first_name, set_first_name, del_first_name)

if __name__ == "__main__":
    p = Person("Tom")
    p.first_name = "Mary"
    print(p.first_name)       # Mary
    # del p.first_name          # AttributeError: Can't delete attribute
    print(id(Person.first_name.fget)==id(Person.get_first_name))        # True,

1.4.3. 将已经存在的方法定义为property-方式2

除了上述提及的方法,还可以使用如下的方式:

  1. 首先,初始化一个空的property对象,之后调用getter、setter和deleter分别对齐进行赋值。
# python=3.8
class Person():
    def __init__(self, first_name):
        self.set_first_name(first_name)

    def get_first_name(self):
        return self._first_name

    def set_first_name(self, value):
        if not isinstance(value, str):
            raise TypeError("Expected a string")
        self._first_name = value

    def del_first_name(self):
        raise AttributeError("Can't delete attribute")

    # 定义一个空的property
    first_name = property()
    # 分别增加getter、setter和deleter属性。
    first_name = first_name.getter(get_first_name)
    first_name = first_name.setter(set_first_name)
    first_name = first_name.deleter(del_first_name)

if __name__ == "__main__":
    p = Person("Tom")
    p.first_name = "Mary"
    print(p.first_name)       # Mary
    # del p.first_name          # AttributeError: Can't delete attribute
    print(id(Person.first_name.fget)==id(Person.get_first_name))        # True,
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值