第六章:面向对象

第一节:关于类

面向对象

  • 面向对象

之前我们介绍过函数,函数是对程序语句的封装与复用;
类是则是对 变量函数 的封装和复用,封装有机关联的变量和函数为类,变量和函数,称为 类的属性方法
由于类比函数的封装又提高了一个层次,因此复用性也得到进一步提升;(试想一下维护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))

执行结果一样
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/xiangchi7/article/details/83538491
今日推荐