Python-面向对象

摘要

本文内容转自网络,个人学习记录使用,请勿传播

面向对象

面向过程VS面向对象

  • 面向过程的程序设计的核心是过程(流水线式思维),过程即解决问题的步骤,面向过程的思想就好比是精心设计好一条流水线,考虑周全什么时候处理什么东西。

  • 优点是:极大的降低了写程序的复杂度,只需要顺着要执行的步骤,堆叠代码即可。

  • 缺点是:一套流水线就是用来解决一个问题,代码牵一发而动全身。

  • 应用场景:

    • 一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以及Apache HTTP Server等。
  • 面向对象OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,并且一个对象包含数据和操作数据的方法。

  • 面向对象的程序设计的核心是对象,要理解对象为何物,必须把自己当成上帝(上帝式思维)。上帝眼里世间存在的万物皆为对象。

  • 形象化场景设计:

    • 面向对象的程序设计好比如来设计西游记,如来要解决的问题是把经书传给东土大唐,”如来”想了想解决这个问题需要四个人(对象):唐僧,沙和尚,猪八戒,孙悟空,每个人都有各自的特征和技能(这就是对象的概念,特征和技能分别对应对象的属性和方法)。然而这并不好玩,于是如来又安排了一群妖魔鬼怪,为了防止师徒四人在取经路上被搞死,又安排了一群神仙保驾护航,这些都是对象。然后取经开始,师徒四人与妖魔鬼怪神仙互相缠斗着直到最后取得真经。“如来”根本不会管师徒四人按照什么流程去取,只关心最后结果是否可以实现。
    • 因此面向对象的核心思想就是使用一个有一个的对象来完成某件具体是事件,且不用关心完成的具体过程!
  • 面向对象的优点:面向对象编程可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率 ,另外,基于面向对象的程序可以使他人更加容易理解你的代码逻辑,从而使团队开发变得更从容。

  • 应用场景:需求经常变化的软件,如互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方。

类和实例

,英文名字Class,有“类别”,“分类”,“聚类”的意思。

必须牢记类是抽象的模板,用来描述具有相同属性和方法的对象的集合,比如Animal类。

实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。

  • Python使用class关键字来定义类,其基本结构如下:

  • 1
    2
    class 类名(): #一般类名首字母是大写
    pass
  • 下面是一个学生类:

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Student():
    #数据
    classroom = '101'
    address = 'beijing'
    #构造方法
    def __init__(self, name, age):
    self.name = name
    self.age = age
    #操作
    def print_age(self):
    print('%s: %s' % (self.name, self.age))
  • 对象的创建

    • 可以通过调用类的实例化方法(有的语言中也叫初始化方法或构造函数)来创建一个类的实例(对象)。

    • Python提供了一个def __init__(self):的实例化机制。任何一个类中,名字为__init__的方法就是类的实例化方法,具有__init__方法的类在实例化的时候,会自动调用该方法,并传递对应的参数。

    • 1
      2
      zhangsan = Student('zhangsan',20)
      lisi = Student('lisi',30)

