面向对象与面向过程

  • 面向过程:根据业务逻辑从上到下写代码。
  • 面向对象:将变量与函数绑定到一起,分类进行封装,每个程序只要负责分配给自己的分类,这样能够更快速的开发程序,减少了重复代码。

面向过程编程最易被初学者接受,其往往用一长段代码来实现指定功能,开发过程的思路是将数据与函数按照执行的逻辑顺序组织在一起,数据与函数分开考虑,面向过程基本是由函数组成的。

面向过程编程

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

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

特点:

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

类和对象

对象是面向对象编程的两个核心概念。

类是对一群具有相同特征或者行为 的事物的一个统称,是抽象的,不能直接使用

  • 特征其实就是一个变量,在类里我们称之为属性。
  • 行为其实就是一个函数,在类里我们称之为方法。
  • 类其实就是由 属性方法 组成的一个抽象概念。

类就相当于制造飞机时的图纸,是一个模板。这个模板只规定了飞机的某些特征(例如大小,颜色,型号等等)和行为(例如起飞,降落,飞行等等),它并不是一个具体的飞机,而是对飞机的一个抽象概念。它出现的目的,是为了让我们的创建飞机对象。
飞机设计图纸.png

对象

对象是由类创建出来的一个具体存在,可以直接使用。由哪一个类创建出来的 对象,就拥有在哪一个类中定义的属性和方法。 对象 就相当于用图纸制造的飞机。在开发中,应该先有类,在类里定义好属性和行为,再根据类来创建对象。
image.png

类和对象的关系

  • 类是模板,对象是根据类这个模板创建出来的,应该先有类,再有对象。
  • 使用同一个类,能够创建出很多对象。
  • 类中定义了什么属性和方法,对象中就有什么属性和方法。
  • 不同对象对应的属性值也会不同。

例如:定义了一个狗类,这个狗类有以下属性:

  • 品种
  • 颜色
  • 性别
  • 名字

现在根据这个类创建出了两条狗,这两条狗分别是 哈士奇、灰色、母、二哈中华田园犬、黄色、公、旺财。我们发现,这两条狗都具有 品种、颜色、性别和名字这些属性,但是每条狗对应的属性值却不一样。

类的设计

在使用面相对象开发前,应该首先分析需求,确定一下,程序中需要包含哪些类!
植物大战僵尸类图.png
在程序开发中,要设计一个类,通常需要满足一下三个要素:

  1. 类名 这类事物的名字,安照大驼峰命名法(每个单词的首字母大写)起名。
  2. 属性 这类事物具有什么样的特征。
  3. 方法 这类事物具有什么样的行为。

    定义类名

    名词提炼法:分析整个业务流程,出现的名词,通常就是找到的类。

    属性和方法的确定

  • 对对象的特征描述,可以定义成属性
  • 对象具有的行为(动词)可以定义成方法

面向对象基本语法

在Python中,对象几乎是无处不在的,我们可以使用dir内置函数来查看这个对象里的方法。

定义简单的类(只包含方法)

面向对象是更大的封装,在一个类中封装多个方法,这样通过这个类创建出来的对象,就可以直接调用这些方法了!

定义类

在Python中要定义一个只包含方法的类,语法格式如下:

  1. class 类名:
  2. def 方法1(self,参数列表):
  3. pass
  4. def 方法2(self,参数列表):
  5. pass
  1. 方法的定义格式和之前学习过的函数一样
  2. 方法里的第一个参数必须是self,大家暂时先记住,稍后介绍 self.
  3. 类名要遵守大驼峰命名法。

self的使用

  1. class Cat:
  2. """这是个猫类"""
  3. def eat(self):
  4. print("小猫在吃东西")
  5. def drink(self):
  6. print("小猫在喝水")
  7. tom = Cat() # 创建了一个Cat对象
  8. tom.eat()
  9. tom.drink()

给对象添加属性

python支持动态属性,当一个对象创建好了以后,直接使用 对象.属性名 = 属性值 就可以很方便的给对象添加一个属性。

  1. tom = Cat()
  2. tom.name = 'Tom' # 可以直接给 tom 对象添加一个 name 属性

