8. 面向对象基础(中)

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

重点:类的继承、父类的调用、静态方法

难点:继承与重写、静态方法

python类语法:类方法(类cls)、实例方法(对象self)、静态方法(无参数要求)

析构方法

  • 当一个对象被删除或者销毁时,python解释器会默认调用一个方法,这个方法时__del__()方法,也成为析构方法。
  • 程序执行结束自动调用__del__方法
  • 手动删除对象时也会自动调用__del__方法: 【del  对象】---可以手动将这个对象清空,后面将不再执行这个对象
  • 析构方法一般用于资源回收,利用del方法销毁对象回收内存等资源
class Animal:
    def __init__(self,name):
        self.name=name
        print('这时构造初始化方法')
        pass
    def __del__(self):         #魔术方法,不需要我们手动调用
          # 主要的应用就是来操作 对象的释放,一旦释放完毕,对象便不能再使用
        print('当在某个作用域下面 没有被使用【引用】的情况下 解析器会自动的调用此函数,来释放内存空间')
        print('这是析构方法')
        print('%s 这个对象 被彻底清理了 内存空间也释放了'%self.name)
        pass
    pass

 调用时使用__del__方法可能出现的情况

#调用1:当整个程序脚本执行完毕之后会自动调用__del__方法
cat=Animal('猫猫')    #执行init方法之后(程序结束),会自动调用del方法

#调用2:当整个程序脚本没有执行完毕,不会调用__del__方法
cat=Animal('猫猫')
input('程序等待中....')    #加一个input函数,将阻止程序的完成,一直在等待输入,此时不输入内容不会执行__del__析构函数

#调用3:当对象被手动销毁时也会自动调用del方法
cat=Animal('猫猫')
del cat    #提前执行__del__方法
input('程序等待中....')    #然后再等待输入。输入完成之后,不会再执行del方法

#调用4:
cat=Animal('猫猫')      #此时程序运行结束,退出程序
del cat       #手动去清理对象cat,执行了__del__方法,此时析构函数会执行
print(cat.name)     #报错!!因为前面清理了cat对象,后面就不能再使用cat了

单继承

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

封装:把内容封装到某个地方,便于后面的使用------把内容封装到某个地方、从另外一个地方去调用被封装的内容                                                                                                                                             对于面向对象的封装来说,其实就是使用初始化构造方法来将内容封装到对象里,通过对象直接或者self来获取被封装的内容

继承:和现实生活中的继承一样,子类可以继承父类的内容【属性和行为】                                             (爸爸有的儿子都有,儿子有的爸爸不一定有)                                                                                      对于面向对象的继承来说,就是将多个类共有的方法提取到父类中,子类仅需继承父类,而不需要一一实现每个方法。极大的提高效率、减少代码的重复编写、精简代码的层级结构、便于拓展。

class 类名(父类1):
    ...
    #子类就可以继承父类中的方法
    ...
    pass

多继承

        子类可以继承一个父类,那么是否可以继承两个或者多个父类呢?答案是肯定的,说就是python的多继承。

        C可以继承A和B两个类,可以将A、B的方法继承过来,C同时拥有A、B的方法和属性。

class 类名(父类A,父类B,父类C):
    ...
    #子类就可以继承父类中的方法
    ...
    pass

 问题是,当多个父类存在相同方法时,应该调用哪一个呢?

class D():
    def eat(self):
        print('D.eat')
        pass
    pass
class C(D):     #子类C的方法可以覆盖父类D的方法
    def eat(self):
        print('C.eat')
        pass
    pass
class B(D):
    pass
class A(B,C):
    pass

a=A()
a.eat()      #  结果是:C.eat
print(A.__mro__)    # 可以显示类的依次继承关系(查询方法的执行顺序)
#从结果来看,在执行eat方法时的执行顺序:(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>)

# 首先到A中去查找;如果A中没有,则继续去B类中找;
# 如果B类中没有,则去C类中去查找(平级里去查找);
# 如果C类中没有,则去D类中查找,如果还没有找到就会报错。

继承的传递:间接的继承

class GrandFather:
    def eat(self):
        print('吃的 方法')
        pass
    pass
class Father(GrandFather):
    pass
class Son(Father):
    pass
# Son类和Father类中什么方法都没有,那么是否可以继承eat这个方法呢?-----可以
son=Son()
son.eat()     #可以继承GrandFather中的eat方法(从父亲的父亲传递过来)
print(Son.__mro__)     #可以显示类的依次继承关系、查询方法的执行顺序
#如果Father类中也有 eat实例方法,那么father中的eat方法将覆盖Grandfather中的方法

