首页 > 其他分享 >进阶面向对象 - 3

进阶面向对象 - 3

时间:2023-03-30 21:03:21浏览次数:36  
标签:__ 调用 进阶 self 面向对象 init print name

11. 进阶面向对象 - 3

super方法

引入

上一节课程我们知道,如果父类中的方法在派生的子类中不能满足其需求的话,可以在子类中通过重写解决这个问题

但是很多情况下,父类中的方法并不是全部一点都不能用,即子类的需求往往是在父类方法实现的功能基础上提出了更多的需求而已,此时如果我们在子类中重写此方法时就会发现出现了很多冗余的代码,这个问题该怎么解决呢?

答:在子类重写的方法中通过调用父类中被重写的方法

代码示例

示例一:

class Father(object):
    def play_game(self):
        print("父类中的play_game")


class Son(Father):
    def play_game(self):
        super().play_game()  # 先调用父类被重写的方法
        print("子类中的play_game")  # 然后再添加子类需要的新功能


son = Son()
son.play_game()  # 调用子类中的方法,因为在子类中重写了play_game方法

运行效果:

父类中的play_game
子类中的play_game

示例二:

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

    def __str__(self):
        return "%s的年龄是: %d" % (self.name, self.age)


class Son(Father):
    def __init__(self, name, age, collage):
        super().__init__(name, age)
        self.collage = collage

    def __str__(self):
        return "%s的年龄是: %d,他的学历是: %s" % (self.name, self.age, self.collage)


father = Father("父亲", 50)
print(father)

son = Son("儿子", 18, "大学")
print(son)

运行结果:

父亲的年龄是: 50
儿子的年龄是: 18,他的学历是: 大学

示例三:

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

    def __str__(self):
        return "%s的年龄是:%d" % (self.name, self.age)


class Son(Father):
    def __init__(self, name, age, collage):
        super().__init__(name, age)
        self.collage = collage

    def __str__(self):
        return "%s的年龄是:%d,他的学历是:%s" % (self.name, self.age, self.collage)


class GrandChild(Son):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        print("----这里模拟其要做的额外事情....----")


father = Father("父亲", 50)
print(father)

son = Son("儿子", 18, "大学")
print(son)

grandchild = GrandChild("孙子", 1, "未上学")
print(grandchild)

运行结果:

父亲的年龄是:50
儿子的年龄是:18,他的学历是:大学
----这里模拟其要做的额外事情....----
孙子的年龄是:1,他的学历是:未上学
简单总结
  • 如果想要在子类方法中调用被重写的父类方法就可以使用super().父类方法名()

多态

面向对象的特征说明

面向对象编程有3个特征:

  • 封装
  • 继承
  • 多态

封装与继承之前我们已经研究过了,本节课研究多态

多态的概念

多态从字面意思来看,就是多种形态的意思。

在python中它的实际功能是:

  • 如果一个变量存储了某一个实例对象的引用,且通过这个变量调用指向的对象中的某个方法,此时如果变量指向的对象是子类创建的那么就调用子类中的方法,如果是父类创建的对象那么就调用父类的方法
代码示例
class Dog(object):
    def bark(self):
        print("狗汪汪叫...")


class LangDog(Dog):
    def bark(self):
        print("狼狗震耳欲聋的叫...")


class ZangAo(Dog):
    pass


class Person(object):
    def pk_dog(self, dog):
        print("人用力的向狗进行了攻击...")
        dog.bark()


anna = Person()
dog1 = Dog()
dog2 = LangDog()
dog3 = ZangAo()

anna.pk_dog(dog1)
anna.pk_dog(dog2)
anna.pk_dog(dog3)

运行效果:

人 用力的向狗进行了攻击...
狗汪汪叫...
人 用力的向狗进行了攻击...
狼狗震耳欲聋的叫...
人 用力的向狗进行了攻击...
狗汪汪叫...
简单总结

想要实现多态,需要的条件如下:

  1. 有继承
  2. 有重写

静态方法

引入

默认情况下,python类中定义的方法是实例方法,即这个方法有一个默认的形参self,这个self会在方法被调用的时候指向对象