这种方法很方便,但是,不建议使用这种方式给对象添加属性。

self的概念

哪个对象调用了方法,方法里的self指的就是谁。 通过 self.属性名 可以访问到这个对象的属性;通过 self.方法名() 可以调用这个对象的方法。

  1. class Cat:
  2. def eat(self):
  3. print("%s爱吃鱼" %self.name)
  4. tom = Cat()
  5. tom.name = 'Tom' # 给 tom 对象添加了一个name属性
  6. tom.eat() # Tom爱吃鱼
  7. lazy_cat = Cat()
  8. lazy_cat.name = "大懒猫"
  9. lazy_cat.eat() # 大懒猫爱吃鱼

直接给对象添加属性的缺点

上述代码中,我们是先创建对象,然后再给对象添加 name 属性,但是这样做会有问题。

  1. tom = Cat()
  2. tom.eat()
  3. tom.anme = "Tom"

程序运行时会报错:

  1. AttributeError: 'Cat' object has no attribute 'name'
  2. 错误提示:'Cat'对象没有 'name' 属性

在日常开发中,不推荐在类的外部直接给对象添加属性这种方式。对象应该具有哪些属性,我们应该封装在类的内部。

魔法方法

Python 里有一种方法,叫做魔法方法。Python 的类里提供的,两个下划线开始,两个下划线结束的方法,就是魔法方法,魔法方法在恰当的时候就会被激活,自动执行。 魔法方法的两个特点:

  • 两侧各有两个下划线;
  • 名字已经由 Python 官方定义好,不能乱写。

    init方法

    __init__()方法,在创建一个对象时默认被调用,不需要手动调用。在开发中,如果希望在创建对象的同时,就设置对象的属性,可以对 __init__ 方法进行改造。 ```python class Cat: “””这是一个猫类””” def init(self,name): # 重写了 init 魔法方法

    1. self.name = name

    def eat(self):

      return "%s爱吃鱼"%self.name
    

    def drink(self):

      return '%s爱喝水'%self.name
    

    “””

      tom = Cat()
      TypeError: __init__() missing 1 required positional argument: 'name'
      这种写法在运行时会直接报错!因为 __init__ 方法里要求在创建对象时,必须要传递 name 属性,如果不传入会直接报错!
    

    “””

tom = Cat(“Tom”) # 创建对象时,必须要指定name属性的值 tom.eat() # tom爱吃鱼

注意:

1. `__init__()`方法在创建对象时,会默认被调用,不需要手动的调用这个方法。
1. `__init__()`方法里的self参数,在创建对象时不需要传递参数,python解释器会把创建好的对象引用直接赋值给self
1. 在类的内部,可以使用self来使用属性和调用方法;在类的外部,需要使用对象名来使用属性和调用方法。
1. 如果有多个对象,每个对象的属性是各自保存的,都有各自独立的地址。
1. 方法是所有对象共享的,只占用一份内存空间,方法被调用时会通过self来判断是哪个对象调用了实例方法。
<a name="75585d88"></a>
### __del__方法
创建对象后,python解释器默认调用`__init__()`方法;<br />而当删除对象时,python解释器也会默认调用一个方法,这个方法为`__del__()`方法。

```python
class Student:
    def __init__(self,name,score):
        print('__init__方法被调用了')
        self.name = name
        self.score = score
    def __del__(self):
        print('__del__方法被调用了')
s = Student('lisi',95)
del s
input('请输入内容')

str方法

__str__方法返回对象的描述信息,使用print()函数打印对象时,其实调用的就是这个对象的__str__方法。

class Cat:
    def __init__(self,name,color):
        self.name = name
        self.color = color
tom = Cat('Tom','white')
# 使用 print 方法打印对象时,会调用对象的 __str__ 方法,默认会打印类名和对象的地址名
print(tom)   # <__main__.Cat object at 0x0000021BE3B9C940>

如果想要修改对象的输出的结果,可以重写 __str__ 方法。

class Person:
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def __str__(self):
        return '哈哈'
p  = Person('张三',18)
print(p)   # 哈哈  打印对象时,会自动调用对象的 __str__ 方法

