面向对象三大特征:继承、多态、封装
重点:类的继承、父类的调用、静态方法
难点:继承与重写、静态方法
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来修饰。