python基础教程之面向对象

面向对象编程(Object Oriented Programming, OOP, 面向对象程序设计)和面向过程编程,是两种不同的编程方式

面向对象编程的关注点在于谁来做

相比较函数,面向对象是更大的封装,根据职责在一个对象中封装多个方法
1.在完成某一个需求钱,首先确定职责---要做的事情(方法)
2.根据职责确定不同的对象,在对象内部封装不同的方法(多个)
3.最后完成的代码,就是顺序地调用不同对象的相应方法

特点

1.注重对象和职责,不同的对象承担不同的职责
2.更加适应对复杂的需求变化,是专门应对复杂项目开发,提供的固定套路
3.需要在面向过程基础上,再学习一些面向对象的语法

面向过程编程的关注点在于怎么做

1.把完成某一个需求的所有步骤从头到尾逐步实现
2.根据开发需求,将某些功能独立的代码封装成一个又一个函数
3.最后完成的代码,就是顺序地调用不同的函数

特点

1.注重步骤与过程,不注重职责分工
2.如果需求复杂,代码会变得很复杂
3.开发复杂项目,没有固定的套路,开发难度很大

面向过程1.png

1.面向对象基本语法

class 类名:类名一般需要遵守大驼峰命名法,每一个单词的首字母都大写
1.class <类名>:
2.class <类名>(object):

# 小明今年18岁,身高1.75,每天早上跑步,跑完会去吃东西
# 小美今年17岁,身高1.65,每天早上不跑步,小美喜欢吃东西
class Student(object):   # 关注这个类有哪些属性和行为
    def __init__(self, name, age, height):
        # 在 __init__ 方法里,以参数的形式定义特征,我们称之为属性
        self.name = name
        self.age = age
        self.height = height

    # 行为定义为一个函数
    def run(self):
        return '跑步'
    
    def eat(self):
        return '吃东西'
    
    def __str__(self):
        return '{}今年{}了,身高{}'.format(self.name, self.age, self.height)

# Student() ==>  会自动调用 __init__方法
# 使用 Student 类,创建了两个实例对象 s1 s2
# s1 和 s2 都会有 name,age,height 属性,同时都会有 run 和 eat 行为
s1 = Student('小明', '18', '175cm')
s2 = Student('小美', '16', '170cm')

# 根据业务逻辑,让不同的对象执行不同的行为
print(s1, '每天早上会去', s1.run(), ',会去', s1.eat(), sep="")  # 小明今年18了,身高175cm每天早上会去跑步,会去吃东西
print(s2, '每天早上不去', s1.run(), ',喜欢', s1.eat(), sep="")  # 小美今年16了,身高170cm每天早上不去跑步,喜欢吃东西

2.self语句的使用

class Student(object):
    # 这个属性直接定义在类里,是一个元组,用来规定对象可以存在的属性
    __slots__ = ('name', 'age', 'city')
    def __init__(self, x, y):
        self.name = x
        self.age = y
    
    def say_hello(self):
        print('大家好,我是', self.name)

# Student('张三', 18) 这段代码具体做了什么呢?
# 1.调用 __new__ 方法,用来申请内存空间
# 2.调用 __init__ 方法传入参数,将self指向创建好的内存空间,填充数据
# 3.变量 s 也指向创建好的内存空间
s = Student('张三', 18)
print(s.name)  # 张三
s.say_hello()  # 大家好,我是 张三
# 直接使用等号给一个属性赋值
# 如果这个属性以前不存在,会给对象添加一个新的属性
# 动态属性
s.city = '杭州'  # 给对象添加了一个city属性
print(s.city)  # 杭州
# 如果这个属性以前存在,会修改这个属性对应的值
s.name = '李四'
print(s.name)  # 李四

3.魔法方法

