python语言基础(十三)面向对象编程(封装、继承、多态)

本文深入探讨了Python中的面向对象编程特性,包括封装、继承、多态和魔法方法。封装通过类隐藏实现细节,确保数据安全。继承允许子类复用父类功能,减少代码冗余。多态则体现了对象的动态性和灵活性,允许不同类的方法具有相同的接口。此外,文章还介绍了Python中的单继承、多继承规则以及调用顺序。魔法方法如__init__、__del__、__call__等在特定场景下自动调用,增强了类的行为。最后,文章提到了单例模式的实现,保证一个类只有一个实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

封装

封装是面向对象编程的一大特点,将属性和方法放到类的内部,通过对象访问属性或者方法,隐藏功能的实现细节,也可以设置访问权限。

class Student():
    def __init__(self,name,age):
        self.name = name     # 将属性封装到类的内部
        self.age = age
    def prin_info(self):
        print(self.name,self.age)

# 在同一个类创建多个对象之间,属性是互不干扰的
zs = Student('张三', 20)
ls = Student('李四',21)
zs.prin_info()
ls.prin_info()

继承(重点)

继承是一种创建新类的方式,如果子类需要复用父类的属性或者方法时,就可以使用继承。当然,子类也可以提供自己的属性和方法。
父类中已经有的方法,在子类中直接继承,不用再重写。避免重复造轮子,减少代码的冗余。

class Father():     # 父类 超类
    pass
class Son(Father):  # 子类 派生类
    pass

注意:在 python 中,新建的类可以继承一个或多个父类
继承作用:避免重复造轮子,减少代码的冗余

新式类与经典类

在 Python2 当中类分为新式类和经典类,如果有继承父类 object 则是新式类,否则为经典类。

class Test():       # 在python2中,经典类
    pass

class Test1(object):    #继承了object,在python2中,新式类
    pass

Test()
Test1()

但是在 Python3 当中,全部都是新式类,默认继承 object
练习:验证 Python3 中,全部都是新式类
实现思路:比较继承与无继承两个空类的成员是否一致
拓展方法:对象.dir() 查看对象的属性与方法

class Test():       # 在python2中,经典类
    pass

class Test1(object):    #继承了object,在python2中,新式类
    pass

d1 = Test()
d2 = Test1()
print(len(d1.__dir__()),d1.__dir__())  # 26个,两者方法完全一致
print(len(d2.__dir__()),d2.__dir__())  # 26个,两者方法完全一致

在object类中有很多的魔法方法,如init、str等等,创建的类直接继承了object类,可以使用object所有的方法。

单继承

从继承几个父类来划分,可以分为单继承和多继承
子类继承父类,则可以直接享受父类中已经封装好的方法
练习:用代码实现如下
在这里插入图片描述

实现代码如下

class Grandfather(object):
    def sleep(self):
        print('Grandfather sleep 11')

class Father(Grandfather):
    def eat(self):
        print('Father eat')

    def drink(self):
        print('Father drink')

class Son(Father):
    def study_python(self):
        print('Son study_python')

    def sleep(self):
        print('Son sleep 8')   # 当子类中有与父类名字相同的方法,就意味着对父类方法进行了重写

T = Son()
T.study_python()  # 自己的方法可以直接使用
T.eat()     # 通过子类的对象可以使用父类的方法, 子类中没有的,可以从父类中找
T.sleep()   # 子类中有就使用子类的方法;子类没有,去父类中找,父类中没有,则继续去父类的父类中去找

当对象调用方法时,查找顺序先从自身类找,如果自身没找到,则去父类找,父类无,再到父类的父类找,直到object类,若还无,则报错。这也称为深度优先机制
在这里插入图片描述

需要注意的是,当子类与父类拥有同名称的方法时,子类对象调用该方法优先执行自身的方法。那么实际上就是子类的方法覆盖父类的方法,也称为重写。
但是实际的开发中,遵循开放封闭原则。我们并不会完全的重写父类的方法,而是希望同时实现父类的功能。这时,我们就需要调用父类的方法了,可以通过 super()函数实现。

super()

super(type[, object-or-type]) 函数是用于调用父类(超类)的一个方法
type --> 类
object-or-type --> 对象或类,一般是 self
练习:继以上练习实现,在Son的sleep方法当中,调用父类的sleep方法。

class Grandfather(object):
    def sleep(self):
        print('Grandfather sleep 11')

class Father(Grandfather):
    pass

