Python面向对象

Python面向对象

  • 1. 面向对象基础语法
  • 2. 初始化方法__init__
  • 3. 属性查找与绑定方法
  • 4. 案例
    • 4.1 跑步案例
    • 4.2 家具案例
  • 6. 私有属性
  • 7. 继承
    • 7.1 面向对象的三大特性
    • 7.2 单继承
      • 7.2.1 继承的概念
      • 7.2.2 继承的语法
      • 7.2.3 方法的重写
      • 7.2.4 父类的私有属性和私有方法
    • 7.3 多继承
      • 7.3.1 概念及语法
      • 7.3.2 多继承使用注意事项
      • 7.3.3 新式类与旧式(经典)类
      • 7.3.4 继承的实现原理
  • 8. 多态
    • 8.1 多态案例演练
    • 8.2 案例总结
  • 9. 类属性和类方法
    • 9.1 类的结构
      • 9.1.1 术语——实例
      • 9.1.2 类是一个特殊的对象
    • 9.2 类属性和实例属性
      • 9.2.1 概念和使用
      • 9.2.2 属性的获取机制
    • 9.3 类方法和静态方法
      • 9.3.1 类方法
      • 9.3.2 静态方法
      • 9.3.3 方法综合案例
      • 9.3.4 案例小结

1. 面向对象基础语法

# -*- coding: utf-8 -*-
# @Time    : 2019/12/9 14:33
# @Author  : 我就是任性-Amo
# @FileName: 1.面向对象基础语法.py
# @Software: PyCharm
# @Blog    :https://blog.csdn.net/xw1680
"""
1.类: 类是对一群具有相同特征或者行为的事物的一个统称,是抽象的,不能直接使用
    特征被称为属性
    行为被称为方法
类就相当于制造飞机时的图纸,是一个模板,是复制创建对象的

2.对象: 对象是由类创建出来的一个具体存在,可以直接使用
    由哪一个类创建出来的对象,就拥有在哪一个类中定义的: 属性和方法
对象就相当于用图纸制造的飞机
在程序开发中,应该先有类,再有对象

3.类和对象的关系
    类是模板,对象是根据类这个模板创建出来的,应该先有类,再有对象
    类只有一个,而对象可以有很多个
    不同的对象之间属性 可能会各不相同
    类中定义了什么属性和方法,对象中就有什么属性和方法,不可能多,也不可能少

4.类的设计
    在程序开发中,要设计一个类,通常需要满足一下三个要素:
        类名: 这类事物的名字,满足大驼峰命名法(每一个单词的首字母大写并且单词与单词之间没有下划线)
        属性: 这类事物具有什么样的特征
        方法: 这类事物具有什么样的行为
    4.1 类名的确定
        名词提炼法 分析整个业务流程,出现的名词,通常就是找到的类
    4.2 属性和方法的确定
        对对象的特征描述,通常可以定义成属性
        对象具有的行为(动词),通常可以定义成方法
        提示: 需求中没有涉及的属性或者方法在设计类时,不需要考虑
"""


# 第一个面向对象程序: 小猫爱吃鱼 小猫要喝水
# 这里的话按照需求首先是不需要定义属性的
class Cat:
    """这是一个猫类"""

    def eat(self):
        # self: 由哪一个对象调用的方法,方法内的self就是哪一个对象的引用
        # 1.在类封装的方法内部,self就表示当前调用方法的对象自己
        # 2.调用方法时,我们是不需要传递self参数的 解释器会自动帮我们传入
        # 3.在方法内部 可以通过self.访问对象的属性 也可以通过self.调用其他的对象方法
        print("%s 爱吃鱼" % self.name)

    def drink(self):
        print("小猫在喝水")


# 创建对象
tom = Cat()
# tom.eat()  # 在设置属性之前 调用方法报错
tom.drink()
# 在类的外部是可以直接通过.的方式去给对象添加属性的,但是不推荐
# 因为对象属性的封装应该封装在类的内部
tom.name = "Tom"
# print(tom.name)
tom.eat()  # tom.eat(tom)
# print(tom.__dict__)  # 可以查看对象绑定了哪些属性
# print(Cat.__dict__)  # 可以查看类绑定了哪些命名空间

lazy_cat = Cat()
lazy_cat.name = "大懒猫"
lazy_cat.eat()
# 在类的外部通过变量名.访问对象的属性和方法
# 在类的封装方法中,通过self.访问对象的属性和方法