# 魔法方法,也叫魔术方法,是内里的特殊的一些方法
# 特点:
# 1.不需要手动调用,会在合适的时机自动调用
# 2.这些方法,都是使用 __开始,使用 __结束
# 3.方法名都是系统规定好的,在合适的时机自己调用
# import datetime
# x = datetime.datetime(2020, 8, 21, 12, 32, 50, 200)
# print(x)  # __str__ 方法
# print(repr(x))  # __repr__ 方法
class Person(object):
    def __init__(self, name, age):
        # 在创建对象时,会自动调用这个方法
        print('__init__ 方法被调用了')
        self.name = name
        self.age = age

    def __del__(self):
        # 当对象被销毁时,会自动调用这个方法
        print('__del__ 方法被调用了')

    def __repr__(self):
        return 'hello'
    
    def __str__(self):
        return '姓名:{}, 年龄:{}'.format(self.name, self.age)
    
    def __call__(self, *args, **kwargs):
        print('__call__ 方法被调用了')
        # args 是一个元组,保存(1, 2)
        # **kwargs 是一个字典 {fn: lambda x, y: x + y}
        print('args = {}, kwargs = {}'.format(args, kwargs))
        fn = kwargs['fn']
        return fn(args[0], args[1])

P = Person('张三', 18)
# 如果不再任何的修改,直接打印一个对象,是文件 __name__.类型以及内存地址
# print(P) # <__main__.Person object at 0x044F64D8>
# 当打印一个对象的时候,会调用这个对象的__str__或者 __repr__方法
# 如果两个方法都写了,选择 __str__
print(P)
# print(repr(P))  # 调用内置函数 repr 会触发对象的 __repr__方法
# print(P.__repr__())  # 魔法方法,一般不手动调用
n = P(1, 2, fn=lambda x, y: x + y)
print(n)

4.面向对象的练习

# 房子(House) 有 户型、总面积、剩余面积(等于总面积的60%) 和家具名称列表属性
# 新房子没有任何的家具
# 将 家具的名称 追加到家具名称列表中
# 判断 家具的面积 是否 超过剩余面积,如果超过,提示不能添加这件家具
# 家具(Furniture) 有 名字 和 占地面积属性,其中
# 席梦思(bed) 占地 4 平米
# 衣柜(chest) 占地 2 平米
# 餐桌(table) 占地 1.5 平米
# 将以上三件 家具 添加到 房子 中
# 打印房子时要求输出:户型、总面积、剩余面积、家具名称列表
class House(object):
    # 缺省参数
    def __init__(self, house_type, total_area, fru_list=None):
        if fru_list is None:  # 如果这个值是None
            fru_list = []     # 将fru_list设置为空列表
        self.house_type = house_type
        self.total_area = total_area
        self.free_area = total_area * 0.6
        self.fru_list = fru_list

    def add_fru(self, x):  # x = bed
        if self.free_area < x.area:
            print('剩余面积不足,放不进去了')
        else:
            self.fru_list.append(x.name)
            self.free_area -= x.area

    # def __repr__(self):
    def __str__(self):
        return '户型={}, 总面积={}, 剩余面积={},家具列表={}'.format(self.house_type, self.total_area, self.free_area, self.fru_list)

class Furniture(object):
    def __init__(self, name, area):
        self.name = name
        self.area = area
# 创建房间对象的时候,传入户型和总面积
house = House('一室一厅', 20)
sofa = Furniture('沙发', 10)
bed = Furniture('席梦思', 4)
chest = Furniture('衣柜', 2)
table = Furniture('餐桌', 1.5)
# 把家具添加到房间里(面向对象关注点:让谁做)
house.add_fru(sofa)
house.add_fru(bed)
house.add_fru(chest)
house.add_fru(table)
# print打印一个对象的时候,会调用这个对象的__repr__或者__str__方法,获取它们的返回值
print(house)

5.运算符相关的魔法方法

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def __eq__(self, other):
        print('__eq__ 方法被调用了, other=', other)
        return self.name == other.name and self.age == other.age

p1 = Person('张三', 18)
p2 = Person('张三', 18)
p3 = Person('张三', 20)
# p1 和 p2 是一个对象吗?
# 怎样比较两个对象是否是同一个对象? 比较的是内存地址
print('0x%X' % (id(p1)))   # 0x3F27478
print('0x%X' % (id(p2)))   # 0x3F27448
# is 身份运算符,可以用来判断两个对象是否同一个对象
print('p1 is p2 为', p1 is p2)  # p1 is p2 为 False
# __eq__ 如果不重写,默认比较依然是内存地址
# p1 == p2 本质是调用 p1.__eq__(p2),获取这个方法的返回值
print('p1 == p2 为', p1 == p2)  # p1 == p2 为 True
print(p1 == p3)  # False
# is 比较两个对象的内存地址
# == 会调用对象的 __eq__ 方法,获取这个方法的比较结果
nums1 = [1, 2, 3]
nums2 = [1, 2, 3]
print(nums1 is nums2)  # False
print(nums1 == nums2)  # True

