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


    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语句,目的是说明当我们定义了一个实例属性和类属性名发生冲突是,类属性会被覆盖掉,输出的就是实例属性的值。所以要避免类属性名和实例属性名的冲突问题。
  • 相关阅读:
    3372 选学霸
    3556 科技庄园
    1025 选菜
    UVA 437 The Tower of Babylon巴比伦塔
    3641 上帝选人
    tyvj P1175 机器人
    1692 子集和的目标值
    1689 建造高塔
    NOI2002 贪吃的九头龙
    NYOJ110 剑客决斗
  • 原文地址:https://www.cnblogs.com/vanishzeng/p/12246305.html
Copyright © 2020-2023  润新知