python之面向对象介绍(四)
对象的三大特性分别是:
- 封装(确保对象中的数据安全)
- 继承(保证了对象的可扩展性)
- 多态(保证了程序的灵活)
我们在面向对象介绍(三)中介绍了封装,现在就让我们来细说对象的继承和多态
1 继承
1.1 继承的简介
1.1.1 继承的引入
情形1:
# 情形1
# 创建一个医生的类
class Doctor():
def __init__(self,name,age):
self.name = name
self.age = age
def work(self):
print('治疗病人.....')
#创建一个军人的类
class Soldie():
def __init__(self,name,age):
self.name = name
self.age = age
def work(self):
print('保卫国家....')
#创建一个教师的类
class Teacher():
def __init__(self,name,age):
self.name = name
self.age = age
def work(self):
print('教书育人....')
# 评价:三个类都存在一个共同点是初始化相同。作为人,都有姓名和名字
# 这样是否可以考虑创建一个人的类,然后让上面三个类继承人的类的方法或者属性
class Person():
def __init__(self,name,age):
self.name = name
self.age = age
情形2:
# 情形2
# 已有一个动物类(跑、睡),需要一个狗类(跑、睡、叫)
# 方法1:直接修改动物类,在这个类中添加我们需要的功能 | 隐患:修改起来比较麻烦,违反了ocp原则
# 方法2:直接创建一个新的类,这个类就是狗类 | 隐患:创建一个新类比较麻烦,需要大量的复制粘贴。维护修改起来比较麻烦
# 方法1
class Animal:
def run(self):
print('动物跑.....')
def sleep(self):
print('动物睡觉....')
# def speak(self):
#
# print('旺旺.....')
# 方法2
class Dog:
def run(self):
print('动物跑.....')
def sleep(self):
print('动物睡觉....')
def speak(self):
print('旺旺.....')
# 通过以上情形,我们同样可以通过创建一个动物的类,承接所有动物的方法和属性,并让特定动物继承其功能
综上,我们大体就知道继承讲的是什么了?
- 定义:面向对象的三大特性之一,通过继承我们可以使一个类获取到其他类中的属性和方法
- 特点:(1)提高了代码的复用性;(2)让类与类之间产生了关系。有了这个关系,才有多态的特性
# 情形1 解决
class Person():
def __init__(self,name,age):
self.name = name
self.age = age
# 创建一个医生的类
class Doctor(Person): # 继承Person的类
def __init__(self,name,age):
super().__init__(name,age) # 通过super()可以直接获取当前类的父类
def work(self):
print('治疗病人.....')
# 情形2 解决
class Animal:
def run(self):
print('动物跑.....')
def sleep(self):
print('动物睡觉....')
class Dog(Animal): #继承Animal的类
def speak(self):
print('旺旺.....')
1.1.2 继承的语法
- 语法就是 class 子类(父类):
# 1. 语法就是 class 子类(父类):
class Parent_Class:
print('我是父类')
class Son_Class(Parent_Class): # 语法就是 (父类)
print('我是子类,继承了父类')
son_class = Son_Class()
print(son_class) # 结果: 我是父类 我是子类,继承了父类 <__main__.Son_Class object at 0x000001CA8E405908>
# Son_Class()的实例化对象son_class继承了其自身和父类的语法
- 检验是否是其父类的语法:issubclass(子类,父类)
# 2.检验是否是其父类的语法:issubclass(子类,父类)
print(issubclass(Son_Class,Parent_Class)) # 结果为True
- object是所有对象的父类
# 3.object是所有对象的父类
print(issubclass(int,object)) # 结果为True
print(issubclass(Son_Class,object)) # 结果为True
print(issubclass(Parent_Class,object)) # 结果为True
1.1.3 继承的顺序
当我们调⽤⼀个对象的方法时:
- 会优先去当前对象中寻找是否具有该方法,如果有则直接调用
- 如果没有,则去当前对象的父类中寻找,如果父类中有则直接调用父类中的方法
- 如果没有,则去父类中的父类寻找,以此类推,直到找到object,如果依然没有找到就报错了
# 继承顺序
'''
当我们调⽤⼀个对象的方法时:
- 会优先去当前对象中寻找是否具有该方法,如果有则直接调用
- 如果没有,则去当前对象的父类中寻找,如果父类中有则直接调用父类中的方法
- 如果没有,则去父类中的父类寻找,以此类推,直到找到object,如果依然没有找到就报错了
'''
class A(object):
def test(self):
print('A....')
class B(A):
def test(self):
print('B....')
class C(B):
def test(self):
print('C....')
c = C()
c.test() # C.... (会优先去当前对象中寻找是否具有该方法,如果有则直接调用)
class A(object):
def test(self):
print('A....')
class B(A):
def test(self):
print('B....')
class C(B):
pass
c = C()
c.test() # B.... (如果没有,则去当前对象的父类中寻找,如果父类中有则直接调用父类中的方法)
class A(object):
def test(self):
print('A....')
class B(A):
pass
class C(B):
pass
c = C()
c.test() # A.... (如果没有,则去父类中的父类寻找,以此类推)
c.speak() #报错: AttributeError: 'C' object has no attribute 'speak' # 子类和父类都没有 直到找到object,如果依然没有找到就报错了
1.2 继承的使用
1.2.1 方法重写
如果在子类中有和父类同名的方法,则通过子类实例去调用方法时,会调用子类的方法而不是父类的方法,这个特点我们称之为方法的重写(覆盖, overrides)。原理在于继承的顺序。
# 方法重写
# - 如果子类中有和父类同名的方法。则通过子类的实例去调用方法时,会调用子类的方法而不是父类的方法。这个特点我们也称之为方法的重写(覆盖)
class Animal:
def run(self):
print('动物跑.....')
def sleep(self):
print('动物睡觉....')
class Dog(Animal):
def run(self):
print('狗跑.....')
def speak(self):
print('旺旺.....')
d = Dog()
d.run() # 结果为 狗跑..... 方法重写(overrides)
1.2.2 多重继承
继承可以分为单继承和多继承。
- 单继承是:一个类继承一个父类(class 子类(父类):)
# 单继承: 一个类继承一个父类(class 子类(父类):)
class grandFather():
print('我是爷爷')
class Parent(grandFather):
print('我是父类')
class SubClass(Parent):
print('我是子类')
sub = SubClass() # 结果:我是爷爷 我是父类 我是子类
- 多继承是:一个类可以继承多个父类(class 子类(父类1,父类2))
# 多重继承 :一个类可以继承多个父类(class 子类(父类1,父类2))
class Parent():
print('我是第一个爹')
class Parent2():
print('我是第二个爹')
class SubClass(Parent, Parent2):
print('我是子类')
sub = SubClass() # 结果:'我是第一个爹 我是第二个爹 我是子类
查找子类的所有的父类用方法__base__()
注意查询的主体是子类,而不是子类的实例化对象
# 查找子类的所有的父类用方法__base__()
# 注意查询的主体是子类,而不是子类的实例化对象
print(SubClass.__bases__) # (<class '__main__.Parent'>, <class '__main__.Parent2'>)
print(sub2.__bases__) # 报错:AttributeError: 'SubClass' object has no attribute '__bases__'
评价:
- 在开发中没有特殊情况,应该尽量避免使用多重继承。因为多重继承会让我们的代码更加复杂
- 如果多个父类中有同名的方法,则会先在第⼀个父类中寻找(父类没有,去object寻找,然后才开始第二个父类,以此同理类推),然后找第二个,找第三个…前面会覆盖后面的
1.2.3 super()函数
(1)定义
- super() 函数是用于调用多重父类(超类)的一个方法。
- super 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。
MRO 就是类的方法解析顺序表, 其实也就是继承父类方法时的顺序表(先在第⼀个父类中寻找,然后找第二个,找第三个…前面会覆盖后面的)。
(2)语法:
super().xxx # xxx 是指父类的方法,或者属性
(3)特点:
super()可以获取当前类的父类并且通过super()返回对象调⽤父类方法时,不需要传递self
# 创建一个父类
class Animal:
def __init__(self,name): # 初始化
self._name = name
def run(self):
print('动物跑.....')
def sleep(self):
print('动物睡觉....')
@property
def name(self):
return self._name
@name.setter
def name(self,name):
self._name = name
# 父类中所有的方法都会被子类继承,包括特殊方法
class Dog(Animal):
def __init__(self,name,age): # age 是狗类自己私有的属性
# 希望可以直接调用父类的__init__
# 我希望有一种动态的方式来获取类的父类
# Animal.__init__(self,name) # 非动态调用,如果有多个类,需要不断修改前面的类名
# 通过super()可以直接获取当前类的父类
super().__init__(name) # 语法 super().xxx 并且通过super()返回对象调⽤父类方法时,不需要传递self
self._age = age
@property
def age(self):
return self._age
@age.setter
def age(self,age):
self._age = age
def run(self):
print('狗跑.....')
def speak(self):
print('旺旺.....')
d = Dog('二哈',8)
d.name = '德牧'
print(d.name) # 德牧 封装修改的方法
print(d.age) # 8
(4)综合练习
参考:类的继承中super的使用
# 创建父类学校成员SchoolMember
class SchoolMember:
def __init__(self, name, age):
self.name = name
self.age = age
def tell(self):
# 打印个人信息
print('Name:"{}" Age:"{}"'.format(self.name, self.age), end=" ")
# 创建子类老师 Teacher
class Teacher1(SchoolMember):
def __init__(self, name, age, salary):
SchoolMember.__init__(self, name, age) # 利用父类进行初始化
self.salary = salary
# 方法重写
def tell(self):
# 语法:print('Name:"{}" Age:"{}"'.format(self.name, self.age), end=" ")
# 继承父类的方法,并加以完善自己要的东西
SchoolMember.tell(self)
print('Salary: {}'.format(self.salary))
member1 = SchoolMember("John", 44)
teacher1 = Teacher1("John", 44, "$60000")
member1.tell() # Name:"John" Age:"44"
teacher1.tell() # 打印 Name:"John" Age:"44" Salary: $60000
评价:上述类的继承中,使用了SchoolMember.init(self, name, age)和SchoolMember.tell(self)两个方法实现实例初始化与tell函数,但更好的编程实践是使用super方法实现继承。
改进:
# 创建子类老师 Teacher2
class Teacher2(SchoolMember):
def __init__(self, name, age, salary):
super().__init__(name,age) # 利用父类进行初始化
self.salary = salary
def tell(self):
super().tell() # 等同于 SchoolMember.tell(self)
print('Salary: {}'.format(self.salary))
teacher2 = Teacher2("Mary", 34, "$70000")
teacher2.tell() # 打印 Name:"Mary" Age:"34" Salary: $70000
'''
# 在子类当中可以通过使用super关键字来直接调用父类的中相应的方法,简化代码。
# 在下面例子中,教师子类调用了父类的__init__()方法、tell()方法。
# super().__init__(name,age)等同于SchoolMember.__init__(self, name, age)
# super().tell()等同于SchoolMember.tell(self)。
# 当你使用Python super()关键字调用父类方法时时,注意去掉括号里self这个参数。
'''
2 多态
2.1 多态简介
多态是⾯向对象的三大特性之⼀。从字面理解就是多种形态,即一个对象⼀个对象可以以不同形态去呈现。
class A:
def __init__(self,name):
self._name = name
@property
def name(self):
return self._name
@name.setter
def name(self,name):
self._name = name
class B:
def __init__(self,name):
self._name = name
@property
def name(self):
return self._name
@name.setter
def name(self,name):
self._name = name
class C:
pass
a = A('葫芦娃')
b = B('钢铁侠')
c = C()
def speak(obj):
print('你好 %s'%obj.name)
# 对应speak()这个函数来说,只要对象中函数有name属性,它就可以作为参数传递
# 这个函数并不考虑对象的类型。只要有name属性就可以
# 对象以多种形态呈现,保证程序的灵活性
def speak2(obj):
# 检查
if isinstance(obj,A):
print('你好 %s' % obj.name)
# 在speak2()中我们做了一个类型检查,也就是只有符合obj是A类型的对象时,才可以使用
# 其他类型的对象使用不了该函数。这个函数就违反了多态
2.2 动态的使用
(1)分类
静态多态性:如任何类型都可以用运算符+进行运算
print('你好!'+ '很开心认识你')
动态多态性
class People:
def talk(self):
print('People is talking')
class Cat:
def talk(self):
print('Cat is miaomiao')
class Dog:
def talk(self):
print('Dog is wangwang')
cat1 = Cat()
dog1 = Dog()
peo1 = People()
# peo、dog、pig都是动物,只要是动物肯定有talk方法
# 于是我们可以不用考虑它们三者的具体是什么类型,而直接使用
peo1.talk()
dog1.talk()
peo1.talk()
# 定义一个统一的接口来访问
def func(obj):
obj.talk()
func(cat1)
2.3 多态特点
-
增加了程序的灵活性:以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(obj)
-
增加了程序de 可扩展性通过继承父类创建了一个新的类,使用者无需更改自己的代码,还是用func(父类)去调用
3 属性和方法
3.1 属性
- 类属性
(1)类属性:直接在类中定义的属性是类属性
(2)类属性可以通过类或类的实例访问到。但是类属性只能通过类对象来修改,无法通过实例对象修改
'''
# 类属性 直接在类中定义的属性
# 类属性可以通过类和类的实例访问到
# 类属性只能通过类对象来修改,无法通过实例对象修改
'''
class A:
name = 'claire' # 创建类的属性
a = A() # 类的实例化对象a
print(A.name) # claire 类属性可以通过类和类的实例访问到
print(a.name) # claire 类属性可以通过类和类的实例访问到
a.name = 'chen'
print(a.name) # chen 类属性只能通过类对象来修改,无法通过实例对象修改
print(A.name) # claire 类属性只能通过类对象来修改,无法通过实例对象修改
- 实例属性
(1)实例属性:通过实例对象添加的属性属于实例属性
(2)实例属性只能通过实例对象来访问和修改,类对象无法访问修改
'''
# 实例属性 通过实例对象添加的属性
# 实例属性只能通过实例对象访问和修改,类对象无法访问和修改
'''
class B:
def __init__(self):
self.name = 'claire' # 实例属性 通过实例对象添加的属性
b = B()
print(b.name)
# print(B.name) # 报错:type object 'B' has no attribute 'name'
B.name = 'chen'
print(B.name) # claire
b.name = 'chen'
print(b.name) # chen 实例属性只能通过实例对象访问和修改,类对象无法访问和修改
3.2 方法
- 实例方法
(1)在类中定义,以self为第⼀个参数的方法都是实例方法
(2)实例方法在调⽤时,Python会将调用对象以self传入
(3)实例方法可以通过类实例和类去调用:
当通过实例调用时,会⾃动将当前调用对象作为self传⼊
(4)当通过类调用时,不会自动传递self,我们必须手动传递self
'''
# 在类中定义,以self第一个参数的方法都是实例方法
# 实例方法在调用的时候 Python会将调用对象以self传入
# 当通过类去调用时,不会自动传递self,此时需要手动传递self
'''
class C:
def fn(self): # 在类中定义,以self第一个参数的方法都是实例方法
print('我是实例方法')
c = C()
c.fn() # 我是实例方法 # 实例方法在调用的时候 Python会将调用对象以self传入
# C.fn() # 报错:fn() missing 1 required positional argument: 'self'
# C.fn(self) # 报错: name 'self' is not defined
C.fn(c) # 我是实例方法 self就是对象 # 当通过类去调用时,不会自动传递self,此时需要手动传递self
- 类方法
(1)类方法 在类的内容以@classmethod 来修饰的方法属性类方法
(2)类方法第⼀个参数是cls 也会自动被传递。cls就是当前的类对象
(3)类方法和实例方法的区别,实例方法的第⼀个参数是self,类方法的第⼀个参数是cls
(4)类方法可以通过类去调用,也可以通过实例调用
''
# 类方法和实例方法的区别 实例方法第一个是参数self 而类方法第一个参数是cls
# 而且类方法需要用@classmethod
# 类方法可以通过类去调用,也可以通过实例调用
'''
class D:
count = 0
@classmethod # 类方法需要用@classmethod
def fn(cls):
print('我是类方法',cls.count)
D.fn() # 我是类方法 0
d = D()
d.fn() # 我是类方法 0
- 静态方法
(1)在类中用@staticmethod来修饰的方法属于静态方法
(2)静态方法不需要指定任何的默认参数,静态方法可以通过类和实例调用
(3)静态方法,基本上是⼀个和当前类无关的方法,它只是⼀个保存到当前类中的函数
(4)静态方法⼀般都是些工具方法,和当前类无关
'''
# @staticmethod 修饰的方法是静态方法
# 静态方法不需要指定任何默认参数。静态方法可以通过类或者实例调用
# 静态方法 基本上是一个和当前类无关的方法。它只是一个保存到当前类的函数
# 静态方法一般都是一些工具方法。和当前类无关
'''
class E:
@staticmethod # @staticmethod 修饰的方法是静态方法
def fn():
print('我是静态方法')
E.fn() # 我是静态方法
e = E()
e.fn() # 我是静态方法