python 之 面相对象(二)
初识继承
继承是什么
继承 --->类与类之间的关系,是一种什么是什么的关系(而组合是什么有什么的关系,组合在后面会讲),是一种创建新类的方式。
继承的功能之一:解决代码重用的问题。
为什么继承可以解决代码重用?
因为,继承是一种创建新类的方式,新建的类可以继承一个或者多个父类的代码的数据属性和功能属性,而不用重新定义。
父类又可以称为基类或者超类。
新建的类又可以成为派生类或者子类。
python中的继承分为:单继承和多继承
class ParentClass1: pass class ParentClass2: pass class SubClass1(ParentClass1): #单继承,继承一个父类 pass class SubClass2(ParentClass1,ParentClass2): #多继承,继承多个父类 pass
查看继承
print(SubClass2.__base__)
print(SubClass1.__bases__) print(SubClass2.__bases__) # __base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类
继承与抽象
抽象
抽象即抽取类似或者说比较像的部分。
抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类
抽象分成两个层次:
1.将奥巴马和梅西这俩对象比较像的部分抽取成类;
2.将人,猪,狗这三个类比较像的部分抽取成父类。
抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)
继承
继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
继承与重用性
上面已经说过了,继承的功能之一就是解决代码重用,下面我们详分析并写出例子
在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时
我们不可能从头开始写一个类B,这就用到了类的继承的概念。
通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用
# 定义一个英雄类 class Hero: def __init__(self,nickname,life_value,aggresivity): self.nickname=nickname self.life_value=life_value self.aggresivity=aggresivity def attack(self,enemy): enemy.life_value-=self.aggresivity # 盖伦 class Garen(Hero): pass # 瑞文 class Riven(Hero): pass # 这样定义,我们就不用分别定义两个英雄类,避免了重复造轮子 g1=Garen('刚们',29,30) print(g1.nickname,g1.life_value,g1.aggresivity)
属性查找
#属性查找小练习 class Foo: def f1(self): print('from Foo.f1') def f2(self): print('from Foo.f2') self.f1() #b.f1() 不要想当然的以为是 Foo里面的f1 class Bar(Foo): def f1(self): print('from Bar.f1') b=Bar() # print(b.__dict__) b.f2()
派生
当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),
需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。
class Hero: def __init__(self,nickname,life_value,aggresivity): self.nickname=nickname self.life_value=life_value self.aggresivity=aggresivity def attack(self,enemy): enemy.life_value-=self.aggresivity class Garen(Hero): camp='Demacia' def attack(self,enemy): # 子类定义自己的方法和属性,调用时以自己为准 print('from Garen Class') class Riven(Hero): camp='Noxus' g=Garen('草丛伦',100,30) r=Riven('锐雯雯',80,50)
在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,
即:类名.func(),此时就与调用普通函数无异了,因此即便是self参数也要为其传值
class Riven(Hero): camp='Noxus' def __init__(self,nickname,aggressivity,life_value,skin): Hero.__init__(self,nickname,aggressivity,life_value) #调用父类功能 self.skin=skin #新属性 def attack(self,enemy): #在自己这里定义新的attack,不再使用父类的attack,且不会影响父类 Hero.attack(self,enemy) #调用功能 print('from riven') def fly(self): #在自己这里定义新的 print('%s is flying' %self.nickname) r1=Riven('锐雯雯',57,200,'比基尼') r1.fly() print(r1.skin)
继承的实现原理
我们现在知道了父类,子类,以及子类继承父类的属性
那么这个继承关系到底是怎样的呢?
如果我们的一个子类有好几个父类,父类又有几个父类,子类要调用某个父类的属性,它是如何找到这个位置的,怎么就那么准呢?
mro列表
python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如:
class O: pass class A(O): pass class B(O): pass class C(O): pass class D(O): pass class E(O): pass class K1(A, B, C): pass class K2(D, B, E): pass class K3(D, A): pass class Z(K1, K2, K3): pass print(Z.__mro__) # 等同于print(Z.mro()) # 结果 [<class '__main__.Z'>, <class '__main__.K1'>, <class '__main__.K2'>, <class '__main__.K3'>,
<class '__main__.D'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>,
<class '__main__.E'>, <class '__main__.O'>, <class 'object'>]
Z K1 K2 K3 D A B C E O object
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
- 子类会先于父类被检查
- 多个父类会根据它们在列表中的顺序被检查
- 如果对下一个类存在两个合法的选择,选择第一个父类
在Java和C#中子类只能继承一个父类,而Python中子类可以同时继承多个父类,如果继承了多个父类,那么属性的查找方式有两种,分别是:深度优先和广度优先
python中的金典类和新式类
#在python2中-》经典类:没有继承object的类,以及它的子类都称之为经典类 class Foo: pass class Bar(Foo): pass #在python2中-》新式类:继承object的类,以及它的子类都称之为新式类 class Foo(object): pass class Bar(Foo): pass #在python3中-》新式类:一个类没有继承object类,默认就继承object class Foo(): pass print(Foo.__bases__)
深度优先和广度优先
深度优先,通俗的说法就是,一路走到头
广度优先,需要了解mro 以及C3算法
验证多继承情况下的属性查找
# 可以通过注释掉属性,来验证是执行哪一步
class A(object): def test(self): print('from A') class B(A): def test(self): print('from B') class C(A): def test(self): print('from C') class D(B): def test(self): print('from D') class E(C): def test(self): print('from E') class F(D,E): # def test(self): # print('from F') pass f1=F() f1.test() print(F.__mro__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性 #新式类继承顺序:F->D->B->E->C->A #经典类继承顺序:F->D->B->A->E->C #python3中统一都是新式类 #pyhon2中才分新式类与经典类
在子类中调用父类的方法
在子类派生出的新方法中,往往需要重用父类的方法,我们有两种方式实现
方式一:指名道姓(不依赖继承),即父类名.父类方法()
class Vehicle: #定义交通工具类 Country='China' def __init__(self,name,speed,load,power): self.name=name self.speed=speed self.load=load self.power=power def run(self): print('开动啦...') class Subway(Vehicle): #地铁 def __init__(self,name,speed,load,power,line): Vehicle.__init__(self,name,speed,load,power) # 指名道姓 self.line=line def run(self): print('地铁%s号线欢迎您' %self.line) Vehicle.run(self) # 指名道姓 line13=Subway('中国地铁','180m/s','1000人/箱','电',13)
方式二:super(),专门用来引用父类属性,依赖于继承
super(自己的类名,self)得到的是一个对象,
如果,没有直接继承关系,super仍然会按照mro继续往后查找
class Vehicle: #定义交通工具类 Country='China' def __init__(self,name,speed,load,power): self.name=name self.speed=speed self.load=load self.power=power def run(self): print('开动啦...') class Subway(Vehicle): #地铁 def __init__(self,name,speed,load,power,line): #super(Subway,self) 就相当于实例本身 在python3中super()等同于super(Subway,self) super().__init__(name,speed,load,power) self.line=line def run(self): print('地铁%s号线欢迎您' %self.line) super(Subway,self).run() # 在python3里,等同于 super().run(),super里面不用加参数
class Mobike(Vehicle):#摩拜单车 pass line13=Subway('中国地铁','180m/s','1000人/箱','电',13) line13.run()
没有直接继承关系,super仍然会按照mro继续往后查找(super不会管父类,只参照此时的mro列表,再基于此时的位置,查找)
#A没有继承B,但是A内super会基于C.mro()继续往后找 class A: def test(self): super().test() class B: def test(self): print('from B') class C(A,B): pass c=C() c.test() #打印结果:from B print(C.mro()) #[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
组合
组合是另一种代码重用的方法,就是,什么有什么的关系
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
eg:老师有课程,学生有课程
把不同的类组合到一起
eg:一个学生有两个课程
1,分别添加一次2,用列表装在一起()
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 O: pass class A(O): pass class B(O): pass class C(O): pass class D(O): pass class E(O): pass class K1(A, B, C): pass class K2(D, B, E): pass class K3(D, A): pass class Z(K1, K2, K3): pass print(Z.mro()) class Vehicle: # 定义交通工具类 Country = 'China' def __init__(self, name, speed, load, power): self.name = name self.speed = speed self.load = load self.power = power def run(self): print('开动啦...') class Subway(Vehicle): # 地铁 def __init__(self, name, speed, load, power, line): # super(Subway,self) 就相当于实例本身 在python3中super()等同于super(Subway,self) super().__init__(name, speed, load, power) self.line = line def run(self): print('地铁%s号线欢迎您' % self.line) super(Subway, self).run() super().run() class Mobike(Vehicle): # 摩拜单车 pass line13 = Subway('中国地铁', '180m/s', '1000人/箱', '电', 13) line13.run() """ class People: # 人的类 school='luffycity' def __init__(self,name,age,sex): self.name=name self.age=age self.sex=sex class Teacher(People): # 老师也是人,继承人的属性 是的关系就是继承 def __init__(self,name,age,sex,level,salary,): super().__init__(name,age,sex) self.level=level self.salary=salary def teach(self): print('%s is teaching' %self.name) class Student(People): # 学生也是人,继承人的属性 是的关系就是继承 def __init__(self, name, age, sex, class_time,): super().__init__(name,age,sex) self.class_time=class_time def learn(self): print('%s is learning' % self.name) class Course: # 定义课程类 ,老师有课程,学生也有课程 有的关系就是组合 def __init__(self,course_name,course_price,course_period): self.course_name = course_name self.course_price = course_price self.course_period = course_period def tell_info(self): print('课程名<%s> 课程价钱<%s> 课程周期<%s>' %(self.course_name,self.course_price,self.course_period)) class Date: # 人 都有出生日期,上课日期 的有关系就是组合 def __init__(self,year,mon,day): self.year=year self.mon=mon self.day=day def tell_info(self): print('%s-%s-%s' %(self.year,self.mon,self.day)) # 实例化 teacher1=Teacher('alex',18,'male',10,3000,) teacher2=Teacher('egon',28,'male',30,3000,) student1=Student('张三',28,'female','08:30:00') student2=Student('李四',38,'female','08:30:00') python=Course('python',3000,'3mons') linux=Course('linux',2000,'4mons') d=Date(1988,4,20) #给老师添加课程属性 teacher1.course=python teacher2.course=python #给学生添加生日属性,和课程属性 student1.birh=d student1.course=python student2.course=[] student2.course.append(python) student2.course.append(linux) #执行 teacher1.teach() teacher2.teach() teacher1.course.tell_info() teacher2.course.tell_info() student1.learn() student2.learn() student1.birh.tell_info() student1.course.tell_info() student2.course[0].tell_info() student2.course[1].tell_info()
抽象类和归一化
接口与归一化设计
1.什么是接口
hi boy,给我开个查询接口。。。此时的接口指的是:自己提供给使用者来调用自己功能的方式\方法\入口
java中的interface使用如下
=================第一部分:Java 语言中的接口很好的展现了接口的含义: IAnimal.java /* * Java的Interface接口的特征: * 1)是一组功能的集合,而不是一个功能 * 2)接口的功能用于交互,所有的功能都是public,即别的对象可操作 * 3)接口只定义函数,但不涉及函数实现 * 4)这些功能是相关的,都是动物相关的功能,但光合作用就不适宜放到IAnimal里面了 */ package com.oo.demo; public interface IAnimal { public void eat(); public void run(); public void sleep(); public void speak(); } =================第二部分:Pig.java:猪”的类设计,实现了IAnnimal接口 package com.oo.demo; public class Pig implements IAnimal{ //如下每个函数都需要详细实现 public void eat(){ System.out.println("Pig like to eat grass"); } public void run(){ System.out.println("Pig run: front legs, back legs"); } public void sleep(){ System.out.println("Pig sleep 16 hours every day"); } public void speak(){ System.out.println("Pig can not speak"); } } =================第三部分:Person2.java /* *实现了IAnimal的“人”,有几点说明一下: * 1)同样都实现了IAnimal的接口,但“人”和“猪”的实现不一样,为了避免太多代码导致影响阅读,这里的代码简化成一行,但输出的内容不一样,实际项目中同一接口的同一功能点,不同的类实现完全不一样 * 2)这里同样是“人”这个类,但和前面介绍类时给的类“Person”完全不一样,这是因为同样的逻辑概念,在不同的应用场景下,具备的属性和功能是完全不一样的 */ package com.oo.demo; public class Person2 implements IAnimal { public void eat(){ System.out.println("Person like to eat meat"); } public void run(){ System.out.println("Person run: left leg, right leg"); } public void sleep(){ System.out.println("Person sleep 8 hours every dat"); } public void speak(){ System.out.println("Hellow world, I am a person"); } } =================第四部分:Tester03.java package com.oo.demo; public class Tester03 { public static void main(String[] args) { System.out.println("===This is a person==="); IAnimal person = new Person2(); person.eat(); person.run(); person.sleep(); person.speak(); System.out.println("\n===This is a pig==="); IAnimal pig = new Pig(); pig.eat(); pig.run(); pig.sleep(); pig.speak(); } }
2. 为何要用接口
接口提取了一群类共同的函数,可以把接口当做一个函数的集合。
然后让子类去实现接口中的函数。
这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。
归一化的好处在于:
- 归一化让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
- 归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合
- 就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。
- 再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样
3. 模仿interface
在python中根本就没有一个叫做interface的关键字,如果非要去模仿接口的概念
可以借助第三方模块:http://pypi.python.org/pypi/zope.interface
也可以使用继承,其实继承有两种用途
一:继承基类的方法,并且做出自己的改变或者扩展(代码重用):实践中,继承的这种用途意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
二:声明某个子类兼容于某基类,定义一个接口类(模仿java的Interface),接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能
class Interface:#定义接口Interface类来模仿接口的概念,python中压根就没有interface关键字来定义一个接口。 def read(self): #定接口函数read pass def write(self): #定义接口函数write pass class Txt(Interface): #文本,具体实现read和write def read(self): print('文本数据的读取方法') def write(self): print('文本数据的读取方法') class Sata(Interface): #磁盘,具体实现read和write def read(self): print('硬盘数据的读取方法') def write(self): print('硬盘数据的读取方法') class Process(Interface): def read(self): print('进程数据的读取方法') def write(self): print('进程数据的读取方法')
上面的代码只是看起来像接口,其实并没有起到接口的作用,子类完全可以不用去实现接口 ,这就用到了抽象类
抽象类
1 什么是抽象类
与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化
2 为什么要有抽象类
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。。。。你永远无法吃到一个叫做水果的东西。
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的,即将揭晓答案
3. 在python中实现抽象类
#一切皆文件 import abc #利用abc模块实现抽象类 class All_file(metaclass=abc.ABCMeta): all_type='file' @abc.abstractmethod #定义抽象方法,无需实现功能 def read(self): '子类必须定义读功能' pass @abc.abstractmethod #定义抽象方法,无需实现功能 def write(self): '子类必须定义写功能' pass # class Txt(All_file): # pass # # t1=Txt() #报错,子类没有定义抽象方法 class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('文本数据的读取方法') def write(self): print('文本数据的读取方法') class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('硬盘数据的读取方法') def write(self): print('硬盘数据的读取方法') class Process(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('进程数据的读取方法') def write(self): print('进程数据的读取方法') wenbenwenjian=Txt() yingpanwenjian=Sata() jinchengwenjian=Process() #这样大家都是被归一化了,也就是一切皆文件的思想 wenbenwenjian.read() yingpanwenjian.write() jinchengwenjian.read() print(wenbenwenjian.all_type) print(yingpanwenjian.all_type) print(jinchengwenjian.all_type)
第二个例子,一样的
import abc class Animal(metaclass=abc.ABCMeta): #只能被继承,不能被实例化 all_type='animal' @abc.abstractmethod def run(self): pass @abc.abstractmethod def eat(self): pass animal=Animal() # 报错:Can't instantiate abstract class Animal with abstract methods eat, run class People(Animal): def run(self): print('people is running') def eat(self): print('people is eating') class Pig(Animal): def run(self): print('people is walking') def eat(self): print('people is eating') class Dog(Animal): def run(self): print('people is walking') def eat(self): print('people is eating') # 实例化 peo1=People() pig1=Pig() dog1=Dog() # 实现抽象功能 peo1.eat() pig1.eat() dog1.eat() # 查看共有属性 print(peo1.all_type)
4. 抽象类与接口
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计
多态与多态性
多态 ,就是同一类事物的多种形态
import abc class Animal(metaclass=abc.ABCMeta): #同一类事物:动物 @abc.abstractmethod def talk(self): pass class People(Animal): #动物的形态之一:人 def talk(self): print('say hello') class Dog(Animal): #动物的形态之二:狗 def talk(self): print('say wangwang') class Pig(Animal): #动物的形态之三:猪 def talk(self): print('say aoao')
多态性:指的是可以在不考虑对象的类型的情况下而直接使用对象
多态性分为: 静态多态性 和 动态多态性
静态多态性:编译时就能被确定其要调用同名函数,如任何类型都可以用运算符+进行运算,不用考虑类型是什么
动态多态性:编译时无法立即确定其所要调用的同名函数,注意是同名函数,这才是多态的体现!要在运行时才能确定其要调用同名函数!
# 动态多态性 示例 peo=People() dog=Dog() pig=Pig() #peo、dog、pig都是动物,只要是动物肯定有talk方法 #于是我们可以不用考虑它们三者的具体是什么类型,而直接使用 peo.talk() dog.talk() pig.talk() #更进一步,我们可以定义一个统一的接口来使用 def func(obj): obj.talk()
func(peo1)
func(pig1)
func(dog1)
为什么要用多态性(多态性的好处)
其实大家从上面多态性的例子可以看出,我们并没有增加什么新的知识,也就是说python本身就是支持多态性的,这么做的好处是什么呢?
1.增加了程序的灵活性
以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)
2.增加了程序额可扩展性
通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用
class Cat(Animal): #属于动物的另外一种形态:猫 def talk(self): print('say miamiao') cat1=Cat() #实例出一只猫 func(cat1) #甚至连调用方式也无需改变,就能调用猫的talk功能 #对于使用者来说,自己的代码根本无需改动
鸭子类型
逗比时刻:
Python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’
python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象
也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。
例1:利用标准库中定义的各种‘与文件类似’的对象,尽管这些对象的工作方式像文件,但他们没有继承内置文件对象的方法
#二者都像鸭子,二者看起来都像文件,因而就可以当文件一样去用 class TxtFile: def read(self): pass def write(self): pass class DiskFile: def read(self): pass def write(self): pass
例2:序列类型有多种形态:字符串,列表,元组,但他们直接没有直接的继承关系
#str,list,tuple都是序列类型 s=str('hello') l=list([1,2,3]) t=tuple((4,5,6)) #我们可以在不考虑三者类型的前提下使用s,l,t s.__len__() l.__len__() t.__len__() len(s) len(l) len(t)
封装
封装,三大特性的最后一个特性
如何封装
在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)
属性前面加 __ eg:__x = 1
实现了,外部无法访问
在定义阶段,会把__开头的属性进行变形操作,隐藏起来,不是真正意义上的隐藏
#其实这仅仅这是一种变形操作 #类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式: class A: __N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N def __init__(self): self.__X=10 #变形为self._A__X def __foo(self): #变形为_A__foo print('from A') def bar(self): self.__foo() #只有在类内部才可以通过__foo的形式访问到. #A._A__N是可以访问到的,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形
这种自动变形的特点:
- 类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。
- 这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。
- 在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
这种变形需要注意的问题是:
1、这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N
2、变形的过程只在类的定义是发生一次,在定义后的赋值操作,不会变形
3、在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
验证3
class A: def foo(self): print('A.foo') def bar(self): print('A.bar') self.foo() #b.foo() class B(A): def foo(self): print('B.foo') b=B() b.bar() # 定义为私有 class A: def __foo(self): #_A__foo print('A.foo') def bar(self): print('A.bar') self.__foo() #self._A__foo() class B(A): def __foo(self): #_B__foo print('B.foo') b=B() b.bar()
封装不是单纯意义的隐藏(封装的意义)
1:封装数据属性
将数据隐藏起来这不是目的。隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制。
#封装数据属性:明确的区分内外,控制外部对隐藏的属性的操作行为 class People: def __init__(self,name,age): self.__name=name self.__age=age def tell_info(self): print('Name:<%s> Age:<%s>' %(self.__name,self.__age)) def set_info(self,name,age): if not isinstance(name,str): print('名字必须是字符串类型') return if not isinstance(age,int): print('年龄必须是数字类型') return self.__name=name self.__age=age p=People('egon',18) p.tell_info() p.set_info('EGON',38) p.tell_info() p.set_info(123,38) p.set_info('egon','38') p.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()
封装方法的其他举例:
- 你的身体没有一处不体现着封装的概念:你的身体把膀胱尿道等等这些尿的功能隐藏了起来,然后为你提供一个尿的接口就可以了(接口就是你的。。。,),你总不能把膀胱挂在身体外面,上厕所的时候就跟别人炫耀:hi,man,你瞅我的膀胱,看看我是怎么尿的。
- 电视机本身是一个黑盒子,隐藏了所有细节,但是一定会对外提供了一堆按钮,这些按钮也正是接口的概念,所以说,封装并不是单纯意义的隐藏!!!
- 快门就是傻瓜相机为傻瓜们提供的方法,该方法将内部复杂的照相功能都隐藏起来了
提示:在编程语言里,对外提供的接口(接口可理解为了一个入口),可以是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。
封装与扩展性
封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。
#类的设计者 class Room: def __init__(self,name,owner,width,length,high): self.name=name self.owner=owner self.__width=width self.__length=length self.__high=high def tell_area(self): #对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积 return self.__width * self.__length #使用者 r1=Room('卧室','egon',20,20,20) r1.tell_area() #使用者调用接口tell_area #类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码 class Room: def __init__(self,name,owner,width,length,high): self.name=name self.owner=owner self.__width=width self.__length=length self.__high=high def tell_area(self): #对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了 return self.__width * self.__length * self.__high #对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码,就可以用上新功能 r1.tell_area()
特性(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 # 使用property装饰器 def bmi(self): return self.weight / (self.height ** 2) p=People('egon',75,1.81) p.bmi=p.weight / (p.height ** 2) print(p.bmi) print(p.bmi()) print(p.bmi) p.height=1.82 print(p.bmi) p.bmi=3333 #无法对其赋值,会报错AttributeError: can't set attribute
例二
import math class Circle: def __init__(self,radius): #圆的半径radius self.radius=radius @property def area(self): return math.pi * self.radius**2 #计算面积 @property def perimeter(self): return 2*math.pi*self.radius #计算周长 c=Circle(10) print(c.radius) print(c.area) #可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值 print(c.perimeter) #同上 ''' 输出结果: 314.1592653589793 62.83185307179586 '''
注意:此时的特性area和perimeter不能被赋值
c.area=3 #为特性area赋值 ''' 抛出异常: AttributeError: can't set attribute '''
为什么要用property
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
ps:面向对象的封装有三种方式:
【public】
这种其实就是不封装,是对外公开的
【protected】
这种封装方式对外不公开,但对朋友(friend)或者子类(形象的说法是“儿子”,但我不知道为什么大家 不说“女儿”,就像“parent”本来是“父母”的意思,但中文都是叫“父类”)公开
【private】
这种封装对谁都不公开
python并没有在语法上把它们三个内建到自己的class机制中,在C++里一般会将所有的所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现
class Foo: def __init__(self,val): self.__NAME=val #将所有的数据属性都隐藏起来 @property def name(self): return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置) @name.setter def name(self,value): if not isinstance(value,str): #在设定值之前进行类型检查 raise TypeError('%s must be str' %value) self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME @name.deleter def name(self): raise TypeError('Can not delete') f=Foo('egon') print(f.name) # f.name=10 #抛出异常'TypeError: 10 must be str' del f.name #抛出异常'TypeError: Can not delete'
绑定方法与非绑定方法
在类内部定义的函数,分为两大类
一:绑定方法:绑定给谁,就应该由谁来调用,谁来调用就回把调用者当作第一个参数自动传入
1、绑定到对象的方法:在类内定义的没有被任何装饰器修饰的
2、绑定到类的方法:在类内定义的被装饰器classmethod修饰的方法
二:非绑定方法:没有自动传值这么一说了,就类中定义的一个普通工具,对象和类都可以使用
非绑定方法:不与类或者对象绑定,用staticmethod装饰器装饰的方法
注意:与绑定到对象方法区分开,在类中直接定义的函数,没有被任何装饰器装饰的,都是绑定到对象的方法,可不是普通函数,对象调用该方法会自动传值,而staticmethod装饰的方法,不管谁来调用,都没有自动传值一说
绑定方法
绑定给对象的方法
绑定给类的方法(classmethod)
import settings # 配置文件 import hashlib import time class People: def __init__(self,name,age,sex): self.id=self.create_id() self.name=name self.age=age self.sex=sex def tell_info(self): #绑定到对象的方法 print('Name:%s Age:%s Sex:%s' %(self.name,self.age,self.sex)) @classmethod def from_conf(cls): obj=cls( settings.name, settings.age, settings.sex ) return obj @staticmethod def create_id(): m=hashlib.md5(str(time.time()).encode('utf-8')) return m.hexdigest() p=People('egon',18,'male') #绑定给对象,就应该由对象来调用,自动将对象本身当作第一个参数传入 p.tell_info() #tell_info(p) #绑定给类,就应该由类来调用,自动将类本身当作第一个参数传入 p=People.from_conf() #from_conf(People) p.tell_info() #非绑定方法,不与类或者对象绑定,谁都可以调用,没有自动传值一说 p1=People('egon1',18,'male') p2=People('egon2',28,'male') p3=People('egon3',38,'male') print(p1.id) print(p2.id) print(p3.id)
classmethod与staticmethod的对比
import settings class MySQL: def __init__(self,host,port): self.host=host self.port=port @staticmethod def from_conf(): return MySQL(settings.HOST,settings.PORT) # @classmethod #哪个类来调用,就将哪个类当做第一个参数传入 # def from_conf(cls): # return cls(settings.HOST,settings.PORT) def __str__(self): # 打印方法,python中的特殊方法 return '就不告诉你' class Mariadb(MySQL): def __str__(self): return '<%s:%s>' %(self.host,self.port) m=Mariadb.from_conf() print(m) #我们的意图是想触发Mariadb.__str__,但是结果触发了MySQL.__str__的执行,打印就不告诉你:
反射
反射:通过字符串的形式操作对象相关的属性
python中的一切事物都是对象(都可以使用反射)
hasattr(obj,‘name’) 判断object中有没有一个name字符串对应的方法或属性
getattr(obj,‘name’,defaulf) 拿到属性,默认值:没有值市返回的数据
setattr(x, y, v) 修改,新增
delattr(x, y) 删除
class People: country='China' def __init__(self,name,age): self.name=name self.age=age def talk(self): print('%s is talking' %self.name) obj=People('egon',18) print(obj.name) #obj.__dict__['name'] print(obj.talk) print(hasattr(obj,'name')) #obj.name #obj.__dict__['name'] print(hasattr(obj,'talk')) #obj.talk print(getattr(obj,'namexxx',None)) print(getattr(obj,'talk',None)) print(getattr(People,'country')) #用于类 People.country setattr(obj,'sex','male') #obj.sex='male' print(obj.sex) delattr(obj,'age') #del obj.age print(obj.__dict__)
反射的应用
根据用户的输入,执行不同的功能,和上一章员工信息表的作业好像
class Service: def run(self): while True: inp = input('>>: ').strip() # cmd='get a.txt' cmds = inp.split() # cmds=['get','a.txt'] if hasattr(self, cmds[0]): func = getattr(self, cmds[0]) func(cmds) def get(self, cmds): print('get.......', cmds) def put(self, cmds): print('put.......', cmds) # 生成对象 obj = Service() # 调用兑现的功能 obj.run()
反射当前模块成员
import sys def s1(): print('s1') def s2(): print('s2') this_module = sys.modules[__name__] print(this_module) print(hasattr(this_module, 's1')) print(getattr(this_module, 's2'))
导入其他模块,利用反射查找该模块是否存在某个方法
""" 程序目录: module_test.py index.py 当前文件: index.py """ import module_test as obj #obj.test() print(hasattr(obj,'test')) getattr(obj,'test')()
为什么用反射之反射的好处
好处一:实现可插拔机制
有俩程序员,一个lili,一个是egon,lili在写程序的时候需要用到egon所写的类,但是egon去跟女朋友度蜜月去了,还没有完成他写的类,lili想到了反射,使用了反射机制lili可以继续完成自己的代码,等egon度蜜月回来后再继续完成类的定义并且去实现lili想要的功能。
总之反射的好处就是,可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种‘后期绑定’,什么意思?即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能
egon还没有实现全部功能
class FtpClient: 'ftp客户端,但是还么有实现具体的功能' def __init__(self,addr): print('正在连接服务器[%s]' %addr) self.addr=addr
不影响lili的代码编写
#from module import FtpClient f1=FtpClient('192.168.1.1') if hasattr(f1,'get'): func_get=getattr(f1,'get') func_get() else: print('---->不存在此方法') print('处理其他的逻辑')