实例变量和类变量

  • 实例变量

    • 实例变量指的是实例(对象)本身拥有的变量。Student类中__init__方法里的name和age就是两个实例变量。

    • 通过实例名加圆点的方式调用实例变量(可以通过对象名打点的方式去调用/访问属于对象的成员)。

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      class Student():
      #init称为构造方法
      def __init__(self,i_name,i_age):
      #只要定义在init方法内部的变量就是【实例/对象变量】
      self.name = i_name #self.name就是定义的实例变量,name是init方法的参数值
      self.age = i_age #self.age就是定义的实例变量,age就是init方法的参数值

      s1 = Student('zhangsan',20) #调用Student类中的init这个构造方法
      s2 = Student('lisi',25)
      #根据对象的引用访问对象的实例变量
      print(s1.name,s1.age) #访问s1对象的name和age这两个实例变量
      print(s2.name,s2.age) #访问s2对象的name和age这两个实例变量
  • 类变量

    • 定义在类中,方法之外的变量,称作类变量。类变量是所有实例公有的变量,每一个实例都可以访问类变量。

    • 在Student类中,classroom和address两个变量就是类变量。可以通过类名或者实例名加圆点的方式访问类变量,比如:

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      class Student():
      #定义在方法外部的变量:类变量
      address = 'Beijing'
      classroom = 167

      #init称为构造方法
      def __init__(self,i_name,i_age):
      #只要定义在init方法内部的变量就是【实例/对象变量】
      self.name = i_name #self.name就是定义的实例变量,name是init方法的参数值
      self.age = i_age #self.age就是定义的实例变量,age就是init方法的参数值

      s1 = Student('zhangsan',20) #调用Student类中的init这个构造方法
      s2 = Student('lisi',25)
      #根据对象的引用访问对象的实例变量
      print(s1.name,s1.age) #访问s1对象的name和age这两个实例变量
      print(s2.name,s2.age) #访问s2对象的name和age这两个实例变量
      • 类变量的特性:

        • 所有的类变量是可以通过类名或者对象名打点的方式访问/调用的。

        • 1
          2
          3
          print(s1.address,s1.classroom) #通过对象名可以访问类变量(不推荐)
          #通过类名可以访问类变量(推荐)
          print(Student.address,Student.classroom)
        • 类变量是可以被所有的对象公用的

        • 1
          2
          print(s1.address,s1.classroom) #通过s1对象访问类变量
          print(s2.address,s2.classroom) #通过s2对象访问类变量
    • 思考:如何修改类变量中存储的数据?

      • 通过对象名访问类变量对其进行内容的修改?

      • 1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        class Student():
        #定义在方法外部的变量:类变量
        address = 'Beijing'
        classroom = 167

        #init称为构造方法
        def __init__(self,i_name,i_age):
        #只要定义在init方法内部的变量就是【实例/对象变量】
        self.name = i_name #self.name就是定义的实例变量,name是init方法的参数值
        self.age = i_age #self.age就是定义的实例变量,age就是init方法的参数值

        s1 = Student('zhangsan',20) #调用Student类中的init这个构造方法
        s2 = Student('lisi',25)

        #想要通过s1对象访问address类变量,且给其修改内容
        s1.address = 'SH'
        print(s2.address)
        #思考:类变量是被所有的对象共享,因此通过s1对象修改了类变量,s2再次访问类变量,情况如何?
        #s2访问的类变量address依然是beijing不是s1修改后的上海,Why?
        #这种情况是说明类变量不是被所有对象共享的吗?一定不是这样!
        • 特别注意:建议大家使用类名访问类变量。如果通过对象名直接访问类变量是没有问题的。但是通过对象名访问类变量后给其进行赋值操作意图修改类变量实则发生的是通过对象打点的方式给对象动态的新增了一个实例变量,并不是在修改类变量。

          • 1
            2
            s1.address = 'SH' #动态的给s1这个变量新增了一个实例变量,这个实例变量叫做address。该操作执行完毕后,则s1这个对象内部多了一个实例变量叫做s1,且s1对象对应的类中也有一个address表示的类变量。
            print(Student.address)#在访问类变量address,这个类变量依然还是Beijing。
      • 通过实例名修改?

1
2
3
4
5
6
#在访问类变量
print(s1.address,s2.address)
#通过类名访问类变量且对其进行修改
Student.address = 'SH'
#查看修改后的结果
print(s1.address,s2.address) #SH SH

注意:

1
2
3
4
5
6
#对象创建好了之后,是可以通过对象名动态的给对象添加新的实例变量。对象添加了实例变量后,只会影响该对象本身,不会对类和其他对象造成影响。

#给s1对象动态添加了一个实例变量book
s1.book = 'sanguo'
#下述语句报错,因为s1动态增加的实例变量,只会影响它自己,不会影响到其他
print(s2.book,Student.book)
  • 测试:

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Student():
    address = 'Beijing'
    #构造方法:用来实例变量初始化赋值
    def __init__(self,addr):
    #构造方法中也可以写入其他操作,但是一般情况下,只需要在构造方法中写给实例变量初始化赋值的操作。
    self.address = addr
    #实例变量名和类变量名一致

    s = Student('SH') #创建对象其实就是在调用类中的init方法
    #如果实例变量和类变量同名,通过对象名访问,优先方位对象的实例变量
    print(s.address) #输出:SH
    print(Student.address)#输出:Beijing

类的方法

Python的类中包含实例方法、静态方法和类方法三种方法。区别在于传入的参数和调用方式不同。

在类的内部,使用def关键字来定义一个方法。