一般情况下,我们在打印一个对象时,可能需要列出这个对象的所有属性。

class Student:
    def __init__(self,name,score):
        self.name = name
        self.score = score
    def __str__(self):
        return '姓名是:{},成绩是{}分'.format(self.name,self.score)
s = Student('lisi',95)
print(s)   # 姓名是:lisi,成绩是95分

repr方法

__repr__方法和__str__方法功能类似,都是用来修改一个对象的默认打印内容。

在打印一个对象时,如果没有重写__str__方法,它会自动来查找__repr__方法。

如果这两个方法都没有,会直接打印这个对象的内存地址。

使用__str____repr__方法,都会修改一个对象转换成为字符串的结果。

一般来说,__str__方法的结果更加在意可读性,而__repr__方法的结果更加在意正确性(例如:datetime模块里的datetime类)

class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score
    def __repr__(self):
        return 'helllo'
class Person:
    def __repr__(self):
        return 'hi'
    def __str__(self):
        return 'good'
s = Student('lisi', 95)
print(s)  # hello
p = Person()
print(p)  # good

call方法

对象后面加括号,触发执行。

class Foo:
    def __init__(self):
        pass
    def __call__(self, *args, **kwargs):
        print('__call__')
obj = Foo()  # 执行 __init__
obj()  # 执行 __call__

内置属性

使用内置函数dir可以查看一个对象支持的所有属性和方法,Python中存在着很多的内置属性。

slots

Python中支持动态属性,可以直接通过语法直接给一个对象添加属性,代码更加的灵活。

但是在某些情况下,我们可能需要对属性进行控制,此时,就可使用__slots__实现。

class Person(object):
    __slots__ = ('name', 'age')
    def __init__(self, name, age):
        self.name = name
        self.age = age
p = Person('张三', 18)
p.name = '李四'
# 对象p只能设置name和age属性,不能再动态添加属性
# p.height = 180 # 报错

doc

表示类的描述信息。

class Foo:
    """ 描述类信息,这是用于看片的神奇 """
    def func(self):
        pass
print(Foo.__doc__)
#输出:类的描述信息

module

__module__ 表示当前操作的对象在那个模块

class Person(object):
    def __init__(self):
        self.name = 'laowang'
from test import Person
obj = Person()
print(obj.__module__)  # 输出 test 即:输出模块


class

__class__ 表示当前操作的对象的类是什么。

class Person(object):
    def __init__(self):
        self.name = 'laowang'
from test import Person
obj = Person()
print(obj.__class__)  # 输出 test.Person 即:输出类

dict

以字典的形式,显示对象所有的属性和方法。

class Province(object):
    country = 'China'
    def __init__(self, name, count):
        self.name = name
        self.count = count
    def func(self, *args, **kwargs):
        print('func')

# 获取类的属性,即:类属性、方法、
print(Province.__dict__)   # 输出:{'__dict__': <attribute '__dict__' of 'Province' objects>, '__module__': '__main__', 'country': 'China', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Province' objects>, 'func': <function Province.func at 0x101897950>, '__init__': <function Province.__init__ at 0x1018978c8>}

obj1 = Province('山东', 10000)

# 获取 对象obj1 的属性
print(obj1.__dict__)      # 输出:{'count': 10000, 'name': '山东'}

# 获取 对象obj2 的属性
obj2 = Province('山西', 20000)
print(obj2.__dict__)      # 输出:{'count': 20000, 'name': '山西'}

字典式操作对象

__getitem_____``_setitem____delitem__,这三个方法,是将对象当做字典一样进行操作。

class Foo(object):
    def __getitem__(self, key):
        print('__getitem__', key)
    def __setitem__(self, key, value):
        print('__setitem__', key, value)
    def __delitem__(self, key):
        print('__delitem__', key)
obj = Foo()

result = obj['k1']      # 自动触发执行 __getitem__

obj['k2'] = 'laowang'   # 自动触发执行 __setitem__

del obj['k1']           # 自动触发执行 __delitem__

实例属性和类属性

在面向对象开发中,使用类创建出来的实例是一个对象,那么,类是否是一个对象呢?

实例属性

