# 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