实例方法
  • 类的实例方法由实例调用,至少包含一个self参数,且为第一个参数。执行实例方法时,会自动将调用该方法的实例赋值给self。

    • self代表的是类的实例,而非类本身。self不是关键字,而是Python约定成俗的命名,你完全可以取别的名字,但不建议这么做。

    • 例如,我们前面Student类中的print_age()就是实例方法:

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      class Student():
      classroot = 167 #类变量
      #构造方法
      def __init__(self,name,age):
      #实例变量
      self.name = name
      self.age = age

      #实例方法:self不是python的关键字,实例方法的第一个参数也可以叫其他的名字,但是约定俗成叫做self。
      #注意:实例方法只可以通过对象调用。
      def study(self,book):#self是不需要手动给其传值
      print('正在学习的书籍是:',book)

      s = Student('zhangsan',20) #调用构造方法
      #只给除了self其他的参数传值
      s.study('高等数学')
    • 实例方法中的第一个参数self到底是什么鬼?

      • 想要在一个实例方法内部调用另一个实例方法?
        • 核心:实例方法只可以被对象调用
    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      class Student():
      classroot = 167 #类变量
      #构造方法
      def __init__(self,name,age):
      #实例变量
      self.name = name
      self.age = age
      #实例方法
      def study(self,book): #self就是study方法的调用者(对象)
      #注意:在study方法内部调用play方法,如何实现?
      self.play('足球') #实例方法必须使用对象调用
      #self表示的就是调用该方法的对象的引用
      print('正在学习的书籍是:',book)
      #实例方法
      def play(self,b):
      print('正在玩的项目是:',b)

      s = Student('zhangsan',20) #调用构造方法
      s.study('高等数学')
      • self就是study方法的调用者(对象),self表示的就是调用该方法的对象的引用

对象之间的交互:设计人狗大战游戏,让他们真正的打一架。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Person():
def __init__(self,name):
self.name = name
self.blood = 100 #人的初始血量
self.gjl = 10 #人的攻击力

def hitDog(self,dog): #dog参数表示的是狗对象
#人打狗后,需要让狗的血量减去人的攻击力
dog.blood -= self.gjl
#展示剩余血量
def showBlood(self):
print('%s,剩下的血量为:%d'%(self.name,self.blood))


class Dog():
def __init__(self,name):
self.name = name
self.blood = 50 #狗的初始血量
self.gjl = 5 #狗的攻击力

def hitPerson(self,p): #参数p就是狗攻击的人那个对象
p.blood -= self.gjl
#展示剩余血量
def showBlood(self):
print('%s,剩下的血量为:%d'%(self.name,self.blood))

p1 = Person('zhangsan')
p2 = Person('lisi')

d1 = Dog('doudou')
d2 = Dog('huanghuang')

p2.hitDog(d1) #lisi去攻击doudou这条狗
d1.showBlood() #查看被攻击后的狗还剩下多少血量

d2.hitPerson(p1) #huanghuang去攻击zhangsan
p1.showBlood() #查看被攻击后的人还剩下多少血量
静态方法
  • 静态方法由类调用,无默认参数。将实例方法参数中的self去掉,然后在方法定义上方加上@staticmethod,就成为静态方法。

  • 静态方法属于类,和实例无关。建议只使用类名.静态方法的调用方式。(虽然也可以使用实例名.静态方法的方式调用)

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class Obj():
    def __init__(self):
    pass

    #定义一个静态方法
    @staticmethod
    def staticFunc(name):#静态方法不需要有任何的必要参数
    print('我是静态方法!,我有一个普通参数:',name)

    Obj.staticFunc('bobo') #通过类名调用(推荐)
    o = Obj()
    o.staticFunc('bobo') #通过对象名调用(不推荐)
    #静态方法既不属于类也不属于对象,仅仅是写在类内部的一个普通函数而已
类方法
  • 类方法由类调用,采用@classmethod装饰,至少传入一个cls(代指类本身,类似self)参数。

  • 执行类方法时,自动将调用该方法的类赋值给cls。建议只使用类名.类方法的调用方式。(虽然也可以使用实例名.类方法的方式调用)

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Obj():
    f = 'classVar' #类变量
    def __init__(self):
    pass
    @classmethod
    def classFunc(cls): #类方法必须要有一个cls的参数,且作为第一个参数
    #cls也不是python的关键字,cls也可以写作其他的形式,比如:name,self
    print('我是类方法!必要参数cls的值为:',cls)
    print('类变量的值为:',cls.f) #类名访问类变量
    #cls表示的是当前类

    o = Obj()
    o.classFunc() #通过对象名访问(不推荐)

    Obj.classFunc() #通过类名访问(推荐)
  • 测试题:

    • 在类方法中是否可以调用实例方法和实例变量?
      • 不能,因为实例变量和实例方法只可以通过对象名访问,但是在类方法内部不存在对象名。
    • 在实例方法中是否可以调用类方法和类变量?
      • 可以,但是不建议这么做!

