文章目录
第一节:关于类
面向对象
- 面向对象
之前我们介绍过函数,函数是对程序语句的封装与复用;
类是则是对变量
和函数
的封装和复用,封装有机关联的变量和函数为类,变量和函数,称为类的属性
和方法
;
由于类比函数的封装又提高了一个层次,因此复用性也得到进一步提升;(试想一下维护100个函数直观容易,还是维护5个类直观容易呢?)
面向过程的编程是以函数
为核心的,而面向对象的编程是以类
为核心的,一切功能的实现,都是通过创建一个司职该功能的类的实例,进而调用其方法去予以实现的;
以类为核心的、面向对象的编程,提高了代码的模块化程度,便于大规模协作的开展;
在面向对象的程序开发(Object-Oriented-Programming或OOP)中,架构师的工作,往往只是模块拆分、接口定义、实例组装,类内部的具体方法接口的实现,则交由其他人去完成;
现如今的高级语言,基本都是面向对象的;
面向对象的三大特性:封装
、继承
、多态
;
另一种提法是四大特性,即三大特性的基础上,再加上一个抽象
;
- 封装
封装就是将常用的代码段封装为函数,常用的函数封装为类,常用的类封装为模块与类库;
封装提高了代码的可维护性和复用性,同时也更利于开源与传播;
封装性催生了模块化,便于大规模协作开发的开展;
- 继承
正如自然界中,动物=>人=>程序员的关系,人是动物类的一个分支,程序员是人的一个分支,在编程中,我们可以通过继承来表达这样的关系;
动物类是人类的父类(又叫超类),人是程序员的父类;
动物类的共同特征,如都有生命、都会新陈代谢、都会死亡等等,在定义人这个类时,是无需重复声明的,这样就在一层层的继承中,节省了大量的代码;
有继承就有发展,否则继承就没有意义;
【编程中的继承】
在编程中,发展体现为:
①在父类的基础上增加新的属性与方法;
②重写(或者叫覆写、覆盖)父类方法;
对父类方法的覆写,又可以分为【颠覆式】和【改良式】两种;
【颠覆式】覆写是指子类全盘否定父类方法实现,由子类自己做一个全新的实现;
【改良式】覆写是指子类先将父类的实现拿过来,再进行新的拓展;
- 多态
在继承的基础上,一个父类会有不同的子类实现,如战士父类可以有骑兵、步兵、弓弩手等不同子类;
多态,即【一个父类可以有多种不同的子类形态】;
【多态的共性与个性】
在多态中,同源子类之间,既存在共性,又存在个性,共性与个性各有其用处;
例如骑兵、步兵、弓弩手都是战士的子类,因此他们都有进攻方法与防守方法,这就是【共性】;
而他们的具体进攻方法各自有不同的实现,骑兵冲锋、步兵肉搏、弓弩手射箭,这就是【个性】;
在一支由不同兵种组成的军队中(即战士实例的集合),当总司令下达全体进攻的命令时,不同兵种做何种具体形式的进攻(个性)是不重要的,重要的是共性;
当司令想要采用一些细腻的战术时,比如先进行一轮齐射,再由骑兵进行一轮踩踏,最后上步兵打扫战场,此时不同兵种的差异化进攻方式则展现其价值,此时个性变得重要;
共性是由父类所带来的,个性是由子类覆写所带来的;
- 抽象
在继承和多态中,如果父类不对某个方法做任何实现,只是留白,具体的实现交由子类自己去完成,这时我们称该方法是抽象的;
如果一个父类中的所有方法都是抽象的,我们就称该类为一个【接口】;
抽象是一种艺术,架构师就是这种艺术的玩弄者;
架构师所做的工作,就是拆分模块,定义接口,完成预组装,形成一具没有血肉的架,然后将填充的工作下放到团队中的不同程序员,填充完成之日即是项目大厦落成之时;
类的封装
例:
-
封装一个人的类Person,需求如下:
1、封装以下属性:姓名、年龄、存款
2、封装自我介绍方法,陈述以上属性
3、创建一个人,设置其基本信息
4、打印此人信息
5、令此人进行自我介绍
# 封装一个Person类,将与人有关的属性、方法组合在一起,以便将来复用
class Person:
# 属性定义和默认值
name = "林阿华"
age = 20
rmb = 50
# 构造方法:外界创建类的实例时会调用
# 构造方法是初始化实例属性的最佳时机
def __init__(self,name,age,rmb):
print("__init__的方法被调用了")
self.name = name
self.age = age
self.rmb = rmb
# 自我介绍方法
# self = 类的实例
def tell(self):
print("我是%s,我%d岁了,我有存款%.2f万元"%(self.name,self.age,self.rmb))
- 创建实例p,并调用Person的tell()方法
# 创建Person类的实例
p = Person("易阿天",60,500)
# 调用实例的tell方法
p.tell()
执行结果:
注意将Person的属性和方法使用一个
标准制表符
缩进在Person的类定义以内;
“_ init _”
是类的构造方法,用于创建类的实例,左右各有两个下划线;
使用PyCharm输入完def __init时系统弹出提示,IDE会自动完成方法的定义;
每个方法在定义时,系统会自动加上一个self
参数在第一个参数位,这个参数代表将来创建的实例本身;
再调用方法时,self
是不必亲自传入的,self是系统用来标识实例本身的;
构造方法的调用形式为:Person(self以外的其它参数)
;
类的私有成员
【成员】就是指类的
属性
和方法
;
【私有】,即不能再类的外界进行访问;
目的是为了保障安全
,如涉及隐私的属性、核心方法实现等;
例:
- 封装一个人的类Person,需求如下:
1、创建一个Person类,添加存款信息
2、保护存款信息,将其设置为私有
3、为存款信息添加保护,使其不能被直接访问
4、增加设置密码功能 ·增加存款查询功能
5、只有输入密码正确的情况下才能查询存款信息
class Person:
# 普通属性与私有属性
name = "林阿华"
age = 20
__rmb = 1000 #(须通过公有方法来访问)
# 私有方法:设置存款
def __setrmb(self,rmb):
self.__rmb = rmb
# 通过普通方法访问私有方法进行存款设置
def setrmb(self,rmb):
pwd = input("请输入设置密码:")
if (pwd == "123456"):
self.__setrmb(rmb)
else:
print("您没有权限")
# 公开一个普通方法,共外界访问私有属性self.__rmb
def getrmb(self):
pwd = input("请输入查询密码:")
if (pwd == "123456"):
return self.__rmb
else:
return "您没有访问权限"
# 普通方法
def tell(self):
print("大家好,我是%s"%(self.name))
- 创建Person实例,并通过公有方法访问私有成员
p = Person()
#通过实例访问类的普通属性
print(p.name)
print(p.age)
# 私有成员不能被直接访问
# print(p.__rmb) # AttributeError: 'Person' object has no attribute '__rmb'
#通过实例访问类的普通方法
p.tell()
#通过普通方法访问私有属性
rmb = p.getrmb()
print("我的存款是:",rmb)
# 通过普通方法访问私有方法
p.setrmb(500)
rmb = p.getrmb()
print("我的存款是:",rmb)
执行结果:
- 注意的几个问题
1、代码中的__rmb
属性、__setrmb
方法都是私有的,在类的外部是无法p.__rmb
进行直接访问的;
2、任何前置两个下划线的成员(属性与方法)都是私有的,只能在类的内部进行访问;
外界访问私有成员的方法是,使用公有方法对外界提供私有成员访问接口
,但在内部先行进行权限校验,如输入密码等,如本例中的setrmb
方法和getrmb
方法;
类的专有方法
- 概述
当类没有继承于任何类时,它默认继承的是 【系统的object】 类
在object类中,定义了许多专有方法,它们的方法名是这样的:_ xxx _
,左右各有两个下划线;
专有方法不是用来给实例直接调用的,而是有其特定的用途,比如_ init _
是在外界调用类名创建实例时调用的;
再比如,当外界print(obj)时,其输出的字符串其实是来源于obj对应的类的_ str _
方法的返回值的;
-
常见专有方法
___init___
: 构造函数,在生成对象时调用
___del___
: 析构函数,释放对象时使用
___str___
:实例的打印样式
___len___
: 获得长度
___ gt___
: 比较大小(对象之间进行算术运算后,应返回一个计算后的结果)
___add___
: 加运算
___sub___
: 减运算
___mul___
: 乘运算
___mod___
: 求余运算
___pow___
: 乘方
例:封装一个更加标准化的Person,需求如下:
在创建对象时打印日志
在对象销毁时打印日志
自定义对象的打印样式
设法统计人的“长度”
# 封装一个Person类,将与人有关的属性、方法组合在一起,以便将来复用
class Person:
# 初始默认的属性
name = "某某某"
age = 0
rmb = 0
# 构造方法:外界创建类的实例时调用 ,构造方法是初始化实例属性的最佳时机
def __init__(self, name, age, rmb):
print("__init__:我被创建了")
self.name = name
self.age = age
self.rmb = rmb
# 析构方法,在对象被删除时调用
def __del__(self):
print("__del__:我被删除了")
# 在对象被打印时,提供一个供打印的字符串
def __str__(self):
return "{name:%s;age:%d;rmb:%.2f}" % (self.name, self.age, self.rmb)
# 返回对象的长度,如何计算长度是自定义的
def __len__(self):
return int(self.rmb)
# 比较当前实例是否大于另一个同类的other实例,比较方法自定义
def __gt__(self, other):
if self.rmb > other.rmb:
return True
else:
return False
# 定义与另一个实例相加的结果
def __add__(self, other):
return Person(self.name + "-" + other.name, min(self.age, other.age), self.rmb + other.rmb)
# 定义与另一个实例相减的结果
def __sub__(self, other):
return Person(self.name + "VS" + other.name, self.age+other.age, abs(self.rmb - other.rmb))
# 定义与另一个实例相乘的结果
def __mul__(self, other):
print("相乘")
return self
# 定义对另一个对象求模的结果
def __mod__(self, other):
print("取余")
return other
# 自我介绍方法
# self = 类的实例
def tell(self):
print("我是%s,我%d岁了,我有存款%.2f万元" % (self.name, self.age, self.rmb))
创建对象,并执行打印、比较、运算等操作
# 创建Person类的实例,程序开始时实例对象会被系统创建,会调用对象的__init__方法,多少个实例,调用多少次
p1 = Person("易阿天", 60, 500)
p2 = Person("林阿华", 20, 10)
# 调用实例方法
p1.tell()
# 调用__str__方法打印对象
print(p1,p2)
# 调用__len__方法求对象长度
print(len(p1))
# 调用__gt__方法进行比较操作
print(p1 > p2)
# 调用__add__方法进行加操作,使用此类操作,会创建一个新对象,再次会短暂调用__str__和__del__方法,
print(p1 + p2)
# 调用__sub__方法进行加操作,同上会创建新对象
print(p1 - p2)
# 调用__mul__方法进行加操作,同上会创建新对象
print(p1 * p2)
# 调用__mod__方法进行加操作,同上会创建新对象
print(p1 % p2)
# 程序结束时实例对象会被系统销毁,会调用对象的__del__方法,多少个实例,调用多少次
执行结果:
- 专有方法__repr__与__str__
__repr__
是代表的意思,与__str__
功能相似,都是返回字符串。
当我们调用print()时,打印一个字符串时,优先使用的是__str__
方法返回的字符串如果未定义则使用__repr__
方法;
但是如果是在终端直接输出时,则直接使用__repr__
方法返回的字符串;
例:在控制台中
正常来说,在IDE中,我们要打印a的值,要使用
print(a)
,直接a
是打印不出来的,但在控制台上却可以。此时的a,的值就是从__repr__
里获得的,而且只能从这里获得。
例:在pycharm中
新建一个文档文件,这里自定义一个名字为“MyRepr”
# 定义一个person类
class Person:
# 定义一个构造方法
def __init__(self,name,age):
self.name = name
self.age = age
def __str__(self):
return "sPerson{name:%s,age:%d}"%(self.name,self.age)
#定义一个__repr__的方法,这个方法在这里只是__str__的备用,当没有时才会调用
def __repr__(self):
return "rPerson{name:%s,age:%d}"%(self.name,self.age)
if __name__ == '__main__':
# 建立一个p对象
p = Person("bill",60)
print(p)
执行结果:
如果使用控制台执行此文档,结果却不一样,因为控制台 print( p )会找 __str__
方法,但p只找 __repr__
方法
类的继承与多继承
-
类的继承
例:人与坏蛋继承人(Person)实现一个坏蛋类,用以存储坏蛋的信息
坏蛋拥有人的一切正常人的属性和功能
坏蛋有【恶习】属性和【作恶】方法
创建一个坏蛋,为其设置其基本信息和恶习
令其作恶
#这是上节课关于人(Person)这个类的代码,这里重写一次并试着封装在myPerson.py模块中。
class Person:
#人的默认属性和值
name = "某某某"
age = 1
rmb = 0
#人类被创建 # 构造方法,人类被创建初始化实例
def __init__(self,name,age,rmb):
print("__init__的方法被调用了")
self.name = name
self.age = age
self.rmb = rmb
# 人类会的方法-自我介绍
def tell(self):
print("我是%s,我%d岁了,我有存款%.2f万元"%(self.name,self.age,self.rmb))
开始继承…
# 导入父类
from myPerson import Person
#定义一个坏蛋类,继承于人类,因为坏蛋也是人
class Bastard(Person):
# 此时什么都不写,也已经具有了父类的所有默认属性和自我介绍的方法
# 定义一个坏蛋特有的属性‘作恶’,默认为空
badHobby = None
# 覆写和父类的构造方法
def __init__(self, name, age, rmb, badHobby):
# 调用父类构造方法实现name,age,rmb的设置
super().__init__(name, age, rmb)
print("__init__:坏蛋自己初始化了")
self.badHobby = badHobby
# 覆写父类的自我介绍方法
def tell(self):
super().tell()
#自己的自我介绍方法
print("劳资乃%s,此路是我开,此树是我栽,要想从此过,留下买路财"%(self.name))
# 特有方法:作恶
def doBadThings(self):
print("老子专好%s" % (self.badHobby))
# 创建坏人实例:
b = Bastard("李逵", 3, 0.01, "喝酒杀人")
b.tell()
b.doBadThings()
执行结果:
修改器与获取器
例:引用回上面的例子,人与坏蛋
# 导入父类
from myPerson import Person
# 继承于Person类
class Bastard(Person):
# 特色属性:恶习
badHobby = None
# 覆写和父类的构造方法
def __init__(self, name, age, rmb, badHobby):
super().__init__(name, age, rmb)
print("__init__:坏蛋自己初始化了")
self.badHobby = badHobby
# 覆写父类方法
def tell(self):
super().tell()
print("劳资乃%s,此路是我开,此树是我栽,要想从此过,留下买路财"%(self.name))
# 设置器,用于修改self.badHobby
def setBadHobby(self, badHobby):
self.badHobby = badHobby
# 获取器
def getBadHobby(self):
return self.badHobby
# 特有方法:作恶
def doBadThings(self):
print("老子专好%s" % (self.badHobby))
#创建实例
b = Bastard("李逵", 3, 0.01, "喝酒杀人")
#修改坏蛋的作恶内容
b.setBadHobby("抢夺财物")
b.tell()
b.doBadThings()
执行结果:
- 多继承
概述
在Python中,一个类可以同时继承于多个类;
如果这些父类的成员之间相互没有冲突,那当然没有问题;
但如果这些父类中存在相同的成员(属性、方法),特别是相同的方法时,就会存在“我到底继承谁的?!”这样的疑问;
答案是,继承时【谁的次序在前,就优先继承谁】
例:Gay,Man Or Woman,需求如下:
封装男人类,继承于Person ,使之有阳刚风格的自我介绍,使之能咆哮
封装女人类,继承于Person,使之有阴柔风格的自我介绍,使之能撒娇
封装Gay类,使之同时具有男人和女人的特性
令其咆哮,令其撒娇
令其偏阳刚地进行自我介绍
令其偏阴柔地进行自我介绍
# 导入父类
from myPerson import Person
#定义男人,继承于‘人’这个父类
class Man(Person):
#自我介绍方法,Man继承了Person,它的tell是阳刚的
def tell(self):
print("劳资乃%s,劳资%d岁了,劳资有存款%.2f"%(self.name,self.age,self.rmb))
#男人的咆啸方法
def roar(self):
print("嗷!劳资天下第一!")
#定义女人,继承于‘人’这个父类
class Woman(Person):
#自我介绍方法,Woman继承了Person,它的tell是阴柔的
def tell(self):
print("伦家乃%s,伦家%d岁了,伦家有存款%.2f"%(self.name,self.age,self.rmb))
#女人的撒娇方法
def sajiao(self):
print("好讨厌了啦!")
#定义一个GAY,同时继承于男人和女人
class Gay(Woman,Man):
# 什么都不写,都已经具有了Man和Woman的全部属性和方法
pass
#创建一个男人实例
m = Man("史泰龙",40,10000)
m.tell()
m.roar()
#创建一个女人实例
w = Woman("凤姐",30,10000)
w.tell()
w.sajiao()
执行结果:
如果现在创建一个GAY的实例,同时继承于男人和女人,当然它都已经具有了Man和Woman的全部属性和方法,可以咆啸,也可以撒娇,那问题来了,自我介绍的方法应该用男人的还是女人的?
#创建GAY实例
g = Gay("库克",50,100000000000)
g.tell()
g.roar()
g.sajiao()
执行结果:
多继承,可以同时继承Man和Woman的衣钵,但对于同名方法,在前的类具有更高的优先级,Woman继承了Person,它的tell是阴柔的,如果想要让Gay变得更Man而不是更Woman,只需要调整其继承顺序即可
类的多态
动态绑定,指的是当调用一个子类实例的父类方法时,系统能够自动识别和调用【该子类自身对父类方法的实现】
例:军队,需求说明:
为帝国创建一支军队,包含骑兵、弓箭手、法师
所有兵种都能够进攻和防守,但形态各异
通过输入将令,控制每个兵种的攻守细节
#士兵有很多种类,所有兵种都能攻守且形态各异,我们为其定义共同父类——战士类Soldier
class Soldier:
# 进攻方法
def attack(self):
print("士兵进攻")
# 防守方法
def defend(self):
print("士兵防守")
#接下来定义各种不同形态的子类(多态),对攻防细节做不同实现
# 骑兵类 (继承于战士类)
class Calvary(Soldier):
def attack(self):
print("骑兵使用了铁蹄碾压")
def defend(self):
print("骑兵防守")
# 弓箭手类 (继承于战士类)
class Archer(Soldier):
def attack(self):
print("弓箭手使用了万箭齐射")
def defend(self):
print("弓箭手防守")
# 法师类 (继承于战士类)
class Master(Soldier):
def attack(self):
print("法师使用了魔法火焰")
def defend(self):
print("法师防守")
# 创建一支军队列表
kingsArmy = []
# 创建不同兵种的战士实例
c = Calvary()
a = Archer()
m = Master()
# 应征入伍,加入列表
kingsArmy.append(c)
kingsArmy.append(a)
kingsArmy.append(m)
while True:
print('')
cmd = input("将军请输入传您的将令:")
# 全体进攻(共性)
if cmd == "01":
for s in kingsArmy:
s.attack()
# 全体防守(共性)
elif cmd == "02":
for s in kingsArmy:
s.defend()
# 骑兵冲锋(个性)
elif cmd == "11":
for s in kingsArmy:
# 判断每个s是否是Calvary骑兵类的实例
if isinstance(s,Calvary):
s.attack()
else: s.defend()
else:
print("将军,请讲国语!")
break
执行结果:
静态方法和类方法
- 实例方法
通常在类中定义一个方法时,系统都会默认带上一个
self
参数,这样的方法称之为【实例方法】;
实例方法也就是,只有通过创建类的实例才能访问的方法;实例方法,只能通过实例
去调用
在面向对象编程中,我们有90%以上的时候是在和实例及实例方法打交道;
- 静态方法和类方法
在类中我们还可以定义两类方法,即【静态方法】、【类方法】,这两种方法不需要创建实例、通过
类名
就能访问;
【静态方法】以@staticmethod
装饰器进行装饰,它相当于一个写在类的作用域中的普通方法;访问不了实例的属性,只能访问类的属性 ,也无法创建实例
【类方法】以@classmethod
装饰器进行装饰,它有一个系统默认参数cls
,代表的是当前类,访问不了实例的属性,只能访问类的属性,但我们可以通过这个cls()
去创建一个类的实例;
由于是写在类的作用域中的,所以静态方法和类方法都能访问类中的“非实例”成员;
静态方法和类方法通过类名
去进行调用,也可以通过实例
去进行调用;
例:
class MyClass:
# 属于类(而非实例)的属性,静态方法和类方法都可以通过类名访问
name = None
# 实例方法,只能通过实例去调用,因为有self
def setName(self, name):
self.name = name
def tell(self):
print("我是", self.name)
# 类方法,访问不了实例的属性,只能访问类的属性 ,可以通过cls创建类的实例
@classmethod
def cMehtod(cls):
print(MyClass.name)
print('cls = ',cls)
# 调用构造方法创建对象
obj = cls()
# 调用对象方法
obj.setName("李练杰,我被构造方法cls()创建的对象setName()了")
obj.tell()
# 静态方法,访问不了实例的属性,只能访问类的属性 ,也无法创建实例。它只是一个写在类的定义域内的普通方法而已
@staticmethod
def sMethod():
print(MyClass.name)
# 创建实例并调用实例方法
m = MyClass()
m.setName("李四")
m.tell()
# 通过实例去调用类方法
m.cMehtod()
# 通过实例去调用静态方法
m.sMethod()
执行结果:
当然,想调用类方法和静态方法也可以不创建实例的,直接调用就可以了
例:
# 只通过类名去调用类方法和静态方法
MyClass.cMehtod()
MyClass.sMethod()
执行结果:
第二节:装饰器
三器合一
- 属性访问器与数据安全
我们常常为类的属性加添相应的访问器,如设置器
setxxx()
,获取器getxxx()
,删除器delxxx()
,(可参考本章的‘类的私有成员案例’)
访问器的目的是为了数据安全(如数据合法性、访问权限等),对于一个没有访问器守护的属性,任何人都可以对它为所欲为
例:
class C():
# 私有属性
__RMB = 2000 # 私有属性
rmb = 1000 # 公有属性
# 获取器(我们可以在这里设置相应的访问权限等)
def getRMB(self):
return self.__RMB
# 设置器(我们可以在这里校验数据的合法性等)
def setRMB(self, value):
# 这里强制x的值必须大于0,否则抛出异常
if value < 0:
raise ValueError("a positive x required")
self.__RMB = value
# 删除器(我们可以在这里设置相应的访问权限等)
def delRMB(self):
# 这里必须输入正确的密码才能执行删除操作
while True:
pwd = input("请输入你的密码:")
if pwd == "123456":
# del self.__RMB
print("密码正确,RMB 已删除!")
break
else:
print("密码错误,请重新输入。。")
对于一个没有访问器守护的属性rmb,任何人都可以对它为所欲为
例:
# 随意进行访问
c = C()
print('你好,你有%s元'%c.rmb)
# 还可以随意设置值
c.rmb = 1
print('你好,你有%s元'%c.rmb)
# 随意删除属性和值
del c.rmb
print(c.rmb)
执行结果:
但是对于有访问器守护的属性__RMB,由于是私有属性,只能通过访问器进行访问了
# 试用原方法访问私有属性,已经不能访问RMB
c = C()
# print('你好,你有%s元'%c.__RMB)
# c.setRMB(-10) # ValueError: a positive x required,再也不能胡乱访问和设置值了
# 访问数据必须使用我给的方法
print(c.getRMB())
# 设置值必须使用我给的方法
c.setRMB(10) # 乖乖设置一个正数,否则报错
print(c.getRMB())
# 删除值必须通过我设定的规则
c.delRMB() # 删除时必须输入密码,输错密码还不行
执行结果:
- property装饰器
property装饰器的作用是将属性的各种访问器(setx,getx,delx)合而为一,俗称‘三器合一’,用于小小地偷个懒
原本的c.setRMB(value)
,加装饰器后就可简化为c.RMB = value
原本的print(c.getRMB())
,加装饰器后就可简化为print(c.RMB)
原本的c.delRMB()
,加装饰器后就可简化为del c.RMB
property是标准库提供的一个类,一个property实例映射一个普通类属性,我们可以在创建property时指定其对应属性的各种访问器,这样就可以了
例:用回上一个例子
class C():
# 私有属性
__RMB = 2000 # 私有属性
rmb = 1000 # 公有属性
# 获取器(我们可以在这里设置相应的访问权限等)
def getRMB(self):
return self.__RMB
# 设置器(我们可以在这里校验数据的合法性等)
def setRMB(self, value):
# 这里强制x的值必须大于0,否则抛出异常
if value < 0:
raise ValueError("a positive x required")
self.__RMB = value
# 删除器(我们可以在这里设置相应的访问权限等)
def delRMB(self):
# 这里必须输入正确的密码才能执行删除操作
while True:
pwd = input("请输入你的密码:")
if pwd == "123456":
# del self.__RMB
print("密码正确,RMB 已删除!")
break
else:
print("密码错误,请重新输入。。")
# 创建property实例的方式,声明属性和它的各种“器”
pRMB = property(fget=getRMB, fset=setRMB, fdel=delRMB, doc="我是 'RMB'的 property装饰器.")
现在__RMB属性通常的getx,setx,delx现在都被简化为pRMB了
c = C()
# 获取器c.getRMB()方法简化成c.pRMB
print(c.pRMB)
# 设置器c.setRMB(value)方法简化成c.pRMB = value
c.pRMB=10
print(c.pRMB)
# 删除器c.delRMB()方法简化成del c.pRMB
del c.pRMB
执行结果完全一样
上面的方法虽然使用了property,实现了三器合一,但property装饰器其实还可以更简洁直观地对访问器进行定义的版本
例:
class C():
__RMB = 2000
# 属性声明,没有获取器时,可以作为获取器使用
@property
def RMB(self):
return self.__RMB
# 获取器(我们可以在这里设置相应的访问权限等)
@RMB.getter
def RMB(self):
return self.__RMB
# 设置器(我们可以在这里校验数据的合法性等)
@RMB.setter
def RMB(self, value):
# 这里强制x的值必须大于0,否则抛出异常
if value < 0:
raise ValueError("a positive x required")
self.__RMB = value
# 删除器(我们可以在这里设置相应的访问权限等)
@RMB.deleter
def RMB(self):
# 这里必须输入正确的密码才能执行删除操作
while True:
pwd = input("请输入你的密码:")
if pwd == "123456":
# del self.__RMB
print("密码正确,RMB 已删除!")
break
else:
print("密码错误,请重新输入。。")
'''
这个时候就不必像上面一样创建property实例的方式,声明属性和它的各种“器”了
因为对__RMB属性的设置、获取、删除,同样全部通过c.RMB一站式了 ,访问器函数都被命名为RMB
'''
实例的调用不变
c = C()
# 获取器c.getRMB()方法简化成c.RMB
print(c.RMB)
# 设置器c.setRMB(value)方法简化成c.RMB = value
c.RMB=10
print(c.RMB)
# 删除器c.delRMB()方法简化成del c.pRMB
del c.RMB
执行结果:
无参装饰器
- 什么是装饰器
装饰器是以
@
写在一个函数的头上,用于装饰这个函数
装饰器函数的原理,是接收一个函数func1
作为参数,并使用一个包装函数func2
将这个func1函数包装起来;
包装函数func2除了调用func1函数以外,还可以在其收尾添加新的代码片段;
最终装饰器函数返回包装函数func2
作为func1函数的替身;
- 使用无参装饰器
一个简单例子:
# 定义一个装饰器函数
def wrapper(func):
# 定义一个包装函数用于接收普通函数
def inner(*args, **kwargs):
print("----------")
func(*args, **kwargs)
print("----------")
return inner
# 定义一个普通函数,并使用wrapper装饰器装饰
@wrapper
def func1(name):
print("name", name)
func1('柠檬茶')
执行结果:
带参装饰器
带参装饰器的原理,就是在无参装饰器的基础上,再加一层嵌套,这一层嵌套的目的,就是为了接收
装饰器参数
;
例:
# 定义一个带参装饰器函数,用于接收参数
def argWrapper(ps=None):
def wrapper(func):
# 定义一个包装函数用于接收普通函数
def inner(*args, **kwargs):
print("-----%s-----"%ps)
func(*args, **kwargs)
print("----------")
return inner
return wrapper
# 定义一个普通函数,并使用argWrapper带参装饰器装饰,参数为ps='标题'
@argWrapper(ps='标题')
def func1(name):
print("name", name)
func1('柠檬茶')
执行结果:
使用类定义装饰器
使用类定义装饰器,其实与带参装饰器的思路很类似;只不过装饰器参数,是以
创建装饰器实例
的方式传入的;对普通函数的封装,则与无参装饰器是类似的,即返回一个包装函数作为源函数的替身;
类定义装饰器的专有方法_ call _
以及系统装饰器@ wraps(func)
则是必须的语法规范;
与带参装饰器相比,使用类的方式来定义装饰器,显得更加灵活和科学;它可以通过构造方法传参,可以添加更多详细功能;可以通过__call__
返回新函数;注意,新函数要使用@wraps(func)装饰
例:同样地以上面的例子作为对比
# 导入系统的一个装饰器,叫wraps
from functools import wraps
# 定义一个装饰器类:本次实现在普通函数的上下留2行虚线作为装饰
class argWrapper:
# 类的构造函数,生成对象时调用,对象实例就是普通函数,由系统创建,装饰器必用,
# 此时默认参数为1,哪怕不传入参数,也会实现1行虚线作为装饰
def __init__(self,lines=1):
self.lines = lines
def __call__(self, func):
# 定义一个包装函数用于接收普通函数,@wraps是必须的,__call__其实就是wraps
@wraps(func)
def inner(*args, **kwargs):
print(("----------"+"\n")*self.lines)
func(*args, **kwargs)
print(("----------" + "\n") * self.lines)
return inner
# 定义一个普通函数,并使用mark带参的类装饰器装饰,参数为lines=2
@argWrapper(lines=2)
def func1(name):
print("name", name)
func1('柠檬茶')
执行结果:
- 装饰器比较
使用类定义装饰与普通嵌套装饰器比较,除了写法更加优雅,还有方便扩展的功能,可以加上更多的逻辑,甚至可增加安全逻辑,使安全性更高,系统的标准库也是全都用类定义的方法所写
例:时间统计装饰器
需求:制作一个装饰器,用于统计函数运行时长
- 普通装饰器实现
import time
# 定义一个装饰器,用于一个函数func传入,返回另一个函数inner
def timeteller(func):
# 定义另一个函数inner,在inner里调用func函数
def inner(*args,**kwargs):
start = time.time()
# func是有结果的,把结果返给inner函数,inner的参数由上层传入
ret = func(*args,**kwargs)
cost = (time.time() - start)*1000
print("此函数耗时%.2f毫秒"%cost)
return ret
return inner
# 一个普通函数,返回a-b的和
@timeteller
def getSum(a,b):
ret = 0
for i in range(a,b+1):
ret += i
return ret
# 创建实例,调用getSum,其实就是调用inner
print(getSum(1,1000000))
执行结果:
- 类定义装饰器实现
# 定义一个装饰器类 timeteller
class timeteller:
def __call__(self, func):
@wraps(func)
def inner(*args,**kwargs):
start = time.time()
ret = func(*args,**kwargs)
cost = (time.time() - start)*1000
print("此函数耗时%.2f毫秒"%cost)
return ret
return inner
# 一个普通函数,返回a-b的和
@timeteller() # 虽然没有其他参数,但括号一定要加
def getSum(a,b):
ret = 0
for i in range(a,b+1):
ret += i
return ret
# 创建实例
print(getSum(1,1000000))
执行结果一样