2. 初始化方法__init__

# -*- coding: utf-8 -*-
# @Time    : 2019/12/9 14:40
# @Author  : 我就是任性-Amo
# @FileName: 2.初始化方法.py
# @Software: PyCharm
# @Blog    :https://blog.csdn.net/xw1680

"""
初始化方法:
    1.当使用类名()创建对象时,会自动执行以下操作:
      为对象在内存中分配空间--创建对象
      为对象的属性设置初始值--初始化方法(init)
    2.__init__方法是专门用来定义一个类具有哪些属性的方法
"""


# 在Cat中增加__init__方法,验证该方法在创建对象时会被调用
class Cat:
    """这是一个猫类"""

    # def __init__(self):
    #     print("这是一个初始化方法")
    #     # 定义用Cat类创建的猫对象都有一个name的属性
    #     self.name = "Tom"

    # 在开发中,如果希望在创建对象的同时,就设置对象的属性,可以对_init__方法进行改造
    # 把希望设置的属性值,定义成__init__方法的参数,在方法内部使用
    # self.属性 = 形参 接收外部传递的参数
    # 在创建对象时,使用类名(属性1, 属性2...)

    def __init__(self, name):
        self.name = name

    def eat(self):
        print("%s 爱吃鱼" % self.name)

    # 如果在开发中,希望使用print输出对象变量时,能够打印自定义的内容,就可以利用__str__这个内置方法了
    def __str__(self):
        return "我是小猫: %s" % self.name

    # def __del__(self):
    # __del__:如果希望在对象被销毁前,再做一些事情,可以考虑此方法
    def __del__(self):
        print("准备删除~~~")


tom = Cat("Tom")  # 创建对象时 会自动调用初始化__init__方法
tom.eat()
lazy_cat = Cat("大懒猫")
lazy_cat.eat()
# 在Python中,使用print输出对象变量,默认情况下,会输出这个变量引用的对象是由哪一个类创建的对象,
# 以及在内存中的地址(十六进制表示)
print(tom)  # <__main__.Cat object at 0x103959bd0>

del lazy_cat
print("-" * 50)

3. 属性查找与绑定方法

# -*- coding: utf-8 -*-
# @Time    : 2019/12/9 15:00
# @Author  : 我就是任性-Amo
# @FileName: 3.属性查找与绑定方法.py
# @Software: PyCharm
# @Blog    :https://blog.csdn.net/xw1680


# 属性查找与绑定方法

class Person:
    """这是一个猫类"""

    # nationality = "中国"

    # def __init__(self, name, age, address, nationality):
    #     self.name = name
    #     self.age = age
    #     self.address = address
    #     self.nationality = nationality 给对象绑定国籍属性
    def __init__(self, name, age, address):
        self.name = name
        self.age = age
        self.address = address

    def eat(self):
        print(f"{self.name}正在吃饭~~~")


# 1.分别创建amo对象和paul对象
# amo = Person("amo", 18, "重庆市沙坪坝区", "美国")
# paul = Person("paul", 22, "重庆市沙坪坝区", "中国")
# 属性: 首先会从对象本身先去查找,如果对象本身没有绑定该属性,则会从类中去找
# print(id(amo.nationality))  # 4406863312
# print(id(paul.nationality))  # 4406863504

amo = Person("amo", 18, "重庆市沙坪坝区")
paul = Person("paul", 22, "重庆市沙坪坝区")
# 如果对象身上没有绑定该属性 则去在类中查找 类属性是被所有对象共用的 所以返回的地址值是一致的.
# print(id(amo.nationality))  # 4461503952
# print(id(paul.nationality))  # 4461503952
# 如果在类中查找该属性 还是没有 程序就会报错
# print(id(paul.nationality))  # AttributeError: 'Person' object has no attribute 'nationality'


# 函数属性:是绑定给对象使用的,绑定到不同的对象是不同的绑定方法,对象调用绑定方法时,会把对象本身作为一个参数进行传入,传给self
print(amo.eat)  # >
print(paul.eat)  # >
# 可以看到 两个的绑定方法的内存地址值是不一致的

print(Person.__dict__)
print(id(Person.eat(amo)))  # 4393136216
print(id(Person.eat(paul)))  # 4393136216

4. 案例

4.1 跑步案例