面向对象的组合用法

  • 组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合

  • 思路设计:一个学生会有一步手机,学生使用手机看电影。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Student():
def __init__(self):
#将创建好的手机对象赋值给了phone这个实例变量
self.phone = Phone('霸王别姬')

class Phone():
def __init__(self,movie_name):
self.movie_name = movie_name

def playMovie(self):
print('手机正在播放的电影是:',self.movie_name)

s1 = Student()
s1.phone.playMovie()

封装、继承和多态

面向对象有三大特性:封装、继承和多态

封装
  • 封装是指将数据与具体操作的实现代码放在某个对象内部,使这些代码的实现细节不被外界发现且不能通过任何形式修改对象内部实现,正是由于封装机制。

  • 作用:

    • 程序在使用某一对象时不需要关心该对象的数据结构细节及实现操作的方法。使用封装能隐藏对象实现细节,使代码更易维护,同时因为不能直接调用、修改对象内部的私有信息,在一定程度上保证了系统安全性。
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class Student:
    classroom = '101'
    address = 'beijing'

    def __init__(self, name, age):
    self.name = name
    self.age = age

    def print_age(self):
    print('%s: %s' % (self.name, self.age))

    # 以下是错误的用法
    # 类将它内部的变量和方法封装起来,阻止外部的直接访问
    print(classroom)
    print(adress)
    print_age()
继承
  • 继承来源于现实世界:

    • 一个最简单的例子就是孩子会具有父母的一些特征,即每个孩子都会继承父亲或者母亲的某些特征,当然这只是最基本的继承关系,现实世界中还存在着更复杂的继承。
  • 在OOP程序设计中,当我们定义一个新类的时候,新的类称为子类(Subclass),而被继承的类称为基类、父类或超类(Base class、Super class)。

    • 继承最大的好处是子类获得了父类的全部变量和方法的同时,又可以根据需要进行修改、拓展。其语法结构如下:

    • 1
      2
      class Foo(superA, superB,superC....):
      pass
    • Python支持多父类的继承机制。

继承示例代码:子类可以继承到父类中所有的成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Father():
address = 'Beijing' #类变量
#构造方法
def __init__(self,fistName,hobby):
#两个实例变量
self.firstName = fistName
self.hobby = hobby
def get_xxx(self):
print('我是Father的实例方法')
@classmethod
def classFunc(cls):
print('我是Father类的类方法')
@staticmethod
def staticFunc():
print('我是Father类的静态方法')
#Son继承了Father这个类
#Son子类,Father父类
class Son(Father):
pass

#1.子类可以继承到父类的构造方法
s = Son('zhang','smoke') #调用子类的构造方法,子类是可以继承到父类的构造方法
#2.子类可以继承到父类的类变量
print(Son.address)
#3.子类可以继承到父类的实例变量
print(s.firstName,s.hobby)
#4.子类可以继承到父类的实例方法
s.get_xxx()
#5.子类可以继承到父类的类方法
Son.classFunc()
#6.子类可以继承到父类的静态方法
Son.staticFunc()
派生
  • 子类添加自己独有的方法和或属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Father():
address = 'Beijing' #类变量
#构造方法
def __init__(self,fistName,hobby):
#两个实例变量
self.firstName = fistName
self.hobby = hobby
def get_xxx(self):
print('我是Father的实例方法')
@classmethod
def classFunc(cls):
print('我是Father类的类方法')
@staticmethod
def staticFunc():
print('我是Father类的静态方法')

class Son(Father):
#子类自己派生出来的独有的方法
def sing(self):
print('子类的实例方法sing')

