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不仅是动态类型,而且在运行时,允许这些对象属性的动态创建。
一个缺陷是,属性在条件语句中创建,如果该条件语句块未被执行,属性也就不存在,而你在后面的代码中试着去访问这些属性,就会发生错误。
核心笔记:实例属性
  1. 在构造器中首先设置实例属性
    1. 构造器是最早可以设置实例属性的地方,因为__init__()是实例创建后第一个被调用的方法。再没有比这更早的可以设置实例属性的机会了。一旦__init__()执行完毕,返回实例对象,即完成了实例化过程。  
  2. 默认参数提供默认的实例安装
    1. 在实际应用中,带默认参数的__init()提供一个有效的方式来初始化实例。在很多情况下,默认值表示设置属性的最常见的情况,如果提供了默认值,我们就没必要显示给构造器传值了。默认参数应当是不变的对象;像列表和字典这样的可变对象可以扮演静态数据,然后在每个方法调用维护它们的内容。  
  3. __init__()应当返回None
    1. 调用类会对象会创建一个实例,也就是说这样一种调用过程返回的对象就是实例。
    2. 如果定义了构造器,它不应当返回任何对象,因为实例对象是自动在实例化调用后返回的。相应的__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,建议创建一个常规函数,除非有特别的原因。毕竟方法代码没有使用实例,没有与类关联其功能,这使得它看起来更像一个常规函数。
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

原文地址:https://www.cnblogs.com/zhangrenguo/p/9833985.html