# -------------------------------------------------------

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

    def __eq__(self, other):
        return self.name == other.name and self.age == other.age

    # def __ne__(self, other):  # 使用 != 运算符会自动调用这个方法
    def __gt__(self, other): # greater than 使用 > 会自动调用这个方法
        return self.age > other.age

    # def __ge__(self, other):  # 使用 >= 运算符会自动调用
    # def __lt__(self, other):  # less than p1<p2
    # def __le__(self, other):  # <= 
    def __add__(self, other):   # 使用 + 运算符会自动调用这个方法
        return self.age + other.age

    def __sub__(self, other):    # 使用 - 运算符会自动调用这个方法
        return self.age - other.age
    # def __truediv__(self, other):  使用 / 运算符会自动调用这个方法   
    def __mul__(self, other):  # 使用 * 运算符会自动调用这个方法
        return self.age * other.age
    def __str__(self):   # 转换成为字符串,默认会成为类型+内存地址
        return 'hello'
    def __int__(self):   # 转换成为整数
        return 20
    def __float__(self):  # 转换成为浮点数
        return 100.5

p1 = Person('张三', 18)
p2 = Person('张三', 18)
p3 = Person('李四', 20)
# == 运算符本质其实是调用对象的 __eq__方法,获取__eq__方法的返回结果
# a == b => a.__eq__(b)
print(p1 == p2)  # True
# != 本质是调用 __ne__ 方法 或者 __eq__ 方法取反
print(p1 != p2)  # False
# > 运算符本质其实是调用对象的 __gt__ 方法,获取 __gt__ 方法的返回结果
print(p1 > p3)   # False
# + 运算符本质其实是调用对象的 __add__ 方法,获取 __add__ 方法的返回结果
print(p1 + p2)   # 36
# - 运算符本质其实是调用对象的 __sub__ 方法,获取 __sub__ 方法的返回结果
print(p1 - p3)   # -2
# * 运算符本质其实是调用对象的 __mul__ 方法,获取 __mul__ 方法的返回结果
print(p1 * p3)   # 360
# str()将对象转换成为字符串,会自动调用 __str__ 方法
# 1.str()类型转换调用 2.打印对象也会调用
print(str(p1))   # hello
# int() ==> 调用对象的 __int__ 方法
print(int(p1))   # 20
# float() == > 调用对象的 __float__ 方法
print(float(p1))  # 100.5    

6.内置属性

class Person(object):
    """
    这是一个人类
    """
    def __init__(self, name, age):
        self.name = name
        self.age =age
    
    def eat(self):
        print(self.name + '正在吃饭')

# 'name': '张三', 'age': 18, 'eat': <function>
p = Person('张三', 18)
print(dir(p))   # 把对象支持的所有属性以及函数列出来
print(p.__class__)   # <class '__main__.Person'>
print(p.__dict__)    # {'name': '张三', 'age': 18}
print(p.__dir__())   # 等价于 dir(p)
print(p.__doc__)     # 对象名 __doc__  查看文档注释
print(Person.__doc__)   # 类名.__doc__  查看文档注释
print(p.__module__)  #__main__

7.把对象当作一个字典使用

class Person(object):
    def __init__(self, name, age, city):
        self.name = name
        self.age = age
        self.city = city
    def __setitem__(self, key, value):  # key='张三'
        print('setitem方法被调用了,key={},value={}'.format(key, value))
        self.__dict__[key] = value
    def __getitem__(self, item):
        return self.__dict__[item]

p = Person('张三', 18, '杭州')
print(p.__dict__)  # 将对象转换成为字典 {'name': '张三', 'age': 18, 'city': '杭州'}
# 不能直接把一个对象当作字典来使用
p['name'] = 'jack'   # [] 语法会调用对象的 __setitem__ 方法
p['age'] = '20'
print(p.name, p.age)   # jack 20
print(p['name'])     # jack  取值会调用 __getitem__ 方法

8.对象属性和类属性

class Person(object):
    type = '人类'   # 这个属性定义在类里,函数之外,我们称之为类属性
    def __init__(self, name, age):
        self.name = name
        self.age = age