但是有些时候,我们并不需要对象的引用,即self没用,那该怎么处理呢?能不写self吗?

答:可以

静态方法的概念

如果一个方法不写self即不需要实例对象的引用,此时在定义方法的时候可以用@staticmethod对函数进行修饰,被修饰的函数就可以不写self

一句话:被@staticmethod修饰的方法,就是静态方法

代码示例
class Calculator(object):
    """计算器类"""

    def __init__(self):
        # 定义2个默认值
        self.num1 = 0
        self.num2 = 0

    @staticmethod
    def show_menu():
        """因为打印菜单功能方法并不需要self指向的对象,所以就考虑使用静态方法"""
        print("    双双牌计算机 V2022.10")
        print("1. 加法")
        print("2. 减法")
        print("3. 乘法")
        print("4. 除法")
        print("5. 退出")

    def get_nums(self):
        self.num1 = int(input("请输入第1个数:"))
        self.num2 = int(input("请输入第2个数:"))

    def add(self):
        print(self.num1 + self.num2)

    def min(self):
        print(self.num1 - self.num2)

    def mul(self):
        print(self.num1 * self.num2)

    def div(self):
        print(self.num1 / self.num2)

    def run(self):
        while True:
            self.show_menu()
            op = input("请输入要进行的操作:")
            if op == "1":
                self.get_nums()
                self.add()
            elif op == "2":
                self.get_nums()
                self.min()
            elif op == "3":
                self.get_nums()
                self.mul()
            elif op == "4":
                self.get_nums()
                self.div()
            elif op == "5":
                break


# 创建一个计算器对象
cal = Calculator()
# 调用计算器的运行方法
cal.run()

运行效果:

    双双牌计算机 V2022.10
1. 加法
2. 减法
3. 乘法
4. 除法
5. 退出
请输入要进行的操作:
简单总结

如果不需要用到对象,那么就可以将方法用@staticmethod进行修饰,如此一来此方法就变成了静态方法。

类属性

引入

默认情况下 ,当通过同一个类创建了多个实例对象之后,每个实例对象之间是相互隔离的

但是有时候有些数据需要在多个对象之间共享,此时该怎么办呢?

答:类属性

类属性的概念

想要在多个对象之间共享数据,即一些属性需要在多个对象之间共享,这样的属性就是类属性

那怎样定义类属性呢?格式如下:

class 类名:
    类属性 = ....

即在class内且在def之外定义的变量,就叫做类属性

代码示例
class Tool(object):
    tools_num = 0  # 定义一个类属性,用来存储共享的数据

    def __init__(self, name):
        self.name = name
        Tool.tools_num += 1

    def print_info(self):
        print("工具的总数为:", Tool.tools_num)

    def print_info2():
        print("工具的总数为:", Tool.tools_num)


tieqiao = Tool("铁锹")
chutou = Tool("锄头")
dianciluo = Tool("电磁炉")

print("工具的总数为:", Tool.tools_num)  # 可以直接通过 类名.类属性操作
tieqiao.print_info()  # 可以通过Tool创建的任意实例对象调用方法,在方法中获取
Tool.print_info2()  # 通过类名调用时,可以看到这个方法在pycharm中提示错误

类方法

引入

为了更好的对类属性进行操作,Python中提供了另外一种方法类方法

类方法的概念

之前在学习静态方法的时候我们知道可以在方法的名字前面添加@staticmethod此时这个方法就是静态方法,

与这种添加@的方式很类似,如果想要让一个方法成为类方法我们只需要在这个方法的前面添加@classmethod即可,与此同时需要在方法的第1个形参位置添加cls

class 类名:
    @classmethod
    def 类方法名(cls):
        pass
示例代码
class Tool(object):
    tools_num = 0  # 定义一个类属性,用来存储共享的数据

    def __init__(self, name):
        self.name = name
        Tool.tools_num += 1

    def print_info(self):
        print("工具的总数为:", Tool.tools_num)

    @classmethod
    def print_info2(cls):
        print("工具的总数为:", cls.tools_num)


tieqiao = Tool("铁锹")
chutou = Tool("锄头")
dianciluo = Tool("电磁炉")

tieqiao.print_info()
Tool.print_info2()
tieqiao.print_info2()