# -*- coding: utf-8 -*-
# @Time    : 2019/12/10 16:28
# @Author  : 我就是任性-Amo
# @FileName: 4.练习1.py
# @Software: PyCharm
# @Blog    :https://blog.csdn.net/xw1680

# 封装是面向对象编程的一大特点
# 面向对象编程的第一步:将属性和方法封装到一个抽象的类中
# 外界使用类创建对象,然后让对象调用方法
# 对象方法的细节都被封装在类的内部

"""
需求:amo和jerry都爱跑步
amo体重75.0公斤
jerry体重45.0 公斤
每次跑步都会减少0.5公斤
每次吃东西都会增加1公斤
"""


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

    def run(self):
        self.weight -= 0.5
        print("%s 爱锻炼,跑步锻炼身体" % self.name)

    def eat(self):
        self.weight += 1
        # 注意: 在对象的方法内部,是可以直接访问对象的属性的
        print("%s 是吃货,吃完这顿在减肥" % self.name)

    def __str__(self):
        return "我的名字是%s,体重%.2f公斤" % (self.name, self.weight)


amo = Person("amo", 75.0)
amo.run()
amo.eat()
amo.eat()
print(amo)
print("-" * 50)
jerry = Person("jerry", 45.0)  # 同一个类创建的多个对象之间,属性互不干扰
jerry.run()
jerry.eat()
jerry.eat()
print(jerry)

代码运行结果如下:
Python面向对象_第1张图片

4.2 家具案例

# -*- coding: utf-8 -*-
# @Time    : 2019/12/20 09:26
# @Author  : 我就是任性-Amo
# @FileName: 5.家具案例.py
# @Software: PyCharm
# @Blog    :https://blog.csdn.net/xw1680

"""
需求:
    1.房子(House)有户型、总面积和家具名称列表
        新房子没有任何的家具
    2.家具(HouseItem)有名字和占地面积,其中
        席梦思(bed)占地4平米
        衣柜(chest)占地2平米
        餐桌(table)占地1.5平米
    3.将以上三件家具添加到房子中
    4.打印房子时,要求输出:户型、总面积、剩余面积、家具名称列表
"""


# 分析: 要去添加家具 首先肯定要有一个家具类 被使用的类 通常应该先被开发
class HouseItem:
    def __init__(self, name, area):
        self.name = name  # 家具名称
        self.area = area  # 占地面积

    def __str__(self):
        return "[%s] 占地面积 %.2f" % (self.name, self.area)


bed = HouseItem("bed", 4)  # 席梦思
chest = HouseItem("chest", 2)  # 衣柜
table = HouseItem("table", 1.5)  # 餐桌
print(bed)
print(chest)
print(table)


class House:

    def __init__(self, apartment, area):
        self.apartment = apartment  # 户型
        self.total_area = area  # 总面积
        self.free_area = area  # 剩余面积开始的时候和总面积是一样的
        self.furniture_list = []  # 家具名称列表 默认是没有放置任何家具的

    def add_item(self, furniture):
        """添加家具"""
        print(f"添加:{furniture.name}")
        # 判断家具的面积是否超过剩余面积,如果超过,提示不能添加这件家具
        if furniture.area > self.free_area:
            print("房间剩余面积不够,不能添置这件家具...")
            return
        self.furniture_list.append(furniture.name)  # 将家具名称追加到家具名称列表中
        self.free_area -= furniture.area  # 用房子的剩余面积-家具面积

    def __str__(self):
        return f"房子户型为:{self.apartment},总面积为:{self.total_area}," \
               f"剩余面积为{self.free_area},家具列表为:{self.furniture_list}"


if __name__ == "__main__":
    my_house1 = House("两室一厅", 60)
    my_house1.add_item(bed)
    my_house1.add_item(chest)
    my_house1.add_item(table)
    print(my_house1)

    my_house2 = House("一室一厅", 10)
    my_house2.add_item(bed)
    my_house2.add_item(chest)
    my_house2.add_item(table)
    my_house2.add_item(bed)
    print(my_house2)

代码运行结果如下:
Python面向对象_第2张图片

6. 私有属性

# -*- coding: utf-8 -*-
# @Time    : 2019/12/20 10:08
# @Author  : 我就是任性-Amo
# @FileName: 6.私有属性.py
# @Software: PyCharm
# @Blog    :https://blog.csdn.net/xw1680