# 对象 p 是通过 Person 类创建出来的实例对象
# name 和 age 是对象属性,在__init__ 方法里,以参数的形式定义的
# 是每一个实例对象都会单独保存一份的属性
# 每个实例对象之间的属性没有关联,互不影响
p1 = Person('张三', 18)
p2 = Person('李四', 20)
# 类属性可以通过类对象和实例对象获取
print(Person.type)   # 可以通过类对象获取类属性
# 可以通过实例对象来获取类属性
print(p1.type)
print(p2.type)
p1.type = 'human'
print(p1.type)   # human 并不会修改类属性,而不是给实例对象添加了一个新的对象属性
# 类属性只能通过类对象来修改,实例对象无法修改类属性
Person.type = 'monkey'    # 修改了类属性
print(p2.type)  # monkey
print(p1.type)  # human

9.私有属性的使用

import datetime
class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
        self.__money = 1000  # 以两个下划线开始的变量是私有变量

    def get_money(self):
        # 记录
        print('{}查询了余额'.format(datetime.datetime.now()))
        return self.__money

    def set_money(self, qian):
        if type(qian) != int:
            print('设置的余额不合法')
            return
        print('修改了余额')
        self.__money = qian
    
    def __demo(self):   # 以两个下划线开始的函数,是私有函数,在外部无法调用
        print('我是demo函数,name={}'.format(self.name))

    def test(self):
        self.__demo()

p = Person('张三', 18)
print(p.name, p.age)  # 张三 18 可以直接获取到
# print(p.__money)    # 不能够直接获取到私有变量
p._Person__demo()     # 我是demo函数,name=张三
p.test()              # 我是demo函数,name=张三
# 获取私有变量的方式:
# 1.使用 对象._类名__私有变量名获取
print(p._Person__money)   # 1000  通过这种方式也能够获取私有属性
# 2.定义get和set方法来获取
print(p.get_money())   # 1000
p.set_money('hello')
print(p.get_money())

10.类方法和静态方法


class Calculator(object):
    @staticmethod
    def add(a, b):
        return a + b

print(Calculator.add(1, 4))

class Person(object):
    type = 'human'
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def eat(self, food):   # 对象方法有一个参数self,指的是实例对象
        print(self.name + '正在吃' + food)
    # 如果一个方法里没有用到实例对象地任何属性,可以将这个方法定义成static
    @staticmethod
    def demo():    # 默认的方法都是对象方法
        print('hello')
    @classmethod
    def test(cls):  # 如果这个函数只用到了类属性,我们可以定义成为一个类方法
        # 类方法会有一个参数 cls,也不需要手动的传参,会自动传参
        # cls 指的是类对象   cls == Person ==> True
        print(cls.type)
        print('yes')
p1 =Person('张三', 18)
# 实例对象在调用方法时,不需要给形参self传参,会自动的把实例对象传递给self
p2 = Person('李四', 20)
# eat 对象方法,可以直接使用实例对象,方法名(参数)调用
# 使用对象名.方法名(参数)调用的方式,不需要传递self
# 会自动将对象名传递给self
p1.eat('红烧牛肉泡面')    #  直接使用实例对象调用方法
# 对象方法还可以使用 类对象来调用类名.方法名()
# 这种方式,不会自动给self传参,需要手动的指定self
Person.eat(p2, '西红柿鸡蛋盖饭')
# 静态方法
Person.demo()
p1.demo()
# 类方法:可以使用实例对象和类对象调用
p1.test()
Person.test()

11.单实例设计模式

class Singleton(object):
    __instance = None   # 类属性
    __is_first = True
    @classmethod
    def __new__(cls, *args, **kwargs):
        if cls.__instance is None:
            # 申请内存,创建一个对象,并把对象的类型黄色至为cls
            cls.__instance = object.__new__(cls)
        return cls.__instance
    def __init__(self, a, b):
        if self.__is_first:
            self.a = a
            self.b = b
            self.__is_first = False
        
# 调用 __new__ 方法申请内存
# 如果不重写 __new__ 方法,会调用object的 __new__方法
# object __new__ 方法会申请内存
# 如果重新了 __new__方法,需要手动的申请内存
s1 = Singleton('呵呵', '嘿嘿嘿')
s2 = Singleton('哈哈', '嘻嘻嘻')
s3 = Singleton('嘎嘎', '嘤嘤嘤')
print(s1 is s2)  # True
print(s1.a, s1.b)  # 呵呵 嘿嘿嘿

