Python3中的特性-----Property介绍

Python的Property详细档案

640?wx_fmt=png

今天我们就来好好聊聊Python3里面的Property

640?wx_fmt=png

特性的引入

特性和属性的区别是什么?

在python 中 属性 这个 实例方法, 类变量 都是属性.

在python 中 数据的属性 和处理数据的方法 都可以叫做 属性.

class Animal:

    name = 'animal'

    def bark(self):

        print('bark')

        pass

    @classmethod

    def sleep(cls):

        print('sleep')

        pass

    @staticmethod

    def add():

        print('add')

在命令行里面执行

    >>> animal = Animal()

    >>> animal.add()

    add

    >>> animal.sleep()

    sleep

    >>> animal.bark()

    bark

    >>> hasattr(animal,'add') #1

    True 

    >>> hasattr(animal,'sleep')

    True

    >>> hasattr(animal,'bark')

    True

可以看出#1 animal 中 是可以拿到 add ,sleep bark 这些属性的.

特性: property 这个是指什么? 在不改变类接口的前提下使用

存取方法 (即读值和取值) 来修改数据的属性.

什么意思呢?

就是通过 obj.property 来读取一个值,

obj.property = xxx ,来赋值

还以上面 animal 为例:

class Animal:

    @property

    def name(self):

        print('property name ')

        return self._name

    @name.setter

    def name(self, val):

        print('property set name ')

        self._name = val

    @name.deleter

    def name(self):

        del self._name

这个时候 name 就是了特性了.

>>> animal = Animal()

>>> animal.name='dog'

property set name 

>>> animal.name

property name 

'dog'

>>> 

>>> animal.name='cat'

property set name 

>>> animal.name

property name 

'cat'

肯定有人会疑惑,写了那么多的代码, 还不如直接写成属性呢,多方便.

比如这段代码:

>>> class Animal:

...     name=None

...     

>>> animal = Animal()

>>> animal.name

>>> animal.name='frank'

>>> animal.name

'frank'

>>> animal.name='chang'

>>> animal.name

'chang'

>>> animal.name=250

>>> animal

<Animal object at 0x10622b850>

>>> animal.name

250

>>> type(animal.name)

<class 'int'>

这里给 animal.name 赋值成 250, 程序从逻辑上来说 没有问题. 但其实这样赋值是毫无意义的.

我们一般希望 不允许这样的赋值,就希望 给出 报错或者警告 之类的.

animal= Animal()

animal.name=100

property set name 

Traceback (most recent call last):

  File "<input>", line 1, in <module>

  File "<input>", line 13, in name

ValueError: expected val is str

其实当name 变成了property 之后,我们就可以对name 赋值 进行控制. 防止一些非法值变成对象的属性.

比如说name 应该是这个字符串, 不应该是数字 这个时候 就可以在 setter 的时候 进行判断,来控制 能否赋值.

要实现上述的效果, 其实也很简单 setter 对value进行判断就好了.

class Animal:

    @property

    def name(self):

        print('property name ')

        return self._name

    @name.setter

    def name(self, val):

        print('property set name ')

        # 这里 对 value 进行判断 

        if not isinstance(val,str):

            raise  ValueError("expected val is str")

        self._name = val

感受到 特性的魅力了吧,可以通过 赋值的时候 ,对 值进行校验,方式不合法的值,进入到对象的属性中. 下面 看下 如何设置只读属性, 和如何设置读写 特性.

假设 有这样的一个需求 , 某个类的属性一个初始化之后 就不允许 被更改,这个 就可以用特性这个问题 , 比如一个人身高是固定, 一旦 初始化后,就不允许改掉.

设置只读特性

class Frank:

    def __init__(self, height):

        self._height = height

    @property

    def height(self):

        return self._height

>>> frank = Frank(height=100)

>>> frank.height

100

>>> frank.height =150

Traceback (most recent call last):

  File "<input>", line 1, in <module>