通过类创建的对象被称为 实例对象,对象属性又称为实例属性,记录对象各自的数据,不同对象的同名实例属性,记录的数据各自独立,互不干扰。

class Person(object):
    def __init__(self, name, age):
        # 这里的 name 和 age 都属于是实例属性,每个实例在创建时,都有自己的属性
        self.name = name
        self.age = age

# 每创建一个对象,这个对象就有自己的name和age属性
p1 = Person('张三',18)
p2 = Person("李四",20)

类属性

类属性就是类对象所拥有的属性,它被该类的所有实例对象所共有,类属性可以通过类对象或者实例对象访问。

class Dog:
    type = "狗"  # 类属性

dog1 = Dog()
dog2 = Dog()

# 不管是dog1、dog2还是Dog类,都可以访问到type属性
print(Dog.type)  # 结果:狗
print(dog1.type)  # 结果:狗
print(dog2.type)  # 结果:狗

使用场景:

  1. 类的实例记录的某项数据始终保持一致时,则定义类属性。
  2. /实例属性要求每个对象为其单独开辟一份内存空间来记录数据,而类属性为全类所共有 ,仅占用一份内存,更加节省内存空间。

    注意点:

  3. 尽量避免类属性和实例属性同名。如果有同名实例属性,实例对象会优先访问实例属性

class Dog(object):
    type = "狗"  # 类属性
    def __init__(self):
        self.type = "dog"  # 对象属性
# 创建对象
dog1 = Dog()
print(dog1.type)     # 结果为 “dog”   类属性和实例属性同名,使用 实例对象 访问的是 实例属性
  1. 类属性只能通过类对象修改,不能通过实例对象修改

**

class Dog(object):
    type = "狗"  # 类属性

# 创建对象
dog1 = Dog()
dog1.type = "dog"   # 使用 实例对象 创建了对象属性type

print(dog1.type)     # 结果为 “dog”   类属性和实例属性同名,访问的是实例属性
print(Dog.type)      # 结果为 "狗"   访问类属性


# 只有使用类名才能修改类属性
Dog.type = "土狗"
print(Dog.type)  # 土狗


dog2 = Dog()
print(dog2.type)  # 土狗
  1. 类属性也可以设置为 **私有**,前边添加两个下划线。 如:
class Dog(object):
    count = 0  # 公有的类属性
    __type = "狗"  # 私有的类属性

print(Dog.count)       # 正确
print(Dog.__type)      # 错误,私有属性,外部无法访问。

私有属性和方法

在实际开发中,对象的某些属性或者方法可能只希望在对象的内部别使用,而不希望在外部被访问到,这时就可以定义私有属性和私有方法。

定义方法

在定义属性或方法时,在属性名或者方法名前增加两个下划线__,定义的就是私有属性或方法。

class Person:
    def __init__(self,name,age):
        self.name = name
        self.age = age
        self.__money = 2000  # 使用 __ 修饰的属性,是私有属性
    def __shopping(self, cost):
        self.__money -= cost  # __money 只能在对象内部使用
        print('还剩下%d元'%self.__money)
    def test(self):
        self.__shopping(200)  # __shopping 方法也只能在对象内部使用
p = Person('张三',18)
# print(p.__money)   这里会报错,不能直接访问对象内部的私有属性
p.test()
# p.__shopping()  这里会报错,__shopping 只能在对象内部使用,外部无法访问

访问私有属性和方法

私有属性不能直接使用,私有方法不能直接调用。但是,通过一些代码,我们也可以在外部访问一个对象的私有属性和方法。

直接访问

使用方式:在私有属性名或方法名前添加 _类名

class Person:
    def __init__(self,name,age):
        self.name = name
        self.age = age
        self.__money = 2000
    def __shopping(self, cost):
        self.__money -= cost
p = Person('李四',20)

print(p._Person__money)   # 使用对象名._类名__私有属性名   可以直接访问对象的私有属性

p._Person__shopping(100)  # 使用对象名._类名__函数名       可以直接调用对象的私有方法
print(p._Person__money)

注意:在开发中,我们强烈 不建议 使用 对象名._类名__私有属性名 的方式来访问对象的私有属性!

定义方法访问私有变量