12.练习

# 定义一个类属性,记录通过这个类创建了多少个对象
class Person(object):
    count = 0
    def __init__(self, name, age):
        Person.count += 1
        self.name =name 
        self.age = age
    
# 每次创建对象,都会调用 __new__ 和 __init__ 方法
p1 = Person('张三', 18)
p2 = Person('李四', 19)
p3 = Person('jack', 20)
print(Person.count)   # 3

# 手动调用 __new__ 和 __init__ 方法
class Person1(object):
    __count = 0   # 类属性
    def __new__(cls, *args, **kwargs):
        cls.__count += 1
        # 申请内存,创建一个对象,并设置类型是Person1类
        return object.__new__(cls)
    def __init__(self, name, age):
        self.name = name
        self.age = age
    @classmethod
    def get_count(cls):
        return cls.__count

p1 = Person1('张三', 18)
p2 = Person1('李四', 19)
p3 = Person1('jack', 20)
print(Person1.get_count())  # 3

13.面向对象的三大特征

# 面向对象编程有三大特性: 封装、继承和多态
# 封装: 函数是对语句的封装
# 继承:类和类之间可以人为手动的建立父子关系,父类的属性和方法,子类可以使用
# 多态:是一种技巧,提高代码的灵活度
# 一个一个的语句
def test():
    a = 23   # 赋值语句
    a += 3   # 算数运算符表达式语句
    print('hello')
    print('good')
class Person(object):
    type = '人类'
    def __init__(self):
        pass
    def eat(self):
        pass

14.继承的使用