AttributeError: can't set attribute

这里初始化 frank后 就不允许 就修改 这个 height 这个值了. (实际上也是可以修改的)

设置读写特性

class Frank:

    def __init__(self, height):

        self._height = height

    @property

    def height(self):

        return self._height

    @height.setter

    def height(self, value):

        """

        给特性赋值 

        """

        self._height = value

比如对人的身高 在1米 到 2米之间 这样的限制

>>> frank = Frank(height=100)

>>> frank.height

100

>>> frank.height=165

>>> frank.height

165

对特性的合法性进行校验

class Frank:

    def __init__(self, height):

        self.height = height  # 注意这里写法

    @property

    def height(self):

        return self._height

    @height.setter

    def height(self, value):

        """

        判断逻辑 属性的处理逻辑

        定义 了 setter 方法之后就  修改 属性 了.

        判断 属性 是否合理 ,不合理直接报错. 阻止赋值,直接抛异常

        :param value:

        :return:

        """

        if not isinstance(value, (float,int)):

            raise ValueError("高度应该是 数值类型")

        if value < 100 or value > 200:

            raise ValueError("高度范围是100cm 到 200cm")

        self._height = value

>>> frank = Frank(100)

>>> frank.height

100

>>> frank.height='aaa'

Traceback (most recent call last):

  File "<input>", line 1, in <module>

  File "<input>", line 21, in height

ValueError: 高度应该是 数值类型

>>> frank.height=250

Traceback (most recent call last):

  File "<input>", line 1, in <module>

  File "<input>", line 23, in height

ValueError: 高度范围是100cm 到 200cm

这样就可以进行严格的控制, 一些特性的方法性 ,通过写setter 方法 来保证数据 准确性,防止一些非法的数据进入到实例中.

Property是什么?

实际上是一个类 , 然后就是一个装饰器. 让一个方法变成 一个特性.

其实特性模糊了方法和数据的界限.

方法是可调用的属性 , 而property 是 可定制化的'属性' . 一般方法的名称是一个动词(行为). 而特性property 应该是名词.

如果我们一旦确定了属性不是动作, 我们需要在标准属性 和 property 之间做出选择 .

一般来说你如果要控制 property 的 访问过程,就要用property. 否则用标准的属性即可 .

attribute属性和property特性的区别在于当property被读取, 赋值, 删除时候, 自动会执行某些特定的动作.

peroperty 详解

特性都是类属性,但是特性管理的其实是实例属性的存取。

----- 摘自 fluent python

下面的例子来自 fluent python

看一下几个例子来说明几个特性和属性区别

>>> class Class:

  """

  data 数据属性和 prop 特性。

  """

...     data = 'the class data attr'

... 

...     @property

...     def prop(self):

...         return 'the prop value'

... 

>>> 

>>> obj= Class() 

>>> vars(obj)

{}

>>> obj.data

'the class data attr'

>>> Class.data

'the class data attr'

>>> obj.data ='bar'

>>> Class.data

'the class data attr'

实例属性遮盖类的数据属性 , 就是说如果obj.data重新修改了 , 类的属性不会被修改 .

下面尝试obj 实例的prop特性

>>> Class.prop

<property object at 0x110968ef0>

>>> obj.prop

'the prop value'

>>> obj.prop ='foo'

Traceback (most recent call last):

  File "<input>", line 1, in <module>

AttributeError: can't set attribute

>>> obj.__dict__['prop'] ='foo'

>>> vars(obj)

{'data': 'bar', 'prop': 'foo'}

>>> obj.prop  #1

'the prop value'

>>> Class.prop ='frank'

>>> obj.prop

'foo'

我尝试修改 obj.prop 会直接报错 ,这个容易理解, 因为property没有实现 setter 方法 . 我直接修改obj.dict

然后 在#1的地方, 发现 还是正常调用了特性 ,而没有属性的值.

当我改变Class.prop变成一个属性的时候 .