在实际开发中,如果对象的变量使用了__ 来修饰,就说明它是一个私有变量,不建议外部直接使用和修改。

如果硬要修改这个属性,可以使用定义getset方法这种方式来实现。

class Person:
    def __init__(self,name,age):
        self.name = name
        self.age = age
        self.__money = 2000    # __money 是私有变量,外部无法访问
    def get_money(self):       # 定义了get_money 方法,在这个方法里获取到 __money
        return self.__money    # 内部可以访问 __money 变量
    def set_money(self,money): # 定义了set_money 方法,在这个方法里,可以修改 __money
        self.__money = money
p = Person('王五',21)
# 外部通过调用 get_money 和 set_money 这两个公开方法获取和修改私有变量
print(p.get_money())
p.set_money(8000)
print(p.get_money())

进阶

类方法、静态方法

类方法

  • 第一个形参是类对象的方法
  • 需要用装饰器 @classmethod来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以cls作为第一个参数。
class Dog(object):
    __type = "狗"
    # 类方法,用classmethod来进行修饰
    @classmethod
    def get_type(cls):
        return cls.__type
print(Dog.get_type())

使用场景:

  • 当方法中 需要使用类对象 (如访问私有类属性等)时,定义类方法
  • 类方法一般和类属性配合使用

静态方法

  • 需要通过装饰器@staticmethod来进行修饰,静态方法既不需要**传递类对象也不需要传递实例对象**(形参没有self/cls)
  • 静态方法 也能够通过 实例对象类对象 去访问。
class Dog(object):
    type = "狗"
    def __init__(self):
        name = None
    # 静态方法    
    @staticmethod
    def introduce():  # 静态方法不会自动传递实例对象和类对象
        print("犬科哺乳动物,属于食肉目..")
dog1 = Dog()
Dog.introduce()    # 可以用 实例对象 来调用 静态方法
dog1.introduce()    # 可以用 类对象 来调用 静态方法

使用场景:

  • 当方法中 既不需要使用实例对象(如实例对象,实例属性),也不需要使用类对象 (如类属性、类方法、创建实例等)时,定义静态方法
  • 取消不需要的参数传递,有利于 减少不必要的内存占用和性能消耗

注意点:

  • 类中定义了同名的方法时,调用方法会执行最后定义的方法
class Dog:
    def demo_method(self):
        print("对象方法")
    @classmethod
    def demo_method(cls):
        print("类方法")
    @staticmethod
    def demo_method():  # 被最后定义
        print("静态方法")
dog1 = Dog()
Dog.demo_method()  # 结果: 静态方法
dog1.demo_method()  # 结果: 静态方法

单例设计模式

newinit

class A(object):
    def __init__(self):
        print("这是 init 方法")
    def __new__(cls):
        print("这是 new 方法")
        return object.__new__(cls)
A()
  • __new__至少要有一个参数cls,代表要实例化的类,此参数在实例化时由Python解释器自动提供
  • __new__必须要有返回值,返回实例化出来的实例,这点在自己实现__new__时要特别注意,可以 返回 父类 __new__出来的实例,或者直接是object的__new__出来的实例
  • __init__有一个参数self,就是这个__new__返回的实例,__init____new__的基础上可以完成一些其它初始化的动作,__init__不需要返回值

    单例设计模式

    举个常见的单例模式例子,我们日常使用的电脑上都有一个回收站,在整个操作系统中,回收站只能有一个实例,整个系统都使用这个唯一的实例,而且回收站自行提供自己的实例。因此回收站是单例模式的应用。
    确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,单例模式是一种对象创建型模式。 ```python

    实例化一个单例

    class Singleton(object): instance = None is_first = True

    def new(cls, age, name):

      if not cls.__instance:
          cls.__instance = object.__new__(cls)
      return cls.__instance
    

    def init(self, age, name):

      if self. __is_first: # 不会再创建第二个对象
          self.age = age
          self.name = name
          Singleton. __is_first = False
    

a = Singleton(18, “张三”) b = Singleton(28, “张三”)

print(id(a)) print(id(b))

print(a.age) # 18 print(b.age) # 18

a.age = 19 print(b.age)

<a name="tdhzE"></a>
## 继承
继承是面向对象软件设计中的一个概念,与多态、封装共为面向对象的三个基本特征。

继承可以使得子类具有父类的属性和方法或者重新定义、追加属性和方法等。

- 在程序中,继承描述的是多个类之间的所属关系。
- 如果一个类A里面的属性和方法可以复用,则可以通过继承的方式,传递到类B里。
- 那么类A就是基类,也叫做父类;类B就是派生类,也叫做子类。
- 传递性:子类拥有父类以及父类的父类中封装的所有属性和方法。

```python
class Animal:
    def __int__(self):
        pass
    """动物类"""
    def sleep(self):
        print('正在睡觉')


