继承
面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过继承机制。继承完全可以理解成类之间的类型和子类型关系。即一个派生类(derived class)继承基类(bass class)字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。
例如,有这样一个设计,一个Cat类型的对象派生自Animal类,这是模拟”是一个(is-a)”关系(例如,Cat是一个Animal)。
继承基本语法:
class 派生类名(基类名1 [, 基类名2....]):
'Optional class documentation string'
class_suite
基类名写在括号里,基本类是在类定义的时候,在元组之中指明的。如果在继承元组中列了一个以上的基类,那么它就被称作”多重继承” 。
Python支持多重继承
继承
在继承中基类的构造(__init__()方法)不会被自动调用,它需要在其派生类的构造方法中专门调用,即子类不会继承基类的__init__()方法。
在调用基类的方法时,需要加上基类的类名前缀,且需要带上self参数变量。区别于在类中调用普通函数时并不需要带上self参数。
Python总是首先查找对应类型的方法,如果它不能在派生类中找到对应的方法,它才开始到基类中逐个查找。(先在本类中查找需要调用的方法,找不到才去基类中找)。
子类只继承父类所有公有的属性和方法,并且也可以在子类中通过父类名来调用,而对于私有的属性和方法,子类是不进行继承的,因此在子类中是无法通过父类名来访问的
继承的本质
对于面向对象的继承来说,其实就是将多个类共有的方法提取到父类中,子类 仅需继承父类而不必一一实现每个方法。
单继承
示例:
#encoding=utf-8
class Parent(object):#父类
parentAttr = 100
def __init__(self):
print("调用父类构造方法")
def parentMethod(self):
print("调用父类方法")
def setAttr(self,attr):
Parent.parentAttr = attr
def getAttr(self):
print("Parent attribute:",Parent.parentAttr)
class Child1(Parent):#子类1
def __init__(self):
print('调用child1的构造方法')
def childMethod(self):
print("调用child1的方法")
Parent.parentMethod(self)#子类里面,用父类调用父类方法,需要加self
class Child2(Parent):#子类2
#没有实现__init__方法,则会调用基类的__init__方法
def childMethod(self):
print("调用child2的方法")
self.parentMethod() #子类调用继承自父类的方法
if __name__ == "__main__":
c1 = Child1()#实例化子类1
c2 = Child2()#实例化子类2
c1.childMethod()#调用子类自己的方法
c2.childMethod()#调用子类自己的方法
c1.parentMethod()#子类调用继承的父类的方法
c1.setAttr(200)#子类调用父类的方法
c1.getAttr()#子类调用父类的方法
在子类中调用基类方法 基类.method(self)
如果子类没有定义构造方法,实例化子类对象时候会调用基类的构造方法
子类调用基类方法
1、 Parent.parentMethod(self)
2、 self.parentMethod()
第一种是直接用父类的类名.方法名去调用父类的方法,但是需要注意的是,这种调用方法必须将self作为参数传进去并且作为第一个参数,表示指向这个类的实例本身。这种方法多用于方法重写时。
第二种是直接用self去调用父类的方法,为什么可以这样调用呢?因为一旦子类继承了父类,那么子类就拥有父类所有的公有方法和属性,所以此时父类的方法和属性就相当于子类自己了,所以可以直接用self去直接调用实例的方法,而不用再传入self参数了
子类调用基类构造方法
1、super(subclassName, self).__init__( [parameter1[,parameter2....]])
2、superclassName.__init__(self, [parameter1[,parameter2....]])
注意:
两次参数列表中的self参数都是必不可少的,如果基类的构造方法中有除了self参数外别的参数,调用时,也必须传入同等数量、同等类型的参数。当子类不定义自己的构造方法时,默认会自动调用父类的构造方法。Python中super函数只能在构造方法中使用。
#encoding=utf-8
class Animal(object):
def __init__(self,type,color):
self.type = type
self.color = color
def print_info(self):
print(self.type,self.color)
class Cat(Animal):
def __init__(self,type,color,age):
#super(Cat,self).__init__(type,color)
Animal.__init__(self,type,color)
self.age = age
def print_info(self):
print(self.type,self.color,self.age)
cat = Cat("cat","black",5)
cat.print_info()
类间的关系判断
Issubclass(sub,parent)
判断子类
class Person(object):
pass
class student(Person):
pass
if issubclass(student,Person):
print("student 是 Person的子类")
else:
print("student 不是 Person的子类")
Isinstance(instance,class)
判断一个对象是否一个类的实例
class Person(object):
pass
class Student(Person):
pass
p = Person()
stu = Student()
if isinstance(p,Person):
print("p 是Person的实例")
if isinstance(stu,Person):
print("stu 是父类Person的实例")
if isinstance(stu,Student):
print("stu 是子类Student的实例")
多重继承
Python支持多重继承,也就是一个子类可以有多个父类,定义子类时,括号中写多个父类,并且父类间用逗号隔开。
在多重继承中,子类有那么多的父类,那子类实例化时,构造方法是怎样调用的呢?
这里需要记住几点:
➢多重继承中,子类在没有定义自己的构造方法时,以第一个父类为中心。(默认调用第一个父类的构造方法)
➢如果子类重新定义了自己的构造方法,就不会调用父类的构造方法。但如果仍想调用父类的构造方法,这个时候调用哪个父类的构造方法,由你自己确定,并且多重继承时,调用具体哪个父类的构造方法时只能使用如下方法:
superclassName.__init__(self, [parameter1[,parameter2....]] )
➢如果父类中有同名的方法时,通过子类的实例对象去调用的该方法也是第一个父类中的方法。同样你可以用我们上边说的“子类调用基类方法”中的的方法去调用具体哪个父类中的方法。Parent.parentMethod(self)
默认调用第一父类的构造方法
class Person(object):
def __init__(self,name):
self.name = name
print("Person 的构造方法")
class Animal(object):
def __init__(self,name):
self.name = name
print("Animal的构造方法")
class Student(Person,Animal):#没有定义__init__默认调用第一父类的构造方法
def get_name(self):
return self.name
stu = Student("zhangsan")
print(stu.get_name())
子类指定调用父类的构造方法
class Person(object):
def __init__(self,name):
self.name = name
print("Person 的构造方法")
class Animal(object):
def __init__(self,name):
self.name = name
print("Animal的构造方法")
class Student(Person,Animal):#没有定义__init__默认调用第一父类的构造方法
def __init__(self,name,age):
Animal.__init__(self,name)
self.age = age
def get_name(self):
return self.name
stu = Student("zhangsan",20)
print(stu.get_name())
多个父类有同名方法,调用第一个父类的方法
class Person(object):
def __init__(self,name):
self.name = name
print("Person 的构造方法")
def print_info(self):
print("Person的 print_info方法")
return self.name
class Animal(object):
def __init__(self,name):
self.name = name
print("Animal的构造方法")
def print_info(self):
print("Animal的 print_info方法")
return self.name
class Student(Person,Animal):
def get_name(self):
return self.name
stu = Student("zhangsan")
#print(stu.get_name())
print(stu.print_info())#默认调用第一个父类的方法
指定之类方法调用父类中的同名方法
class Person(object):
def __init__(self,name):
self.name = name
print("Person 的构造方法")
def print_info(self):
print("Person的 print_info方法")
return self.name
class Animal(object):
def __init__(self,name):
self.name = name
print("Animal的构造方法")
def print_info(self):
print("Animal的 print_info方法")
return self.name
class Student(Person,Animal):
def get_name(self):
return self.name
def print_info(self):
Animal.print_info(self) #子类方法显式调用父类的同名方法
stu = Student("zhangsan")
#print(stu.get_name())
print(stu.print_info())#
多重继承 方法的调用顺序
如果继承的多个类每个类中都定了相同的函数,那么那一个会被使用呢?
1、Python的类可以继承多个类,Java和C#中则只能继承一个类
2、Python的类如果继承了多个类,那么其寻找方法的方式有两种,分别是:深度优先和广度优先
➢当类是经典类时,多继承情况下,会按照深度优先方式查找(py2才有)
➢当类是新式类时,多继承情况下,会按照广度优先方式查找
经典类和新式类,从字面上可以看出一个老一个新,新的必然包含了跟多的功能,也是之后推荐的写法,从写法上区分的话,如果 当前类或者类继承了object类,那么该类便是新式类,否则便是经典类
class D:
def bar(self):
print("D.bar")
class C(D):
def bar(self):
print("C.bar")
class B(D):
def bar(self):
print("B.bar")
class A(B,C):
def bar(self):
print("A.bar")
a = A()
a.bar()
# 执行bar方法时
# 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续
去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错# 所以,查找顺序:A --> B --> C --> D# 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了。
经典类(深度优先):首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错
新式类(广度优先):首先去A类中查找,如果A类中没 有,则继续去B类中找,如果B类中么有,则继续去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错注意:在上述查找过程中,一旦找到,则寻找过程立即中断,便不会再继续找了
示例2:
class House(object):
def __init__(self,name):
print("Calling house constructor")
class Car(object):
def __init__(self):
print("Calling car constructor")
class CarHouse(House,Car):#没有构造方法
pass
class BusHouse(House,Car):
def __init__(self):
#super(BusHouse,self).__init__("sr")
Car.__init__(self)
c = CarHouse("ad")#CarHouse没有定义构造方法,默认调用第一类的构造方法
b = BusHouse()#BusHouse显式调用Car的构造方法
class House(object):
def __init__(self,name):
print("Calling house constructor")
class Car(object):
def __init__(self):
print("Calling car constructor")
class CarHouse(House,Car):
pass
class BusHouse(House,Car):
def __init__(self):
super(BusHouse,self).__init__("sr")#此种方法还是会调用第一父类的构造方法
#Car.__init__(self)
c = CarHouse("ad")
b = BusHouse()
class House(object):
def getHeight(self):
print("调用house类的方法")
class Car(object):
def getHeight(self):
print("调用Car类的方法")
class CarHouse(House,Car):
def __init__(self):
pass
def carHeight(self):
Car.getHeight(self)
c = CarHouse()
c.carHeight()
class House(object):
def getHeight(self):
print("调用house类的方法")
class Car(object):
def getHeight(self):
print("调用Car类的方法")
class CarHouse(House,Car):
def __init__(self):
pass
def carHeight(self):
#Car.getHeight(self)
self.getHeight() #默认调用第一父类的同名方法
c = CarHouse()
c.carHeight()
封装2
将对象的数据与操作数据的方法相结合,通过方法将对象的数据与实现细节保护起来,就称为封装。外界只能通过对象的方法访问对象,因此封装同时也实现了对象的数据隐藏。通过封装和数据隐藏机制,将一个对象相关的变量和方法封装为一个独立的软件体,单独进行实现和维护,并使对象能够在系统内方便地进行传递,另外也保证了对象数据的一致性并使程序易于维护,封装可以让调用者不用关心对象是如何构建的而直接进行使用。在Python中,所有的特性都是公开可用的,但是程序员应该在直接访问对象数据与操作数据时谨慎行事,因为他们可能无意中使得这些特性在某些方面不一致。
示例:
class Animal(object):
def __init__(self,name):
self.name = name
print(self.name)
def accessibleMethod(self):
print("I have a self! current name is:")
print(self.name)
print("the secret message is:")
self.__inaccessible()
def __inaccessible(self):
print("U cannot see me...")
@staticmethod
def staticmethod():
print("this is a static method")
def setName(self,name):
print("setName:")
self.name = name
def getName(self):
print("getName:")
return self.name
a = Animal("learns")
print(a.getName)
a.setName("sr")
print("new name: ",a.getName())
a.staticmethod()
a.accessibleMethod()
property 方法
#encoding=utf-8
class Student(object):
def __init__(self,name,age,score):
self.name = name
self.age = age
self.__score = score
def set_score(self,score):
if not isinstance(score,int):
raise ValueError("score must be an interger!")
if score < 0 or score > 100:
raise ValueError("score must be between 0 - 100!")
self.__score = score
def get_score(self):
return self.__score
s = Student("tom",19,90)
print("score is: ",s.get_score())
try:
s.set_score(999)
except Exception as e:
print("score error")
这样就不能对score进行随便设置了。但是上面的调用方法略显复杂,没有直接使用属性简单。
#encoding=utf-8
from datetime import datetime
class Student(object):
@property
def birth(self):
return self._birth
@birth.setter
def birth(self,value):
self._birth = datetime.strptime(value,"%Y-%m-%d")
print(self._birth)
@property
def age(self):
currentTime = datetime.now()
currentDate = currentTime.strftime("%Y-%m-%d")
timedelta = datetime.strptime(currentDate,"%Y-%m-%d") - self._birth
return timedelta.days // 365
if __name__ == "__main__":
s = Student()
s.birth = "1992-08-18"#调用 def birth(self,value):函数,并把置赋值给 self._birth
print("现在年龄: ",s.age)#打印age()函数的返回值
上面的birth是可读写属性,而age就是一个只读属性,因为age可以根据birth和当前时间计算出来。
多态
方法多态1
#encoding=utf-8
class F1(object):
pass
class S1(F1):
def show(self):
print("S1.show")
class S2(F1):
def show(self):
print("S2.show")
def func(obj):
obj.show()
s1_obj = S1()
func(s1_obj)
s2_obj = S2()
func(s2_obj)
多态方法2
#encoding=utf-8
class Calculator:
def count(self,args):
return 1
calc = Calculator()
from random import choice
obj = choice(["hello,world",[1,2,3],calc])
print(obj)
print(type(obj))
print(obj.count("a"))#当obj为calc实例对象时候,会调用count()方法
多态-”鸭子类型“
#encoding=utf-8
class Duck(object):
def quack(self):
print("Quaaaaaack!")
def feathers(self):
print("The duck has white and gray feathers.")
class Person(object):
def quack(self):
print("The person imitates a duck.")
def feathers(self):
print("The person takes a feather from the ground and shows it.")
def in_the_forest(duck):
duck.quack()
duck.feathers()
def game():
donald = Duck()
john = Person()
in_the_forest(donald)
in_the_forest(john)
game()
多态—运算符多态
#encoding=utf-8
def add(x,y):
return x + y
print(add(1,2))
print(add("hello","world"))
print(add(1,"abc"))
方法重写
在类层次结构中,当子类的成员变量与父类的成员变量同名时,子类的成员变量会隐藏父类的成员变量;当子类的方法与父类具有相同的名字、参数列表、返回值类型时,子类的方法重写(overriding)了父类的方法,在父类定义的方法就被隐藏。“隐藏”的含义是,通过子类对象调用子类中与父类同名的变量和方法时,操作的是这些变量和方法是在子类中的定义。子类通过成员变量的隐藏和方法的重写可以把父类的状态和行为改成为自身的状态和行为。重写是指子类重写父类的成员方法。子类可以改变父类方法所实现的功能,但子类中重写的方法必须与父类中对应的方法具有相同的方法名。也就是说要实现重写,就必须存在继承。简单来讲,就是如果父类的方法不能满足子类的需求,子类就可以重写从父类那继承过来的方法。
重写父类的方法
#encoding=utf-8
class Person(object):
def getInfo(self):
print("Person info")
def parent_m(self):
print("父类未被重写的方法")
class Student(Person):
def getInfo(self):
print("Stuent info")
stu = Student()
stu.getInfo()#调用子类自己的重写的父类方法
stu.parent_m()#调用从父类继承的未重写的方法