目录
(一)@property、@*.setter装饰器用法
(二)删除器@*.deleter
(三)使用__slots__对属性加以限制
(四)使用已有方法定义访问器/修改器/删除器
(五)@classmethod
(六)@abstractmethod
(七)@staticmethod
在Python中,如果我们仅仅使用property()函数为类的属性设置装饰器,那么该属性是只读的,不可以被修改。
如果,我们需要为类属性添加一个可写的装饰器,那么我们需要使用 @propertyname.setter
来修饰类的属性,但此时类的属性是可以被删除的。
如果,我们需要为类属性添加一个删除的装饰器,那么我们需要使用 @propertyname.deleter
来修饰类的属性。
(一)@property、@*.setter装饰器用法
在数据的读取和存入前常常需要对数据进行预处理,通过@property和@*.setter两个装饰器就可以方便的实现。
@property装饰器可以总结为两个作用:
- 让函数可以像普通变量一样使用
- 对要读取的数据进行预处理
@*.setter装饰器可以总结为两个作用:
- 在数据从外部存入类属性前对数据进行预处理
- 设置可读属性(不可修改)
注意:@*.setter装饰器必须在@property装饰器的后面,且两个被修饰的函数的名称必须保持一致,* 即为函数名称。
class Circle(object):
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, radius):
self._radius = radius if radius > 0 else 0
if __name__ == '__main__':
radius = float(input('请输入游泳池的半径: '))
small = Circle(radius)
一、@property作用
不加@property时我们可以使用small.radius()调用radius,但 radius更像是small实例的一个属性,而非它的一个函数,使用@property装饰器我们就看可以使用small的属性方式来得到radius
print(small.radius)
对于radius来说使用类属性 small.radius 的方式获取会比 small.radius() 更加的合理。
二、@*.setter作用
1)数据存入small.radiu前先对其进行处理再存入,实例中大于0则存入类属性_radius中,实现在存入数据之前对数据进行处理。
2)设置只读属性,即无法对属性进行修改。
class Circle(object):
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, radius):
print('不能修改')
if __name__ == '__main__':
radius = float(input('请输入游泳池的半径: '))
small = Circle(radius)
print(small.radius)
在为radius赋值时会立即触发@radius.setter装饰器下的radius函数中的提示语句或者异常处理语句。
综合实例
通过@*.setter和@property的组合使用我们就可以实现密码的密文存储和明文输出,具体步骤为:用户输入明文->转化为密文后存入->用户读取时先转化为明文再输出。
class User():
def __init__(self, name):
self.name = name
self._password = '' # 密文存储
@property
def password(self):
return decryption(self._password) # 解密
@password.setter
def password(self,word):
self._password = encryption(word) # 加密
user = User('xiao')
user.password = '123' #明文输入
print(user.password) #明文输出
总结:为两个同名函数打上@*.setter装饰器和@property装饰器后,当把函数作为变量赋值时会触发@*.setter对应的函数,当把函数作为变量读取时会触发@property对应的函数,因此我们可以将其用于数据的预处理。
(二)删除器@*.deleter
class Car(object):
__slots__ = ('_brand', '_max_speed')
def __init__(self, brand, max_speed):
self._brand = brand
self._max_speed = max_speed
@property
def brand(self):
return self._brand
@brand.setter
def brand(self, brand):
self._brand = brand
@property
def max_speed(self):
return self._max_speed
@max_speed.setter
def max_speed(self, max_speed):
if max_speed < 0:
raise ValueError('Invalid max speed for car')
self._max_speed = max_speed
def __str__(self):
return 'Car: [品牌=%s, 最高时速=%d]' % (self._brand, self._max_speed)
car = Car('QQ', 120)
del car.brand
未添加删除器运行del 属性时:
添加删除器:
class Car(object):
def __init__(self, brand, max_speed):
self._brand = brand
self._max_speed = max_speed
@property
def brand(self):
return self._brand
@brand.setter
def brand(self, brand):
self._brand = brand
@brand.deleter
def brand(self):
del self._brand
print('已删除brand')
@property
def max_speed(self):
return self._max_speed
@max_speed.setter
def max_speed(self, max_speed):
if max_speed < 0:
raise ValueError('Invalid max speed for car')
self._max_speed = max_speed
def __str__(self):
return 'Car: [品牌=%s, 最高时速=%d]' % (self._brand, self._max_speed)
car = Car('QQ', 120)
del car.brand
成功将类属性删除。
(三)使用__slots__对属性加以限制
在python新式类中,可以定义一个变量__slots__,它的作用是阻止在实例化类时为实例分配dict
默认情况下每个类都会有一个dict,通过__dict__访问,这个dict维护了这个实例的所有属性,举例如下:
class Car(object):
val=3
def __init__(self):
pass
car = Car()
car.x=2
print(car.__dict__)
dict只保存实例中变量,对于类的属性是不保存的,类的属性包括变量和函数。
由于每次实例化一个类都要分配一个新的dict,存在空间的浪费,因此有了__slots__。__slots__是一个元组,包括了当前能访问到的属性。
当定义了slots后,slots中定义的变量变成了类的描述符,相当于java,c++中的成员变量声明,类的实例只能拥有slots中定义的变量,不能再增加新的变量。注意:定义了slots后,就不再有dict。
class Car(object):
__slots__ = ('_brand', '_max_speed')
def __init__(self, brand, max_speed):
self._brand = brand
self._max_speed = max_speed
@property
def brand(self):
return self._brand
@brand.setter
def brand(self, brand):
self._brand = brand
@brand.deleter
def brand(self):
del self._brand
print('已删除brand')
@property
def max_speed(self):
return self._max_speed
@max_speed.setter
def max_speed(self, max_speed):
if max_speed < 0:
raise ValueError('Invalid max speed for car')
self._max_speed = max_speed
def __str__(self):
return 'Car: [品牌=%s, 最高时速=%d]' % (self._brand, self._max_speed)
car = Car('QQ', 120)
print(car)
car.current_speed = 80
不允许设置除了slot中变量以外的其他实例变量。
(四)使用已有方法定义访问器/修改器/删除器
属性全部由函数定义,最后用property()封装为属性brand的方法。
class Car(object):
def __init__(self, brand, max_speed):
self.set_brand(brand)
self.set_max_speed(max_speed)
def get_brand(self):
return self._brand
def set_brand(self, brand):
self._brand = brand
def get_max_speed(self):
return self._max_speed
def set_max_speed(self, max_speed):
if max_speed < 0:
raise ValueError('Invalid max speed for car')
self._max_speed = max_speed
def __str__(self):
return 'Car: [品牌=%s, 最高时速=%d]' % (self._brand, self._max_speed)
# 用已有的修改器和访问器定义属性
brand = property(get_brand, set_brand)
max_speed = property(get_max_speed, set_max_speed)
property() 函数
语法:
property(fget=None, fset=None, fdel=None, doc=None) -> property attribute
说明:
fget 是获取属性值的方法。
fset 是设置属性值的方法。
fdel 是删除属性值的方法。
doc 是属性描述信息。如果省略,会把 fget 方法的 docstring 拿来用(如果有的话)
brand = property(get_brand, set_brand,del_brand)
表示当获取属性值时执行函数get_brand的代码,当设置属性值时执行函数set_brand的代码,当删除属性值时执行函数del_brand的代码。
(五)@classmethod
from time import time, localtime, sleep
class Clock(object):
"""数字时钟"""
def __init__(self, hour=0, minute=0, second=0):
self._hour = hour
self._minute = minute
self._second = second
@classmethod
#这里第一个参数是cls, 表示调用当前的类名
def now(cls):
ctime = localtime(time())
#返回用获取的时间初始化的类
return cls(ctime.tm_hour, ctime.tm_min, ctime.tm_sec)
def run(self):
"""走字"""
self._second += 1
if self._second == 60:
self._second = 0
self._minute += 1
if self._minute == 60:
self._minute = 0
self._hour += 1
if self._hour == 24:
self._hour = 0
def show(self):
"""显示时间"""
return '%02d:%02d:%02d' % \
(self._hour, self._minute, self._second)
它的作用就是有点像静态类,比静态类不一样的就是它可以传进来一个当前类作为第一个参数。
调用:
clock = Clock.now()
while True:
print(clock.show())
sleep(1)
clock.run()
调用的时候相当于先调用clock.now()得到localtime(time())当前本地时间,然后才用当前本地时间作为参数对Clock类进行初始化。
@classmethod也可用作对输入先进行处理之后在对处理之后的数据创建类。这样的好处就是你以后重构类的时候不必要修改构造函数,只需要额外添加你要处理的函数,然后使用装饰符 @classmethod 就可以了。
(六)@abstractmethod
有时,我们抽象出一个基类,知道要有哪些方法,但并不实现功能,只能继承,而不能被实例化,但子类必须要实现该方法,要用到抽象基类。
from abc import ABCMeta, abstractmethod
class Employee(object, metaclass=ABCMeta):
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
@abstractmethod
def get_salary(self):
pass
class Manager(Employee):
# 想一想: 如果不定义构造方法会怎么样
def __init__(self, name):
# 想一想: 如果不调用父类构造器会怎么样
super().__init__(name)
def get_salary(self):
return 12000
class Programmer(Employee):
def __init__(self, name):
super().__init__(name)
def set_working_hour(self, working_hour):
self._working_hour = working_hour
def get_salary(self):
return 100 * self._working_hour
class Salesman(Employee):
def __init__(self, name):
super().__init__(name)
def set_sales(self, sales):
self._sales = sales
def get_salary(self):
return 1500 + self._sales * 0.05
if __name__ == '__main__':
emps = [Manager('武则天'), Programmer('狄仁杰'), Salesman('白元芳')]
for emp in emps:
if isinstance(emp, Programmer):
working_hour = int(input('请输入%s本月工作时间: ' % emp.name))
emp.set_working_hour(working_hour)
elif isinstance(emp, Salesman):
sales = float(input('请输入%s本月销售额: ' % emp.name))
emp.set_sales(sales)
print('%s本月月薪为: ¥%.2f元' % (emp.name, emp.get_salary()))
抽象基类只能继承而不能实例化,子类要实例化必须先实现该方法。
例子中定义职工基类,要有读取salary的功能,在子类manager、programmer、salesman中分别重写该方法,这三个子类才能够被实例化,否则不能创建实例化对象。
(七)@staticmethod
而它所装饰的方法既不和实例化对象绑定,也不和类绑定所以它既不能调用实例化对象的属性,也不能使用类的属性。
使用场景
- 在类中实现一些与类相关的操作,但不需要访问类的属性和方法时。
- 定义一个与类无关的辅助函数,但又不想将其定义在类之外。
from math import sqrt
class Triangle(object):
def __init__(self, a, b, c):
self._a = a
self._b = b
self._c = c
# 静态方法
@staticmethod
def is_valid(a, b, c):
return a + b > c and b + c > a and c + a > b
# 实例方法
def perimeter(self):
return self._a + self._b + self._c
def area(self):
p = self.perimeter() / 2
return sqrt(p * (p - self._a) * (p - self._b) * (p - self._c))
if __name__ == '__main__':
# 用字符串的split方法将字符串拆分成一个列表,再通过map函数对列表中的每个字符串进行映射处理成小数
a, b, c = map(float, input('请输入三条边: ').split())
# 先判断给定长度的三条边能否构成三角形,如果能才创建三角形对象
if Triangle.is_valid(a, b, c):
tri = Triangle(a, b, c)
print('周长:', tri.perimeter())
print('面积:', tri.area())
# 如果传入对象作为方法参数也可以通过类调用实例方法
print('周长:', Triangle.perimeter(tri))
print('面积:', Triangle.area(tri))
# 看看下面的代码就知道其实二者本质上是一致的
print(type(tri.perimeter))
print(type(Triangle.perimeter))
else:
print('不能构成三角形.')
调用是使用类名.方法名(参数…)调用,当然也可以使用对象进行调用(本质上一样,因为对象调用会先在对象的字典中找方法,找不到再去类的字典中找)。