运行效果:

工具的总数为: 3
工具的总数为: 3
工具的总数为: 3
简单总结
  • 定义类方法时,前面添加@classmethod
  • 类方法的第1个形参,一般都叫做cls(当然了叫什么名字可以任意,但一般都用cls
  • 调用类方法的时候,可以用实例对象类对象调用,但无论用哪种方式调用,类方法中的cls指向类对象

类对象

引入

之前在学习类属性的时候,我们提到过:类属性是可以在多个实例对象之间共享的属性

那么问题来了,类属性到底存在哪里呢?

答:类对象

类对象的概念

之前我们说到通过class定义的就是类(就是一个要创建的商品的模板),通过类名()创建出来的叫做实例对象

其实,定义的类(即用class定义的类)实际上也是一个对象(试想即使我们把 类称之为模板,模板也不是空的啊,也是需要占用内存的对吗)

定义的类其实就是一个对象,为了能够将这个对象与其创建出来的实例对象进行区分,将这个class定义的类叫做类对象

类对象的作用

我们知道实例对象是类 (即类对象)创建出来的,所以类对象对于实例对象而言是共享的,既然是共享的那么就干脆将实例对象都有的而且不变化的内容存储到 类对象 即可,这样会减少内容的占用

那,哪些东西在类对象中存储呢?

  • 类属性
  • 所有的方法

对你没有看错,除了熟知的类属性之外,类对象中存储了class定义的所有的方法(无论是魔法方法、实例方法、静态方法 、类方法都在类对象中存储),因为方法(即函数)的代码是不变的,变化的仅仅是数据而已。

实例对象怎么用类对象

每个实例对象中都会有1个额外默认的属性__class__,这个属性指向了创建当前对象的模板即类对象,所以当调用实例对象.xxx()时,实际上实例对象.__class__.xxx()

dir() 方法

既然我们知道了实例对象中有默认的__class__,那除了它之外还有哪些呢?怎么查看呢?

dir(实例对象)

例如:

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

这么多的__开始的属性,用到什么我们就研究 什么,不用现在立刻研究。

多继承以及MRO顺序

多继承中调用父类方式不同结果不同

单独调用父类的方法

print("******多继承使用类名.__init__发生的状态******")
class Parent(object):
    def __init__(self, name):
        print('parent的init开始被调用')
        self.name = name
        print('parent的init结束被调用')

class Son1(Parent):
    def __init__(self, name, age):
        print('Son1的init开始被调用')
        self.age = age
        Parent.__init__(self, name)
        print('Son1的init结束被调用')

class Son2(Parent):
    def __init__(self, name, gender):
        print('Son2的init开始被调用')
        self.gender = gender
        Parent.__init__(self, name)
        print('Son2的init结束被调用')

class Grandson(Son1, Son2):
    def __init__(self, name, age, gender):
        print('Grandson的init开始被调用')
        Son1.__init__(self, name, age)  # 单独调用父类的初始化方法
        Son2.__init__(self, name, gender)
        print('Grandson的init结束被调用')

gs = Grandson('grandson', 12, '男')
print('姓名:', gs.name)
print('年龄:', gs.age)
print('性别:', gs.gender)

print("******多继承使用类名.__init__发生的状态******\n\n")

运行结果:

******多继承使用类名.__init__发生的状态******
Grandson的init开始被调用
Son1的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son1的init结束被调用
Son2的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son2的init结束被调用
Grandson的init结束被调用
姓名: grandson
年龄: 12
性别: 男
******多继承使用类名.__init__发生的状态******

多继承中super调用被重写的父类方法

print("******多继承使用super().__init__发生的状态******")
class Parent(object):
    def __init__(self, name, *args, **kwargs):  # 为避免多继承报错,使用不定长参数,接受参数
        print('parent的init开始被调用')
        self.name = name
        print('parent的init结束被调用')

class Son1(Parent):
    def __init__(self, name, age, *args, **kwargs):  # 为避免多继承报错,使用不定长参数,接受参数
        print('Son1的init开始被调用')
        self.age = age
        super().__init__(name, *args, **kwargs)  # 为避免多继承报错,使用不定长参数,接受参数
        print('Son1的init结束被调用')

class Son2(Parent):
    def __init__(self, name, gender, *args, **kwargs):  # 为避免多继承报错,使用不定长参数,接受参数
        print('Son2的init开始被调用')
        self.gender = gender
        super().__init__(name, *args, **kwargs)  # 为避免多继承报错,使用不定长参数,接受参数
        print('Son2的init结束被调用')

class Grandson(Son1, Son2):
    def __init__(self, name, age, gender):
        print('Grandson的init开始被调用')
        # 多继承时,相对于使用类名.__init__方法,要把每个父类全部写一遍
        # 而super只用一句话,执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因
        # super(Grandson, self).__init__(name, age, gender)
        super().__init__(name, age, gender)
        print('Grandson的init结束被调用')

print(Grandson.__mro__)

gs = Grandson('grandson', 12, '男')
print('姓名:', gs.name)
print('年龄:', gs.age)
print('性别:', gs.gender)
print("******多继承使用super().__init__发生的状态******\n\n")

运行结果:

******多继承使用super().__init__发生的状态******
(<class '__main__.Grandson'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Parent'>, <class 'object'>)
Grandson的init开始被调用
Son1的init开始被调用
Son2的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son2的init结束被调用
Son1的init结束被调用
Grandson的init结束被调用
姓名: grandson
年龄: 12
性别: 男
******多继承使用super().__init__发生的状态******

上述两种调用父类的方法是有区别的

  1. 如果2个子类中都继承了父类,当在子类中通过父类名调用时,parent被执行了2次
  2. 如果2个子类中都继承了父类,当在子类中通过super调用时,parent被执行了1次
单继承中的super
print("******单继承使用super().__init__发生的状态******")
class Parent(object):
    def __init__(self, name):
        print('parent的init开始被调用')
        self.name = name
        print('parent的init结束被调用')

class Son(Parent):
    def __init__(self, name, age):
        print('Son1的init开始被调用')
        self.age = age
        super().__init__(name)  # 单继承不能提供全部参数
        print('Son1的init结束被调用')

class Grandson(Son):
    def __init__(self, name, age, gender):
        print('Grandson的init开始被调用')
        self.gender = gender
        super().__init__(name, age)  # 单继承不能提供全部参数
        print('Grandson的init结束被调用')

gs = Grandson('grandson', 12, '男')
print('姓名:', gs.name)
print('年龄:', gs.age)
print('性别:', gs.gender)
print("******单继承使用super().__init__发生的状态******\n\n")

运行结果:

******单继承使用super().__init__发生的状态******
Grandson的init开始被调用
Son1的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son1的init结束被调用
Grandson的init结束被调用
姓名: grandson
年龄: 12
性别: 男
******单继承使用super().__init__发生的状态******
简单总结
  1. super().__init__相对于类名.__init__,在单继承上用法基本没有区别
  2. 但在多继承上有区别,super方法能保证每个父类的方法只会执行一次,而使用类名的方法会导致方法被执行多次,具体看前面的输出结果
  3. 多继承时,使用super方法,对父类的传参,由于super的算法导致的原因,必须把参数全部传递,否则会报错
  4. 单继承时,使用super方法,则不能全部传递,只能传父类方法所需的参数,否则会报错
  5. 多继承时,相对于使用类名.__init__方法,要把每个父类全部写一遍, 而使用super方法,只需写一句话便执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因
面试题

以下代码将会输出什么?

class Parent(object):
    x = 1
 
class Child1(Parent):
    pass
 
class Child2(Parent):
    pass
 
print(Parent.x, Child1.x, Child2.x)
Child1.x = 2
print(Parent.x, Child1.x, Child2.x)
Parent.x = 3
print(Parent.x, Child1.x, Child2.x)

输出结果:

1 1 1
1 2 1
3 2 3

使你困惑或是惊奇的是关于最后一行的输出是 3 2 3 而不是 3 2 1。为什么改变了 Parent.x 的值还会改变 Child2.x 的值,但是同时 Child1.x 值却没有改变?

这个答案的关键是,在 Python 中,类变量在内部是作为字典处理的。如果一个变量的名字没有在当前类的字典中发现,将搜索祖先类(比如父类)直到被引用的变量名被找到(如果这个被引用的变量名既没有在自己所在的类又没有在祖先类中找到,会引发一个 AttributeError 异常 )。

因此,在父类中设置 x = 1 会使得类变量 x 在引用该类和其任何子类中的值为 1。这就是因为第一个 print 语句的输出是 1 1 1。

随后,如果任何它的子类重写了该值(例如,我们执行语句 Child1.x = 2),然后,该值仅仅在子类中被改变。这就是为什么第二个 print 语句的输出是 1 2 1。

最后,如果该值在父类中被改变(例如,我们执行语句 Parent.x = 3),这个改变会影响到任何未重写该值的子类当中的值(在这个示例中被影响的子类是 Child2)。这就是为什么第三个 print 输出是 3 2 3。

标签:__,调用,进阶,self,面向对象,init,print,name
From: https://www.cnblogs.com/wt-poppies/p/17274277.html

相关文章

  • 深入面向对象 - 2
    10.深入面向对象-2隐藏数据引入classCat(object):def__init__(self,new_name,new_age):self.name=new_nameself.age=new_agedefprint_info(self):print("我是%s,今年%s了"%(self.name,self.age))#创建猫对象cat=Ca......
  • 初识面向对象 - 1
    9.初识面向对象-1引入面向对象本节将通过一个案例,先用之前我们编写的代码方式进行实现,然后分析利弊。从而引出面向对象。需求编写一个程序,完成1个学生的基本操作学生有姓名、年龄可以输出学生信息(姓名、年龄)使用面向过程的方式实现#遍历打印所有姓名-年龄defprin......
  • 函数进阶 - 2
    8.函数进阶-2函数参数的高级用法缺省参数引入缺省参数也叫做默认参数,是指定义函数时形参变量有默认值,如果调用函数时没有传递参数,那么函数就用默认值,如果传递了参数就用传递的那个数据。示例:defprint_info(name,age=35):print(f'name:{name}')print(f'age:......
  • 面向对象七大原则
    1开闭原则(TheOpen-ClosedPrinciple,OCP)2单一职责原则3里氏替换原则(LiskovSubstitutionPrinciple,LSP)4迪米特原则(最少知道原则)(LawofDemeter,LoD)5接口隔离原则(InterfaceSegregationPrinciple,ISP)6依赖倒置原则(DependencyInversionPrinciple,DIP)7合成复......
  • HCIP-ICT实战进阶10-BFD原理与配置
    HCIP-ICT实战进阶10-BFD原理与配置0引言之前学习的比如链路聚合、STP、RSTP、MSTP以及一些路由协议,所有的协议都可以实现一种能力:冗余备份网络中如果真的发生了设备的故障或者是链路故障,则以上的各种协议需要检测到故障,然后进行网络的链路切换.生成树协议发生故障生成树......
  • 面向对象和面向过程(封装继承多态)
    面向对象&面向过程面向过程思想步骤清晰,第一步做什么第二步做什么面对过程适合处理一些简单的问题面向对象思想物以类聚,分类的思维模式对于描述复杂的事物,为了从......
  • vue全家桶进阶之路24:Mock
    Mock是一个JavaScript库,用于生成随机数据或模拟HTTP请求响应,用于前端开发中的单元测试、功能测试、集成测试等场景。Mock可以生成各种类型的数据,包括字符串、数字、......
  • 面向对象设计原则
    面向对象设计原则面向对象设计原则包括:1、单一职责原则不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,如若不然,就应该把类拆分。2、里氏替换原则......
  • C++ 树进阶系列之笛卡尔树的两面性
    1.前言笛卡尔树是一种特殊的二叉树数据结构,融合了二叉堆和二叉搜索树两大特性。笛卡尔树可以把数列(组)对象映射成二叉树,便于使用笛卡尔树结构的逻辑求解数列的区间最值或......
  • C#面向对象核心-继承
    继承继承主要实现重用代码,来节省开发时间。1继承基本概念一个类B继承一个类A,被继承的类A称为父类、基类、超类,继承的类B称为子类、派生类。子类会继承父类的所有成......