Python学习笔记(九)之面向对象编程(下)

僤鯓⒐⒋嵵緔 提交于 2020-01-31 19:32:55

0. 附上Python面向对象学习笔记上的链接:

Python学习笔记(八)之面向对象编程(上)

1. 引言

  • 上一节类比java的面向对象学习了Python中的面向对象的基础知识,以及在最后的总结中比较了一些Python和java中的面向对象的不同。
  • 在java的学习中我了解到,数据封装、继承和多态是面向对象的三大特点,所以在这一章节中,我将继续学习Python的继承和多态的语法和技巧,更加深入的了解Python的面向对象,依旧是类比java学习。
  • 两天没有学习Python面向对象的知识了,所以现在稍微回顾一下上面学到的内容。
    • 首先是定义一个类,可以通过class 类名(继承自)的方式定义一个新类;
    • 接着自然是写一个构造函数了,所以__init__函数登场了,在类的的函数中,默认会传入参数self,因为Python的构造函数只能有一个,所以参数的传入要比较谨慎的书写,可以通过关键字参数传入参数的方式来实现。
    • 再者就是数据封装这个面向对象的三大特点之一,即将对对象的属性的操作放在类内部的函数中,即类的方法中,在类的各个方法中实现对属性的操作。
    • 接着是访问限制问题,这就涉及到和java中的private型的属性一个道理了,当然在Python中实现更加简洁,只要通过属性的名称即可判断是否为private型,即在属性名的开头加上__双下划线即可,且结尾不能跟着下划线。
    • 最后是说道Python中的一些面向对象的注意事项,当我们设置了属性为private型时,Python本身还可以通过其他的方式访问到类中的private型的属性,这就涉及到了通过_类名__属性名(实例名前为一个下划线,属性名前为两个下划线)的方式来调用;且在修改属性值时,我们可能错误的用实例名.__属性名= 新的值的方式来修改实例中的属性值,但这本身并没有真正的修改成功,反而是新建了一个变量且为它赋值了。所以上面的两种方法都是不可靠的,都是禁止使用的
    • 补充一条,就是Python中的setter和getter方法,其实本质上实现是和java中一样对,通过该方法可以访问和修改实例中的private型的属性值。
  • 那么回顾完上次学习到的内容后,就下来就进入正题,接着学习面向对象的其他两个特点,继承和多态。

2. 继承和多态

2.1 继承

2.1.1 继承的含义

  • 在OOP(Object Oriented Programming, 即面向对象编程)程序设计中,当我们定义一个class时,我们可以选择该class所要继承的类,在之前写的类中,我们继承的对象都是object,这是所有类最后都要继承的对象,而这个新的class我们就称为object的子类(subclass),而被继承的object这个class则称为基类父类超类(Base class、Super class)。

2.1.2 继承实例

  • 来一个简单的实例来说明一下继承的优点:

    • 例2.1.2.1:

      # !user/bin/python
      # coding=utf-8
      
      
      __author__ = "zjw"
      
      
      class Shape(object):
      
          def describe(self):
              print "我是一个形状"
      
      
      class Rectangle(Shape):
          pass
      
      
      class Circle(Shape):
          pass
      
      
      if __name__ == '__main__':
          shape = Shape()
          rectangle = Rectangle()
          circle = Circle()
          # 分别调用三个实例的describe()
          shape.describe()
          rectangle.describe()
          circle.describe()
    • 输出:

      我是一个形状
      我是一个形状
      我是一个形状

    • 分析:

      • 可以看到RectangleCircle这两个类继承自Shape类,我们在父类Shape中实现了describe方法,但是两个子类中并没有真正的实现该方法。可以在main函数中看到,当我们实例了三个对象后,两个子类对象调用describe方法时,实际上是直接调用了父类的describe方法。
      • 这里就体现了继承的好处?最大的好处就是子类获得了父类的全部功能。而在上面例子中的体现就是,子类RectangleCircle虽然没有写describe方法,但是他们继承了Shape,那么在实例化后,他们也可以直接调用父类的describe方法。
  • 类比总结:

    • 可以看到在Python中的继承实际上和java中的继承是相似的,只不过Python中的类可以什么东西都不写,只要通过pass语句即可实现。
    • 在继承语法上有些不同,在java中我们是通过extends关键字实现继承的;而在Python中,只要在类名后面的括号中写入要继承的类即可。
    • 在java的学习中,多态其实是继承的另一个优点,那么接下来就来详细说一说多态。

