本文章最初发布在 XJHui’s Blog,未经允许,任何人禁止转载!
注意:最新修改版本已发布在 这里,点击前往查看!
析构方法
当一个对象被删除或者被销毁时,python解释器默认会调用一个_del_()方法也叫析构方法。
方法特点
- _del_()方法是一个魔术方法
- 程序运行结束会释放所有内存,就是通过调用del方法实现的
- 在程序中删除一个对象也会调用del方法
使用方法
情况一:程序结束,解释器自动调用del方法释放内存:
1
2
3
4
5
6
7
8
9
10class Animal:
def __init__(self, name):
self.name = name
print('对象【{}】已经创建!'.format(self.name))
def __del__(self):
print('正在回收内存,对象【{}】已被删除!'.format(self.name))
cat = Animal('小花猫')运行结果:
情况二:程序中存在主动删除对象的内容:
1
2
3
4
5
6
7
8
9
10
11
12class Animal:
def __init__(self, name):
self.name = name
print('对象【{}】已经创建!'.format(self.name))
def __del__(self):
print('正在回收内存,对象【{}】已被删除!'.format(self.name))
cat = Animal('小花猫')
del cat
inPut = input('程序等待中...') # 让程序一直运行避免与1相矛盾运行结果:
OOP三大特征
封装
继承
多态
封装
定义:把内容封装到某个地方,便于以后使用
使用:通过初始化方法(init)将内容封装到对象中,然后通过对象直接获取或通过self获取
继承
单继承
定义:子类可以继承父类的内容【属性和方法】(父类有的子类都有,但子类有的父类不一定有)
案例:创建两个对象,其方法分别如下:
思路1:
1
2
3
4
5
6
7
8
9
10class Cat:
喵喵叫
吃
喝
class Dog:
汪汪叫
吃
喝
cat = Cat()
dog = Dog()思路2:共有方法放在同一个类中
1
2
3
4
5
6
7
8
9class Animal:
吃
喝
class Cat(Animal): # 继承动物类
喵喵叫
class Dog(Animal): # 继承动物类
汪汪叫
cat = Cat()
dog = Dog()注意:比较上面两种思路,2nd代码更简洁,这也是继承的优点
单继承子类语法:
1
2class 子类名(父类名): # 子类继承父类
代码块总结:
- 将多个类中共有的方法、属性提取到【父类】中,而特有的方法在各自类【子类】中。
- 继承可以极大地提高代码效率,避免代码过度冗余
多继承
继承单个父类称为单继承,继承多个父类就是多继承
多继承子类语法:
1
2class 子类名(父类1名, 父类2名 ...): # 子类继承父类,多个父类之间使用逗号分隔
代码块案例:创建孙悟空【类】其继承自神仙【类】和猴子【类】,并为其实例化一个对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class Shenxian:
def fly(self):
print('神仙会飞!')
class Monky:
def chitao(self):
print('猴子喜欢吃桃!')
class Sunwukong(Shenxian, Monky): # 多继承中多个父类之间使用逗号分隔
def __init__(self, name):
self.name = name
print('创建【{}】对象成功!'.format(self.name))
swk = Sunwukong('孙悟空')
swk.fly() # 调用Shenxian【类】的方法
swk.chitao() # 调用Monky【类】的方法运行结果:
调试时输出异常:
本以为是python版本的原因才会与老师的输出结果不同,直到百度到下面这句话:
再看一眼我代码:
总结,感谢 rayshaw13:
- 当方法中已经使用了print,调用时不要再次使用,否则会多输出一个None
- 若必须使用print,那也可以在方法中使用return来避免多输出内容
同名方法
当多个父类中存在相同的方法的时候,应该调用哪一个?
案例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class D:
def eat(self):
print('D.eat()')
class C(D): # C【类】继承D【类】
def eat(self):
print('C.eat()')
class B(D): # B【类】继承D【类】
pass
class A(B, C): # A【类】继承B【类】、C【类】
pass
a = A() # 创建A类的实例对象
a.eat() # 调用a的eat方法,判断该eat方法属于谁运行结果:
根据上例可知,其符合【广度优先遍历】的原则:
__mro__方法:
查看类的继承顺序(优先级)
1
print(A.mro()) # 注意是A.mro()【A类】 而不是实例对象a
运行结果:
总结:将此顺序与1st中的【最终顺序】比较可知是一致的。
继承的传递
子类继承父类,孙子类继承子类,孙子类可调用【父类】的方法
案例:判断下面程序能否正常输出:
1
2
3
4
5
6
7
8
9
10
11
12class D:
def eat(self):
print('D.eat()')
class C(D):
pass
class A(C):
pass
a = A()
a.eat()运行结果:
使用mro方法查看类的继承顺序:
1
print(A.mro())
运行结果:
总结:可以使用任意祖先【类】的方法。
重写方法
是什么?
在子类中有一个和父类相同名字的方法,子类中的方法会覆盖掉父类中的方法。
为什么?
父类的方法已经不能满足子类的需要,那么子类可以重写父类或者完善父类中的方法。
案例1:创建Keji【类】继承自父类Dog【类】,并重写父类Bark方法
1
2
3
4
5
6
7
8
9
10
11
12class Dog: # 父类Dog【类】
def Bark(self): # 父类方法
print('汪汪汪...')
class Keji(Dog): # 子类Keji【类】,继承父类Dog【类】
def Bark(self): # 重写父类方法
print('嗷嗷嗷...')
kj = Keji() # 创建实例对象
kj.Bark() # 调用之类中法重写后的Bark方法运行结果:
案例2:在父类方法基础上进行修改,以在init中添加实例属性为例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class Dog:
def __init__(self, name, color): # 父类方法中原有2个参数
self.name = name
self.color = color
class Keji(Dog):
def __init__(self, name, color, height): # 在父类方法的基础上填加1个新的参数
super().__init__(name, color) # 自动找到父类中的init方法,法1
# 法2:Dog.__init__(self, name, color) 注意self不能省略
self.height = height
def __str__(self):
return '【{}】 的颜色是:{} 身高是:{}'.format(self.name, self.color, self.height)
kj = Keji('路由器', 'black', '10')
print(kj)运行结果:
案例3:在父类方法基础上进行修改,以普通方法为例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26class Dog: # 父类Dog【类】
def __init__(self, name, color):
self.name = name
self.color = color
def Bark(self): # 父类方法
print('汪汪汪...')
class Keji(Dog): # 子类Keji【类】,继承父类Dog【类】
def __init__(self, name, color, height):
super().__init__(name, color)
# Dog.__init__(self, name, color)
self.height = height
def __str__(self):
return '【{}】 的颜色是:{} 身高是:{}'.format(self.name, self.color, self.height)
def Bark(self): # 重写父类方法
super().Bark()
print('嗷嗷嗷...')
kj = Keji('路由器', 'black', '10') # 创建实例对象
print(kj)
kj.Bark()运行结果:
类、实例属性
创建和使用
类属性:就是类对象所拥有的属性,它被所有类对象的实例对象所共有,类对象和实例对象均可以访问
1
2
3
4
5
6
7class Test:
name = '路由' # 类属性
test = Test() # 创建实例变量
print(test.name) # 通过实例变量访问类属性
print(Test.name) # 通过类变量访问类属性运行结果:
实例属性:实例对象所拥有的属性,只能通过实例对象访问
1
2
3
4
5
6
7
8
9
10class Test:
name = '路由' # 类属性
def __init__(self, age): # 使用init方法定义实例属性
self.age = age
test = Test(10) # 创建实例变量
print(test.age) # 通过实例变量访问实例属性
print(Test.age) # 通过类变量访问实例属性(错误)运行结果:
属性的修改
类属性的修改:
错误方法:通过实例对象修改类属性
1
2
3
4
5
6
7class Test:
name = '路由' # 类属性
test = Test() # 创建实例对象
print(test.name) # 打印类属性
test.name = '湘湘' # 通过实例对象修改类属性
print(Test.name) # 再次打印类属性运行结果:
注意:根据运行结果可知,通过实例对象修改类属性是行不通的,上面的做法只是新创建了一个实例属性。
正确方法:通过类对象修改类属性
1
2
3
4
5
6
7
8class Test:
name = '路由' # 类属性
test = Test() # 创建实例对象
print(test.name) # 打印类属性
Test.name = '湘湘' # 通过实例对象修改类属性的值
print(Test.name) # 再次打印类属性运行结果:
总结:实例对象只拥有类属性的使用权,而修改权归类对象所有。
实例属性的修改:
实例属性只能通过实例对象访问,修改肯定也只能通过实例对象修改
1
2
3
4
5
6
7
8
9
10
11class Test:
name = '路由' # 类属性
def __init__(self, age):
self.age = age # 实例属性
test = Test(2) # 创建实例对象
print(test.age) # 打印实例属性
test.age = 2.5
print(test.age)运行结果:
类、静态方法
类方法
区别实例方法:
1
2
3
4
5
6
7
8
9
10
11class People:
name = '湘湘'
# 实例方法
def printData(self):
return self.name
# 实例方法
def printData(cls): # 区别2:类方法,默认参数为cls(可修改但不可省略)
return cls.name创建、使用:
1
2
3
4
5
6
7
8
9
10
11class People:
name = '湘湘'
def printData(cls):
return cls.name # 返回类属性
print(People.printData()) # 通过类对象调用类方法
p1 = People()
print(p1.printData()) # 通过实例对象调用类方法运行结果:
使用类方法修改类属性:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class People:
name = '湘湘'
def printData(cls):
return cls.name # 返回类属性
def changName(cls, data):
cls.name = data # 通过类对象修改类属性
print(People.printData())
People.changName('路由')
print(People.printData())运行结果:
静态方法
区别实例方法:
1
2
3
4
5
6
7
8
9
10
11class People:
name = '湘湘'
# 实例方法
def printData(self):
return self.name
# 静态方法
def getdata(): # 区别2:静态方法无默认参数
return People.name创建、使用:
1
2
3
4
5
6
7
8
9
10
11class People:
name = '湘湘'
def getdata():
return People.name
print(People.getdata()) # 通过类对象访问静态变量
p1 = People()
print(p1.getdata()) # 通过实例对象访问静态变量运行结果:
注意:一般情况下是不会通过实例对象去调用静态方法的。
为什么使用静态方法?
- 主要用来存放逻辑性的代码,和类以及实例对象没有交互
- 因为可以直接通过类对象调用,从而避免了因创建实例对象而消耗的内存和空间
案例:返回系统时间
1
2
3
4
5
6
7
8
9
10import time # 导入第三方的包
class Time: # 时间类
def getTime():
return time.strftime('%H:%M:%S', time.localtime())
print(Time.getTime()) # 通过类变量调用静态方法运行结果:
实例、类、静态方法
- 实例方法:第一个参数是self(默认,可修改但不可省略),通过self可引用类属性或实例属性。若同时存在类属性和实例属性,实例属性优先级更高。
- 类方法:第一个参数是cls(默认,可修改但不可省略),通过cls可引用类对象的属性和方法。
- 静态方法:无默认参数,使用时必须通过类对象调用。
多态
基本概念
含义:
定义时的类型和使用时的类型不一样,此时就成为多态【同一种行为对于不同的子类对象有不同的行为表现】。
必须要遵守的条件:
- 继承:多态必须发生在子类和父类之间
- 重写:子类重写父类的方法
使用方法
案例:
1
2
3
4
5
6
7
8
9
10
11
12class Animal:
def showData(self):
print('这是个动物类!')
class Duck(Animal): # 有继承
def showData(self): # 有重写
print('这是个鸭子类!')
duck = Duck()
duck.showData()运行结果:
总结:有继承和重写就是多态【个人看法】
优点:
- 增加程序的灵活性
- 增加程序的拓展性
鸭子类型
当看到一只鸟,走起路来像鸭子,游起泳来像鸭子,叫起来像鸭子,那么就可以把这只鸟承做鸭子。
案例(本案例与多态无关):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Animal: # 动物类
def showData(self):
print('这是个动物类!')
class People: # 人类
def showData(self):
print('这是个人类!')
def Func(obj): # 调用传入对象的showData方法
obj.showData()
listA = [Animal(), People()] # 创建两个实例对象
for obj in listA: # 枚举列表中的每个实例对象
Func(obj) # 将枚举的对象作为参数传入Func函数中运行结果:
总结:上面案例中obj就是鸭子类型的参数,因为无论该参数是哪个对象的实例只要其包含showData方法,就能成功调用。
不足之处,欢迎留言,会及时回复,及时更正!
创作不易,感谢支持!