重写父类和调用父类的方法:子类覆盖父类 

重写父类--------同名方法,在子类中使用新的内容

  • 在子类中,有一个和父类同名的方法,在子类中的方法就会覆盖父类中的同名方法
  • 子类覆盖父类的原因:父类方法不能满足子类的需要你,子类就可以重写父类 或者 需要进一步完善自己的方法

调用父类--------同名方法,在子类中使用父类的内容

  • 在子类中,有一个和父类同名的方法,但是在子类中还需要用父类里的属性或者内容,就需要调用父类。调用方法:
  • 父类.函数方法名(参数)Dog.__init__(self,name,color)
    super( ).函数方法名(参数)super( ).bark()
  • class Dog:
        def __init__(self,name,color):
            self.name=name
            self.color=color
            pass    
        def bark(self):
            print('汪汪叫...')
            pass
        pass
    class kejiquan(Dog):
        def __init__(self,name,color):#属于重写父类的方法,重写之后不能再调用父类里的属性,要想使用父类的属性,必须调用
            # 调用完成之后,就可以使用父类中的实例属性
            Dog.__init__(self,name,color)  #调用父类中的方法__init__:执行完毕就可以具备name,color这两个实例属性了
            # 另一种调用父类的方法
            # super().__init__(name,color)    #super()是自动找到父类 进而调用方法__init__,假设继承了对个父类,那么会按照顺序逐个的去找 去调用
            #拓展其他的属性
            self.height=90     #定义自己的属性(子类)
            self.weight=20
            pass
        def __str__(self):
            return '{}的颜色是{},身高是{},体重是{}'.format(self.name,self.color,self.height,self.weight)
        # 狗都会叫,但是柯基的声音和其他品种的狗可能不一样,因此需要父类重写
        def bark(self):         #属于重写父类的方法------子类覆盖父类
            super().bark ()      #调用父类的方法 bark   ==Dog.bark()   
            print('叫的跟神一样')
            print(self.name)
            pass
        pass
    kj=kejiquan()
    kj.bark()
    print(kj)
    #输出:
    #      汪汪叫...
    #      叫的跟神一样
    #      柯基犬
    #      柯基犬的颜色是红色,身高是90,体重是20

    多态

多态:顾名思义就是多种状态、形态,就是同一种行为对于不同的子类【对象】有不同的行为表现,有自己独特的实现方式 或者 定义时和运行时的类型不一样

要实现多态,必须要有两个前提需要遵守:

1. 必须存在一种继承关系:多态必须发生在父类和子类之间 

2. 重写:子类需要重写父类的方法 。

  满足这两个条件 我们就说是多态的体现。

3.案例演示:   同一种方法:def say_who(self)    在不同的子类中有不同的行为表现

class Animal:
    def say_who(self):
        print('我是一个动物')
        pass
    pass
class Duck(Animal):
    def say_who(self):   # 写一个和父类同名的方法,可以重写父类/覆盖父类的方法
        print('我是一只漂亮的鸭子')    #同一个行为,对不同的子类有不同的行为体现
        pass
    pass
class Dog(Animal):
    def say_who(self):    # 重写父类
        print('我是一只温顺的小狗')
        pass
    pass
class Cat(Animal):
    def say_who(self):    # 重写父类
        print('我是一只可爱的小猫')
        pass
    pass
#class Bird(Animal):
    '''
    新增鸟类
    '''
    #def say_who(self):
        #print('我是一只黄鹂鸟')
        #pass
    #pass
def commenInvoke(obj):     # 定义 统一的调用函数
    '''
    充分体现多态-------统一的调用函数
    :param obj:对象的实例
    :return:
    '''
    obj.say_who()    #将不同对象的行为say_who表现出来
    pass
listObj=[Duck(),Dog(),Cat(),Bird()]   #将子类放在列表里,方便循环
for item in listObj:    #循环每个子类
    commenInvoke(item)   #调用函数commenInvoke,进一步调用公有的行为

#此时如果想要加一个子类,只需要增加一个class Bird(Animal):....  再在liatObj中添加Bird()子类即可.
#此时可以直接增加类,不需要修改之前的代码
*********************************************************************
#输出:
#我是一只漂亮的鸭子
#我是一只温顺的小狗
#我是一只可爱的小猫
*********************************************************************
#常规的调用方式
duck1=Duck()
duck1.say_who()   #我是一只漂亮的鸭子
dog1=Dog()
dog1.say_who()     #我是一只温顺的小狗
cat1=Cat()
cat1.say_who()    #我是一只可爱的小猫
  • 在方法中,不需要关注obj是什么类型,只需要关注obj中有没有say_who()方法即可。
  • 在其它语言中是不可能实现的
  • 在’鸭子类型‘中关注的不是对象本身,而是它如何使用的
  • 不管继承的类是谁,只要是有相同的 方法say_who()就可以使用多态。
  • python天生就是支持多态,因为他是弱类型语言,不需要指定类型。