2.2 多态

2.2.1 多态的含义

  • 首先我们对上面的例子进行小小的修改,为两个子类加上describe方法:

    • 例2.2.1.1:(这里只贴出修改部分)

      class Rectangle(Shape):
      
          def describe(self):
              print "我是一个长方形"
      
      
      class Circle(Shape):
      
          def describe(self):
              print "我是一个圆圈"
    • 输出:

      我是一个形状
      我是一个长方形
      我是一个圆圈

    • 分析:可以看到当我们为子类添加上describe方法后,子类的describe方法就将父类的覆盖掉了,后面当我们实例化子类时,调用到的就是子类的describe。这就说明了继承的另一个好处,多态

2.2.2 多态实例

  • 先来一个实例看看:

    • 例2.2.2.1:

      # !user/bin/python
      # coding=utf-8
      
      
      __author__ = "zjw"
      
      
      class Shape(object):
      
          def describe(self):
              print "我是一个形状"
      
          def area(self):
              pass
      
          def perimeter(self):
              pass
      
      
      class Rectangle(Shape):
      
          def __init__(self, length, width):
              self.__length = length
              self.__width = width
      
          def describe(self):
              print "我是一个长方形"
      
          def area(self):
              return self.__length * self.__width
      
          def perimeter(self):
              return 2 * (self.__length + self.__width)
      
      
      if __name__ == '__main__':
          shape = Shape()
          shape.describe()
          print shape.area()
          print shape.perimeter()
      
          rectangle = Rectangle(2, 2)
          rectangle.describe()
          print rectangle.area()
          print rectangle.perimeter()
    • 输出:

      我是一个形状
      None
      None
      我是一个长方形
      4
      8

    • 分析:

      • 在这个实例中,我们可以看到ShapeRectangle的父类,在父类中的area方法和perimeter方法并没有内容,而在Rectangle方法中我们则写了相应的内容,来输出该形状的面积和周长。在main函数的实践中,可以看到子类的方法已经覆盖了父类的方法,且调用时可以实现不同的功能。
      • 可以想象一下,我们学过计算很多中形状的面积和周长,这是当我们想要使用写某种新的形状类时,我们大可以直接继承自Shape父类,并重写一下area和perimeter方法,即可轻松实现方法的覆盖。
  • 再来一个直观实例展示多态的好处:

    • 例2.2.2.2:

      # !user/bin/python
      # coding=utf-8
      
      
      __author__ = "zjw"
      
      
      class Shape(object):
      
          def describe(self):
              print "我是一个形状"
      
          def twice_describe(self):
              # isinstance来判断类型
              print "是否是一个Shape?", isinstance(self, Shape)
              print "是否是一个Rectangle?", isinstance(self, Rectangle)
              print "是否是一个Circle?", isinstance(self, Circle)
              self.describe()
              self.describe()
              print
      
      
      class Rectangle(Shape):
      
          def describe(self):
              print "我是一个长方形"
      
      
      class Circle(Shape):
      
          def describe(self):
              print "我是一个圆圈"
      
      
      if __name__ == '__main__':
          shape = Shape()
          rectangle = Rectangle()
          circle = Circle()
          # 分别调用三个实例的describe()
          shape.twice_describe()
          rectangle.twice_describe()
          circle.twice_describe()
      
    • 输出:

      是否是一个Shape? True
      是否是一个Rectangle? False
      是否是一个Circle? False
      我是一个形状
      我是一个形状

      是否是一个Shape? True
      是否是一个Rectangle? True
      是否是一个Circle? False
      我是一个长方形
      我是一个长方形

      是否是一个Shape? True
      是否是一个Rectangle? False
      是否是一个Circle? True
      我是一个圆圈
      我是一个圆圈

    • 分析:

      • 这里请注意看父类中的twice_describe方法,在这个方法里,我们首先通过isinstance函数来判断self的类型,接着我们调用两次self.describe()来输出此时的形状,最后一个print起到再换行的作用。

      • 我们可以看到父类的实例shape只是一个Shape,而子类的rectangle实例不仅是一个Rectangle还是一个Shape,子类的circle实例同理。这让我想起java中继承我们要遵循子类 is a 父类 原则。

      • 接着说这样写的好处:通过该多态形式的实现,我们将每个子类不同的方法写在子类自己的内部,而将统一的方法写在父类中,父类中这个统一的方法可以调用子类中不同的方法,那么我们就可以实现代码的精简。我们不必在子类中再一一写统一的那个方法,直接调用父类的方法即可完美的实现。为什么这么说呢,当我们再想来一个椭圆类,我们想在椭圆类的describe方法中写下一句“你好,我是椭圆形”。这时我们调用父类的twice_describe方法依旧是可行的。就不会说同一个方法,我们在每个子类中都重写一遍,那么有成百上千个子类,那怎么办?那么这就体现了多态的好处啦。

      • 通过该例子,我们来说一下多态的真正的威力:调用方只管调用,不管细节,而当我们新增一种Shape子类时,我们只要确保describe()方法编写正确即可,不用管原来的代码是如何调用的。这就是著名的开闭原则

        1. 对扩展开放:允许新增Shape子类;

        2. 对修改封闭:不需要修改依赖Shape类型的twice_describe()等函数。