class Son(Father):
    def study_python(self):
        print('Son study_python')

    def sleep(self):
        print('Son sleep 8')
        # 在执行自己的方法的时候,仍然继承父类的方法
        # super(Son, self).sleep()    # 两个sleep的方法都会被调用,通过Son对象去调用父类的sleep方法
        # super().sleep()     # 两个sleep的方法都会被调用,参数不写也可以
        Grandfather.sleep(self) # 通过 类名.方法名(self),必须写self,self是当前T的对象,去调用了sleep
        # 不管在子类还是父类,self永远都是指当前实例化T的对象

T = Son()
T.sleep()

总结:
1.单继承:深度优先,先从自己找,再往父类找,然后是父类的父类找,最终的父类是object类,没有的话就报错
2.重写: 防止执行父类当中的方法
3.self永远指的都是执行该方法的调用者
4.super(当前类,self).父类中的方法

注意:
1.__init__方法也会继承,同实例方法一致,深度优先

class Father(object):
    def __init__(self):
        print('Father')

class Son(Father):
    pass
    # 如果自己有__init__方法,则会重写,深度优先
T = Son()   # Fther

2.私有属性以及私有方法没有被继承

class Father(object):
    def __init__(self):
        self.name = '张三'
        self.__age = 20

    def test(self):
        print('test')

    def __test2(self):
        print('__test2')


class Son(Father):
    def get_test(self):
        print(self.name)  # 张三
        # 私有属性不会被继承
        # print(self.__age)   # 报错 '_Son__age' Son类中没有__age属性
        self.test()     # test
        # 私有方法也不会被继承
        self.__test2    # 报错,'_Son__test2' Son类中没有__test2方法


T = Son()
T.get_test()

多继承

所谓多继承,即子类有多个父类,并且具有它们的特征(属性和方法),JAVA和C#不支持多继承,无法判断子类继承时,调用哪一个类的相同的方法。

情景1

Son类继承Father类和Father1类,两个父类均有run方法,应该继承哪一个呢?遵循左边优先的原则

class Father(object):
    def run(self):    # 实例方法
        print('Father can run')

class Father1(object):
    def run(self):
        print('Father1 can run')


class Son(Father, Father1):
    pass


T = Son()
T.run()     # Father can run  左侧优先

同时继承多个类时,写在括号左侧的类优先被继承。

情景2

Son类继承Father类和Father1类两个父类,Father类继承GrandFather类,Father1和GrandFather类中均有 sleep方法,那么用Son类去调用sleep方法时,应该继承哪一个?遵循左边一条路走到底的原则

class GrandFather(object):
    def sleep(self):
        print('GrandFather sleep')

class Father(GrandFather):
    def run(self):    # 实例方法
        print('Father can run')

class Father1(object):
    def run(self):
        print('Father1 can run')

    def sleep(self):
        print('Father1 sleep')


class Son(Father, Father1):
    pass


T = Son()
T.sleep()     # GrandFather sleep 左边一条路走到底

情景3

Son类继承Father类和Father1类两个父类,Father类和Father1类又同时继承GrandFather类,Father1类和GrandFather类均有sleep方法,那么用Son类去调用sleep方法时,应该继承哪一个?
遵循左边优先,根(同一个父类)最后执行的原则

class GrandFather(object):
    def sleep(self):
        print('GrandFather sleep')


class Father(GrandFather):
    def run(self):    # 实例方法
        print('Father can run')

class Father1(GrandFather):
    def run(self):
        print('Father1 can run')

    def sleep(self):
        print('Father1 sleep')


class Son(Father, Father1):
    pass


T = Son()
T.sleep()     # Father1 sleep 两个类有同一个根的时候,根最后执行,Father1 进行了重写父类的方法

总结多继承

python支持多继承,遵循以下原则:1.左边优先,2.一条道路走到底,3.同一个根时,根最后执行。可以通过 (类名.mro ) 方法查看代码的执行顺序。

print(Son.__mro__)  # 基于C3算法
# (<class '__main__.Son'>, <class '__main__.Father'>, <class '__main__.Father1'>, <class '__main__.GrandFather'>, <class 'object'>)

多态

python是动态语言,具有多态的特征。
多态的概念是应用于 Java 和 C# 这一类强类型语言中,而 Python 崇尚"鸭子类型"
动态语言调用实例方法时不检查类型,只要方法存在,参数正确,就可以调用。
这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
所谓多态:定义时的类型和运行时的类型不一样,此时就成为多态。

class Person(object):
    def print_self(self):
        print('自我介绍')

