继承

课件:继承、多继承、魔法方法

思考

问题一:两个中有大量重复的代码,是否能够只写一次 ?

问题二: 继承的意义是什么 ?

面向对象的编程带来的好处之一是代码的重用,实现这种重用方法之一是通过继承机制。

继承是两个类或多个类之间的父子关系,子类继承了基类的所有公有数据属性和方法,并且可以通过编写子类的代码扩充子类的功能。

开个玩笑地说,如果人类可以做到儿女继承了父母的所有才学并加以拓展,那么人类的发展至少是现在的数万倍。继承实现了数据属性和方法的重用,减少了代码的冗余度。

那么我们何时需要使用继承呢?如果我们需要的类中具有公共的成员,且具有一定的递进关系,那么就可以使用继承,且让结构最简单的类作为基类。一般来说,子类是父类的特殊化,如下面的关系:

哺乳类动物——>狗——>柯基

特定狗种类继承狗类,狗类继承哺乳动物类,狗类编写了描述所有狗种公有的行为的方法而特定狗种类则增加了该狗种特有的行为。

不过继承也有一定弊端,可能基类对于子类也有一定特殊的地方,如某种特定狗种不具有绝大部分狗种的行为,当程序员没有理清类间的关系时,可能使得子类具有了不该有的方法。

另外,如果继承链太长的话,任何一点小的变化都会引起一连串变化,我们使用的继承要注意控制继承链的规模。

继承语法:class子类名(基类名1,基类名2.…)基类写在括号里。

知识点一:继承

继承关系图

关系继承图

家族继承图

class Father():  # 爸爸类

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

    def myself(self):
        print('大家好!我是%s,今年%s岁'%(self.name,self.age))
        
class Son(Father):  # 儿子类
    pass

a = Son('aa',18)
a.myself()

继承查找的方式跟变量的作用域类似   它会现在当期的类里找,找不到就向父类找,再找不到就再向上找,找不到就报错

'''
一:继承:基类和派生类
区分“属于”(is-a)和“具有”(has-a)这两种关系。“属于”即为继承:在这种关系中,派生类的一个对象也属于“基类”的一个对象。
“具有”即为“合成”:这种关系中,一个对象“具有”对其他类的一个或多个对象的引用,后者是前者的成员。
基类即超类,派生类即子类
创建派生类的格式:
'''
class Paishenclassname(Jileiclassname):
    pass

#class  派生类名(基类名):
    pass
#即:表示Paishenclass  类   继承了  Jileiclass的所有属性、方法

知识点二:多继承

如果有多个基类,则需要全部都写在括号里,这种情况称为多继承。在Python中继承有以下一些特点:

1) 在继承中基类初始化方法_init_不会被自动调用。如果希望子类调用基类的_init方法,需要在子类的_init方法中显示调用了它。这与C++和C#区别很大。
2) 在调用基类的方法时,需要加上基类的类名前缀,且带上self参数变量。注意在类中调用该类中定义的方法时不需要self参数。
3) Python总是首先查找对应类的方法,如果在子类中没有对应的方法,Python才会在继承链的基类中按顺序查找。
4) 在Python继承中,子类不能访问基类的私有成员。

class Dog(object):  #object类 是所有类的父类
    pass

print(dir(Dog))

class Father:
    #2
    __sfmoney = '200w'

    def __init__(self,name,money):
        self.name = name
        self.money = money
    def eat(self):
        print('吃了一顿%s'%self.money)

class Son(Father):  #son类继承了爸爸类
    pass

#class Son(Father):
#    def myself(self):
#        print('111111111')

zhangs=Son('张三','100万')
zhangs.eat()
# print(dir(zhangs))
# print(zhangs._Father)
print(Son.__bases__)    #查看Son类的直接基类(父类)


#调用父类的init方法  父类.__init__(self)

继承

#多继承
class Base:
    def play(self):
        print('我是你祖宗')

class A(Base):
    def play(self):
        print("我是祖宗的儿子")

class B(Base):
    def play(self):
        print('我是祖宗的女儿')

class C(B,A):   # 谁先继承就用谁
    pass

c = C()
c.play()
# 那么子类继承了父类 但是子类不想用父类的方法 应该怎么做呢?
# 我们可以直接覆盖掉

#先后顺序,有两个值就去父亲类找,只有一个值,就去母类找,由上至下,先在父亲类,后在母类,最后在object类中
  

PPT 那大家思考一下  重写父类方法之后,如果又需要使用父类的方法呢?

教大家两种方法:
class Dog:
    def specialty(self):   /'sbeishuolti/
        print('汪,汪,汪')