s = Son('zhang','smoke')
s.sing()
方法的重写
  • 重写:子类继承到父类的方法,如果满足不了子类的需求,则子类可以重写从父类中继承到的方法。重写父类方法有两种方式:1完全重写,2部分重写

    • 1.完全重写:完全重新将父类的方法进行的全新的定义/实现(毫无保留父类方法原始的功能)

      • 1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        class Father():
        def __init__(self,firstName):
        self.firstName = firstName

        def hobby(self):
        print('我喜欢读书,运动和跳舞!')

        class Son(Father):
        #完全重写
        def hobby(self):
        print('我喜欢吃鸡,王者!')

        s = Son('zhang')
        s.hobby()
    • 2.部分重写:在父类方法功能实现的基础上新增了其他操作/功能

      • 1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        class Father():
        def __init__(self,firstName):
        self.firstName = firstName

        def hobby(self):
        print('我喜欢读书,运动和跳舞!')

        class Son(Father1):
        #部分重写
        def hobby(self):
        #调用一下父类的hobby方法
        super().hobby() #调用父类的方法
        print('我喜欢吃鸡,王者!')

        s = Son('zhang')
        s.hobby()
    • super()只可以作用在类的内部,然后表示的是父类对象的引用。

  • super函数

    • 如果你想强制调用父类的成员该如何实现呢?使用super()函数!这是一个非常重要的函数,最常见的就是通过super调用父类的实例化方法__init__

      • 语法:super(子类名, self).方法名(),需要传入的是子类名和self,调用的是父类里的方法,按父类的方法需要传入参数。

      • 1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        class Father():
        def __init__(self,firstName):
        self.firstName = firstName

        def hobby(self):
        print('我喜欢读书,运动和跳舞!')

        class Son(Father):
        #子类需要有属于自己的实例变量
        def __init__(self,firstName,classroom,score):
        super().__init__(firstName)
        #子类自己派生出的独有的实例变量
        self.classroom = classroom
        self.score = score
        #部分重写
        def hobby(self):
        #调用一下父类的hobby方法
        super().hobby() #调用父类的方法
        print('我喜欢吃鸡,王者!')

        s = Son('zhang',102,100)
        s.hobby()
  • 继承的作用:

    • 实现了程序的高复用,大大缩短程序的开发周期!
  • 在多继承中,继承关系的优先级

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      class Father1():
      def hobby(self):
      print('我是Father1,我喜欢唱歌!')
      class Father2():
      def hobby(self):
      print('我是Father2,我喜欢跳舞!')

      #注意:越靠前的父类越优先被继承
      class Son(Father1,Father2):
      #方法的重写
      def hobby(self):
      #思考:现在Son有两个父类,super()表示的是哪一个父类对象?
      #输出结果显示super表示的是Father1对象
      super().hobby()
      print('我是Son,我喜欢吃鸡!')
      s = Son()
      s.hobby()

type和isinstance

  • isinstance() 函数来判断一个对象是否是一个已知的类型,类似 type()。

  • isinstance() 方法的语法:

    • 1
      2
      3
      4
      5
      6
      isinstance(object, classinfo)
      参数:
      object -- 实例对象。
      classinfo -- 可以是类名、基本类型或者有它们组成的元组。
      返回值:
      如果对象的类型与参数二的类型(classinfo)相同则返回 True,否则返回 False。
    • 1
      2
      3
      4
      5
      6
      7
      8
      class Father():
      pass
      class Son(Father):
      pass
      s = Son() #子类对象
      print(isinstance(s,Son)) #True
      #s这个对象是属于Son这个数据类型的。
      #python的数据类型:内置类型(8中),自定义类型(类)
    • 1
      2
      3
      4
      5
      6
      7
      class Father():
      pass
      class Son(Father):
      pass
      s = Son() #子类对象
      print(isinstance(s,Father)) #True
      #注意:子类对象可以被视为是一种父类类型
    • 1
      2
      3
      4
      5
      6
      class Father():
      pass
      class Son(Father):
      pass
      s = Son() #子类对象
      print(type(s)) #<class '__main__.Son'>
  • type:返回对象的类型

  • isinstance() 与 type() 区别:

    • type() 不会认为子类是一种父类类型,不考虑继承关系。

    • isinstance() 会认为子类是一种父类类型,考虑继承关系。