再次调用obj.prop才调用到了 实例属性.

再看一个例子 添加 特性

class Class:

    data = 'the class data attr'

    @property

    def prop(self):

        return 'the prop value'

>>> obj.data

'bar'

>>> Class.data

'the class data attr'

# 把类的data 变成 特性

>>> Class.data = property(lambda self:'the "data" prop value')

>>> obj.data

'the "data" prop value'

>>> del Class.data

>>> obj.data

'bar'

>>> vars(obj)

{'data': 'bar', 'prop': 'foo'}

改变 data 变成特性后, obj.data也改变了. 删除这个特性的时候 , obj.data 又恢复了.

本节的主要观点是, obj.attr 这样的表达式不会从 obj 开始寻找 attr,而是从

obj.__class__ 开始,而且,仅当类中没有名为 attr 的特性时, Python 才会在 obj 实

例中寻找。这条规则适用于特性 .

property 实际上 是一个类

    def __init__(self, fget=None, fset=None, fdel=None, doc=None): 

        pass

    # known special case of property.__init__

完成 的要实现一个特性 需要 这 4个参数, get , set ,del , doc 这些参数.但实际上大部分情况下,只要实现 get ,set 即可.

Property的两种写法

第一种写法

使用 装饰器 property 来修饰一个方法

# 方法1 

class Animal:

    def __init__(self, name):

        self._name = name

    @property

    def name(self):

        print('property name ')

        return self._name

    @name.setter

    def name(self, val):

        print('property set name ')

        if not isinstance(val, str):

            raise ValueError("expected val is str")

        self._name = val

    @name.deleter

    def name(self):

        del self._name

第二种写法

直接 实现 set get delete 方法 即可, 通过property 传入 这个参数

# 方法二 

class Animal2:

    def __init__(self, name):

        self._name = name

    def _set_name(self, val):

        if not isinstance(val, str):

            raise ValueError("expected val is str")

        self._name = val

    def _get_name(self):

        return self._name

    def _delete_name(self):

        del self._name

    name = property(fset=_set_name, fget=_get_name,fdel= _delete_name,doc= "name 这是特性描述")

if __name__ == '__main__':

    animal = Animal2('dog')

>>> animal = Animal2('dog')

>>> 

>>> animal.name

'dog'

>>> animal.name

'dog'

>>> help(Animal2.name)

Help on property:

    name 这是特性描述

>>> animal.name='cat'

>>> animal.name

'cat'

替换背景的新方法:选择背景图设置后,左边模板、收藏、剪贴板和图库都可以点击,根据选择的内容,设置背景到当前的编辑布局上。如果选择了的是图片,都把图片设置被背景图。如果选择了一个带背景的模板,就把这个模板的背景给复制过来。

常见的一些例子

A、对一些值进行合法性校验.

在举一个小例子 比如 有一个货物, 有重量 和 价格 ,需要保证 这两个属性是正数 不能是 0 , 即>0 的值 

基础版本的代码:

class Goods:

    def __init__(self, name, weight, price):

        """

        :param name: 商品名称

        :param weight:  重量

        :param price: 价格

        """

        self.name = name

        self.weight = weight

        self.price = price

    def __repr__(self):

        return f"{self.__class__.__name__}(name={self.name},weight={self.weight},price={self.price})"

    @property

    def weight(self):

        return self._weight

    @weight.setter

    def weight(self, value):

        if value < 0:

            raise ValueError(f"expected value > 0, but now value:{value}")

        self._weight = value

    @property

    def price(self):

        return self._price

    @price.setter

    def price(self, value):

        if value < 0:

            raise ValueError(f"expected value > 0, but now value:{value}")

        self._price = value

>>> goods = Goods('apple', 10, 30)

... 

>>> goods

Goods(name=apple,weight=10,price=30)

>>> goods.weight

10

>>> goods.weight=-10

