XX学Python·面向对象

  • 面向对象的思维方式

    • 面向对象,是一个编程思想,并不是一项技术,重在理解

    • 面向过程:一步一步的完成功能:自上而下,逐步细化

    • 面向对象:找到或者构造一个可以完成功能的主体:找到实体,功能完备

  • 类和对象

    • 类就是一系列拥有相同或相似功能的对象的集合,或者说类就是一系列事物的统称

    • 对象就是类的具体的表现形式

1、手机是对象还是类?  类
2、苹果手机,是对象还是类?  类
3、iPhonex 手机是对象还是类?  类
4、我手里的苹果手机,是对象还是类?  对象

类的定义

  • 经典类
    • class 类名:
  • 新式类
    • class 类名(父类名):
# 经典类
# 不由任何类派生,或者说不继承任何类
class student:
    pass  # 为了保证代码结构完整,在类下边必须书写表达式,如没有内容,要使用pass占位

# 新式类
# 括号内就是父类,也就是存在一定的继承关系
# 有些地方称其为object的派生类
class teacher(object):
    # pass
    ...  # 为了保障代码结构完整,也可以使用...来进行占位

类的实例化

  • 类的实例化又叫做创建对象
  • 类中实例化后的对象可以调用类中的方法
  • 一个类理论上可以实例化无数个对象
  • 格式:对象名 = 类名()
# 定义一个类
class Student(object):
    # 定义方法.定义方式和函数定义类似
    def study(self):
        print('我在听直播课,贼有意思,就是学习非常不努力我也能听懂')
    def eat(self):
        print('我在吃脑白金,补补脑子继续学习')


# 类的实例化(创建对象)
s1 = Student()
# 我们可以直接打印对象,得到的是其所对应的类和所在的内存地址
print(s1)  # <__main__.Student object at 0x7f9be20848e0>
# 也可以打印对象的类型,就是我们创建对象所使用的类
print(type(s1))  # <class '__main__.Student'>

# 实例对象可以调用实例方法
s1.study()
s1.eat()

# 理论上类可以创建无数个实例对象
s2 = Student()
print(s2)

# 类名的定义要使用大驼峰命名法
# 类名严格区分大小写,类名遵循标识符的命名规则
# class ChineseStudent():
#     pass
#
# s3 = student()  NameError: name 'student' is not defined

self

  • self就是将调用方法的实例对象传入方法内部,在方法内部可调用实例的属性和方法
# 在类的内部定义方法的时候,自动传入一个self
# 在调用实例方法时,不需要对self 进行传值
# self到底是什么?有什么用?
class Student(object):
    def study(self):
        # 由于s1和self指向同一块内存空间,所以其必为同一个对象
        # 也就是说在对象调用方法时会将对象本身传入方法内部进行使用
        print(self)  # <__main__.Student object at 0x7fa2654848e0>
        print('我要学习了,谁也不要打扰我,我知道你们为了超过我不择手段,但是没有用')

    def eat(self):
        # 可以在方法内部调用实例所拥有的属性或者方法
        print('我要吃饭了吃完就学习')
        self.study()


# 实例化对象
s1 = Student()
print(s1)  # <__main__.Student object at 0x7fa2654848e0>
s1.study()
s1.eat()
# 方法是定义在类的内部,所有对象共有一个类,所以调用方法的时候,需传入调用方法所使用的对象

# s2 调用study方法时所指向的空间和s1无关所以两个对象指向不同的内存空间
s2 = Student()
s2.study()

实例属性的添加和获取

  • 在类的外部添加和获取实例属性

    • 添加:对象名.属性名 = 值

    • 获取:对象名.属性名

    • 创建对象后,我们对其中一个对象添加实例属性,其他对象不发生变化

# 定义类
class Person(object):
    def eat(self):
        print('早饭吃了油条和包子,血糖110')


# 实例化属性
p1 = Person()
# 给p1添加实例属性
p1.name = 'xiaoming'
# 调用实例属性
print(p1.name)  # xiaoming

