Python 基础知识----面向对象编程
一、面向对象编程介绍
面向过程编程:
- 核心是过程二字,过程指的是解决问题的步骤,即先做什么再干什么然后干什么。
- 基于该思想编写程序好比在设计一条流水线,是一种机械式的思维方式。
- 优点:复杂的问题流程化、进而简单化
- 缺点:扩展性差
面向对象编程:
- 核心二字是对象,对象是数据值和行为特性的融合体。即造出来什么对象,让这个对象具有做某些事的本领。
- 基于该思想编写程序就好比上帝在创造一个世界,是一种上帝式的思维方式。
- 优点:扩展性强
- 缺点:编程的复杂度要高于面向过程编程
- 应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方
面向对象编程和面向过程编程是一种编程思想。
面向对象的程序设计并不是全部。对于一个软件质量来说,面向对象的程序设计只是用来解决扩展性。
二、类与对象
类即类别、种类,是面向对象设计最重要的概念,对象是特征与技能的结合体,而类则是一系列对象相似的特征与技能的结合体。
在现实世界中:先有对象,再有类
- 对象是具体的存在,而类仅仅只是一个概念,并不真实存在
在程序中:务必保证先定义类,后产生对象
- 这与函数的使用是类似的,先定义函数,后调用函数,类也是一样的,在程序中需要先定义类,后调用类
- 不一样的是,调用函数会执行函数体代码返回的是函数体执行的结果,而调用类会产生对象,返回的是对象
2.1 创建类
Python类使用class关键字来创建。简单地类的声明可以是关键字后紧跟类名:
class ClassName(): 'class documentation string' # '类文档字符串' class_suite # 类体
类就像一个Python容器类型。
2.2 类属性
- 什么是属性呢?属性就是属于另一个对象的数据或者函数元素,可以通过我们熟悉的句点属性标识法(也就是 . )来访问。一些Python类型比如复数有数据属性(实部和虚部),而另外一些,像列表和字典,拥有方法(函数属性)。
- 如果你正访问一个属性时,它同时也是一个对象,拥有它自己的属性,可以访问,这导致了一个属性链
- 类属性仅与其被定义的类相绑定,并且因为实例对象在面向对象编程中用的多。实例数据属性是我们以后将会一直用到的主要数据属性。类数据属性仅当需要有更加‘静态’数据类型时才变得有用,它和任何实例都无关。
静态表示一个值,不会因为函数调用完毕而消失,它在每两个函数调用的间隙都存在。或者说,一个类中的一些数据对所有的实例来说,都是固定的。
2.2.1 类的数据属性
数据属性仅仅是所定义的类的变量。它们可以向任何其他变量一样在类创建后被使用,并且,要么是由类中的方法来更新,要么是在主程序其他什么地方被更新。
这种属性即静态变量,或者是静态数据。它们表示这些数据是他们所属的类对象绑定的,不依赖于任何类实例。
class C(): foo = 100 print(C.foo) C.foo = C.foo+1 print(C.foo)
2.2.2 Methods
a. 方法
方法,比如下面,类Student中的choose_course方法,仅仅是一个作为类定义一部分定义的函数(这使得方法称为类属性)。这表示choose_course仅应用在Student类型的对象(实例)上。
这里,choose_course是通过句点属性表示法与它的实例绑定的。
class Student(): def choose_course(self): print('学生正在选课。。。') zrg = Student() zrg.choose_course()
任何像函数一样对choose_course自身的调用都将失败:
因为在全局名称空间中没有这样的函数存在。这就告诉你choose_course是一个方法,它表示属于一个类,而不是全局名称空间中的名字。
b. 绑定(绑定与非绑定方法)
没有实例,方法是不能被调用的。这种限制即Python所描述的绑定概念,在此,方法必须绑定(到一个实例)才能直接被调用。非绑定的方法可能可以被调用,但实例对象一定要明确给出,才能确保调用成功。然而,不管是否绑定,方法都是它所在的类的固有属性,即使它们几乎总是通过实例来调用的。
2.2.3 决定类的属性
要知道一个类有哪些属性,有两种方法。最简单的就是使用dir()内置函数。另外就是通过访问类的字典属性__dict__,这是所有类都具备的特殊属性之一。
class Student(): #创建类 name = 'zrg' #数据属性(静态数据) age = 18 school = '北京大学' def choose_course(self): #方法 print('学生正在选课。。。')
print(dir(Student))
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'choose_course', 'name', 'school']
print(Student.__dict__)
{'__module__': '__main__', 'name': 'zrg', 'age': 18, 'school': '北京大学', 'choose_course': <function Student.choose_course at 0x102b1c620>, '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}
从上面可以看出,dir()返回的仅是对象的属性的名字列表,而__dict__返回的是一个字典,它的键(key)是属性名,键值(value)是响应的属性对象的数据值。
2.3 实例
如果说类是一种数据结构定义类型,那么实例则声明了一个这种类型的变量。换言之,实例就是有生命的类。就像设计万一张蓝图后,就是设法让它和成为现实。实例是那些主要用在运行期时的对象,类被实例化得到实例,该实例的类型就是这个被实例化的类。
2.3.1 初始化(实例化):通过条用类对象来创造实例
class Student(): # 创建类 name = 'zrg' # 数据属性(静态数据) def choose_course(self): # 方法 print('学生正在选课。。。') obj = Student() #类名加括号,实例化,创建实例,返回值obj就是一个实例,是一个对象
2.3.2 __init__() ‘构造器’方法
当类被调用,实例化的第一步是创建实例对象。一旦对象创建了,Python检查是否实现了__init__()方法。默认情况下,如果没有定义(或覆盖)特殊方法__init__(),对实例不会施加任何特别的操作。任何所需的特定操作,都需要程序员实现__init__(),覆盖它的默认行为。如果__init__()没有实现,则返回它的对象,实例化过程完毕。
然而,如果__init__()已经被实现,那么它将被调用,实例对象作为第一个参数(self)被传递进去,像标准方法调用一样。调用类时,传进去的任何参数都交给了__init__()。实际中,你可以想像成这样:把创建实例的调用当成是对构造器的调用。
2.3.3 __new__()
2.3.4 __del__()
2.4 实例属性
实例仅拥有数据属性(方法严格来说是类属性),或者只是与某个类的实例相关联的数据值,并且可以通过句点属性标识法来访问。这些值独立于其它实例或类。当一个实例被释放后,它的属性同时也被清除了。
2.4.1 ‘实例化’实例属性()
设置实例的属性可以在实例创建后任意时间进行,也可以在能够访问实例的代码中进行。构造器__init__()是设置这些和随行的关键点之一。
能够在‘运行时’创建实例属性,是Python类的优秀特性之一,Python不仅是动态类型,而且在运行时,允许这些对象属性的动态创建。
一个缺陷是,属性在条件语句中创建,如果该条件语句块未被执行,属性也就不存在,而你在后面的代码中试着去访问这些属性,就会发生错误。
- 在构造器中首先设置实例属性
- 构造器是最早可以设置实例属性的地方,因为__init__()是实例创建后第一个被调用的方法。再没有比这更早的可以设置实例属性的机会了。一旦__init__()执行完毕,返回实例对象,即完成了实例化过程。
- 默认参数提供默认的实例安装
- 在实际应用中,带默认参数的__init()提供一个有效的方式来初始化实例。在很多情况下,默认值表示设置属性的最常见的情况,如果提供了默认值,我们就没必要显示给构造器传值了。默认参数应当是不变的对象;像列表和字典这样的可变对象可以扮演静态数据,然后在每个方法调用维护它们的内容。
- __init__()应当返回None
- 调用类会对象会创建一个实例,也就是说这样一种调用过程返回的对象就是实例。
- 如果定义了构造器,它不应当返回任何对象,因为实例对象是自动在实例化调用后返回的。相应的__init__()就不应当返回任何对象(应当为None);否则,就可能出现冲突,因为只能返回实例。
2.4.2 查看实例属性
内置函数dir()可以显示类属性,同样还可以打印所有的实例属性。
class Student(): school = '北京大学' def choose_Course(self): print('选课') student1 = Student() student1.name = '张仁国' student1.age = 18 student1.hobby = ['read','swimming','music'] print(dir(student1))
结果:
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'choose_Course', 'hobby', 'name', 'school']
与类相似,实例也有一个__dict__()特殊属性(可以调用vars()并传入一个实例来获取),它是实例属性构成的一个字典。
class Student(): school = '北京大学' def choose_Course(self): print('选课') student1 = Student() student1.name = '张仁国' student1.age = 18 student1.hobby = ['read','swimming','music'] print(student1.__dict__)
结果:
__dict__属性由一个字典组成,包含一个实例的所有属性。键是属性名,值是属性对应的数据值。字典中仅有实例属性,没有类属性或特殊属性。
{'name': '张仁国', 'age': 18, 'hobby': ['read', 'swimming', 'music']}
2.4.5 实例属性vs类属性
类属性仅是与类相关的数据值,和实例属性不同。类属性与实例属性无关。这些像静态成员那样被引用,即使在多次实例化中调用类,它们的值都保持不变。不管如何,静态成员不会因为实例而改变它们的值,除非实例中显示改变他们的值(实例属性与类属性的比较,雷诗雨自动变量和静态变量,但这只是笼统的类推。)
类和实例都是名称空间。类是类属性的名称空间,实例则是实例属性的名称空间。
关于类属性和实例属性,可以采用类来访问类属性,如果实例没有同名的属性的话,也可以用实例来访问。
a、访问类属性
类属性可以通过类或实例来访问。Python首先会在实例的名称空间中查找,然后是类,再就是继承书中的基类。
class Student(): #定义类 school = '北京大学' #静态成员 zrg = Student() #实例化 print(Student.school) #通过类来访问 print(zrg.school) #通过实例来访问 Student.school = '清华大学' #通过类(只能这样)来更新 print(Student.school) #通过类来访问 print(zrg.school) #通过实例来访问,其值已经被改变了 结果: 北京大学 北京大学 清华大学 清华大学
注意:
我们只有当使用类能修改类属性的值!
b、从实例中访问类属性需谨慎
与通常Python变量一样,任何对实例属性的赋值都会创建一个实例属性(如果不存在的话)并且对其赋值。如果类属性中存在同名的属性
c、类属性持久化
类属性的修改会影响所有的实例。
2.5 绑定和方法调用
Python中绑定(binding)的概念,它主要与方法调用相关联。
首先,方法仅仅是类内部定义的函数(这意味着方法是类属性而不是实例属性)。
其次,方法只有在其属的类拥有实例时,才能被调用。当存在一个实例时,方法才被认为是绑定到那个实例了。没有实例时方法就是未绑定的。
最后,任何一个方法定义中的第一个参数都是变量self,它表示调用此方法的实例对象。
self变量用于在类实例方法中引用方法所绑定的实例。因为方法的实例在任何方法调用中总是作为第一个参数传递的,self被选中用来代表实例。必须在方法声明中放上self,但可以在方法中不适用实例(self)。如果再方法中国没有用到self,建议创建一个常规函数,除非有特别的原因。毕竟方法代码没有使用实例,没有与类关联其功能,这使得它看起来更像一个常规函数。
2.5.1 调用绑定方法
方法,不管绑定与否,都是由相同的代码组成的。唯一的不同在于是否存在一个实例可以调用此方法。在很多情况下,调用的都是一个绑定的方法。
2.5.2 调用非绑定方法
2.6 静态方法和类方法
2.6.1 staticmethod()和classmethod()内奸函数
2.6.2 使用函数修饰符
2.7 组合(让一个类的对象具备一个属性,而这个属性的值是另外一个类的对象)
一个类被定义后,目标就是要把它当成一个模块来使用,并把这些对象嵌入到你的代码中,同其他数据类型及逻辑执行流混合使用。有两种方法可以在你的代码中利用类。第一种是组合(composition)。就是让不同的类混合并加入到其他类中,来增加功能和代码重用性。你可以在一个大点的类中创建你自己的类的实例,实现一些其他属性和方法来增强对原来的类对象。另一种方法是通过派生。
# 父类 class People(): school = '北京大学' def __init__(self, id, name, age, sex, subject): self.id = id self.age = age self.sex = sex self.name = name self.subject = subject # 课程类 class Course(): def __init__(self, name, price): self.name = name self.price = price def course_info(self): msg = """ 课程名:%s 课程价格:%s """%(self.name,self.price) print(msg) # 学生类 class Student(People): def __init__(self, id, name, age, sex, subject): People.__init__(self, id, name, age, sex, subject) def choose_Course(self): print('选课') # 老师类 class Teacher(People): def __init__(self, id, name, age, sex, subject, level): People.__init__(self, id, name, age, sex, subject) self.level = level def score(self, stu, num): stu.score = num print('学生%s的成绩是:%s' % (stu.name, num)) # 课程实例 python = Course('Python全栈开发', 20000) linux = Course('linux运维', 17000) # 学生实例 zrg = Student(1, '张仁国', 18, '男', 'Python') zrg.choose_Course() # 老师实例 yj = Teacher(1, '杨静', 19, '女', '地理', '高级教师') # 学生、老师与课程组合 zrg.course = python yj.course = linux zrg.course.course_info() yj.course.course_info()
2.8 子类和派生
面向对象编程的强大功能之一是能够使用一个已经定义好的类,扩展它或者对其进行修改,而不会影响系统中使用现存类的其他代码片段。允许类特征在子孙类或子类中进行继承。这些子类从基类(或成祖先类、超类)继承它们的核心属性。而且,这些派生可能会扩展到多代。在一个层次的派生关系中的相关类(或者是在类树图中垂直相邻)是父类和子类关系。从同一个父类派生出来的这些类(或者是在类树图中水平相邻)是同胞关系。父类和所有高层类都被认为是祖先。
2.9 继承
什么是继承?
继承是一种新建类的方式,新建的类称为子类,被继承的类称为父类
继承的特性是:子类会继承父类的属性
为什么用继承?
继承的好处就是可以减少代码的冗余
如何用继承?
class Parent(): #父类 pass class Children(Parent): #子类 pass
经典类与新式类
1.只有在python2中才分新式类和经典类,python3中统一都是新式类 2.在python2中,没有显式的继承object类的类,以及该类的子类,都是经典类 3.在python2中,显式地声明继承object的类,以及该类的子类,都是新式类 3.在python3中,无论是否继承object,都默认继承object,即python3中所有类均为新式类
2.9.1 __bases__类属性
对任何(子)类,它是一个包含其父类的集合的元组。
class Parent1(): #父类 pass class Parent2(): pass class Children(Parent1,Parent2): #子类 pass print(Parent1.__bases__) print(Parent2.__bases__) print(Children.__bases__)
(<class 'object'>,) (<class 'object'>,) (<class '__main__.Parent1'>, <class '__main__.Parent2'>)
注意:父类是相对所有基类(它包括了所有祖先类)而言的。那些没有父类的类,它们的__bases__属性为空。
2.9.2 通过继承覆盖方法
2.9.3
2.9.4 多重继承
Python允许子类继承多个基类。这种特性就是通常所说的多重继承。概念容易,但最难的工作是,如何正确找到没有在当前(子)类定义的属性。当使用多重继承时,有两个不同的方面要记住。首先,还是要找到合适的属性。另一个就是当你重写方法时,如何调用对应父类方法以发挥他们的作用,同时,在子类中处理好自己的义务。
1、方法解释顺序
2、简单属性查找示例
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类
3、菱形效应引起MRO问题
4、总结
方式一:知名道姓,直接用父类名.函数名。与继承无关 class Teacher(People): def __init__(self, id, name, age, sex, subject, level): People.__init__(self, id, name, age, sex, subject) self.level = level 方式二:严格依赖继承属性查找关系 super()会得到一个特殊的对象,该对象就是专门用来访问父类中的属性的(按继承的关系来查找属性) class Teacher(People): def __init__(self, id, name, age, sex, subject, level): super().__init__(id, name, age, sex, subject) self.level = level
2.10 类、实例和其他对象的内置函数
多态和多态性
什么是多态?
多态指的是同一种事物的多种形态。
为何要用多态?
多态性:继承同一个类的多个子类中有相同的方法名,那么子类产生的对象就可以不用考虑具体的类型而直接调用功能。
如何用?
鸭子类型
111