"""
应用场景:
  在实际开发中,对象的某些属性或方法可能只希望在对象的内部被使用,而不希望在外部被访问到
  私有属性就是对象不希望公开的属性
  私有方法就是对象不希望公开的方法
定义的方式: 在定义属性或方法时,在属性名或者方法名前增加两个下划线,定义的就是私有属性或方法
在java中的话是使用private关键字
"""


class Women:
    def __init__(self, name):
        self.name = name
        self.__age = 18

    def __secret(self):
        print("我的年龄是 %d" % self.__age)


xiao_fang = Women("小芳")
# 私有属性外界不能直接访问
# print(xiao_fang.__age) 报错:AttributeError: 'Women' object has no attribute '__age'
# 私有方法,外部不嫩直接调用
# xiao_fang.__secret()

# 在日常开发中,不要使用这种方式,访问对象的私有属性或私有方法
# 在Python中,并没有真正意义的私有
# 在给属性、方法 命名时,实际是对名称做了一些特殊处理,使得外界无法访问到
# 处理方式:在名称前面加上 _类名 => _类名__名称


print(xiao_fang.__dict__)  # 查看名称空间
print(xiao_fang._Women__age)
# print(Women.__dict__)
xiao_fang._Women__secret()

程序运行结果如下:
Python面向对象_第3张图片

7. 继承

7.1 面向对象的三大特性

  • 封装: 根据职责将属性和方法封装到一个抽象的类中
  • 继承: 实现代码的重用,相同的代码不需要重复的编写
  • 多态: 不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活度

7.2 单继承

7.2.1 继承的概念

继承的概念: 子类拥有父类的所有方法和属性

Python面向对象_第4张图片

# ---------不使用继承开发动物和狗---------------
class Animal:

    def eat(self):
        print("吃")

    def drink(self):
        print("喝")

    def run(self):
        print("跑")

    def sleep(self):
        print("睡")


class Dog:

    def eat(self):  # 不使用继承重复书写的代码太多
        print("吃")

    def drink(self):
        print("喝")

    def run(self):
        print("跑")

    def sleep(self):
        print("睡")

    def bark(self):
        print("汪汪叫")


# 创建一个狗对象
wang_cai = Dog()

wang_cai.eat()
wang_cai.drink()
wang_cai.run()
wang_cai.sleep()
wang_cai.bark()
# # ---------使用继承开发动物和狗---------------
class Animal:
    def eat(self):
        print("吃---")

    def drink(self):
        print("喝---")

    def run(self):
        print("跑---")

    def sleep(self):
        print("睡---")


class Dog(Animal):
    def bark(self):
        print("汪汪叫---")


# 创建一个狗对象
wang_cai = Dog()
wang_cai.eat()
wang_cai.drink()
wang_cai.run()
wang_cai.sleep()
wang_cai.bark()  # 直接享受父类中已经封装好的方法 不需要再次开发

7.2.2 继承的语法

class 类名(父类名):
	pass
  1. 子类继承自父类,可以直接享受父类中已经封装好的方法,不需要再次开发
  2. 子类中应该根据职责,封装子类特有的属性和方法
  3. 专业术语
    • Dog类是Animal类的子类,Animal类是Dog类的父类,Dog类从Animal类继承
    • Dog类是Animal类的派生类,Animal类是Dog类的基类,Dog类从Animal类派生
  4. 继承的传递性
    C类从B类继承,B类又从A类继承,那么C类就具有B类和A类的所有属性和方法
# -*- coding: utf-8 -*-
# @Time    : 2019/12/21 10:52
# @Author  : 我就是任性-Amo
# @FileName: 9.继承的传递性.py
# @Software: PyCharm
# @Blog    :https://blog.csdn.net/xw1680


class Animal:

    def eat(self):
        print("吃---")

    def drink(self):
        print("喝---")

    def run(self):
        print("跑---")

    def sleep(self):
        print("睡---")


class Dog(Animal):

    def bark(self):
        print("汪汪叫")


class XiaoTianQuan(Dog):

    def fly(self):
        print("我会飞")


# 创建一个哮天犬的对象
xtq = XiaoTianQuan()

xtq.fly()
xtq.bark()
xtq.eat()

程序运行结果如下:
Python面向对象_第5张图片

7.2.3 方法的重写

  1. 子类拥有父类的所有方法和属性
  2. 子类继承自父类,可以直接享受父类中已经封装好的方法,不需要再次开发
  3. 应用场景: 当父类的方法实现不能满足子类需求时,可以对方法进行 重写(override)
    Python面向对象_第6张图片
  4. 重写父类的方法有两种情况
  • 覆盖父类的方法