多态的好处:多态可以增加程序的灵活性、增加程序的拓展性、

类属性和实例属性

类属性:就是类对象所拥有的属性,它被所有类对象的实例对象所共有,类对象和实例对象可以访问

实例属性:实例对象所拥有的属性,只能通过实例对象访问。

类对象(可用来修改类属性的值)实例对象
类名.属性名,Person.name

对象.属性,Lm.name

  • 实例属性可以访问类属性、实例属性; 类对象只能访问类属性。实例属性只能由实例对象访问。
  • 所有实例对象的类属性指针指向同一个对象(同一个地址,共有一个类属性); 实例属性在每个实例中独有一份。
  • 类属性的值只能通过类对象修改。
class Student:
    name='李明'    
    def __init__(self,age):
        self.age=age  
        pass
    pass
#---------------通过实例对象去访问类属性、实例属性----------
lm=Student(18)      #实例对象
print(lm.name)  
print(lm.age)
#---------------通过类对象去访问类属性----------
print(Student.name)  
#---------------类对象访不能问实例属性----------
print(Student.age)     # 报错!
#--------------------------------------------------------------------------------
#---------------再申请一个实例对象 xh----------
xh=Student(28)
print(xh.name)    # 结果仍是 李明。因为所有实例对象引用的类属性都指向同一地址
xh.name='小花'    # 通过实例对象对类属性进行修改,可以吗?不可以,不会修改类属性的值,只是覆盖
print(xh.name)    #小花。 只是覆盖了类属性的值,并不是修改类属性
print(lm.name)    # 结果仍是 李明。类属性没有修改

#---------------对 类属性 进行修改(通过类对象修改,也可以类方法)----------
Student.name='李易峰'   #通过类对象修改类属性可以吗?   是可以的,因为name的所拥有的权力属于类属性
print(lm.name)    # 结果是  李易峰  。类属性被修改

类方法

类方法:类对象所拥有的方法,需要用装饰器@classmethod来标识其所拥有的方法,

    如果不想通过实例来调用类的函数属性,而直接用类调用函数方法,则这就是类方法,通过内置装饰器@calssmethod

   类名.类方法(cls类对象本身)

       既然已经知道了A类的属性A()实例对象属性是不一样的,再回到前面的实例方法概念上,实例方法是A()实例对象的方法。 既然A()实例对象有实例方法,那么A类当然也有类方法的概念了,于是可以在方法上加上@classmethod装饰器声明它是类方法,并且括号第一个参数cls是指类本身

class A(object):
    count = 0

    def __init__(self):
        self.age = 18
        self.name = "yoyo"
# A只有count属性----类属性
print(A.count)

# A() 实例化对象---实例属性
a = A()
print(a.count)
print(a.name)
print(a.age)
  • 类方法,第一个参数必须是类对象,一般以cls作为第一个参数; 
  • 类方法主要可以对类属性进行访问、修改。
  • 类方法:和类属性调用方式一样,都可以被类对象、实例对象直接调用
class People:
    country='china'
    #类方法(归类所有)  用classmethod 来进行修饰
    @classmethod     #在类方法上面,写这个标识符,直接就变成了类方法
    def get_country(cls):   #类方法,尽量用cls
        return cls.country  #访问类属性
        pass
    @classmethod
    def change_country(cls,data):
        cls.country=data    #修改类属性的值   在类方法中
        pass
    pass
print(People.get_country())   #通过类对象调用类方法(访问类属性)

p=People()                    #通过实例对象调用类方法 (访问类属性)
print('实例对象访问%s'%(p.get_country())) 

People.change_country('英国')    #通过类对象调用类方法(修改类属性)参数是类方法中需要的data
print(People.get_country()) #通过类对象去引用

p=People()                       #通过实例对象调用类方法(修改类属性)
print(p.change_country('yingguo'))
print(People.get_country())

 类方法使用场景

class DataTest(object):
    day = 0
    month = 0
    year = 0

    def __init__(self, year=0, month=0, day=0):
        self.day = day
        self.month = month
        self.year = year

    def out_date(self):
        print("year :", self.year)
        print("month :", self.month)
        print("day :", self.day)

