面向对象编程
面向对象编程 |
在前面所学内容,实现程序复杂功能的方法是通过多个函数分别实现不同的小功能,而程序的运行过程则是一系列函数的顺序执行。这样的编程方式被称为面向过程的程序设计。
假设想要知道一个学校的具体信息,那么首先需要使用一个字典存储学校的信息,即
school = { 'name':'厦门大学', 'type':'公立', 'addr':'福建省厦门市' }
如果想要打印学校的具体信息,则需要添加一个函数来实现,定义函数为:
def printInfo(x): print('name:%s,type:%s,addr:%s' %(school['name'],school['type'],school['addr']))
调用函数:printInfo(school),得到的结果为:
name:厦门大学,type:公立,addr:福建省厦门市
在面向对象的编程思想中,首先考虑的不是程序的执行过程,而是会将学校看出看出一个对象,包含名字(name)、类型(type)、地址(addr)三个属性。假设想要打印学校个具体信息,则需要创建一个球员的实际对象,然后通过对象调用printInfo()的方法将学校的具体信息打印出来,即:
class School(): def __init__(self,name,type,addr): self.name = name self.type = type self.addr = addr def printInfo(self): print('name:%s,type:%s,addr:%s' %(self.name,self.type,self.addr)) s = School('厦门大学','公立','福建省厦门市') s.printInfo()
在以上的程序中,首先定义了一个学校的类,然后定义学校的实例:s,最后通过调用printInfo()打印出厦门大学的信息。
一、类
有一句古话:“物以类聚,人以群分”大概意思就是性情品位相投的人会聚在一起,而相似的东西会划分在一起,Python的类也是如此。
一个类是指相同事物相同特征提取,把相同的属性方法提炼出来定义在类中。
定义类的语法如下:
class 类名 : pass
Python中使用class关键字修饰类,类名一般采用首字母大写,如果类名后面加上小括号,则小括号里面表示继承,所有类最后都继承自object。
在类中可以定义属性和方法。
二、对象
我们经常提到的一句话:“世界万物皆对象”,其实就是我们每天在这个世界上所看到接触的事物都是对象,包括Python中所有数据项都是对象,而且每个对象都有自己的属性和行为。
总结一下类和对象的关系:一个对象是类的实例;对象是具体的,类是抽象。
创建实例对象的语法如下:
实例名 = 类名() #实例化的语法是在类名的后面加上小括号
比如我们创建一个狗类,并且实例化一只小狗。代码如下:
class Dog: #定义一个狗类 pass smalldog = Dog() #实例化一个小狗
在上述代码中,Dog就是一个类,而smalldog就是一个具体的对象。
三、属性和方法
根据上面狗的例子我们来分析狗有哪些属性和行为。可以看出狗的属性有名字、种类、性别等,狗的行为有伸舌头、会叫等。
1.属性的定义和访问
不管是属性还是方法都需要定义在class类中,下面再Dog中定义属性,具体代码如下:
class Dog: name = '旺财' type = '藏獒' gender = '公'
属性定义好之后,可以通过对象(实例)进行访问,当然类本身也可以访问,现在想要在类的外面打印name属性以及gender属性,代码如下:
smalldog = Dog() print(smalldog.name) #结果为:旺财 print(Dog.gender) #结果为:公
2.方法的定义和使用
狗都有伸舌头和叫的行为,接下来在Dog类中定义方法:
class Dog: name = '旺财' type = '藏獒' gender = '公' def tongue(self): print('狗正在伸舌头') def call(self): print('狗正在叫')
可以发现在类中定义方法和定义函数的语法一样,唯独有一点区别,即参数self的问题:函数中定义小括号里面放参数列表,方法的第一个是self,后面是参数列表,这个地方的self表示的是当前对象。
那么方法定义完成之后,应该如何调用方法呢?这个地方用对象名.方法名()即可,代码如下:
smalldog = Dog() smalldog.call() #结果为:狗正在叫
四、至关重要的方法__init__
方法__init__是最重要的方法之一,这是一个初始化方法,在创建对象后被立即调用。这是创建实例变量的理想之地,它确保同一个类的所有对象都支持同一组变量。
对于Dog类来说,smalldog是Dog的实例(对象),一个类可以有多个对象,利用上面创建好的Dog类来实例化多个对象:
smalldog1 = Dog() print(smalldog1.name) #结果为:旺财 smalldog2 = Dog() print(smalldog2.name) #结果为:旺财
代码中的smalldog1、smalldog2都是Dog类的实例(对象),但是有一个问题,就是打印出来的两只狗的名字都是“旺财”,这样的话定义属性的时候已经写死了固定的名称,这时候初始化方法就可以让每个对象能够定义自己的属性值。具体代码如下:
class Dog: def __init__(self,name,type,gender): self.name = name self.type = type self.gender = gender def tongue(self): print('狗正在伸舌头') def call(self): print('狗正在叫')
__init__在创建对象后被立即调用,不需要自己手动调用,它自己会执行。下面来看下代码使用方式的变化:
smalldog1 = Dog('旺财','藏獒','公') print(smalldog1.name) #结果为:旺财 smalldog2 = Dog('小白','哈士奇','母') print(smalldog2.name) #结果为:小白
可以看到Dog()其实是在执行__init__方法,只需要传递需要的参数,上面的两个对象的名字不一样了。
五、私有属性以及私有方法
类中定义的属性与方法可以再细分为公有属性、方法和私有属性、方法,公有的属性、方法可以在类的外部访问到,而私有是指只在类的内部可以访问。
如何定义私有成员呢?只需要在你想定义的私有属性和方法的名称加上双下划线__即可。代码如下:
class Dog: def __init__(self,name,type,gender): self.name = name self.__type = type self.gender = gender def __tongue(self): print('狗正在伸舌头') def call(self): print('狗正在叫') smalldog1 = Dog('旺财','藏獒','公') print(smalldog1.__type) #报错 smalldog1.__tongue #报错
代码中分别在tongue()方法和type属性的前面加上了双下滑线__的前缀修饰,表示type现在为私有属性、tongue()为一个私有方法,即在类的外部无法访问。
既然在类的外部无法访问,那么在类中应该如何访问呢?请看以下代码:
class Dog: def __init__(self,name,type,gender): self.name = name self.__type = type self.gender = gender def __tongue(self): print('狗正在伸舌头') def call(self): self.__tongue() print(self.__type) print('狗正在叫') smalldog1 = Dog('旺财','藏獒','公') smalldog1.call()
执行结果为:
狗正在伸舌头
藏獒
狗正在叫
上述代码在call()方法中使用self调用了__tongue()方法和__type属性。
六、继承
面向对象编程带来的好处之一是代码的重用,实现重用的方法之一是通过继承机制。一个类继承另一个类时,它将自动获得另一个类的所有属性和方法,原有的类称为父类,而新类称为子类。子类继承了其父类的所有属性和方法,同时还可以定义自己的属性和方法。
1.继承的实现
一般情况下继承的父类都写在子类类名后的小括号内,即:
class Animal(): #父类(动物类) pass class Dog(Animal): #子类(狗类) pass class Cat(Animal): #子类(猫类) pass
2.继承的特点
子类继承父类,那么是可以继承到父类的全部属性和方法吗?答案是不可以,也就是说子类只能继承父类的公有属性和方法,私有的属性和方法无法继承。
3.多重继承
多重继承顾名思义就是子类可以继承多个父类,多重继承在代码中的使用方法和单继承一样都是写在小括号内,多个父类的话可通过逗号分隔开。
class Animal(): #动物类 pass class Dog(): #狗类 pass class Medium_dog(Animal,Dog): #中型狗类 pass
在上面的代码中,Medium_dog(中型狗)分别继承狗类和动物类,所以动物类和狗类下面的公有属性和方法在Medium_dog中都可以访问。除了这种直接继承,还有一种间接继承,具体代码如下:
class Animal(): #动物类 pass class Dog(Animal): #狗类 pass class Medium_dog(Dog): #中型狗类 pass
这段代码在Medium_dog类中同样可以访问Animal和Dog中的共有属性和方法。
4.super
在类的继承中,如果重定义某个方法,那么该方法就会覆盖父类的同名方法,但有时,我们希望能实现父类的功能,这时就需要调用父类的方法了,可通过使用super来实现,具体代码实现如下:
class Animal(): #动物类 def __init__(self,name): self.name = name def info(self): print('from Animal') class Dog(Animal): #狗类 def __init__(self,name): self.name = name def info(self): super().info() print('from Dog') d = Dog('旺财') d.info()
在Dog类下的info()方法中通过super访问父类中的方法,在实例化对象调用info()方法时,会打印两条信息,分别为“from Animal”和“from Dog”。
在子类中除了super还可以通过“父类名称.方法名()”进行调用外,我们选择super的另一个好处是避免硬编码。
硬编码一般是指在代码中写死的编码。与它相对应的是配置项,可以在程序发布后进行修改。
七、封装
封装分为三个层面。
第一层面的封装:类本身就是一种封装,为什么这样说呢?我们来看下面代码:
class Animal(): #动物类 def __init__(self,name): self.name = name def info(self): print('from Animal')
在外界使用的时候只需要对Animal进行实例化,那么属性和方法就可以通过实例化后的对象访问到。
class Animal(): #动物类 def __init__(self,name): self.name = name def info(self): print('from Animal') a = Animal('老虎') print(a.name) #结果为:老虎 a.info() #结果为:from Animal
这样一来,在外面观察代码时,你无法知道内部的info方法到底是如何执行的、究竟做了什么,这些逻辑都被封装了起来,调用的时候很简单,只需访问即可,不用指定内部实现的细节。
第二层面的封装:类中定义私有的,只在类的内部使用,外部无法访问。单双下划线的使用,但只是一种约定。我们来看下具体代码:
class Dog: def __init__(self,name,type,gender): self._name = name #属性前加一个单下划线,那它就属于内部的属性,不能被外部调用 self.type = type self.gender = gender def __tongue(self): #方法前加两个单下划线,私有方法 print('狗正在伸舌头') def call(self): print('狗正在叫')
我们现在对Dog进行实例化,并执行以下代码:
smalldog1 = Dog('旺财','藏獒','公') print(smalldog1._name) #结果为:旺财 smalldog1.__tongue #报错 smalldog1._Dog__tongue() #狗正在伸舌头
为什么smalldog1._name会调用到类内部的属性,我们明明约定好了,只要属性前加一个单下划线,那它就属于内部的属性,不能被外部调用,为何还能调用?因为它只是一种约定。而双下划线也是如此,虽然调用smalldog1.__tongue会报错,但我们依旧有办法在类外调用私有方法。这就是一种约定,也是一种封装。
第三层面的封装:明确区分内外,内部的实现逻辑,外部无法知晓,并且为封装到内部的逻辑提供一个访问接口给外部使用,具体代码如下:
class Dog: def __init__(self,name,type,gender): self._name = name self.type = type self.gender = gender def __tongue(self): print('狗正在伸舌头') def call(self): print('狗正在叫') def get_star(self): #接口函数 return self.__tongue() smalldog1 = Dog('旺财','藏獒','公') smalldog1.get_star() #结果为:狗正在伸舌头
在类中定义一个函数接口给外部使用,而接口无需知道__tongue方法到底是如何执行的、究竟做了什么,这些逻辑都被封装了起来。
八、多态
多态的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。下面来对一段代码进行分析:
class H2o: def __init__(self,name,temperature): self.name = name self.temperature = temperature def turn_ice(self): if self.temperature < 0: print('%s温度太低结冰了' %self.name) elif self.temperature > 0 and self.temperature < 100: print('%s液化成水' %self.name) elif self.temperature > 100: print('%s温度太高变成了水蒸气' %self.name) class Water(H2o): pass class Ice(H2o): pass class Steam(H2o): pass w1 = Water('水',25) l1 = Ice('冰',-22) s1 = Steam('蒸汽',1300) def func(x): #定义一个方法,接收传入的对象 x.turn_ice() func(w1) func(l1) func(s1)
有没有理解多态?其实很简单,多态不用对具体的子类型进行了解,到底调用哪一个方法会在运行的时候由该对象的确切类型决定。使用多态,我们只管调用,不用管细节。
九、组合
在Python中,组合与继承都是有效地利用已有类的资源的重要方式。但是二者的概念不同:
组合:当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好
继承:当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好
组合的具体代码实现如下:
class School: def __init__(self,name,addr): self.name = name self.addr = addr class Course: def __init__(self,name,price,period,school): self.name = name self.price = price self.period = period self.school = school s1 = School('北京大学','北京') s2 = School('浙江大学','浙江') s3 = School('厦门大学','厦门') mag = ''' 1.北京大学 2.浙江大学 3.厦门大学 ''' while True: print(mag) menu = { '1': s1, '2': s2, '3': s3 } choice = input('选择学校>>:') school_obj = menu[choice] name = input('课程名>>:') price = input('课程费用>>:') period = input('课程周期>>:') new_course = Course(name,price,period,school_obj) print('课程%s属于%s学校,总学时为%s' %(new_course.name,new_course.school.name,new_course.period))
上述代码中使用了组合的方式,在一个类中以另外一个类的对象作为数据属性。