class Animal(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def sleep(self):
        print(self.name + '正在睡觉')

class Dog(Animal):
    def bark(self):
        print(self.name + '正在叫')

class Student(Animal):
    def study(self):
        print(self.name + '正在好好学习')

# Dog() 先调用__new__ 方法,再调用 __init__ 方法
# Dog 里没有 __new__ 方法,会查看父类是否重写了 __new__ 方法
# 父类里也没有重写 __new__ 方法,查找父类的父类,找到了 object
# 调用 __init__ 方法,Dog类没有实现,会自动找 Animal 父类 
d1 = Dog('小黑', 2)
# 父类里定义的属性,字类可以直接使用
print(d1.name)  # 小黑
# 父类的方法字类实例可以直接调用
d1.sleep()   # 小黑正在睡觉
d1.bark()    # 小黑正在叫

s1 = Student('小明', 18)
s1.sleep()   # 小明正在睡觉
s1.study()   # 小明正在好好学习

15.python里继承的特点

class A(object):
    def demo_a(self):
        print('我是A类里的方法demo_a')

    def foo(self):
        print('我是B类里的foo方法')

class B(object):
    def demo_b(self):
        print('我是B类的方法demo_b')

    def foo(self):
        print('我是B类里的foo方法')

# 如果不写父类,python3以后,默认继承自object
# python里允许多继承

class C(A, B):
    pass

c = C()
c.demo_a()   # 我是A类里的方法demo_a
c.demo_b()   # 我是B类的方法demo_b

# 如果两个不同的父类有同名方法,有一个类 __mro__ 属性可以查看方法的调用顺序
c.foo()   # 我是B类里的foo方法
print(C.__mro__)  # (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
  

16.私有属性的继承特点

class Animal(object):
    def __init__(self, name, age):
        self.name = name 
        self.age = age
        self.__money = 1000

    def eat(self):
        print(self.name + '正在吃东西')

    def __test(self):
        print('我是Animal类里的test方法')

class Person(Animal):
    def __demo(self):
        print('我是Person里的私有方法')
    
p = Person('张三', 18)
print(p.name)  # 张三
p.eat()   # 张三正在吃东西
# 自己类里定义的私有方法   对象名._类名__私有方法名()
p._Person__demo()   # 我是Person里的私有方法
# 可以通过 对象名._父类名__私有方法调用()
p._Animal__test()   # 我是Animal类里的test方法
# p._Person__test() # 父类的私有方法,子类没有继承
print(p._Animal__money)   # 1000

17.新式类和经典类


# -*- coding:utf8-*-
# 手动指定 Student类继承自object
class Student(object):  # 兼容性问题
    pass

# 没有指定Dog的父类,python3里默认继承自object
class Dog:
    pass

# 新式类和经典类的概念:
# 1. 新式类:继承自 object 的类我们称之为新式类
# 2. 经典类:不继承自 object 的类

# 在python2里,如果不手动指定一个类的父类时object,这个类就是一个经典类
# 在python3里,不存在 经典类,都是新式类
s = Student()
d = Dog()
print(dir(s))
print(dir(d))

18.对象相关的运算符

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

class X(object):
    pass

class Student(Person, X):
    pass

p1 = Person('张三', 18)
p2 = Person('张三', 18)
s = Student('李四', 20)
# 获取两个对象的内存地址 id(p1) == id(p2)
# is 身份运算符是用来比较是否是同一个对象
print(p1 is p2)  # False
# type(p1)  其实获取的就是类对象
print(type(p1) == Person)  # True
# s这个实例对象是否由Student类创建的?
print(type(s) == Student)   # True
print(type(s) == Person)    # False
# isinstance 用来判断一个对象是否由指定的类(或者子类)实例化出来的
print(isinstance(s, (Student, X)))  # True
print(isinstance(s, Person))  # True
print(isinstance(p1, Person))  # True
print(isinstance(p1, Student))  # False
# issubclass 用来判断一个类是否是另一个类的子类
print(issubclass(Student, (Person, X)))   # True
print(issubclass(Person, Student))  # False

19.子类重写父类方法

# 继承特点:如果一个类A继承类B,由类A创建出来的实例对象都能直接使用类B里定义的方法
class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def sleep(self):
        print(self.name + '正在睡觉')

class Student(Person):
    def __init__(self, name, age, school):
        # self.name = name
        # self.age =age
        # 子类在父类实现的基础上,又添加了自己新的功能
        # 调用父类方法的两种方式:
        # 1. 父类名.方法名(self, 参数列表)
        # Person.__init__(self, name, age)
        # 2.使用super直接调用父类的方法,推荐使用第二种方式
        super(Student, self).__init__(name, age)
        self.school = school

    def sleep(self):
        print(self.name + '正在课间休息时睡觉')
    
    def study(self):
        print(self.name + '正在学习')

s = Student('瑞文', 20, '德玛西亚学院')  # 调用了父类的 __init__ 方法
# 调用了父类的 sleep 方法
s.sleep()  # 瑞文正在课间休息时睡觉
print(Student.__mro__)  # (<class '__main__.Student'>, <class '__main__.Person'>, <class 'object'>)
# 1.子类的实现和父类的实现完全不一样,子类可以选择重写父类的方法
# 2.子类在父类的基础上又由更多的实现

20.不适用多态

class PoliceDog(object):
    def attack_enemy(self):
        print('警犬正在攻击坏人')

class BlindDog(object):
    def lead_road(self):
        print('导盲犬正在领路')

class DrugDog(object):
    def search_durg(self):
        print('缉毒犬正在搜毒')

class Person(object):
    def __init__(self, name):
        self.name = name
        # self.dog = dog 

    def work_with_pd(self):
        # print(self.name + '正在工作')
        self.dog.attack_enemy()

    def work_with_bd(self):
        self.dog.lead_road()

    def work_with_dd(self):
        self.dog.search_durg()

p = Person('张三')

pd = PoliceDog()
p.dog = pd
p.work_with_pd()

bd = BlindDog()
p.dog = bd
p.work_with_bd()

dd = DrugDog()
p.dog = dd
p.work_with_dd()

21.多态的使用

# 多态时基于继承,通过子类重写父类的方法,达到不同的子类对象调用相同的父类方法,得到不同的结果
# 提高代码的灵活度
class Dog(object):
    def work(self):
        print('狗正在工作')

class PoliceDog(Dog):
    def work(self):
        print('警犬正在攻击敌人')

class BlindDog(Dog):
    def work(self):
        print('导盲犬正在领路')

class DrugDog(Dog):
    def work(self):
        print('缉毒犬正在搜毒')

class Person(object):
    def __init__(self, name):
        self.name = name
        self.dog = None
    
    def work_with_dog(self):
        if self.dog is not None and isinstance(self.dog, Dog):
            self.dog.work()

p = Person('张三')

pd = PoliceDog()
p.dog = pd
p.work_with_dog()

bd = BlindDog()
p.dog = bd
p.work_with_dog()

dd = DrugDog()
p.dog = dd
p.work_with_dog()