t = DataTest(2021, 11, 12)
t.out_date()

 输出:

year : 2021
month : 11
day : 12

 如果用户输入的是 “2018-1-11” 这样的字符格式,那么就需要调用DateTest 类前做一下处理:

string_date = '2021-11-12'
year, month, day = map(int, string_date.split('-'))
s = DataTest(year, month, day)
print(s.out_date())

 先把‘2018-8-18’ 分解成 year,month,day三个变量,然后转成int,再调用DataTest(year, month, day) 也很符合期望。 那我可不可以把这个字符串处理的函数放到 DateTest 类当中呢? 那么@classmethod 就开始出场了!!

class DataTest(object):
    day = 0
    month = 0
    year = 0

    def __init__(self, year=0, month=0, day=0):
        self.day = day
        self.month = month
        self.year = year

    def out_date(self):
        print("year :", self.year)
        print("month :", self.month)
        print("day :", self.day)

    @classmethod
    def get_data(cls, string_date):
        """处理'2021-11-12'字符格式"""
        year, month, day = map(int, string_date.split('-'))
        return cls(year, month, day)

定义一个get_data类方法,处理完字符串后返回这个类的实例对象 

r = DataTest.get_data('2021-11-12')
r.out_date()

静态方法

静态方法:类对象所拥有的方法,需要用@staticmethod来表示静态方法,静态方法不需要任何参数(参数可有可无)

我们可以在函数里面写一个类,于是会想到,在类里面是不是也可以写一个函数呢?于是就有了静态方法(@staticmethod),静态方法的出现就是为了在类里面可以写一个函数,当普通的函数去调用。

尽量不与类属性、实例属性交融,和他们分开,逻辑是独立的,不依赖于某个类属性或实例属性的使用。类对象和实例对象都可以调用,但一般不用实例对象调用(因为实例对象的创建会占用内存,没有必要这样做)。

class People:
    country='china'     #类属性
    @staticmethod      #静态方法:没有参数
    def getData():
        return People.country    #静态方法:通过类对象去访问类属性
        pass
    @staticmethod      #静态方法:带参数
    def add(x,y):
        return x+y
        pass
    pass
print(People.getData())   #通过类对象访问静态方法---不用实例化也能调用

p=People()                #  实例化也能调用
print('实例对象访问%s'%(p.get_country()))   # 我们一般不会通过实例对象去访问静态方法

print(People.add(10,56))    #带参数的静态方法
  • ()可以通过类对象去访问静态方法,一般不会通过实例对象访问静态方法。
  • 为什么要使用该静态方法呢?                                                                                                            由于静态方法 主要来存放逻辑性的代码,本身和类以及实例对象没有交互, 也就是说,在静态方法中,不会涉及到类中方法和属性的操作。
  • 数据资源能够得到有效的充分利用 -------不需要创建新的对象。
  • 在某个逻辑中,不会牵扯到类对象和实例对象的使用,这个方法是完全独立的-----这个方法就可以用静态方法。

案例:返回当前时间

import time  #引入第三方的时间模块
class TimeTest:
    def __init__(self,hour,min,second):
        self.hour=hour
        self.min = min
        self.second = second
        pass
    @staticmethod
    def showTime():
        return time.strftime('%H:%M:%S',time.localtime())    #得到当前时间
        pass
    pass
#在某个逻辑中,不会牵扯到类对象和实例对象的使用,这个方法是完全独立的-----这个方法就可以用静态方法
print(TimeTest.showTime())    #输出:14:44:33 ----当前时间

t=TimeTest(2,10,15)
print(t.showTime())      #输出:14:44:33。没有必要通过这种方式访问 
#静态方法:虽然定义了实例对象,但是返回的还是当前时间

从方法角度可以看出来:

  • 类方法的第一个参数是 【类对象cls】,进而通过cls去引用的类对象的属性和方法。
  • 实例方法的第一个参数是 【实例对象self】,通过self引用的可能是类属性,也可能是实例属性。
    不过在存在相同名称的类属性和实例属性的情况下,实例属性优先级更高。
  • 静态方法中参数没有要求(也可以有参数)。类对象和实例对象均可调用。
  • 静态方法只是名义上归属类管理,但是不能使用类变量和实例变量,是类的工具包。放在函数前(该函数不传入self和cls),所以不能访问类属性和实例属性。
  • 因此在静态方法中引用类属性的话,必须通过【类对象或实例对象】来使用。 必须用装饰器 @staticmethod来修饰。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值