继承注意事项
  • 实例方法中的self参数是一个相对的值?

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      class Father():
      def get_xxx(self):
      print(self)
      class Son(Father):
      pass
      f = Father()
      f.get_xxx()
      #输出:<__main__.Father object at 0x103536580>
      #说明self表示的是父类类型的对象
    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      class Father():
      def get_xxx(self):
      print(self)
      class Son(Father):
      pass
      s = Son()
      #是子类对象从父类中继承过来,self原本是写在父类的实例方法中的
      s.get_xxx()
      #输出结果:<__main__.Son object at 0x100e2a880>
      • self所处的位置不同,表示的对象类型也是不同的!
  • 类方法中的cls参数是一个相对的值?

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      class Father():
      #类变量
      class_var = 'father var'

      @classmethod
      def getVar(cls):
      print(Father.class_var)
      class Son(Father):
      #类变量:和父类的类变量同名,子类同名的类变量就会覆盖从父类中继承过来的类变量
      class_var = 'son var'

      Son.getVar() #输出:father var
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      class Father():
      #类变量
      class_var = 'father var'

      @classmethod
      def getVar(cls):
      print(cls.class_var)
      class Son(Father):
      #类变量:和父类的类变量同名,子类同名的类变量就会覆盖从父类中继承过来的类变量
      class_var = 'son var'

      Son.getVar() #输出:son var
      • 注意:cls存在哪个类中,就表示的是当前的类。

类方法的作用

思考:实例方法中的self参数的值是从哪来的?

1
2
3
4
5
6
7
8
9
Python 中的__new__和__init__的区别:
【同】
  二者均是Python面向对象语言中的函数,__new__比较少用,__init__则用的比较多。

【异】
__new__是在实例创建之前被调用的,因为它的任务就是创建实例然后返回该实例对象。
__init__是在对象创建好之后被调用的,init给对象的实例变量赋值,说明得事先存在实例变量,实例变量属于对象,实例变量存在则对象肯定也已经存在了。所以说对象存在了,则实例变量存在,实例变量存在则才可使用/调用init给实例变量赋值。

也就是: __new__先被调用,__init__后被调用,__new__的返回值(实例)将传递给__init__方法的第一个参数,然后__init__给这个实例设置一些参数。
类方法
  • 如下场景:

    假设我有一个学生类和一个班级类,想要实现的功能为:

    学生类继承自班级类,每实例化一个学生对象,班级人数都能增加;
    最后,我想定义一些学生,获得班级中的总人数。
    
    • 要求:
      • 必须通过班级类获取所有的学生对象的个数
      • 不能创建班级对象,只能创建学生对象
    • 思路:
      • 需要在班级类中定义一个变量,让该变量记录学生的人数(每创建好一个学生对象则让班级类中的该变量进行加1操作),那么该变量如何创建(类变量or实例变量)?
        • 应该创建类变量:上述要求明确指明了,不可以创建班级对象,因此无法访问实例变量,所以应该创建一个类变量。
      • 如果班级类中有一个类变量,该类变量记录学生的个数,需要给该类变量提供一个访问的接口,该接口应该使用类方法getNum来实现。类方法可以直接被班级类名访问。
      • 当每创建好一个学生对象,如何同步使得班级类中的类变量num进行加一操作呢?
        • 在班级类中提供一个类方法addNum,该类方法可以给num类变量进行加一操作。该方法必须保证,当创建好一个学生对象的时候被调用一次。
        • 如何保证每创建好一个学生对象让addNum这个类方法调用一次呢?
          • 需要在班级类中重写new方法(该new方法是可以继承给学生这个子类),该new方法可以继承给Student子类,因此每创建一个子类对象,就会调用一次new方法,则在new方法中就可以执行给num类变量进行加一操作。
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    class ClassRoom():
    num = 0 #记录学生对象的个数

    #类方法是用来显示计数器num的数值
    @classmethod
    def getNum(cls):
    print(cls.num)
    #是用来给num进行加一操作,该方法必须保证,当创建好一个学生对象的时候被调用一次
    @classmethod
    def addNum(cls):
    cls.num += 1
    #new最终是可以被继承给Student这个子类
    def __new__(cls, *args, **kwargs):
    ClassRoom.addNum()
    return super().__new__(cls)

    class Student(ClassRoom):
    def __init__(self,name):
    self.name = name

    s1 = Student('zhangsan')
    s2 = Student('lisi')
    s3 = Student('wangwu')
    s4 = Student('bobo')

    ClassRoom.getNum()

成员保护和访问限制

在类的内部,有各种变量和方法。这些数据成员,可以在类的外部通过实例或者类名进行调用,例如:

1
2
3
4
5
6
7
8
class Student():
def __init__(self,name):
self.name = name
def study(self):
print('正在学习!')

s = Student('lisi')
s.study()

上面的调用方式是我们大多数情况下都需要的,但是往往我们也不希望所有的变量和方法能被外部访问,需要针对性地保护某些成员,限制对这些成员的访问。这样的程序才是健壮、可靠的,也符合业务的逻辑。