如果在开发中,父类的方法实现和子类的方法实现,完全不同。就可以使用覆盖的方式,在子类中重新编写父类的方法实现。具体的实现方式,就相当于在子类中定义了一个和父类同名的方法并且实现。重写之后,在运行时,只会调用子类中重写的方法,而不再会调用父类封装的方法

# -*- coding: utf-8 -*-
# @Time    : 2019/12/21 10:55
# @Author  : 我就是任性-Amo
# @FileName: 10.覆盖父类方法.py
# @Software: PyCharm
# @Blog    :https://blog.csdn.net/xw1680


class Animal:

    def eat(self):
        print("吃---")

    def drink(self):
        print("喝---")

    def run(self):
        print("跑---")

    def sleep(self):
        print("睡---")


class Dog(Animal):

    def bark(self):
        print("汪汪叫")


class XiaoTianQuan(Dog):

    def fly(self):
        print("我会飞")

    def bark(self):
        print("叫得跟神一样...")


xtq = XiaoTianQuan()

# 如果子类中,重写了父类的方法
# 在使用子类对象调用方法时,会调用子类中重写的方法
xtq.bark()
  • 对父类方法进行扩展

如果在开发中,子类的方法实现中包含父类的方法实现。父类原本封装的方法实现是子类方法的一部分。就可以使用扩展的方式:
1. 在子类中重写父类的方法
2. 在需要的位置使用super().父类方法来调用父类方法的执行
3. 代码其他的位置针对子类的需求,编写子类特有的代码实现

  1. 关于super
    • Pythonsuper是一个特殊的类
    • super()就是使用super类创建出来的对象
    • 最常使用的场景就是在重写父类方法时,调用在父类中封装的方法实现
    • 在Python 2.x时,如果需要调用父类的方法,还可以使用以下方式:父类名.方法(self)
    • 这种方式,目前在 Python 3.x 还支持这种方式
    • 这种方法不推荐使用,因为一旦父类发生变化,方法调用位置的类名同样需要修改
    • 在开发时,父类名和 super()两种方式不要混用
    • 如果使用当前子类名调用方法,会形成递归调用,出现死循环
# -*- coding: utf-8 -*-
# @Time    : 2019/12/21 11:03
# @Author  : 我就是任性-Amo
# @FileName: 11.扩展父类方法.py
# @Software: PyCharm
# @Blog    :https://blog.csdn.net/xw1680


class Animal:

    def eat(self):
        print("吃---")

    def drink(self):
        print("喝---")

    def run(self):
        print("跑---")

    def sleep(self):
        print("睡---")


class Dog(Animal):

    def bark(self):
        print("汪汪叫")


class XiaoTianQuan(Dog):

    def fly(self):
        print("我会飞")

    def bark(self):
        # 1.针对子类特有的需求,编写代码
        print("神一样的叫唤...")
        # 2.使用super().调用原本在父类中封装的方法
        # super().bark()
        # 父类名.方法(self)
        Dog.bark(self)  # 但是一旦父类名改变 这里也要随之改变
        # 注意: 如果使用子类调用方法,会出现递归调用-死循环!
        # XiaoTianQuan.bark(self)
        # 3. 增加其他子类的代码
        print("$%^*%^$%^#%$%")


xtq = XiaoTianQuan()

# 如果子类中,重写了父类的方法
# 在使用子类对象调用方法时,会调用子类中重写的方法
xtq.bark()

程序运行结果如下:
Python面向对象_第7张图片

7.2.4 父类的私有属性和私有方法

  • 子类对象不能在自己的方法内部,直接访问父类的私有属性或私有方法
  • 子类对象可以通过父类的公有方法间接访问到私有属性或私有方法
  • 示例:
    Python面向对象_第8张图片
  • B的对象不能直接访问 __num2属性
  • B的对象不能在demo方法内访问 __num2属性
  • B的对象可以在demo方法内,调用父类的test方法
  • 父类的test方法内部,能够访问__num2属性和__test方法
# -*- coding: utf-8 -*-
# @Time    : 2019/12/21 11:11
# @Author  : 我就是任性-Amo
# @FileName: 13.父类的公有方法.py
# @Software: PyCharm
# @Blog    :https://blog.csdn.net/xw1680


