测试开发之Python核心笔记(22):组合、继承与多态

22.1 多用组合

不同的类可以混合使用,加入到其他类中,来增强类的功能和代码重用性。也就是一个类的属性可以是其他类的实例。当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好。

来看一个例子:

class Name:
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    def __str__(self):
        return "{}.{}".format(self.first_name, self.last_name)


class Address:
    def __init__(self, province, city):
        self.province = province
        self.city = city

    def __str__(self):
        return "{}-{}".format(self.province, self.city)


class Person:
    def __init__(self, name, address):
        self.name = name
        self.address = address

    def __str__(self):
        return "姓名:{},地址:{}".format(self.name, self.address)


if __name__ == '__main__':
    p = Person(Name("Chunming", "liu"), Address("beijing", "beijing"))
    print(p)

我们知道圆环是由两个圆组成的。圆环的面积是外圆的面积减去内圆的面积,圆环的周长是外圆周长加上内圆的周长。用类的组成思路,定义圆环,在圆环类中组合圆形的实例作为自己的属性:

from math import pi


class Circle:
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return pi * self.radius * self.radius

    def perimeter(self):
        return 2 * pi * self.radius


class Ring:
    def __init__(self, inner_radius, outer_radius):
        self.inner_radius = Circle(inner_radius)  # 组合圆形类作为自己的属性
        self.outer_radius = Circle(outer_radius)

    def area(self):
        return self.outer_radius.area() - self.inner_radius.area()   # 重用圆形类的方法

    def perimeter(self):
        return self.inner_radius.perimeter() + self.outer_radius.perimeter()


if __name__ == '__main__':
    r = Ring(10, 20)
    print(r.area())
    print(r.perimeter())

22.2 少用继承

  • 继承,指的是子类既拥有父类的特征(类的属性和函数)。
  • 子类也拥有不同于父类的独特特征(自定义的属性和函数)。
  • Python支持多继承,但是尽量别这么做。
  • 子类可以覆盖父类的同名方法
  • 所有的类默认情况下都是继承自object类
  • 用issubclass() 检查类继承关系
  • 通过__bases__属性可以类的所有父类

22.2.1 单继承

看例子,学继承

class Entity(object):  # 父类
    def __init__(self, object_type, title):
        print('parent class init called')
        self.object_type = object_type
        self.title = title

    def get_context_length(self):
        return None

    def __repr__(self):
        return self.title + "." + self.object_type


class Document(Entity):  # 1.括号中写父类,可以写多个,表示继承多个类
    def __init__(self, object_type, title, author, context):
        print('Document class init called')
        super(Document, self).__init__(object_type, title)  # 2.自定义了__init__,则必须显示调用父类构造方法
        self.author = author  # 3.新增自己的属性
        self.__context = context

    def get_context_length(self):  # 4.覆盖父类的方法
        return len(self.__context)


class Video(Entity):
    pass  # 没有复写__init__方法,则会自动调用父类的__init__方法完成初始化。


class Music(Entity):
    def __init__(self, object_type, title, singer, category):
        print('Document class init called')
        super().__init__(object_type, title)  
        self.singer = singer 
        self.category = category

    def get_context_length(self): 
        return "此音乐时长是4分18秒"


if __name__ == '__main__':
    doc = Document("docx", "Python教程", "chunming", "写给测试人员的Python教程")
    print(doc)
    print(doc.get_context_length())
    video = Video('mp4', "Python视频教程")  
    print(video)
    print(issubclass(Video, Entity))
    print(video.get_context_length())

Entity是父类,Document和Video是继承自Entity的子类,他们都继承了父类的object_type和title属性,以及get_context_length方法。Document还有自己的独特属性author和__context。Document类的get_context_length方法覆盖了父类的方法。

当子类调用父类的方法时,可以通过super(子类,self)方式。看看官网中super的介绍:

class super(object)
| super() -> same as super(class, )
| super(type) -> unbound super object
| super(type, obj) -> bound super object; requires isinstance(obj, type)
| super(type, type2) -> bound super object; requires issubclass(type2, type)
| Typical use to call a cooperative superclass method:
| class C(B):
| def meth(self, arg):
| super().meth(arg)
| This works for class methods too:
| class C(B):
| @classmethod
| def cmeth(cls, arg):
| super().cmeth(arg)

从定义中可以看到,通常用来调用对应的父类的方法。super() 与 super(子类,self)的方式是等价的,他们都会返回一个父类对象。

判断一个类是不是另外一个类的子类,可以通过issubclass判断。

22.2.2 多继承

虽然可以,但是尽量不用。

22.2.3 object和type的关系

在面向对象体系里面,存在两种关系:

  • 继承关系,可以通过__bases__属性可以类的所有父类(因为Python支持多继承)。
  • 类与实例关系,可以通过__class__属性查看实例的类型,或者使用type()函数查看。

在Python的世界中,所有类都是object的子类;所有对象都是type的实例。


class Parent:
    pass


class Child(Parent):
    pass


if __name__ == '__main__':
    p = Parent()
    c = Child()
    print(Parent.__bases__)  # 获取Parent类的父类,输出为(,)
    print(Child.__bases__)  # 获取Child类的父类,输出(,)
    print(c.__class__)  # 获取c的类型,输出
    print(type(c))  # 等同于c.__class__

    print(object.__class__)  # object类是type类型,输出
    print(object.__bases__)  # object类 无父类,输出为()

    print(type.__class__)  # type类是type类型,输出
    print(type.__bases__)  # type类 的父类是object类,输出为(,)

    print(list.__class__)  # list类是type类型,输出
    print(list.__bases__)  # list类的父类是object类,输出(,)

    print(tuple.__class__)  # tuple类是type类型,输出
    print(tuple.__bases__)  # tuple类的父类是object类,输出(,)

    print(dict.__class__)  # dict类是type类型,输出
    print(dict.__bases__)  # dict类的父类是object类,输出(,)

可见object类、type类、list类、tuple类、dict类都是type类型,也就是说是type类的实例。type类、list类、tuple类、dict类的父类都是object类,object类本身没有父类。

22.3 多态很方便

还是用上面的例子,学多态的应用。在main里面,实例化三个类,并且定义了一个统一的接口来访问实例的方法。

if __name__ == '__main__':
    doc = Document("docx", "Python教程", "chunming", "写个测试人员的Python教程")
    music = Music('mp4', "风吹麦浪", "李键", "抒情")
    video = Video('mp4', "Python视频教程")

    # print(doc.get_context_length())
    # print(music.get_context_length())

    def get_length(entity):  # 统一接口,根据传进来的实例类型自动找到它的方法执行
        print(entity.get_context_length())   # 访问实例的方法。

    get_length(doc)
    get_length(music)
    get_length(video)

这就是多态,指在不考虑实例类型的情况下使用实例的方法。通过这个例子,能够直观感受到多态的优点:

  • 增加了程序的灵活性

以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如get_length(picture)

  • 增加了程序额可扩展性

通过继承Entity类创建了一个新的类Picture,使用者无需更改自己的代码,还是用get_length(picture)去调用

class Picture:
    def __init__(self, photographer, pixel):
        self.photographer = photographer
        self.pixel = pixel

    def get_context_length(self):  # 声明同样的方法
        return "像素是 {}".format(self.pixel)

这种行为称为多态。也就是说,get_length()方法调用将作用在 参数entity的实际类型上。entity是Document类型,它实际上拥有自己的 get_context_length()方法以及从 Entity类继承的 get_context_length()方法,但调用 entity.get_context_length()总是先查找它自身的定义,如果没有定义,则顺着继承链向上查找,直到在某个父类中找到为止,例如上面例子中的video。

由于Python是动态语言,所以,传递给函数get_length()的参数 entity 不一定是 Entity 或 Entity 的子类型。任何数据类型的实例都可以,只要它有一个get_context_length()的方法即可。

你可能感兴趣的:(Python,python)