在Python中,如果要让内部成员不被外部访问,可以在成员的名字前加上两个下划线__,这个成员就变成了一个私有成员(private)。私有成员只能在类的内部访问,外部无法访问。

1
2
3
4
5
6
7
8
9
10
11
class Student():
def __init__(self,name):
#name属性就变成了私有属性
self.__name = name
def study(self):
#可以访问私有成员属性
print('正在学习!学习的人员是:',self.__name)

s = Student('lisi')
s.study()
print(s.__name) #无法访问私有成员
  • 思考:私有成员是否可以被子类继承?

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Person():
    def __init__(self,p):
    #私有成员
    self.__p = p
    class Student(Person):
    def get_p(self):
    print(self.__p)

    s = Student(1)
    print(s.get_p()) #无法访问私有成员
    #程序执行会报错!
    • 注意:私有成员是无法被继承的。

那么,以双下划线开头的数据成员是不是一定就无法从外部访问呢?其实也不是!本质上,从内部机制原理讲,外部不能直接访问__age是因为Python解释器对外把__age变量改成了_People__age,也就是_类名__age(类名前是一个下划线)。因此,投机取巧的话,你可以通过_ People__age在类的外部访问__age变量:

1
2
3
4
5
6
7
class Person():
def __init__(self,p):
#私有成员
self.__p = p

p = Person(1)
print(p._Person__p) #无法访问私有成员

也就是说:Python的私有成员和访问限制机制是“假”的,没有从语法层面彻底限制对私有成员的访问。这一点和常量的尴尬地位很相似。

reflect反射

首先,我们要区分两个概念——“标识名”和“字符串”。两者字面上看起来一样,却是两种东西,比如下面的func函数和字符串“func”:

1
2
3
def func():
pass
s = "func()" #字符串,不是函数调用

前者是函数func的函数名,后者只是一个叫“func”的字符串,两者是不同的事物。我们可以用func()的方式调用函数func,但我们不能用”func”()的方式调用函数。说白了就是,不能通过字符串来调用名字看起来相同的函数!

实例分析

考虑有这么一个场景:需要根据用户输入url的不同,调用不同的函数,实现不同的操作,也就是一个WEB框架的url路由功能。

首先,有一个commons.py文件,它里面有几个函数,分别用于展示不同的页面。

1
2
3
4
5
6
7
8
9
# commons.py
def login():
print('这是登录页面!')

def logout():
print('这是退出页面')

def home():
print('这是主页面')

其次,有一个visit.py文件,作为程序入口,接收用户输入,并根据输入展示相应的页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# visit.py
import commons
def run():
inp = input('请输入您要访问的网址:')
if inp == 'login':
commons.login()
elif inp == 'logout':
commons.logout()
elif inp == 'home':
commons.home()
else:
print('404')
if __name__ == '__main__':
run()

运行visit.py,输入home,页面结果如下:

1
2
请输入您想访问页面的url:  home
这是网站主页面!

这就实现了一个简单的url路由功能,根据不同的url,执行不同的函数,获得不同的页面。

然而,让我们思考一个问题,如果commons文件里有成百上千个函数呢(这很常见)?难道在visit模块里写上成百上千个elif?显然这是不可能的!那么怎么办?

仔细观察visit.py中的代码,会发现用户输入的url字符串和相应调用的函数名好像!

如果能用这个字符串直接调用函数就好了!但是,前面已经说了字符串是不能用来调用函数的。为了解决这个问题,Python提供了反射机制,帮助我们实现这一想法!

现在将前面的visit.py修改一下,代码如下:

1
2
3
4
5
6
7
8
9
# visit.py
import commons
def run():
inp = input('请输入您要访问的网址:')
#inp == "login"
func = getattr(commons,inp) #func == login
func() #login()
if __name__ == '__main__':
run()

func = getattr(commons,inp)语句是关键,通过getattr()函数,从commons模块里,查找到和inp字符串“外形”相同的函数名,并将其返回,然后赋值给func变量。变量func此时就指向那个函数,func()就可以调用该函数。

getattr()函数的使用方法:接收2个参数,前面的是一个类或者模块,后面的是一个字符串,注意了!是个字符串!

这个过程就相当于把一个字符串变成一个函数名的过程。这是一个动态访问的过程,一切都不写死,全部根据用户输入来变化。

