不同的类可以混合使用,加入到其他类中,来增强类的功能和代码重用性。也就是一个类的属性可以是其他类的实例。当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好。
来看一个例子:
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())
__bases__
属性可以类的所有父类看例子,学继承
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
判断。
虽然可以,但是尽量不用。
在面向对象体系里面,存在两种关系:
__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类本身没有父类。
还是用上面的例子,学多态的应用。在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()
的方法即可。