class Man(Person):
    def print_self(self):
        print('Man的自我介绍')

def print_self(obj):
    obj.print_self()

zs = Person()
zs.print_self()  # 自我介绍
print_self(zs)   # 自我介绍  通过函数传参,调用类

ls = Man()
ls.print_self()  # Man的自我介绍
print_self(ls)   # Man的自我介绍

一个函数实现了多个不同类方法的调用。不同功能的函数使用相同功能的函数名,就可以用函数名调用不同功能的函数。Person和Man中的print_self实现的功能不一样,可以用一个函数名去调用不同功能的方法,体现出来了多态的含义。

魔法方法介绍

在Python中,有一些内置好的特定方法,这些方法在进行特定的操作时会自动被调用,称为魔法方法。每一个魔法方法都有特定的功能,遇到特定的场景就会被访问,被调用。
魔法方法的命名总是被双下划线包围,比如__名称__

常用魔法方法

1__doc__ :用来查看类的说明文档

查看列表类的说明文档
class list(object):
    """
    list() -> new empty list
    list(iterable) -> new list initialized from iterable's items
    """
从说明文档中看到,列表也是类。
list()   # 创建列表对象的过程
print(list().__doc__)
输出的结果:
list() -> new empty list
list(iterable) -> new list initialized from iterable's items

li = list('abc')   # 其实就是实例化的过程,被赋值的变量也是对象。
print(li.__doc__)
运行的结果为:
list() -> new empty list
list(iterable) -> new list initialized from iterable's items

s = '12'
print(s.__doc__)
运行的结果为:
str(object='') -> str
str(bytes_or_buffer[, encoding[, errors]]) -> str

自己定义类的魔法属性,也可以用说明文档__doc__查看,默认继承了object类。

class Test():
    '''
    我是注释
    '''
    pass

dis = Test()
print(dis.__doc__)		# 
                        我是注释

print(dis.__module__)   #  __main__  当前类所在的模块
print(Test.__class__)   # <class 'type'> 类的类型,Test是type的对象,需要查阅type元类的资料
print(dis.__class__)    # <class '__main__.Test'> dis是Test的对象

2__dict__:用于获取类或者实例的属性字典
注意:
1.实例属性存储在对象中,所以通过 对象__dict__ 获取的是实例属性
2.除实例属性以外的成员都存储在类中,所以通过 类__dict__ 来获取

class Test():
    country = '中国'
    def __init__(self):
        self.name = 'zhang'
        self.__age = 18
    def test(self):
        self.gender = 'male'
        print('test')

dis = Test()
dis.test()  # test
print(dis.__dict__)     # 字典,{'name': 'zhang', '_Test__age': 18, 'gender': 'male'}
# 通过对象去访问,只能访问到存储在对象中的成员(实例属性),实例方法、静态方法、类方法、类属性都存放在类中。
print(Test.__dict__)    # 字典,类名去访问,就可以访问到存储在类当中的成员(除了实例属性之外的属性与方法)
print(dis.__dir__())    # 列表,类中所有的成员,__dict__更倾向于是__dir__()的子集

3__del__():叫做析构方法。当由该类创建的实例对象,被删除或者说在内存中被释放,将会自动触发执行。
总结:
1.当代码全部执行完毕才自动触发__del__()
2.如果需要提前触发,则需要通过del关键字,删除所有对象后触发__del__()

class Test():
    def __del__(self):
        print('我被回收了')

dis = Test()
print('--'*10)
print('--'*10)
# 在此处触发del,在所有的代码都执行完毕之后,才会执行__del__方法

运行的结果是:

--------------------
--------------------
我被回收了

如果在执行前加入了del,通过关键字 del 将对象删除,则会主动的执行__del__方法

class Test():
    def __del__(self):
        print('我被回收了')

dis = Test()
print('--'*10)
del dis         # 在此处触发 del
print('--'*10)

运行的结果是:
--------------------
我被回收了
--------------------
class Test():
    def __del__(self):
        print('我被回收了')

dis = Test()   # dis 指向 类的内存地址
d1 = dis	   # d1 也同时指向了类的内存地址
print('--'*10)
del dis
print('--'*10)
# 在结尾处触发 del,d1还是指向类的内存地址,仍然存在
# 只有当对象全部释放后,才会触发__del__
'''
注意:
1.不需要重写del方法,重写后就没有实际的意思,只能打印
2.python使用过程中并不需要关心内存的分配和释放,python解释器会有自动的垃圾回收机制,
我们并不需要主动的封装__del__()
若在del dis 处 同时删除 d1,则对象被完全删除了,在此处触发__del__
'''