class Dog(Animal):
    """Dog类继承自Animal类"""
    def __init__(self):
        pass

class Cat(Animal):  # 定义类时,在括号后面传入父类的类名,表示子类继承父类
    """Cat类继承自Animal类"""
    def __int__(self):
        pass

# Dog 和 Cat 都继承自Animal类,可以直接使用Animal类里的sleep方法
dog = Dog()
dog.sleep()

cat = Cat()
cat.sleep()

在Python中,继承可以分为单继承、多继承和多层继承。
继承对比图示.png

单继承

子类只继承一个父类,子类用于父类的所有的方法和属性。

class 类名(父类名):
    pass
  • 子类继承自父类,可以享受父类中已经封装好的方法,不需要再次定义
  • 子类中应该根据职责,封装子类特有的属性和方法。

    多继承

    子类可以拥有多个父类,并且具有所有父类的属性和方法。
    class 子类名(父类名1,父类名2...)
      pass
    
    如果不同的父类中存在同名的方法,子类对象在调用方法时,会调用哪个父类的方法?

答案

当然,开发中,应该尽量避免这种容易产生混淆的情况。如果多个父类之间存在同名的属性后者方法,应该尽量避免使用多继承。

MRO

  • Python中针对类提供了一个内置属性__mro__可以用来查看方法的搜索顺序。
  • MRO 是method resolution order的简称,主要用于在多继承时判断方法属性的调用顺序。

    print(C.__mro__)
    

    输出结果:

    (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
    
  • 在调用方法时,按照__mro__的输出结果从左至右的顺序查找。

  • 如果再当前类中找到方法,就直接执行,不再向下搜索。
  • 如果没有找到,就顺序查找下一个类中是否有对应的方法,如果找到,就直接执行,不再继续向下搜索。
  • 如果找到了最后一个类,依然没有找到方法,程序就会报错。

    新式类和旧式(经典)类

    object是Python中所有对象的基类,提供了一些内置的属性和方法,可以时候用dir函数查看。

  • 新式类:以object为基类的类,推荐使用

  • 经典类:不以object为基类的类,不推荐使用
  • 在 Python3.x 以后定义类时,如果没有指定父类,这个类会默认继承自 object,所以,python3.x版本定义的类都是新式类。
  • 在Python2.x中定义类时,如果没有指定父类,则不会继承自object.

为了保证代码在Python2.x和Python3.x中都能够运行,在定义类时,如果一个类没有父类,建议统一继承自’object’

class 类名(object):
    pass

内置函数

Python中的身份运算符用来判断两个对象是否相等;isinstance用来判断对象和类之间的关系;issublcass用啊里判断类与类之间的关系。

身份运算符

身份运算符用来比较两个对象的内存地址,看这两个对象是否是同一个对象。

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
p1 = Person('张三', 18)
p2 = Person('张三', 18)
p3 = p1
print(p1 is p2)  # False
print(p1 is p3)  # True

isinstance

instance内置函数,用来判断一个实例对象是否是由某一个类(或者它的子类)实例化创建出来的。

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
class Student(Person):
    def __init__(self, name, age, score):
        super(Student, self).__init__(name, age)
        self.score = score
class Dog(object):
    def __init__(self, name, color):
        self.name = name
        self.color = color
p = Person('tony', 18)
s = Student('jack', 20, 90)
d = Dog('旺财', '白色')
print(isinstance(p, Person))  # True.对象p是由Person类创建出来的
print(isinstance(s, Person))  # True.对象s是有Person类的子类创建出来的
print(isinstance(d, Person))  # False.对象d和Person类没有关系

issubclass

issubclass 用来判断两个类之间的继承关系。

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
class Student(Person):
    def __init__(self, name, age, score):
        super(Student, self).__init__(name, age)
        self.score = score
class Dog(object):
    def __init__(self, name, color):
        self.name = name
        self.color = color
print(issubclass(Student, Person))  # True
print(issubclass(Dog, Person))  # False

多态

面向对象的三大特性:

  • 封装:这是定义类的准则,根据对象的特点,将行为和属性抽象出来,封装到一个类中。
  • 继承:这是设计类的技巧。父类与子类,主要体现在代码的重用,不需要大量的编写重复代码。
  • 多态:不同的子类调用相同的父类方法,产生不同的执行结果,可以增加代码的外部灵活度。多态是以继承和重写父类方法为前提的,它是一种调用方法的技巧,不会影响到类的内部设计。

    场景

  • 提供三个类:缉毒犬、军犬、人

  • 缉毒犬—>追查毒品,军犬—>攻击假人,人—>让小狗干活
  • 设计类来完成功能。

没有使用多态.png

class ArmyDog(object):
    def bite_enemy(self):
        print('追击敌人')

class DrugDog(object):
    def track_drug(self):
        print('追查毒品')

class Person(object):
    def work_with_army(self, dog):
        dog.bite_enemy()
    def work_with_drug(self, dog):
        dog.track_drug()

ad = ArmyDog()
dd = DrugDog()

p = Person()
p.work_with_army(ad)
p.work_with_drug(dd)

思考:这段代码设是否有问题?
新增需求:此时,又多了一个犬种,就又需要在Person类里新建一个方法,让这个方法操作新的狗。

class XiaoTianDog(object):
    def eat_moon(self):
        print('哮天犬把月亮吃了')


class Person(object):
    def work_with_xiaotian(self, dog):  # 添加方法
        dog.eat_moon()

Person 类总是不断的添加新的功能,每次都需要改动Person类的源码,程序的扩展性太差了!

  • 最好是提供一个父类 Dog,具备 work 的功能,其他小狗继承它,这样只要是小狗类,则行为被统一起来了,我们人类完全可以保证,只要是小狗的子类,找它干活肯定不会有问题。
  • 这样人只要一个方法就能逗任意种类的狗玩,哪怕是添加新的狗,人的类都不需要修改。

图示如下:

继承方法的重写.png

class Dog(object):
    def work(self):  # 父类提供统一的方法,哪怕是空方法
        pass

class ArmyDog(Dog):   # 继承 Dog
    def work(self):  # 子类重写方法,并且处理自己的行为
        print('追击敌人')

class DrugDog(Dog):
    def work(self):
        print('追查毒品')

class Person(object):
    def work_with_dog(self, dog):
        dog.work()    # 使用小狗可以根据对象的不同而产生不同的运行效果, 保障了代码的稳定性

# 子类对象可以当作父类来使用
dog = Dog()
ad = ArmyDog()
dd = DrugDog()


p = Person()
p.work_with_dog(dog)
p.work_with_dog(ad)  # 同一个方法,只要是 Dog 的子类就可以传递,提供了代码的灵活性
p.work_with_dog(dd)  # 并且传递不同对象,最终 work_with_dog 产生了不同的执行效果

最终效果

  • Person 类中只需要调用 Dog 对象 work() 方法,而不关心具体是 什么狗
  • work() 方法是在 Dog 父类中定义的,子类重写并处理不同方式的实现
  • 在程序执行时,传入不同的 Dog 对象作为实参,就会产生不同的执行效果

    总结

  • 定义:多态是一种使用对象的方式,子类重写父类方法,调用不同子类对象的相同父类方法,可以产生不同的执行结果

  • 好处:调用灵活,有了多态,更容易编写出通用的代码,做出通用的编程,以适应需求的不断变化!
  • 实现步骤:
    • 定义父类,并提供公共方法
    • 定义子类,并重写父类方法
    • 传递子类对象给调用者,可以看到不同子类执行效果不同