# 如果我们再创建一个对象,其实例属性name是否存在?  不存在
p2 = Person()
p2.age = 18
# AttributeError: 'Person' object has no attribute 'name'
# print(p2.name)
# print(p1.age)
# 结论:对象被创建后,添加实例属性,对其他的对象不产生影响

# 如果我们对同一个实例属性添加两次值会怎样?
p1.name = 'Rose'
print(p1.name)
# 如果当前对象的该属性名存在,则重新赋值,如果不存在,则新建一个属性
# 类似于dict

# 可以通过__dict__去查询对象的属性,该属性以字典形式保存
# 对象属性保存在一个字典结构空间内,多次赋值会覆盖原来的值,给新的属性赋值,会增加属性数量
class Student(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return f'姓名为{self.name},年龄{self.age}'


s1 = Student('xx', '18')
print(s1)  # 姓名为xx,年龄18
print(s1.__dict__)  # {'name': 'xx', 'age': '18'}
s1.__dict__ = {'name': 'lsl', 'age': '18'}
print(s1.name)  # 


# 对象属性删除del
del p1.name
# AttributeError: 'Person' object has no attribute 'name'
print(p1.name)
  • 在类的内部添加和获取实例属性

    • 添加:self.属性名 = 值

    • 获取:self.属性名

    • 一般实例属性写在实例方法中,调用该方法才能获取实例属性,对象创建后,其中一个实例调用该方法,获取实例属性,其余对象不发生变化

class Person(object):
    def add_attr(self):
        self.name = 'xiaoming'
        self.age = 18
        self.gender = '女'


# 实例化对象
p1 = Person()
# 创建完成后,p1的实例属性添加了么?
# AttributeError: 'Person' object has no attribute 'name'\
# print(p1.name, p1.age, p1.gender)
# 为什么没有属性呢?  我们定义的添加实例属性的方法没有被调用
# 所以需要先调用添加实例属性的方法,才能使用实例属性
p1.add_attr()
print(p1.name, p1.age, p1.gender)  # xiaoming 18 女

p2 = Person()
# AttributeError: 'Person' object has no attribute 'name'
# 执行方法在第1个对象中添加了实例属性,对第2个不产生影响,如想添加实例属性需再次调用方法
# print(p2.name)

# 在类的外部可以修改类内部添加的实例属性么?  可以
p1.name = 'Rose'
print(p1.name)  # Rose

# 同一个对象在类的内部和外部添加实例属性 本质上是一样的
# 在类的外部使用对象名,其实使用的是对象的引用地址,在其引用地址位置添加了对应的实例属性
# 在类的内部使用self,其实也代表该应用地址,也是在其应用地址位置添加了对饮的实例属性

# 为什么在类的内部要使用self 而不使用对象名?   简便,灵活.复用性高
# 1.每次使用对象不一致,如使用对象名,需每次传入不同对象名或每个对象定义一个方法,不利于代码复用.
# 2.某时在没有将对象赋值给变量的时候,就需要添加其属性,这个时候,没有办法获取对象的名称.

魔术方法

  • 一般是系统在特定时机自动调用的函数或方法,绝大多数情况不需要程序员手动调用
# __func__()格式定义的函数或方法,我们自己定义禁用这种方式。
# 以下是系统内置变量,不是函数也不是方法
print(__name__)
print(__file__)

__init__()方法

  • __init__()方法在对象创建完成后,初始化对象时,自动调用

  • 在init方法中添加的属性,由于每个对象都会执行该方法,所以都包含该属性,被称之为共有属性

  • 在init方法之外添加的属性,由于不是每个对象都拥有,所以被称之为独有属性

  • 带参数的__init__()方法

    • init方法在对象被创建时,可将“类名()”这里括号添加的参数传递到init方法内部

    • 在接收到参数时,可以动态给对象添加实例属性

    • 如果init方法添加了参数,那么在创建对象时,必须给其赋值,否则报错

# 每次我们创建对象时,如果使用init方法,是不是只能添加同一个值的属性呢?
# 如果我们能够将参数传递到init方法中,是不是就可以在创建对象时,动态添加属性值了呢?
# 我们怎样给init进行传参呢?
# 在实例化对象时,类名(参数1, 参数2....)这些参数会传递给init方法,进行使用

# class Person(object):
#     def __init__(self, name, age):
#         print(name, age)


# TypeError: __init__() missing 2 required positional arguments: 'name' and 'age'
# p1 = Person()
# 既然我们给init方法中添加了参数,就必须传值,否则就会报错
# 传参的数量,就是init方法中除了self之外的位置参数的数量
# p1 = Person('Jack', 18)  # Jack 18
# 结论: 在Person类创建对象时,在()内添加参数,可被init接收

# 怎样实现动态的实例属性添加呢?
class Person(object):
    def __init__(self, name, age):
        # self.属性名 = 参数  将函数外部传递进来的参数赋值给对象,创建实例属性
        self.name = name
        self.age = age


# 实例化对象时要正确传参
p1 = Person('Rose', 17)
print(p1.name, p1.age)
# 创建第二个对象,查看属性是否动态传递成功
p2 = Person('Jack', 18)
print(p2.name, p2.age)

# 在创建对象时可指定其不同对象属性的值不同,但是所有的对象包含的属性类别相同
# 要给每一个对象单独赋值,或给init方法中的属性一些默认值,否则会报错

__str__()方法

  • 在类的内部实现__str__()方法,会在我们将对象转换为str类型时自动调用,返回其return内的数据
  • str方法内只能返回str类型的数据
  • str方法自动调用的场景
    • 强制类型转换: str(对象)
    • 隐式类型转换: %s作为占位符接收对象,或者 print打印等,都会自动调用
# __str__()方法是在数据被转换为str类型时自动调用的方法
class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age


    def __str__(self):
        # 在__str__方法中只能返回字符串类型数据,否则就会报错
        # TypeError: __str__ returned non-string (type int)
        # return 123
        # return None
        return f'我的名字是{self.name}, 我的年龄是{self.age}'


p1 = Person('Rose', 18)
# 如果我们打印p1会在控制台输出什么?
# 默认会输出对象类型,和内存地址
# print(p1)  # <__main__.Person object at 0x7fb70db848e0>
# 我们如果让其在打印时输出我们想要输出的内容?  重写str方法

# 重写str方法后
# 结论:打印p1时,会自动调用__str__()方法
print(p1)  # 我的名字是Rose, 我的年龄是18

# 是因为print方法我们才将p1变为我们改写的str方法中的内容么?  不是
# 其实我们再执行print时,会做一次隐式的数据类型转换 也就是使用str(对象)
str1 = str(p1)
print(str1)

__del__()方法

  • 对象被释放前,自动执行__del__()方法
  • 切断引用或释放对象的几个场景
    • 出了函数作用域后,局部变量被释放
    • 程序执行完成后,所有变量被释放
    • 执行del操作后,可以提前释放变量
# 之前我们学过del操作
# del 变量名  或者  del (变量名)

# del操作  可以切断数据和引用位置的联系
# 切断引用后,a 没有引用任何数据,1也没有任何变量引用,所以双双被释放掉
# a = 1
# del a
# print(a)

# __del__()方法,在c语言中成为析构函数
# 在对象被释前自动执行该方法,执行后,对象立即被释放

# 定义类
# class Person(object):
#     def __init__(self,name, age):
#         self.name = name
#         self.age = age


# p1 = Person('Rose', 18)

# del p1
# NameError: name 'p1' is not defined
# 在这种情况下,我们能否知道p1已经被释放了?  没有提示
# 如果已经被释放了还继续使用,是不是会报错? 会报错
# 我么你怎样去进行提示?  使用__del__()
# print(p1)


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

    def __del__(self):
        print('我被释放了,真爽',self.name)


p1 = Person('Rose', 18)

# del p1 # 使用del造成p1被提前释放,在程序结束前将对象释放了
# p1被释放后,我们就接收到了提示,证明p1不存在了,之后就不要使用了
# print(p1)
# 如果没有del操作,则在程序结束后,会将所有的变量进行统一释放
# print('程序结束')

# 结论:在对象被释放时,会自动调用__del__方法,使用del操作可提前释放对象,否则在程序结束后,也会将变量统一释放

# 如果一个对象,或者说同一块内存空间,被多个变量引用,使用del可以释放么?
# p2 = p1  # p1和p2指向同一内存空间,或者说两个变量引用同一个数据
# del p1
# # 如果只删除p1的引用,对象还被p2引用着,该对象不会被释放,必须切断所有引用,才能正常释放
# del p2
# # 如果将p2的引用也切断了,则对象正常释放
# print('程序结束')


# 结论:对象被引用时无法释放,除非程序终止,如1个对象被多个变量引用,必将所有引用切断才能正常释放,否则无法释放对象
# 举例:多个主人牵一条狗,如果有一个主人没有撒手,狗也跑不了
p4 = None
def func():
    p3 = Person('xiaoming', 15)
    global p4
    p4 = p3
    print(p3)

func()
# 在函数执行完后,出了作用域,会将函数内所有的临时变量释放掉,除非其被外部变量引用

print('程序结束')

手机案例

'''
需求:
1.创建phone类  Phone
2.在类中添加方法,  充电   听歌  打电话  玩游戏
3.每个手机都有初始的电量,并且在创建对象时可以手动输入电量
4.充电可以输入充电时长, 充电1小时获得20个单位的电量
5.听歌(15)  打电话(10)  玩游戏(30)都会消耗电量
6.电量0到100,充电到100会结束。使用手机,如果电量不足以支撑完成操作则警告,并自动关机
'''

# 分析:
'''
1.在上述需求中有哪些类?  一个 Phone
2.在上述类中有哪些属性(名词)?  一个 电量
3.在上述类中有哪些方法(动词)?  四个  充电  听歌 打电话 玩游戏
4.有哪些数值判断:在使用手机和充电过程中,让电量范围保持在0-100间
'''


# 定义类
class Phone(object):
    def __init__(self, power):
        if power >= 100:
            self.power = 100
        elif power <= 0:
            self.power = 0
        else:
            self.power = power

    def add_power(self, time):
        print(f'充电开始,共充电{time}小时')
        self.power += 20 * time
        if self.power >= 100:
            self.power = 100
            print('充满电了')
        else:
            print(f'充电结束,当前电量为{self.power}')

    def music(self):
        print('音乐真好听呀,再来一首大河向东流')
        self.power -= 15
        if self.power > 0:
            print(f'听歌结束,剩余电量为{self.power}')
        else:
            self.power = 0
            print(f'手机没电了赶紧充电吧,别听歌了')

    def call(self):
        print('给女朋友打个电话,希望还没睡觉')
        self.power -= 10
        if self.power > 0:
            print(f'电话打完了,成功分手,剩余电量为{self.power}')
        else:
            self.power = 0
            print(f'手机没电了赶紧充电吧,别打电话了')

    def play_game(self):
        print('我最爱玩游戏了,每次都赢没办法,就是这么厉害')
        self.power -= 30
        if self.power > 0:
            print(f'游戏打完了,太爽了,我打游戏,舍友打我,剩余电量{self.power}')
        else:
            self.power = 0
            print('手机没电了,赶紧充电吧,别玩游戏了')


p1 = Phone(20)
p1.music()
p1.call()
p1.play_game()
p1.add_power(4)
p1.add_power(4)
print(p1.power)

封装

  • 面向对象的三大特性:封装、继承、多态

  • 将属性和方法写到类的内部,使实例获得较为全面的功能,且可将属性和方法设置私有权限,保证暴露接口的安全性

  • 封装的优点:

    • 可以找到一个对象能够完成所有的功能或者业务,迭代和维护较为方便

    • 可以设置私有属性或方法,提高代码安全性,只暴露想要暴露的接口

    • 可以降低模块或类的使用难度,暴露少量接口即可完成全部功能

  • 私有属性,私有方法

    • 当前属性或方法只能在类的内部进行调用,在类的外部无法使用,则称该属性,者方法为私有属性,或私有方法
# 格式:在属性或方法前加上两个下划线  __属性名  或者 __方法名
class Person(object):
    def __drive(self):
        print('我开着小汽车,嘟嘟嘟')

    def go_shopping(self):
        self.__drive()
        print('我去购物了')


p1 = Person()
p1.go_shopping()


# 如果 我只想使用购物方法, 开车只不过是一个中间方法,不会单独使用
# 暴露多个方法反而让用户不知调用什么方法,故将无用的方法进行私有化,降低类或模块使用难度
# AttributeError: 'Person' object has no attribute '__drive'
# 在类的外部无法调用drive方法,所以私有化成功
# p1.__drive()
# 私有属性或私有方法,也是告诫其他人不要轻易调用

# 私有属性
class Women(object):
    def __init__(self, name, age):
        self.name = name
        self.__age = age

    def __str__(self):
        # 私有属性和私有方法在类的内部是随意调用的
        return f'我的名字是{self.name}, 我的年龄是{self.__age}'


w1 = Women('Rose', 45)
print(w1.name)
# AttributeError: 'Women' object has no attribute '__age'
# print(w1.__age)
# 在类的外部添加的属性,不是私有属性,可以在类的外部进行调用
w1.__gender = '女'
print(w1.__gender)

# 拓展  使用__dict__查询私有属性
print(w1.__dict__)
# 在类的外部怎么调用私有属性?_类名__私有属性名。在类外部可通过该方法获取私有属性并修改
# 但不要使用,因为如果别人定义了私有属性,大概率是不想让修改
print(w1._Women__age)
  • 私有属性的修改和获取方法
# 一般情况下,不会强行调用私有属性和方法,我们会设置get方法和set方法进行调用和修改
class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.__age = age

    def get_age(self):
        return self.__age

    def set_age(self, age):
        self.__age = age


p1 = Person('Rose', 18)
# 使用get方法,可以直接调用私有属性
print(p1.get_age())
# 在使用set方法时,可以对私有属性进行赋值
p1.set_age(45)
print(p1.get_age())
#结论:使用get,set方法可以对私有属性进行赋值和获取,在类的外部也可以使用他

# 为什么花很大力气将其进行私有化,然后又花了更大的力气把数据进行了提取和修改?
class Person1(object):
    def __init__(self, name, age):
        self.name = name
        self.__age = age

    # 如果一个数据只能存储数据,无法使用其中的数据,那这个数据存储方案将没有任何意义
    def get_age(self):
        # 需求:获取年龄的时候,为了觉得自己年轻,返回的年龄少5岁,如小于18岁,则说18岁
        age = self.__age - 5
        if age < 18:
            return 18
        else:
            return age

    def set_age(self, age):
        # 需求:保存的数据必须是0到100,如果大于100存入100,如果小于0则存入0
        if age > 100:
            self.__age = 100
        elif age < 0:
            self.__age = 0
        else:
            self.__age = age

p1 = Person1('Rose', 35)
# 在使用get方法提取数据的时候,可以对提取规则进行指定
print(p1.get_age())
# 使用set方法存入数据的时候,可以对规则进行指定
p1.set_age(115)
print(p1.get_age())

# 结论:某些数据,在存入或提取时,需按照指定规则进行加工,这时使用get和set方法极为方便
# 例: 提取手机号或身份证号进行脱敏
# 如:存入数据逻辑存在偏差,如存储性别,0,1和男女,可用set进行规则调整,保证存入数据一致性

继承

# 继承:子类对象可以使用父类中的属性或方法的过程,叫做继承
# 格式: class 子类名(父类名):
# object是所有类的公共父类,基类,顶级类
# 如果使用经典类,或者新式类中括号内什么也不写,其实默认就继承了object
class Person(object):
    pass

class Man(Person):
    pass

class Boy(Man):
    pass
  • 单继承就是某个类只继承自一个父类,同时,继承关系中可以有多级继承

    • 继承过程中,子类可以使用父类的所有非私有属性或方法
    • 如果父类或更高级的父类,实现了init方法,并且进行了参数设定,实例化子类对象时必须传值
  • MRO(Method Resolution Order)方法解析顺序,可通过类名.__mro__类名.mro()获得“类的层次结构”,方法解析顺序也是按照这个“类的层次结构”寻找到。

# 定义一个Person类
class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.__age = age

# 定义一个Father类,继承Person
class Father(Person):
    def __sing(self):
        print('我会唱学猫叫,跟我一起来')

    def dance(self):
        print('我会跳四小天鹅,就是天鹅还缺仨')

# 定义一个Son类,继承Father
class Son(Father):
    def play(self):
        # AttributeError: 'Son' object has no attribute '_Son__sing'
        # 继承父类时,只能继承父类中的非私有属性和方法
        self.__sing()
        self.dance()

# 实例化一个Son对象
# TypeError: __init__() missing 2 required positional arguments: 'name' and 'age'
# son继承了father,father继承person,person中书写了init方法的参数,所以此处必须传参
# s1 = Son()
s1 = Son('xiaoming', 12)
# s1继承了父类的属性和方法,Son类中没有书写任何内容,但可调用父类及其父类的父类中的方法
# s1.sing()
# 但是父类中的私有属性或者方法,无法调用
# AttributeError: 'Son' object has no attribute '__age'
# print(s1.__age)
# AttributeError: 'Son' object has no attribute '__sing'
# s1.__sing()
# s1.play()

# 查看类的继承链条
# (<class '__main__.Son'>, <class '__main__.Father'>, <class '__main__.Person'>, <class 'object'>)
print(Son.__mro__)
  • 多继承:一个子类,继承多个父类的过程就是多继承

    • 在多继承中,子类可以调用多个父类中的非私有方法或者属性

    • 多继承中,如出现同名属性或方法,优先调用继承位置靠前的父类中的方法或属性

  • 子类中重写父类方法

    • 子类中重写父类方法,则调用方法时,直接调用子类中的方法,不会调用父类的

    • 重写时只要方法名称相等即可,不需要进行参数的校对

    • 为什么可以重写父类方法,因为在调用方法或者属性时,会按照继承层级依次查找

# 定义一个Person类
class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age


# 定义一个Father类,继承Person
class Father(Person):
    def sing(self):
        print('我会唱学猫叫,跟我一起来')

    def dance(self):
        print('我会跳四小天鹅,就是天鹅还缺仨')


# 定义一个Son类,继承Father
class Son(Father):
    # 需求:在Son执行sing方法时,让他唱一分钱
    def sing(self):
        print('我喜欢唱一分钱, 你自己学猫叫吧')

    # 我们进行方法 重写的时候,不需要关注参数,只需方法名相同即可
    def __init__(self):
        pass


# s1 = Son('xiaoming', 12)
# s1.sing()

# 为什么子类中重写了父类方法就不能进行调用了?
# 之前我么讲了__mro__  打印继承顺序,同时其也是方法或属性的调用顺序,例如想使用Son对象调用sing方法,但是不知道sing在哪个类中
# 所以,系统先去当前Son类中查找,查看是否存在sing方法
# 如果存在,则调用,如果不存在,则去父类中 Father中查找,如果Father类中存在sing则调用,如果不存在,则去更高级父类(person)中查找
# 直到查询到object类中,如果依然不存在,则报错
# 所以如果子类中书写了对应的方法,则父类中的同名方法无法被调用

# 可不可以让Son类不需要使用name和age就可以创建对象呢? 在Son中重写init方法
s1 = Son()
s1.sing()
  • 在子类中调用父类方法

    • super().方法名()

    • 类名.方法名(self)

    • spuer(要从哪一个类的上一级类开始查找,self).方法名()

    • 子类调用父类方法时,一般都是想对父类方法进行扩展

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

    def driver(self):
        print('开车太好玩了 ,10迈,太快了')


class Father(Person):
    # 如果想在原有父类方法基础上扩展,例如现在需要重写一个init方法
    # 可以接收 name, age ,gender三个属性
    def __init__(self, name, age, gender):
        # 在父类方法中已经添加了name,和age我们可以直接使用
        super().__init__(name, age)
        # 在父类方法的基础上在添加一个子类方法独有的功能
        self.gender = gender

    def driver(self):
        print('我要去天安门完,开挖掘机不让我进')

    def __str__(self):
        return f'我的姓名是{self.name},我的年龄是{self.age},我的性别是{self.gender}'

class Son(Father):
    def driver(self):
        # 调用Person中的dirver
        # TypeError: driver() missing 1 required positional argument: 'self'
        # Person.driver()
        Person.driver(self)
        # 从Father类的上一级类开始查找方法,并进行调用
        super(Father,self).driver()

        # 调用Father中的dirver
        super().driver()
        # 格式:super(从哪个类的上一级开始查找,self).方法名()
        # 如果类名是当前类,可以省略括号内的内容
        super(Son, self).driver()

# 所有的参数都传递到了Father类中,并且添加为当前对象的属性
print(Father('Jack', 28, '男'))
s1 =Son('xiaoming', 12, '男')
s1.driver()

# 子类中调用父类方法的三种方式:
# super().方法名()   # 只能调用当前类的上一级类中的方法或函数
# 类名.方法名(self)  # 所使用的类名,必须在当前类的继承关系中.这种方法可以调用不在类中的类方法,但是不能使用self作为对象出现
# super(要从哪一个类的上级类开始查询,self).方法名()  # 类名必须在继承关系内,如果类名是当前所在的类,则可以将括号内内容省略,就是第一中方式

多态

  • 在继承链条中,子类重写父类方法,即多个子类和父类中都拥有同名方法,将其对象传入函数或方法内部,执行相同方法后,所展示的效果完全不同,这种现象叫做多态
class Person(object):
    def driver(self):
        print('开车太好玩了')


class Father(Person):
    def driver(self):
        print('爸爸要去天安门完,开挖掘机不让进')


class Mother(Person):
    def driver(self):
        print('妈妈会开小汽车,嘟嘟嘟')


class Son(Father):
    def driver(self):
        print('儿子会骑自行车,真好玩')


def go_shopping(who):
    who.driver()


# 在我调用go_shopping时,可以将什么对象传进来?
p1 = Person()
f1 = Father()
s1 = Son()
m1 = Mother()
go_shopping(p1)  # 开车太好玩了
go_shopping(f1)  # 爸爸要去天安门完,开挖掘机不让进
go_shopping(s1)  # 儿子会骑自行车,真好玩
go_shopping(m1)  # 妈妈会开小汽车,嘟嘟嘟


# 如果创建一个Monkey对象,能否传入go_shopping并正确执行?
class Monkey(object):
    def driver(self):
        print('猴子在骑自行车')


# 一个无继承关系的类,存在指定方法,可进行对象的传递,并在方法或函数内使用,但逻辑会有偏差
# 这种语法没问题,但逻辑上有严重偏差的方式叫做"鸭子类型"(扩展,不要求掌握)
monkey1 = Monkey()
go_shopping(monkey1)

面向对象的其他特性

  • 类属性:所有对象所共有的属性,在对其修改所有对象的类属性发生了改变
    • 实例属性:每个对象独有的,添加修改实例属性对其他对象不产生影响
# 类属性,有些地方也叫类变量.就是在类中创建的属于所有对象的属性
class Chinese(object):
    # 类属性是所有对象所共有的
    color = 'yellow'
    def __init__(self, name):
        self.name = name


c1 = Chinese('xiaohong')
c2 = Chinese('xiaohuang')
c3 = Chinese('xiaolv')

# 上述三个对象拥有的实例属性是什么?  name
# 他们每个人的实例属性相同么?之间有联系么?  不相同,每个对象间的实例属性互不相关
# 但三个对象的类属性完全相同
print(c1.color)
print(c2.color)
print(c3.color)
# 类属性的获取方式
# 格式1:对象名.类属性名 在实例属性中,不能有与类属性同名的属性,否则不能通过这种方式提取
# 格式2:类名.类属性名  (推荐)

# 修改类属性.格式:类名.类属性名 = 值
Chinese.color = 'orange'
# 注意:修改类属性不能使用  对象名.属性名 = 值  这种方式会添加一个实例属性
print(c1.color)
print(c2.color)
print(c3.color)

# 类属性使用场景:
# 可以进行计数
# 可以控制或者包含多个对象
class Apple(object):
    apple_list = []
    def __init__(self):
        Apple.apple_list.append(self)

    count = 10
    def eat(self):
        Apple.count -= 1

a1 = Apple()
a2 = Apple()
a3 = Apple()
a4 = Apple()

a1.eat()
a2.eat()
a3.eat()
a4.eat()
print(Apple.count)
print(Apple.apple_list)
  • 类方法
    • 在方法内部不需要使用实例属性和实例方法,但要使用类属性或类方法就定义类方法
    • 定义方式:需要在方法上方写@classmethod
    • 在类方法中会自动传入cls,这个参数代表的是当前类本身
class Apple(object):
    num = 10
    def __init__(self):
        self.eat_num = 0

    def eat(self):
        self.eat_num += 1  # 每次吃苹果,当前的食用数量加1
        Apple.num -= 1  # 每次吃苹果,让苹果总数 -1
    # 当方法中不适用实例属性和实例方法,只使用到类属性和类方法的时候我们就选择类方法
    # 因为类方法,不需要创建实例去进行调用,可以直接使用类名调用
    @classmethod
    def eat_apple_num(cls):
        # 在类方法中传入的cls即为当前类的类名,cls.num相当于Apple.num
        print(f'一共被吃了{10-cls.num}个,还剩{cls.num}个')

# 类方法的调用。格式: 类名.类方法名
Apple.eat_apple_num()

# 创建对象
a1 = Apple()
a2 = Apple()
a3 = Apple()
a4 = Apple()
# 吃苹果
a1.eat()
a2.eat()
a3.eat()
a4.eat()
a4.eat()

# 调用类方法
Apple.eat_apple_num()

# 查看每人吃了几个苹果
print(a1.eat_num)
print(a2.eat_num)
print(a3.eat_num)
print(a4.eat_num)

# 类方法可以使用对象调用么?
# a1.eat_apple_num()  不推荐这样使用
  • 静态方法
    • 既不依赖于实例,也不依赖于类,这种方法我们就可以定义为静态方法
    • 静态方法中,不会传入self,也不会传入cls.所以在使用静态方法时,静态方法中不要使用类或对象的属性或方法
class Person(object):
    @staticmethod
    def func():
        print('我是一个静态方法')


Person.func()
# 静态方法就是一个普通函数,放到类内部就是为了封装,方便我们去继承和导入模块

# def func():
#     print('我是一个静态方法')
# 一般能定义为函数的内容,都可改写为静态方法,为更好的封装,我们会将其写到类中
  • 小案例
class Game(object):
    top_score = 100

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

    @staticmethod
    def print_game_info():
        print('游戏信息展示')

    @classmethod
    def show_top_score(cls):
        print(f'最高分为:{cls.top_score}')

    def start_game(self):
        print(f'{self.name}开始游戏')


Game.print_game_info()  # 游戏信息展示
Game.show_top_score()  # 最高分为:100
# 实例方法必须使用实例进行调用,使用类调用报错
# Game.start_game() 
# TypeError: start_game() missing 1 required positional argument: 'self'

g1 = Game('xx')
g1.start_game()  # xx开始游戏
g1.print_game_info()  # 游戏信息展示。实例可调用静态方法
g1.show_top_score()  # 最高分为:100。实例可调用类方法
posted @ 2022-10-04 22:57  PORTB  阅读(45)  评论(0编辑  收藏  举报