Traceback (most recent call last):

  File "<input>", line 1, in <module>

  File "<input>", line 26, in weight

ValueError: expected value > 0, but now value:-10

>>> goods.price

30

>>> goods.price=-3

Traceback (most recent call last):

  File "<input>", line 1, in <module>

  File "<input>", line 37, in price

ValueError: expected value > 0, but now value:-3

>>> goods

Goods(name=apple,weight=10,price=30)

>>> goods.price=20

>>> goods

Goods(name=apple,weight=10,price=20)

代码 可以正常的判断出来 ,这些非法值了. 这样写 有点问题是什么呢? 就是 发现 weight ,price 判断值的逻辑 几乎是一样的代码… 都是判断是 大于 0 吗? 然而我却写了 两遍相同的代码 .

优化后的代码

有没有更好的解决方案呢?

是有的, 我们可以写一个 工厂函数 来返回一个property , 这实际上是两个 property 而已.

下面 就是工厂函数 ,用来生成一个 property 的.

def validate(storage_name):

    """

    用来验证 storage_name 是否合法性 , weight  , price

    :param storage_name:

    :return:

    """

    pass

    def _getter(instance):

        return instance.__dict__[storage_name]

    def _setter(instance, value):

        if value < 0:

            raise ValueError(f"expected value > 0, but now value:{value}")

        instance.__dict__[storage_name] = value

    return property(fget=_getter, fset=_setter)

class Goods:

    weight = validate('weight')

    price = validate('price')

    def __init__(self, name, weight, price):

        """

        :param name: 商品名称

        :param weight:  重量

        :param price: 价格

        """

        self.name = name

        self.weight = weight

        self.price = price

    def __repr__(self):

        return f"{self.__class__.__name__}(name={self.name},weight={self.weight},price={self.price})"

>>> goods = Goods('apple', 10, 30)

>>> goods.weight

10

>>> goods.weight=-10

Traceback (most recent call last):

  File "<input>", line 1, in <module>

  File "<input>", line 16, in _setter

ValueError: expected value > 0, but now value:-10

>>> goods

Goods(name=apple,weight=10,price=30)

>>> goods.price=-2

Traceback (most recent call last):

  File "<input>", line 1, in <module>

  File "<input>", line 16, in _setter

ValueError: expected value > 0, but now value:-2

>>> goods

Goods(name=apple,weight=10,price=30)

B、缓存某些值

... from urllib.request import urlopen

... class WebPage:

... 

...     def __init__(self, url):

...         self.url = url

... 

...         self._content = None

... 

...     @property

...     def content(self):

...         if not self._content:

...             print("Retrieving new page")

...             self._content = urlopen(self.url).read()[0:10]

... 

...         return self._content

...     

>>> 

>>> 

>>> url = 'http://www.baidu.com'

>>> page = WebPage(url)

>>> 

>>> page.content

Retrieving new page

b'<!DOCTYPE '

>>> page.content

b'<!DOCTYPE '

>>> page.content

b'<!DOCTYPE '

可以看出 第一次调用了 urlopen 从网页中读取值, 第二次就没有调用urlopen 而是直接返回content 的内容.

总结

python的特性算是python的高级语法,不要因为到处都要用这个特性的语法.实际上大部分情况是用不到这个语法的. 如果代码中,需要对属性进行检查就要考虑用这样的语法了. 希望你看完之后不要认为这种语法非常常见, 事实上不是的. 其实更好的做法对属性检查可以使用描述符来完成. 描述符是一个比较大的话题,本文章暂未提及,后续的话,可能 会写一下 关于描述的一些用法 ,这样就能更好的理解python,更加深入的理解python.

参考文档

fluent python(流畅的Python)

Python3面向对象编程

Python为什么要使用描述符?

https://juejin.im/post/5cc4fbc0f265da0380437706

https://tech-summary.readthedocs.io/en/latest/python_property.html

640?wx_fmt=png

"欢迎关注,了解更多python内幕"

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值