class A:

    def __init__(self):
        self.num1 = 100
        self.__num2 = 200

    def __test(self):
        print("私有方法 %d %d" % (self.num1, self.__num2))

    def test(self):
        print("父类的公有方法 %d" % self.__num2)

        self.__test()


class B(A):

    def demo(self):
        # 1. 在子类的对象方法中,不能访问父类的私有属性
        # print("访问父类的私有属性 %d" % self.__num2)

        # 2. 在子类的对象方法中,不能调用父类的私有方法
        # self.__test()

        # 3. 访问父类的公有属性
        print("子类方法 %d" % self.num1)

        # 4. 调用父类的公有方法
        self.test()
        pass


# 创建一个子类对象
b = B()
print(b)

b.demo()
# 在外界访问父类的公有属性/调用公有方法
# print(b.num1)
# b.test()

# 在外界不能直接访问对象的私有属性/调用私有方法
# print(b.__num2)
# b.__test()

7.3 多继承

7.3.1 概念及语法

  • 子类可以拥有多个父类,并且具有所有父类的属性和方法
  • 例如:孩子会继承自己 父亲和母亲的特性
    Python面向对象_第9张图片
  • 语法:
class 子类名(父类名1, 父类名2...):
	pass
class A:

    def test(self):
        print("test 方法")


class B:

    def demo(self):
        print("demo 方法")


class C(A, B):
    """多继承可以让子类对象,同时具有多个父类的属性和方法"""
    pass


# 创建子类对象
c = C()

c.test()
c.demo()

7.3.2 多继承使用注意事项

  • 如果不同的父类中存在同名的方法,子类对象在调用方法时,会调用哪一个父类中的方法呢?
提示:开发时,应该尽量避免这种容易产生混淆的情况。
如果父类之间存在同名的属性或者方法,应该尽量避免使用多继承

Python面向对象_第10张图片

class A:

    def test(self):
        print("A --- test 方法")

    def demo(self):
        print("A --- demo 方法")

class B:

    def test(self):
        print("B --- test 方法")

    def demo(self):
        print("B --- demo 方法")


class C(B, A):
    """多继承可以让子类对象,同时具有多个父类的属性和方法"""
    pass


# 创建子类对象
c = C()

c.test()
c.demo()

# 确定C类对象调用方法的顺序
print(C.__mro__)

7.3.3 新式类与旧式(经典)类

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

在Python3中,使用dir函数查看效果图如下:
在这里插入图片描述
在Python2中,使用dir函数查看效果如下:
Python面向对象_第11张图片

  • 新式类:以object为基类的类,推荐使用
  • 经典类:不以object为基类的类,不推荐使用
  • 在Python3.x中定义类时,如果没有指定父类,会默认使用object 作为该类的基类——Python 3.x 中定义的类都是新式类
  • 在Python2.x中定义类时,如果没有指定父类,则不会以object作为基类
  • 为了保证编写的代码能够同时在Python 2.x和Python3.x运行!今后在定义类时,如果没有父类,建议统一继承自object

7.3.4 继承的实现原理

python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如

(, , , , , , )

为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:

  • 子类会先于父类被检查
  • 多个父类会根据它们在列表中的顺序被检查
  • 如果对下一个类存在两个合法的选择,选择第一个父类
  • 在Java和C#中子类只能继承一个父类,而Python中子类可以同时继承多个父类,如果继承了多个父类,那么属性的查找方式有两种,分别是:深度优先和广度优先
    Python面向对象_第12张图片
    Python面向对象_第13张图片
    示例代码:
# -*- coding: utf-8 -*-
# @Time    : 2019/12/24 09:30
# @Author  : 我就是任性-Amo
# @FileName: 15.新式类与经典类.py
# @Software: PyCharm
# @Blog    :https://blog.csdn.net/xw1680


class A(object):
    def test(self):
        print('from A')


class B(A):
    def test(self):
        print('from B')


class C(A):
    def test(self):
        print('from C')


class D(B):
    def test(self):
        print('from D')


class E(C):
    def test(self):
        print('from E')


class F(D, E):
    # def test(self):
    #     print('from F')
    pass


f1 = F()
f1.test()
print(F.__mro__)  # 只有新式才有这个属性可以查看线性列表,经典类没有这个属性
# import inspect  # 使用inspect模块中的getmro()方法可以查看python2.x的mro顺序

MRO顺序如下:

# 新式类继承顺序如下:
# , 
# , 
# , 
# , 
# , 
# , 
# 