3. 实例属性和类属性

  • 刚开始看到这两个属性时,有点一头雾水,因为我在java的学习中,从来就只有属性的概念,并没有将属性在细分成实例属性和类属性。但是学完之后便焕然大悟。
  • 实例属性:顾名思义,就是实例化后的属性值,所以给实例属性赋值的方法可以通过实例变量,或则是self变量实现的。我们前面接触到的属性应都属于实例属性。
  • 类属性:则是类中一开始就存在的属性值,有点像java中的静态属性值,它是类内部的变量,所以一旦改变就会影响到整体。
  • 实例属性属于各个实例所以,互不干扰;
  • 类属性属于类所有,所有实例共享一个属性。

  • 来个实例说明一下:

    • 例3.1:

      # !user/bin/python
      # coding=utf-8
      
      
      __author__ = "zjw"
      
      
      class Test(object):
          count = 0
      
          def __init__(self):
              Test.count = Test.count + 1
      
      
      if __name__ == '__main__':
          test1 = Test()
          test2 = Test()
          test3 = Test()
          test4 = Test()
          test5 = Test()
          print test1.count
          print test2.count
          print test3.count
          print test4.count
          print test5.count
          print Test.count
      
          test1.count = 0
          print "此时的test1的count值时:", test1.count
      
    • 输出:

      5
      5
      5
      5
      5
      5

      此时的test1的count值时: 0

    • 分析:

      • 可以看到,此时类Test中的count就是一个类属性,我们实现了创建多少个Test实例就计数多少次的功能,并将加1功能放置在构造函数中。我们看到在构造函数中我们并不是用self.count来调用类属性count的,因为在上面我们说过,这样调用就是实例的属性了,而通过Test.count即可调用到此时的类属性count,并进行赋值。
      • 在main函数中,我们创建了5个Test实例,并以此输出每个实例的count值,可以发现此时的类属性count是大家所共有的,即是相同的。
      • 在看到我们在输出后面有加了一句test1.count = 0语句,目的是说明当我们定义了一个实例属性和类属性名发生冲突是,类属性会被覆盖掉,输出的就是实例属性的值。所以要避免类属性名和实例属性名的冲突问题。
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!