今天学习了组合、封装、property装饰器和多态
一、组合
软件重用的重要方式除了继承之外还有另外一种方式,即:组合
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
class Equip: #武器装备类 def fire(self): print('release Fire skill') class Riven: #英雄Riven的类,一个英雄需要有装备,因而需要组合Equip类 camp = 'Noxus' def __init__(self,nickname): self.nickname = nickname self.equip = Equip() #用Equip类产生一个装备,赋值给实例的equip属性 >>>r1 = Riven('锐雯雯') >>>r1.equip.fire() #可以使用组合的类产生的对象所持有的方法 release Fire skill
组合与继承都是有效利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同,
1.继承的方式
通过继承建立了派生类与基类之间的关系,它是一种‘是’的关系,比如白马是马,人是动物。
当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如老师是人,学生是人
2.组合的方式
用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python和linux课程,教授有学生s1、s2、s3。。。
class People: def __init__(self,name,age,sex): self.name = name self.age =age self.sex = sex class Course: def __init__(self,name,period,price): self.name = name self.period = period self.price = price def tell_info(self): print('<%s %s %s>'%(self.name,self.period,self.price)) class Teacher(People): def __init__(self,name,age,sex,job_title): People.__init__(self,name,age,sex) self.job_title = job_title self.course = [] self.students = [] class Student(People): def __init__(self,name,age,sex): People.__init__(self,name,age,sex) self.course = [] egon = Teacher('egon',18,'male','沙河霸道金牌讲师') s1 = Student('牛榴弹',18,'female') python = Course('python','3mons',3000.0) linux = Course('linux','3mons',3000.0) #为老师egon和学生s1添加课程 egon.course.append(python) egon.course.append(linux) s1.course.append(python) #为老师egon添加学生s1 egon.students.append(s1) #使用 for obj in egon.course: obj.tell_info()
当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好
二、封装
从封装本身的意思去理解,封装就好像是拿来一个麻袋,将好多东西一起装进麻袋,然后把麻袋封上口子。照这种逻辑看,封装='隐藏',这种理解是相当片面的。
在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)
#其实这仅仅只是一种变形操作且仅仅只在类定义阶段发生变形 #类中所有双下划线开头的名称如__x都会在类定义时自动变形成:_类名__x的形式 class A: __N = 0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N def __init__(self): self.__X = 10 #变形为self._A__X def __foo(self): #变形为self._A__foo print('from A') def bar(self): self.__foo() #只有在类内部才可以通过__foo的形式访问到。 #A._A__N是可以访问到的, #这种,在外部是无法通过__x这个名字访问到。
这种变形需要注意的问题是:
1.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形,主要用来限制外部的直接访问。
2.变形的过程只在类的定义时发生一次,在定以后的赋值操作,不会变形
3.在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
#正常情况 >>>class A: def fa(self): print('from A') def test(self): self.fa() >>>class B(A): def fa(self): print('from B') >>>b = B >>>b.test() from B #把fa定义成私有的,即__fa >>>class A: def __fa(self): #在定义时就变形为_A__fa print('from A') def test(self): self.__fa() #只会与自己所在的类为准,即调用_A__fa >>>class B(A): def __fa(self): print('from B') b = B() b.test() from A
三、封装不是单独意义的隐藏
封装的真谛在于明确地区分内外,封装的属性可以直接在内部使用,而不能被外部直接使用,然而定义属性的目的终归是要用,外部要想要类隐藏的属性,需要我们为其开辟接口,让外部能够间接地用到我们隐藏起来的属性,那这么做的意义何在???
1:封装数据:将数据隐藏起来这不是目的。隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制。
class Teacher: def __init__(self,name,age): self.__name = name self.__age = age self.set_info(name,age) def tell_info(self): print('姓名:%s,年龄:%s'%(self.__name,self.__age)) def set_info(self,name,age): if not isinstance(name,str): raise TypeError('姓名必须是字符串类型') if not isinstance(age,int): raise TypeError('年龄必须是整型') t = Teacher('egon',18) t.tell_info() t.set_info('egon',19) t.tell_info()
2:封装方法:目的是隔离复杂度
#取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱 #对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做 #隔离了复杂度,同时也提升了安全性 class ATM: def __card(self): print('插卡') def __auth(self): print('用户认证') def __input(self): print('输入取款金额') def __print_bill(self): print('打印账单') def __take_money(self): print('取款') def withdraw(self): self.__card() self.__auth() self.__input() self.__print_bill() self.__take_money() a = ATM() a.withdraw()
四、特征(property)
什么是特征property
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
例:BMI指数(BMI是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)
成人的BMI数值:
过轻:低于18.5
正常:18.5-23.9
过重:24-27
肥胖:28-32
非常肥胖:高于32
体质指数(BMI)=体重(kg) / 身高^2(m)
EX:70kg / (1.75*1.75) = 22.86
class People: def __init__(self,name,weight,height) self.name = name self.weight = weight self.height = height @property def bmi(self): return self.weight / (self.height**2) p1 = People('egon',75,1.85) print(p1.bmi)
为什么要用property
将一个类的函数定义成特征以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
五、多态性
一、什么是多态动态绑定(在继承的背景下使用时,有时也称为多态性)
多态性是指在不考虑实例类型的情况下使用实例
在面下对象方法中一般是这样表述多态性:向不同的对象发送同一条消息(!!! obj.func():是调用了obj的方法func,
又称为向obj发送了一条消息func),不同的对象在接受时会产生不同的行为(即方法)。也就是说,每个对象可以用自己
的方法去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
比如:老师.下课铃响了(),学生.下课铃响了(),老师执行的是下班操作,学生执行的是放学操作,虽然二者消息一样,
但是执行的效果不同。
多态性分为静态多态性和动态多态性
静态多态性,如任何类型都可以用运算符+进行运算
动态多态性:如下
peo = People() dog = Dog() pig = Pig() #peo、dog、pig 都是动物,只要是动物肯定有talk方法 #于是我们可以不用考虑它们三者的具体是什么类型,而直接使用 peo.talk() dog.talk() pig.talk() #更进一步,我们可以定义一个统一的接口来使用 def func(obj): obj.talk()
二、为什么要用多态性(多态性的好处)
1.增加了程序的灵活性
以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)
2.增加了程序可扩展性
通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用
>>>class Cat(animal): #属于动物的另外一种形态:猫 def talk(self): print('say miao') >>>def func(animal): #对于使用者来说,自己的代码根本无需改动 animal.talk() >>>cat1 = Cat() #实例出一只猫 >>>fun(cat1) #甚至连调用方式也无需改变,就能调用猫的talk功能 say miao ''' 这样我们新增了一个形态Cat,由Cat类产生的实例cat1,使用者可以在完全不
需要修改自己代码的情况下。使用和人、狗、猪一样的方式cat1的talk方法,即func(cat1) '''
三 鸭子类型
逗比时刻:
Python崇尚鸭子类型,即如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子
python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。
例:利用标准库中定义的各种'与文件类似'的对象,尽管这些对象的工作方式像文件,但他们没有继承内置文件对象的方法
#二者都像鸭子,二者看起来都像文件,因而就可以当文件一样去用 class TxtFile: def read(self): pass def write(self): pass class DiskFile: def read(self): pass def write(self): pass