# 经典类继承顺序如下:
# ,
# ,
# ,
# ,
# ,
# 

8. 多态

不同的子类对象调用相同的父类方法,产生不同的执行结果

  • 多态可以增加代码的灵活度
  • 以继承和重写父类方法为前提
  • 是调用方法的技巧,不会影响到类的内部设计
    Python面向对象_第14张图片

8.1 多态案例演练

  • 在Dog类中封装方法 game 普通狗只是简单的玩耍
  • 定义XiaoTianDog继承自Dog,并且重写game方法 哮天犬需要在天上玩耍
  • 定义 Person 类,并且封装一个和狗玩的方法 在方法内部,直接让狗对象调用game方法Python面向对象_第15张图片
# -*- coding: utf-8 -*-
# @Time    : 2019/12/24 15:20
# @Author  : 我就是任性-Amo
# @FileName: 17.多态.py
# @Software: PyCharm
# @Blog    :https://blog.csdn.net/xw1680


class Dog(object):

    def __init__(self, name):
        self.name = name

    def game(self):
        print("%s 蹦蹦跳跳的玩耍..." % self.name)


class XiaoTianDog(Dog):

    def game(self):
        print("%s 飞到天上去玩耍..." % self.name)


class Person(object):

    def __init__(self, name):
        self.name = name

    def game_with_dog(self, dog):
        print("%s 和 %s 快乐的玩耍..." % (self.name, dog.name))

        # 让狗玩耍
        dog.game()


# 1. 创建一个狗对象
# wang_cai = Dog("旺财")
wang_cai = XiaoTianDog("飞天旺财")

# 2. 创建一个小明对象
xiao_ming = Person("小明")

# 3. 让小明调用和狗玩的方法
xiao_ming.game_with_dog(wang_cai)

程序运行结果如下:
Python面向对象_第16张图片

8.2 案例总结

  1. Person类中只需要让狗对象调用game方法,而不关心具体是什么狗 game方法是在Dog父类中定义的
  2. 在程序执行时,传入不同的狗对象实参,就会产生不同的执行效果
  3. 多态更容易编写出通用的代码,做出通用的编程,以适应需求的不断变化!

9. 类属性和类方法

9.1 类的结构

9.1.1 术语——实例

  1. 使用面相对象开发,第1步是设计类
  2. 使用类名()创建对象,创建对象的动作有两步:
    • 在内存中为对象分配空间
    • 调用初始化方法__init__为对象初始化
  3. 对象创建后,内存中就有了一个对象的实实在在的存在—实例
    Python面向对象_第17张图片
    因此,通常也会把:
    1. 创建出来的对象叫做类的实例
    2. 创建对象的动作叫做实例化
    3. 对象的属性叫做实例属性
    4. 对象调用的方法叫做实例方法

在程序执行时:对象各自拥有自己的实例属性 调用对象方法,可以通过 self.访问自己的属性和调用自己的方法

结论:

  • 每一个对象都有自己独立的内存空间,保存各自不同的属性
  • 多个对象的方法,在内存中只有一份,在调用方法时,需要把对象的引用传递到方法内部

9.1.2 类是一个特殊的对象

Python中一切皆对象,class AAA: 定义的类属于类对象 obj1 = AAA() 属于实例对象

  • 在程序运行时,类同样会被加载到内存
  • 在Python中,类是一个特殊的对象–类对象
  • 在程序运行时,类对象在内存中只有一份,使用一个类可以创建出很多个对象实例
  • 除了封装实例的属性和方法外,类对象还可以拥有自己的属性和方法
    • 类属性
    • 类方法
  • 通过类名.的方式可以访问类的属性或者调用类的方法
    Python面向对象_第18张图片

9.2 类属性和实例属性

9.2.1 概念和使用

  • 类属性就是给类对象中定义的属性
  • 通常用来记录与这个类相关的特征
  • 类属性不会用于记录具体对象的特征

示例需求:

  1. 定义一个工具类
  2. 每件工具都有自己的name
  3. 需求——知道使用这个类,创建了多少个工具对象?

Python面向对象_第19张图片

class Tool(object):

    # 使用赋值语句,定义类属性,记录创建工具对象的总数
    count = 0

    def __init__(self, name):
        self.name = name

        # 针对类属性做一个计数+1
        Tool.count += 1


# 创建工具对象
tool1 = Tool("斧头")
tool2 = Tool("榔头")
tool3 = Tool("铁锹")