class God:
    def specialty(self):
        print('我是雷神托尔')

class God_Dog(Dog,god):
	pass  # 自己的属性
'''
    def specialty(self):
        print('haha')
        super().specialty()
        Dog.specialty(self)
'''

tuoer = God_Dog()
tuoer.specialty()

第一种:super().play()   # 祖宗类
第二种:God.specialty(self)


class Base:
    def play(self):
        print('这是个鬼')

xiaoming = Base()
print(Base.mro())
或
print(Son.__mro__)	#可以查看继承顺序

#面向对象三大特性:封装、继承、鸭子类型(多态)

运用

Mix-in设计模式

知识点三:__ new __

__ new __ (cls[,...])的参数,__ new __ 方法的第一个参数是这个类,而其余的参数会在调用成功后全部传递给 __ init __ 方法初始化。

所以, __ new __ 方法(第一个执行)先于 __ init __ 方法执行:

我们比较两个方法的参数,可以发现__new__方法是传入类(cls),而__init__方法传入类的实例化对象(self),而有意思的是,__ new __ 方法返回的值就是一个实例化对象(ps:如果__new__方法返回None,则__init__方法不会被执行,并且返回值只能调用父类中的__new__方法,而不能调用毫无关系的类的__new__方法)。

我们可以这么理解它们之间的关系,__new__是开辟疆域的大将军,而__init__是在这片疆域上辛勤劳作的小老百姓,只有__new__执行完后,开辟好疆域后,__init__才能工作。

class Base:
    def __init__(self):
        print('这是初始化方法里面')
    def __new__(cls, *args, **kwargs):
        print('这个cls是:',cls)  # cls 就是Base类
        print('这是在new方法里面')
        return object.__new__(cls)  # 必须有返回值
#实例的时候会先调用_new_方法,然后再调用初始化方法
test = Base()

绝大多数情况下,我们都不需要自己重写__new__方法,但在当继承一个不可变的类型(例如str类,int类等)时,它的特性就尤显重要了。我们举下面这个例子:

INIT

class CapStr(str):
    def __init__(self,string):
        self = string.upper()
 
a = CapStr("I love China!")
print(a)

NEW

class CapStr(str):
    def __new__(cls, string):
        string = string.upper()
        return super().__new__(cls, string)

我们可以根据上面的理论可以这样分析,我们知道字符串是不可改变的,所以第一个例子中,传入的字符串相当于已经被打下的疆域,而这块疆域除了将军其他谁也无法改变,__init__只能在这块领地上干瞪眼,此时这块疆域就是”I love China!“。而第二个例子中,__new__大将军重新去开辟了一块疆域,所以疆域上的内容也发生了变化,此时这块疆域变成了”I LOVE CHINA!“。

小结:__new____init__相互配合才是python中真正的类构造器。

1、单例模式(难点)

单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。

比如,某个服务器程序的配置信息存放在一个文件中,客户端通过一个 AppConfig 的类来读取配置文件的信息。如果在程序运行期间,有很多地方都需要使用配置文件的内容,也就是说,很多地方都需要创建 AppConfig 对象的实例,这就导致系统中存在多个 AppConfig 的实例对象,而这样会严重浪费内存资源,尤其是在配置文件内容很多的情况下。事实上,类似 AppConfig 这样的类,我们希望在程序运行期间只存在一个实例对象。

在 Python 中,我们可以用多种方法来实现单例模式

class Person:
    pass
xiaoming = Person()
xiaohong = Person()
print(id(xiaoming))
print(id(xiaohong)) 地址是不是都是不一样的

# 单例模式要实现的效果就是--- 每一次实例化所创建的实例都是同一个,内存地址都是一样的

class A:
    _instance = None     # 实例
    def __new__(cls,*args, **kwargs):
        if cls._instance == None:
            cls._instance = object.__new__(cls)
            return cls._instance
        else:
            return cls._instance

a = A()
b = A()
print(id(a))
print(id(b))
----------------------------------
class Person:
    def __new__(cls, *args, **kwargs):   # self 实例本身   cls 类本身
        if not hasattr(cls,'instance'):  
            cls.instance = super().__new__(cls)
        return cls.instance
    
    def __init__(self, name):
        self.name = name

xiaoming = Person('小明')
laowang = Person('老王')
print(id(xiaoming))
print(id(laowang))

print(laowang.name)
print(xiaoming.name)

单例的运用:任务管理器 回收站 项目日志 多线程的线程池的设计一般也是采用单例模式

posted @ 2023-08-31 21:24  csh逐梦  阅读(9)  评论(0编辑  收藏  举报