注意:
此方法一般不需要定义,因为 Python 是一门高级语言,程序员在使用时无需关心内存的分配与释放,一般都是交给 Python 解释器来执行。所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行。
5__call__():用于将对象变成一个可调用的对象。也就是说,当一个类中有__call__()方法时,其实例化得到的对象便是可调用的(callable)

class Test():
    def __init__(self):
        print('aaa')

dis = Test()
dis()       # 相当于Test()(),TypeError: 'Test' object is not callable

使用或者重写__call__()方法可以让对象变成可以调用的

class Test():
    def __init__(self):
        print('aaa')
    def __call__(self, *args, **kwargs):
        print('我可以调用')

dis = Test()
dis()      # aaa
           # 我可以调用

6__new__():用于创建与返回一个对象。在类准备将自身实例化时调用。

class Test1(object):
    def __init__(self):
        print('__init__')

    def __new__(cls, *args, **kwargs):  # args是位置参数,kwargs是关键字参数
    	# 重写了父类的new方法,覆盖了之前的方法,对象并未创建成功,不会再触发__init__方法
        print('__new__')

T = Test1()		# __new__  调用对象的时候自动执行__new__
class A(object):
    def test(self):
        print('创建对象')

class B(A):
    def test(self):
        print('重写方法')

T = B()
T.test()	# 重写方法,重写后进行了覆盖

注意:
1__new__() 方法用于创建对象,并进行返回
2__init__() 方法在创建对象之后,自动调用执行的方法
3__new__(),重写了父类的__new__() 方法,覆盖了父类__new__() 创建对象的功能,所以对象并没有创建成功,就不会执行__init__() 方法,所以仅执行__new__() 方法内部代码。
对象创建执行顺序
1.通过__new__() 方法 创建对象
2.并将 对象返回,传给__init__()

在自定义类中实现创建对象
class Test1(object):
    def __init__(self):
        print('__init__')

    def __new__(cls, *args, **kwargs):
        print('__new__')
        # super().__new__(cls)    # 执行了父类创建对象的代码,没有将创建的代码返回除了,没有自动执行__init__,传入cls代表当前的类
        return super().__new__(cls)     # 将创建的对象返回出去,自动执行__init__方法

T = Test1()  # __new__   __init__

思路:
1.重写父类__new__() 方法
2.并且在该方法内部,调用父类的__new__() 方法
注意:
1.在创建对象时,一定要将对象返回,才会自动触发__init__()方法
2__init__()方法当中的self,实际上就是__new__返回的实例,也就是该对象

3__init__实例方法,__new__静态方法
4__init__在对象创建后自动调用,__new__创建对象的方法

单例模式介绍

单例模式是一种常用的软件设计模式。也就是说该类只包含一个实例。
通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
通常应用在一些资源管理器中,比如日志记录等。

class Single_1(object):
    def __new__(cls, *args, **kwargs):
        return super().__new__(cls)   # 重写了new方法,需要调用父类创建对象的方法,然后要返回出去

S1 = Single_1()
S2 = Single_1()
print(id(S1))       # 3066933048768
print(id(S2))       # 3066933048264 

实例化两个对象后,他们的内存地址不一样,实例多个对象,每个的内存地址也均不相同,并非单例模式。

单例模式实现

思路
1.当对象不存在时,创建对象
2.当对象存在时,永远返回当前已经创建对象,如果不重写创建对象的方法,就不能实现。

class Single_1(object):
    __isinstance = None       # 1.类属性,建立标识
    def __new__(cls, *args, **kwargs):
        if cls.__isinstance is None:  # 通过类对象可以访问到类属性,None代表当前没有创建类对象
            # 当对象不存在的时候,创建对象,并进行返回
            # return super().__new__(cls)
            # 当类属性为None的时候,创建对象赋值给当前的类属性
            # 2.将一个对象存储到__instance类属性中,此时__instance不为None,就不会再执行if语句
            cls.__isinstance = super().__new__(cls)   # 创建类属性
            # 3.返回cls.__instance,也就是创建的对象
            return cls.__isinstance
        else:
            # 4.__instance不为None,意味着已经存储着一个对象,直接进行返回
            return cls.__isinstance

S1 = Single_1()
S2 = Single_1()
S3 = Single_1()
print(id(S1))       # 2495110149232
print(id(S2))       # 2495110149232
print(id(S3))       # 2495110149232
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值