瑕疵:前面的代码还有个小瑕疵,那就是如果用户输入一个非法的url,比如jpg,由于在commons里没有同名的函数,肯定会产生运行错误

那怎么办呢?python提供了一个hasattr()的内置函数,用法和getattr()基本类似,它可以判断commons中是否具有某个成员,返回True或False。现在将代码修改一下:

1
2
3
4
5
6
7
8
9
10
11
# visit.py
import commons
def run():
inp = input('请输入您要访问的网址:')
if hasattr(commons,inp):
func = getattr(commons,inp)
func()
else:
print('404')
if __name__ == '__main__':
run()

这下就没有问题了!通过hasattr()的判断,可以防止非法输入导致的错误,并将其统一定位到错误页面。

单例模式

  • 单例模式是一种常用的软件设计模式。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于被外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。

  • 单例模式的要点有三个;一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。

应用场景

比如,某个服务器的配置信息存在在一个文件中,客户端通过AppConfig类来读取配置文件的信息.如果程序的运行的过程中,很多地方都会用到配置文件信息,则就需要创建很多的AppConfig实例,这样就导致内存中有很多AppConfig对象的实例,造成资源的浪费.其实这个时候AppConfig我们希望它只有一份,就可以使用单例模式.

1
2
3
4
5
6
7
8
9
class Singleton():
def __new__(cls, *args, **kwargs):
if not hasattr(cls,'instance'):
Singleton.instance = super().__new__(cls)
return Singleton.instance

s1 = Singleton()
s2 = Singleton()
print(id(s1),id(s2))

链表

  • 链表就是用来修正/优化列表存在的某一个弊端的。

  • 列表存在的弊端:

    • 列表开启的内存空间是连续的,导致如果向列表中增加或者删除元素,需要将增加或者删除元素后面的所有元素批量移动,该行为会严重影响列表元素添加或者删除的性能。
    • 列表是不适合进行高频的元素添加和删除操作的。
  • 分析:

    • 链表如何进行列表弊端的优化?
      • 优化核心在于,元素的内存空间不可以连续开辟,因此链表的内存空间不是连续开辟,则意味着链表就丧失了索引的机制。
    • 链表的作用:
      • 如果需求需要进行高频的元素增加和删除,则使用链表最合适。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    class Node():
    def __init__(self,item):
    self.item = item
    self.next = None

    class Link():
    def __init__(self):
    #head指向第一个节点
    self.head = None
    def addHead(self,item): #向链表头部插入新的节点
    node = Node(item)
    node.next = self.head
    self.head = node
    def travel(self): #遍历链表中节点的item
    # print(self.head.item)
    # print(self.head.next.item)
    # print(self.head.next.next.item)
    # print(self.head.next.next.next.item)
    # print(self.head.next.next.next.next.item)
    cur = self.head
    while cur != None:
    print(cur.item)
    cur = cur.next

    def isEmpty(self):
    #判定链表是否为空,空返回True,否则返回False
    return self.head == None

    def length(self):
    #返回链表中节点的个数
    count = 0 #记录节点的个数
    cur = self.head
    while cur != None:
    count += 1
    cur = cur.next
    return count
    #向链表尾部添加新的节点
    def append(self,item):
    node = Node(item)
    if self.isEmpty():#如果链表为空
    self.head = node
    else:#链表为非空的情况
    pre = None #pre要指向cur前面的一个节点
    cur = self.head
    while cur != None:
    pre = cur
    cur = cur.next
    #循环结束后cur指向了None而pre指向了最后一个节点
    pre.next = node

    #将新的节点插入到pos表示的位置中
    def insertNode(self,item,pos):
    node = Node(item)
    pre = None
    cur = self.head
    if pos == 0:
    self.addHead(item)
    return
    for i in range(pos):
    pre = cur
    cur = cur.next
    pre.next = node
    node.next = cur

    #从链表中删除item表示的节点
    def removeNode(self,item):
    pre = None
    cur = self.head
    if self.head.item == item:#删除的是第一个节点
    self.head = cur.next
    return
    while cur != None:
    if cur.item == item:
    #条件成立则cur就是我们要删除的节点
    pre.next = cur.next
    break
    else:
    pre = cur
    cur = cur.next



    link = Link()
    link.addHead(1)
    link.addHead(2)
    link.addHead(3)
    link.addHead(4)
    link.addHead(5)
    link.append(6)
    link.removeNode(5)
    link.travel()