# property装饰器

# property属性

一种用起来像是使用实例属性一样的特殊属性,但实际上是对应于某个方法

既要保护类的封装特性,又要让开发者可以使用 对象.属性 的方式操作方法,@property 装饰器​,可以直接通过方法名来访问方法,不需要在方法名后添加一对 ()​ 小括号。

来看下求圆的面积的例子

class Circle(object):

    PI = 3.14

    def __init__(self, r):
        # r圆的半径
        self.r = r
        self.__area = self.PI * self.r * self.r
  
    @property
    def area(self):
        return self.__area

    def get_area(self):
        return self.__area


In [2]: c = Circle(10)

In [3]: c.area
Out[3]: 314.0


property属性的定义和调用要注意一下几点:

  • 定义时,在实例方法的基础上添加 @property​ 装饰器;并且仅有一个 self​ 参数
  • 调用时,无需括号 ()

# property属性的有两种方式

  • 装饰器 即:在方法上应用装饰器 @property​​
  • 类属性 即:在类中定义值为 property​​ 对象的类属性 property()​​

# 装饰器方式

在类的实例方法上应用 @property​​ 装饰器

Python中的类有旧式类​​ 和 新式类​​,新式类​​ 的属性比 旧式类​​的属性丰富。

# 旧式类

旧式类,具有一种 @property​​ 装饰器

class Goods:
  
    def __init__(self, name):
        self.name = name
  
    @property
    def price(self):
        return 100
  
# ipython测验
In [10]: g = Goods('手表')

In [11]: g.price
Out[11]: 100

# 新式类

新式类,具有三种 @property​ 装饰器


class Goods:
    """
    python3中默认继承object类
    以python2、3执行此程序的结果不同,因为只有在python3中才有@xxx.setter  @xxx.deleter
    """
    @property
    def price(self):
        print('@property')

    @price.setter
    def price(self, value):
        print('@price.setter')

    @price.deleter
    def price(self):
        print('@price.deleter')

      
# ipython测验
In [13]: g = Goods()

In [14]: g.price
@property

In [15]: g.price = 100
@price.setter

In [16]: del g.price
@price.deleter

  • g.price​​ 单独调用自动执行 @property​​ 修饰的 price​​ 方法,并获取方法的返回值
  • g.price = 100​​ 赋值自动执行 @price.setter​​ 修饰的 price​​ 方法,并将 100​​ 赋值给方法的参数
  • del g.price​​ 删除自动执行 @price.deleter​​ 修饰的 price​​ 方法

# 注意

  • 旧式类中的属性只有一种访问方式,其对应被 @property​​ 修饰的方法

  • 新式类中的属性有三种访问方式,并分别对应了三个被@property​​、@方法名.setter​​、@方法名.deleter​​ 修饰的方法

    由于新式类中具有三种访问方式,我们可以根据它们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除。

# Goods类@property应用

class Goods(object):

    def __init__(self, name, price):
        # 原价
        self.original_price = price

        # 折扣
        self.discount = 0.8

    @property
    def price(self):
        # 实际价格 = 原价 * 折扣
        new_price = self.original_price * self.discount
        return new_price

    @price.setter
    def price(self, value):
        self.original_price = value

    @price.deleter
    def price(self):
        print('删除商品原价')
        del self.original_price

      
# ipython测验
In [22]: g = Goods('小米手机', 2000)

In [23]: g.price
Out[23]: 1600.0

In [24]: g.price = 3000

In [25]: g.price
Out[25]: 2400.0

In [26]: del g.price
删除商品原价

In [27]: g.price
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-27-38ee45b469f2> in <module>
----> 1 g.price

<ipython-input-18-d5ea66eb7ece> in price(self)
     12     def price(self):
     13         # 实际价格 = 原价 * 折扣
---> 14         new_price = self.original_price * self.discount
     15         return new_price
     16

AttributeError: 'Goods' object has no attribute 'original_price'

# 类属性方式

创建值为 property​ 对象的类属性,当使用类属性的方式创建 property​ 属性时,旧式类​ 和 新式类​无区别


class Foo:
  
    def get_bar(self):
        return 'get_bar'

    BAR = property(get_bar)
  
  
# ipython 测验
In [32]: f = Foo()

In [33]: f.BAR
Out[33]: 'get_bar'

f.BAR​ 自动调用 get_bar()​ 方法,并获取方法的返回值

property()​ 中有个四个参数

  • 第一个参数是方法名,调用 对象.属性 时自动触发执行方法
  • 第二个参数是方法名,调用 对象.属性 = XXX 时自动触发执行方法
  • 第三个参数是方法名,调用 del 对象.属性 时自动触发执行方法
  • 第四个参数是字符串,调用 对象.属性._doc**​ _ ,此参数是该属性的描述信息
class Foo(object):

    def __init__(self, bar):
        self.bar = bar
  
    def get_bar(self):
        print('get_bar')
        return self.bar

    def set_bar(self, value): 
        """必须要有两个参数"""
        print('set bar ' + value)
        self.bar = value

    def del_bar(self):
        print('del bar')
        del self.bar

    BAR = property(get_bar, set_bar, del_bar, "bar description...")

  
# ipython测验
In [50]: f = Foo('python')

In [51]: f.BAR
get_bar
Out[51]: 'python'

In [52]: f.BAR = 'Java'
set bar Java

In [53]: f.BAR
get_bar
Out[53]: 'Java'

In [54]: del f.BAR
del bar

# 手写property

  • 前置知识:装饰器的本质:函数调用和对象替换、描述器
import inspect

def debug(func):
    def wrapper(*args, **kwargs):
        # 获取函数名称
        function_name = func.__name__
        # 获取局部变量
        local_vars = inspect.currentframe().f_back.f_locals
        # 打印函数名和局部变量
#         print(f"""
# Calling {function_name} 
#     with args: {args} 
#     kwargs: {kwargs} 
#     local variables: {local_vars}
  
#         """)

        return func(*args, **kwargs)
    return wrapper


class Property:
    @debug
    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        self.__doc__ = doc

    @debug
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

    @debug
    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)

    @debug
    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

    @debug
    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)
  
    @debug
    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)
  
    @debug
    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.__doc__)


class Rectangle:
    def __init__(self, width, height):
        self._width = width
        self._height = height

    @Property
    def width(self):
        return self._width

    @width.setter
    def width(self, value):
        self._width = value

    @Property
    def height(self):
        return self._height

    @height.setter
    def height(self, value):
        self._height = value

    @Property
    def area(self):
        return self._width * self._height


r = Rectangle(3, 4)

print(r.width)  # Output: 3


print(r.height)  # Output: 4
print(r.area)  # Output: 12

r.width = 5
print(r.width)  # Output: 5
print(r.area)  # Output: 20

del r.height
print(r.height)  # Raise AttributeError: can't delete attribute