# 知道使用 Tool 类到底创建了多少个对象?
print("现在创建了 %d 个工具" % Tool.count)

Python面向对象_第20张图片

9.2.2 属性的获取机制

在Python中属性的获取存在一个向上查找机制

Python面向对象_第21张图片
因此,要访问类属性有两种方式:

  • 类名.类属性
  • 对象.类属性 (不推荐)

如果使用对象.类属性 = 值赋值语句,只会给对象添加一个属性,而不会影响到类属性的值

9.3 类方法和静态方法

9.3.1 类方法

  • 类属性就是针对类对象定义的属性
    • 使用赋值语句在class关键字下方可以定义类属性
    • 类属性用于记录与这个类相关的特征
  • 类方法就是针对类对象定义的方法
    • 在类方法内部可以直接访问类属性或者调用其他的类方法
  • 语法如下
@classmethod
def 类方法名(cls):
	pass
  • 类方法需要用@classmethod来标识,告诉解释器这是一个类方法
  • 类方法的第一个参数应该是cls
    • 由哪一个类调用的方法,方法内的cls就是哪一个类的引用
    • 这个参数和实例方法的第一个参数是self类似
    • 提示使用其他名称也可以,不过习惯使用cls
  • 通过类名.调用类方法,调用方法时,不需要传递cls参数
  • 在方法内部
    • 可以通过cls.访问类的属性
    • 也可以通过cls.调用其他的类方法
      Python面向对象_第22张图片
# 定义一个工具类 每件工具都有自己的name 需求—在类封装一个show_tool_count的类方法,输出使用当前这个类,创建的对象个数
@classmethod
def show_tool_count(cls):
	"""显示对象工具的总数"""
	print("工具对象的总数 %d" % cls.count)
# 在类方法内部,可以直接使用cls访问类属性或者调用类方法

9.3.2 静态方法

  • 在开发时,如果需要在类中封装一个方法,这个方法:
    • 既不需要访问实例属性或者调用实例方法
    • 也不需要访问类属性或者调用类方法
  • 这个时候,可以把这个方法封装成一个静态方法
  • 语法如下
@staticmethod
def 静态方法名():
    pass
  • 静态方法需要用@staticmethod来标识,告诉解释器这是一个静态方法
  • 通过类名. 调用静态方法
class Dog(object):
    
    # 狗对象计数
    dog_count = 0
    
    @staticmethod
    def run():
        
        # 不需要访问实例属性也不需要访问类属性的方法
        print("狗在跑...")

    def __init__(self, name):
        self.name = name

9.3.3 方法综合案例

  • 设计一个Game类
  • 属性:
    • 定义一个类属性top_score记录游戏的历史最高分
    • 定义一个实例属性player_name记录当前游戏的玩家姓名
  • 方法:
    • 静态方法show_help显示游戏帮助信息
    • 类方法show_top_score显示历史最高分
    • 实例方法start_game开始当前玩家的游戏
  • 主程序步骤
    1. 查看帮助信息
    2. 查看历史最高分
    3. 创建游戏对象,开始游戏Python面向对象_第23张图片
class Game(object):

    # 游戏最高分,类属性
    top_score = 0

    @staticmethod
    def show_help():
        print("帮助信息:让僵尸走进房间")
        
    @classmethod
    def show_top_score(cls):
        print("游戏最高分是 %d" % cls.top_score)

    def __init__(self, player_name):
        self.player_name = player_name

    def start_game(self):
        print("[%s] 开始游戏..." % self.player_name)
        
        # 使用类名.修改历史最高分
        Game.top_score = 999

# 1. 查看游戏帮助
Game.show_help()

# 2. 查看游戏最高分
Game.show_top_score()

# 3. 创建游戏对象,开始游戏
game = Game("小明")

game.start_game()

# 4. 游戏结束,查看游戏最高分
Game.show_top_score()
# 如果方法内部即需要访问实例属性,又需要访问类属性,应该定义成什么方法?
# 应该定义实例方法
# 因为,类只有一个,在实例方法内部可以使用类名.访问类属性

9.3.4 案例小结

  • 实例方法 —— 方法内部需要访问实例属性
    • 实例方法内部可以使用类名.访问类属性
  • 类方法 —— 方法内部只需要访问类属性
  • 静态方法 —— 方法内部,不需要访问实例属性和类属性

你可能感兴趣的:(Python,python,面向对象编程)