Day6 - Python基础6 面向对象编程
面向过程介绍
1.核心是过程,过程指的是解决问题的步骤,先干什么再干什么,好比精心设计一条流水线是一种机械式的思维方式
2.优点:复杂的问题的流程化、简单化
3.缺点:一套程序就是用来解决一个问题,适合简单的场景
4.应用场景:一旦完成基本很少变更的场景,如Linux内核
面向对象介绍
1.核心是对象,对象是特征和技能的结合体,面向对象编程就好比创造一个世界,你自己就是这个世界的上帝,存在皆为对象,不存在也可创造出来,面向对象更注重对现实世界的模拟,是一种“上帝式”的思维方式
2.优点:解决了程序的可扩展性,对某一个对象的单独修改,会立刻反映到全体系
3.缺点:1.编程的复杂度高于面向过程;2.不能象面向过程能精准预测处理流程和结果
4.应用场景:需求经常变化的软件
类与对象
类是类别和种类,是面向对象最重要的概念,对象是特征和技能的结合体,类是一系列有相似特征和技能对象的结合体
现实世界:先有对象再有类 在程序中:先定义类,再产生对象
在程序中特征用变量表识,技能用函数表示;类中最常见的是变量和函数的定义
世界万物,皆可分类;只要是对象,就肯定属于某种品类;只要是对象,就肯定有属性
类的2种属性:1.数据属性是所有对象共享的;2.函数属性是绑定给对象用的
构造函数
__init__方法
1.该方法内可以有任意python代码;2.一定不能有返回值
析构函数:在实例释放、销毁的时候执行的,通常用于做一些收尾工作,如关闭一些数据库链接和临时文件
私有方法,私有属性:只有实例内部能访问,通过设置私有方法可以访问
封装
继承
继承是创建新类的一种方式,新建的类可以继承一个或多个类,父类称为基类或超类,新建类称为派生类或子类。子类继承父类的属性,从而解决代码重用的问题。
Python3.x版本默认都是新式类
继承与抽象:父子类的关系是怎么产生的呢,先抽象再继承,抽取比较像或类似的地方,抽象的主要作用是划分类别,隔离关注点,降低复杂度。继承是抽象的结果,先抽象再继承,才能通过继承的方式表达抽象的结构。
1 >>> class Foo: 2 ... def f1(self): 3 ... print('Foo.f1') 4 ... def f2(self): 5 ... print('Foo.f2') 6 ... self.f1() 7 ... 8 >>> class Bar(Foo): 9 ... def f1(self): 10 ... print('Bar.f1') 11 ... 12 >>> b=Bar() 13 >>> b.f2() #Bar中没有f2方法,就云父类Foo中查找,执行时因Bar中定义了f1,所以优先执行Bar中的f1 14 Foo.f2 15 Bar.f1
组合与重用性
指的是,在一个类中以另外一个类的对象为数据属性,称为类的组合。
1 >>> class Equip: #武器装备类 2 ... def fire(self): 3 ... print('release Fire skill') 4 ... 5 >>> class Riven: #英雄Riven的类,一个英雄需要有装备,因而需要组合Equip类 6 ... camp='Noxus' 7 ... def __init__(self,nickname): 8 ... self.nickname=nickname 9 ... self.equip=Equip() #用Equip类产生一个装备,赋值给实例的equip属性 10 ... 11 >>> r1=Riven('锐雯雯') 12 >>> r1.equip.fire() #可以使用组合的类产生的对象所持有的方法 13 release Fire skill
继承和组合是有效利用现有类的重要方式,只是概念和应用场景不同
1.继承:通过继承建立了类和派生类的关系,它是一种“是”的关系,如人是动物,当类中有很多相同的功能 ,可以把这种相似的共同功能提取为基类,这种情况下用继承比较好
2.组合:用组合建立了类和组合的关系,它是一种“有”的关系,当类之间有很大不同,且较小类是较大类所需要的组件时,用组合比较好
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 class People: 2 def __init__(self,name,age,sex): 3 self.name=name 4 self.age=age 5 self.sex=sex 6 7 class Course: 8 def __init__(self,name,period,price): 9 self.name=name 10 self.period=period 11 self.price=price 12 def tell_info(self): 13 print('<%s %s %s>' %(self.name,self.period,self.price)) 14 15 class Teacher(People): 16 def __init__(self,name,age,sex,job_title): 17 People.__init__(self,name,age,sex) 18 self.job_title=job_title 19 self.course=[] 20 self.students=[] 21 22 23 class Student(People): 24 def __init__(self,name,age,sex): 25 People.__init__(self,name,age,sex) 26 self.course=[] 27 28 29 egon=Teacher('egon',18,'male','沙河霸道金牌讲师') 30 s1=Student('牛榴弹',18,'female') 31 32 python=Course('python','3mons',3000.0) 33 linux=Course('python','3mons',3000.0) 34 35 #为老师egon和学生s1添加课程 36 egon.course.append(python) 37 egon.course.append(linux) 38 s1.course.append(python) 39 40 #为老师egon添加学生s1 41 egon.students.append(s1) 42 43 44 #使用 45 for obj in egon.course: 46 obj.tell_info()
接口和归一化
接口提取了一群类共同的函数,可以当做是一个函数的集合,然后让子类云实现接口的函数,这叫归一化,什么是归一化,就是基于同一接口实现的类 ,所有这些类产生的对象都有共同的使用方法
好处:1.使用者无需关系对象的类是什么,只需要知道这此对象具备某些功能就可以了,降低使用难度
2.使用者可以不加区分处理所有接口兼容的对象集合
实现方法:python没有interface的关键字
1.可以借助第三方模块;
2.使用继承
2.1.继承基类的方法,并且做出自己的改变或者扩展(代码重用):实践中,继承的这种用途意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
2.2.声明某个子类兼容于某基类,定义一个接口类(模仿java的Interface),接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能
抽象类
抽象类是一个介于类和接口之间的概念,同时具备类和接口的部分特性,可以用做归一化设计
1.抽象类也要基于模块实现,只是被继承,不能实例化,如水果这个类,你不能实例化
2.为什么要有抽象类
如果说类是从一堆对象中抽取相同的内容而来,那么抽象类就从一堆类中抽取的相同内容而来,这一点和接口有点相似,但又有不同
子类调用父类的方法
二种方法都可以,最好不要混用
方法一:指名道姓,父类名.父类方法()
1 class Vehicle: #定义交通工具类 2 Country='China' 3 def __init__(self,name,speed,load,power): 4 self.name=name 5 self.speed=speed 6 self.load=load 7 self.power=power 8 9 def run(self): 10 print('开动啦...') 11 12 class Subway(Vehicle): #地铁 13 def __init__(self,name,speed,load,power,line): 14 Vehicle.__init__(self,name,speed,load,power) 15 self.line=line 16 17 def run(self): 18 print('地铁%s号线欢迎您' %self.line) 19 Vehicle.run(self) 20 21 line13=Subway('中国地铁','180m/s','1000人/箱','电',13) 22 line13.run()
方法二:super
1 class Vehicle: #定义交通工具类 2 Country='China' 3 def __init__(self,name,speed,load,power): 4 self.name=name 5 self.speed=speed 6 self.load=load 7 self.power=power 8 9 def run(self): 10 print('开动啦...') 11 12 class Subway(Vehicle): #地铁 13 def __init__(self,name,speed,load,power,line): 14 #super(Subway,self) 就相当于实例本身 在python3中super()等同于super(Subway,self) 15 super().__init__(name,speed,load,power) 16 self.line=line 17 18 def run(self): 19 print('地铁%s号线欢迎您' %self.line) 20 super(Subway,self).run() 21 22 class Mobike(Vehicle):#摩拜单车 23 pass 24 25 line13=Subway('中国地铁','180m/s','1000人/箱','电',13) 26 line13.run()
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 #A没有继承B,但是A内super会基于C.mro()继续往后找 2 class A: 3 def test(self): 4 super().test() 5 class B: 6 def test(self): 7 print('from B') 8 class C(A,B): 9 pass 10 11 c=C() 12 c.test() #打印结果:from B 13 14 15 print(C.mro()) 16 #[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
多态
1.一种事物有多种形态,如动物形态有:人、猪、狗
2.多态性:指不考虑实例的类型的情况下使用实例
面向对象中是这样表述多态性的:向不同的对象发送同样的消息(obj.func(),调用obj的func方法,又称为向obj发送一条消息func),不同对象在接收时产生不同的行为,每个对象可以用自己的方式去响应共同的消息。消息就是指调用函数,不同的行为就是不同的实现,即执行不同的函数 。
如下课铃响了,老师是下班,同学是下课。
多态性分为静态多态性和动态多态性
静态多态性:如任何类型都可运算符+运算
动态多态性;
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 class Animal: 2 def __init__(self,name): 3 self.name = name 4 def talk(self): 5 pass 6 7 @staticmethod 8 def func(obj): 9 obj.talk() 10 11 class Person(Animal): 12 def talk(self): 13 print('%s:Hello...'%self.name) 14 15 class Cat(Animal): 16 def talk(self): 17 print('%s:Miao....'%self.name) 18 19 class Dog(Animal): 20 def talk(self): 21 print('%s:Wa....'%self.name) 22 23 person1 = Person("人") 24 cat1 = Cat("猫") 25 dog1 = Dog("狗") 26 27 # person1.talk() 28 # cat1.talk() 29 # dog1.talk() 30 31 Animal.func(person1) 32 Animal.func(cat1) 33 Animal.func(dog1)
3.多态性的好处
a.增加程序的灵活性,不变应万变,不论对象千变万化,用户可以用同样的方法调用
b.增加程序的可扩展性
python默认支持多态
1 #Python的序列类型有多种形态:字符串,列表,元组,多态性体现如下 2 #str,list,tuple都是序列类型 3 s=str('hello') 4 l=list([1,2,3]) 5 t=tuple((4,5,6)) 6 7 #我们可以在不考虑三者类型的前提下使用s,l,t 8 s.__len__() 9 l.__len__() 10 t.__len__() 11 12 len(s) 13 len(l) 14 len(t) #可以使用同样的方法调用
封装
1.封装=隐藏,这种理解是片面的
2.如何隐藏
在类中使用双下划线开头的方式将属性隐藏(设置私有的),
注意事项:
1.这种不是严格意义上的限制外部访问,只是语法上的变形,仅限制外部直接访问
2.变形只在类的定义发生一次,后面的赋值不会变形
3.在继承中,父类如不想让子类覆盖自己的方法,可以将方法定义为私有的
1 #其实只是一种变形操作,只在类定义时发生 2 #"__x"在类定义时都自动变成"_类名__x" 3 class A: 4 __N=0 #变形为_A__N 5 def __init__(self): 6 self.__X=10 #变形为self._A__X 7 def __foo(self): #变形为_A__foo 8 print('from A') 9 def bar(self): 10 self.__foo() #只有类内部可以用__foo方式访问 11 12 # print(A.__dict__) 13 a = A() 14 print(a._A__N) #在外面用变形后的方式可以访问到 15 a._A__foo() 16 a.bar()
封装不是单纯意义上的隐藏
封装的真谛是严格区分内外部访问,内部可以直接访问,外部不能直接访问。然而定义属性的目的终归是要用,外部要想用类的隐藏属性,需要我们开辟接口,让外部能间接的访问到隐藏的属性
1.封装数据:将数据隐藏不是目的,隐藏属性然后提供外部访问接口,我们可以在接口上附加上对该数据操作的限制,以此完成对数据属性操作的严格控制
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 class Teacher: 2 def __init__(self,name,age): 3 # self.__name=name 4 # self.__age=age 5 self.set_info(name,age) 6 def tell_info(self): 7 print('name:%s,age:%s'%(self.__name,self.__age)) 8 def set_info(self,name,age): 9 if not isinstance(name,str): 10 raise TypeError('name必须是string') 11 if not isinstance(age,int): 12 raise TypeError('age必须是整型') 13 self.__name=name 14 self.__age=age 15 16 t=Teacher(1,18) 17 t.tell_info() 18 t.set_info('jack',18) 19 t.tell_info()
2.封装的目的:是为了隔离复杂度
封装方法举例:
1. 电视机本身是一个黑盒子,隐藏了所有细节,但是一定会对外提供了一堆按钮,这些按钮也正是接口的概念,所以说,封装并不是单纯意义的隐藏!!!
2. 快门就是傻瓜相机为傻瓜们提供的方法,该方法将内部复杂的照相功能都隐藏起来了
提示:在编程语言里,对外提供的接口(接口可理解为了一个入口),可以是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。
特性@property
使用property的原因:绑定属性时,如果我们直接把参数暴露出去,写起来简单,但没法检查参数的合法性
1.property是一种属性,把一个方法变成属性,访问它时会先执行一段功能(函数)然后返回值
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 import math 2 class Circle: 3 def __init__(self,radius): #圆的半径radius 4 self.radius=radius 5 6 @property 7 def area(self): 8 return math.pi * self.radius**2 #计算面积 9 10 @property 11 def perimeter(self): 12 return 2*math.pi*self.radius #计算周长 13 14 c=Circle(10) 15 print(c.radius) 16 print(c.area) #可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值 17 print(c.perimeter) #同上 18 ''' 19 输出结果: 20 314.1592653589793 21 62.83185307179586 22 '''
2.将一个类的函数定义成特性后,对象再去使用函数时,根本不会觉得是在执行一个函数计算出来的,这遵循了统一访问的原则
ps:面向对象的封装有三种方式:
【public】
这种其实就是不封装,是对外公开的
【protected】
这种封装方式对外不公开,但对朋友(friend)或者子类(形象的说法是“儿子”,但我不知道为什么大家 不说“女儿”,就像“parent”本来是“父母”的意思,但中文都是叫“父类”)公开
【private】
这种封装对谁都不公开
封装与扩展性
封装可以区分内外部,所以类实现者可以修改内部代码而不影响外部调用者的代码 ,外部用户只知道一个接口,只要接口不变,使用者的代码永远不需改变,这提供一个良好的合作基础。
静态方法和类方法
有效的进行了代码隔离,有利于组织代码,有利于命名空间的整洁。
静态方法@staicmethod:不用访问类变量和实例变量,也不能接收self参数传值。但对象和类可以访问它
用处:可以用来组织类之间有逻辑关系的函数。在很多情况下,有些函数和类相关,但又不需要任何类和实例变量就可以实现一此功能 。如环境变量的设置,修改另一个类的属性,假如我们只是想实现类之间的交互而不是通过实例,我们可以在类之外创建一个函数实现这些功能,但这会使代码扩散到类外,不利于代码的维护。
类方法@classmethod:只能访问类变量,不能访问实例变量
用处:尤其适合在创建类实例之前做些预设置,实例创建前不能使用实例方法,只能使用classmethod.另一个好处是,以后重构类时不需要修改构造函数,只需要添加要处理的函数,然后加classmethod即可,相当于我们拥有了多样化的构造函数。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
class Dog(object): name = '我是類變量' def __init__(self, name): self.name = name @classmethod def eat(self): print("%s is eating" % self.name) d1 =Dog('jack') d1.eat() Dog.eat() #结果 我是類變量 is eating 我是類變量 is eating