29.Python面向对象类:主要讲初始化方法__init__,垃圾回收,继承&多继承,方法重写,super()函数
Posted 孤寒者
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了29.Python面向对象类:主要讲初始化方法__init__,垃圾回收,继承&多继承,方法重写,super()函数相关的知识,希望对你有一定的参考价值。
目录:
每篇前言:
🏆🏆作者介绍:【孤寒者】—CSDN全栈领域优质创作者、HDZ核心组成员、华为云享专家Python全栈领域博主、CSDN原力计划作者
- 🔥🔥本文已收录于Python全栈系列专栏:《Python全栈基础教程》
- 🔥🔥热门专栏推荐:《Django框架从入门到实战》、《爬虫从入门到精通系列教程》、《爬虫高级》、《前端系列教程》、《tornado一条龙+一个完整版项目》。
- 📝📝本专栏面向广大程序猿,为的是大家都做到Python从入门到精通,同时穿插有很多很多习题,巩固学习。
- 🎉🎉订阅专栏后可私聊进一千多人Python全栈交流群(手把手教学,问题解答); 进群可领取Python全栈教程视频 + 多得数不过来的计算机书籍:基础、Web、爬虫、数据分析、可视化、机器学习、深度学习、人工智能、算法、面试题等。
- 🚀🚀加入我一起学习进步,一个人可以走的很快,一群人才能走的更远!
Python面向对象(二)
1.1.1 初始化方法__init__
初始化方法,作用是在实例化的时候,自动调用。(它是一种魔法方法)
“初始化”特殊方法:
-
在Python中有很多以双下划线开头且以双下划线结尾的固定方法,它们会在特定的时机被触发执行。
__init__
就是其中之一,它会在实例化之后自动被调用。以完成实例的初始化。可以在创建对象时,为对象实现一些初始化的操作,提供一些默认值,需要为类定义实例属性的时候才会执行
__init__
方法。
# -*- coding: utf-8 -*-
"""
__author__ = 孤寒者
"""
class Person:
def __init__(self, name):
self.name = name # self.name是实例属性
def sing(self):
print('孤寒者最帅~')
wumou = Person("吴某") # 实例化 自动调用我们的初始化方法,把吴某这个名字给了name
#(这句代码意味着 wumou.name = "吴某"(定义一个实例属性) wumou.name相当于self.name,"吴某"相当于给了name)
魔法方法:每一个魔法方法都有它特定的功能。
- 小知识点:
在python中,指向被删除了,就会被回收。
del删除指向,一个变量指向没有了就会被回收。
del调用的就是__init__
魔法方法。
下面我们就来深入探讨一下:
1.1.2 python对象销毁(垃圾回收)
析构,也叫销毁初始化方法(销毁上面的那个),是在这个文件运行完然后触发。(魔法方法)
__del__就是一个析构函数,当使用del删除对象时,会调用它本身的析构函数。提示开发者,对象被销毁了,方便调试,进行一些必要的清理工作
浅显点理解就是:(当实例对象引用数为0的时候就会调用这个魔术方法)
即当类里面使用了del魔法方法的时候,如果使用了del删除,那么就会调用__del__魔法方法;如果没有使用del删除,那么在程序的最后,会自动回收内存地址,就会调用__del__魔法方法。
来两个浅显的例子讲解一下什么叫这个文件运行完然后触发:
# -*- coding: utf-8 -*-
"""
__author__ = 孤寒者
"""
# python中一个程序执行完了,内存空间就会被回收,回收掉那么指向就没有了。
class Person:
def __del__(self): #相当于重写了python的__del__方法。在列表里使用del删除时调用的是python默认的__del__方法。
print('好好学习')
guhanzhe = Person()
print(11111)
print(22222)
来个对比的例子仔细看看:
# -*- coding: utf-8 -*-
"""
__author__ = 孤寒者
"""
# 当指向删除完了之后会调用__del__魔法方法。
class Person:
def __del__(self):
print('好好学习')
guhanzhe = Person()
print(11111)
del guhanzhe
print(22222)
- Python 使用了引用计数这一简单技术来跟踪和回收垃圾。
- 在 Python 内部记录着所有使用中的对象各有多少引用。
- 一个内部跟踪变量,称为一个引用计数器。
- 当对象被创建时, 就创建了一个引用计数, 当这个对象不再需要时, 也就是说, 这个对象的引用计数变为0 时, 它被垃圾回收。但是回收不是"立即"的, 由解释器在适当的时机,将垃圾对象占用的内存空间回收。
a = 40 # 创建对象 <40>
b = a # 增加引用, <40> 的计数
c = [b] # 增加引用. <40> 的计数
del a # 减少引用 <40> 的计数
b = 100 # 减少引用 <40> 的计数
c[0] = -1 # 减少引用 <40> 的计数
-
垃圾回收机制不仅针对引用计数为0的对象,同样也可以处理循环引用的情况。循环引用指的是,两个对象相互引用,但是没有其他变量引用他们。这种情况下,仅使用引用计数是不够的。Python 的垃圾收集器实际上是一个引用计数器和一个循环垃圾收集器。作为引用计数的补充, 垃圾收集器也会留心被分配的总量很大(及未通过引用计数销毁的那些)的对象。 在这种情况下, 解释器会暂停下来, 试图清理所有未引用的循环。
-
析构函数
__del__
,__del__
在对象销毁的时候被调用,当对象不再被使用时,__del__
方法运行:
实例:
# -*- coding: utf-8 -*-
"""
__author__ = 小小明-代码实体
"""
class Point:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __del__(self):
class_name = self.__class__.__name__
print("实例%s被销毁,所属类是%s" % (self, class_name))
pt1 = Point() # 创建一个对象
pt3 = pt2 = pt1
print(id(pt1), id(pt2), id(pt3)) # 打印对象的id
del pt1
del pt2
del pt3
- 上例中,先创建了一个Point对象,然后将pt1、pt2、pt3的引用均指向该对象
打印id,发现确实都是同一个对象。 - 删除这三个引用时,就是断开对这个对象的引用
- 当三个引用全部被断开时,这个对象已经没有被任何变量引用变成"垃圾"
- 从而可能触发垃圾回收,在垃圾回收的时候会运行对象的
__del__
方法
1.1.5 类的继承
-
面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过继承机制。继承完全可以理解成类之间的类型和子类型关系。
-
继承还可以一级一级地继承下来,就好比从爷爷到爸爸、再到儿子这样的关系。而任何类,最终都可以追溯到根类object,这些继承关系看上去就像一颗倒着的树。比如如下的继承树:
-
继承可以把父类的所有功能都直接拿过来,这样就不必从零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写;
-
这样我们就可以处理功能的迭代更新,以及拓展重用代码,方便代码的管理和修改。
-
有了继承,才能有多态。在调用类实例方法的时候,尽量把变量视作父类类型,这样,所有子类类型都可以正常被接收。
-
小知识点(其实前面讲过):
类名.__bases__
可以查看类的父类有哪些,注意要print。 -
语法:
class SubClassName (ParentClass1[, ParentClass2, ...]):
'Optional class documentation string'
class_suite
在python中继承中的一些特点:
- 在继承中父类的构造(
__init__()
方法)不会被自动调用,它需要在其子类的构造中亲自专门调用。 - 在调用父类的方法时,需要加上父类的类名前缀,且需要带上self参数变量。区别于在类中调用普通函数时并不需要带上self参数
- Python总是首先查找对应类型的方法,如果它不能在派生类中找到对应的方法,它才开始到基类中逐个查找。(先在本类中查找调用的方法,找不到才去基类中找)。
- 如果在继承元组中列了一个以上的类,那么它就被称作"多重继承" 。
代码实战:
# -*- coding: utf-8 -*-
"""
__author__ = 小小明-代码实体
"""
class Parent: # 定义父类
parentAttr = 100
def __init__(self):
print("调用父类构造函数")
def parentMethod(self):
print('调用父类方法')
def method(self):
print('调用父类method方法')
class Child(Parent): # 定义子类
def __init__(self):
super().__init__()
print("调用子类构造方法")
def childMethod(self):
print('调用子类方法 child method')
def method(self):
super().method()
print('调用子类method方法')
c = Child() # 实例化子类
c.childMethod() # 调用子类的方法
c.parentMethod() # 调用父类方法
c.method() # 调用覆写的方法
1.1.6 多重继承
class A: # 定义类 A
pass
class B: # 定义类 B
pass
class C(A, B): # 继承类 A 和 B
pass
检测函数:
- issubclass() - 布尔函数判断一个类是另一个类的子类或者子孙类,语法:issubclass(sub,sup)
- isinstance(obj, Class) 布尔函数如果obj是Class类的实例对象或者是一个Class子类的实例对象则返回true。
print(issubclass(C,A))
print(issubclass(C,B))
print(issubclass(A,B))
print(issubclass(A,C))
True
True
False
False
objA, objB, objC = A(), B(), C()
print(isinstance(objA, C),isinstance(objA, B))
print(isinstance(objB, A),isinstance(objB, C))
print(isinstance(objC, A),isinstance(objC, B))
False False
False False
True True
- 代码实战:
# -*- coding: utf-8 -*-
"""
__author__ = 小小明-代码实体
"""
# 类定义
class people:
# 定义基本属性
name = ''
age = 0
# 定义私有属性,私有属性在类外部无法直接进行访问
__weight = 0
# 定义构造方法
def __init__(self, n, a, w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print("%s 说: 我 %d 岁。" % (self.name, self.age))
# 单继承示例
class student(people):
grade = ''
def __init__(self, n, a, w, g):
# 调用父类的构函
people.__init__(self, n, a, w)
self.grade = g
# 覆写父类的方法
def speak(self):
print("%s 说: 我 %d 岁了,我在读 %d 年级" % (self.name, self.age, self.grade))
# 另一个类,多重继承之前的准备
class speaker():
topic = ''
name = ''
def __init__(self, n, t):
self.name = n
self.topic = t
def speak(self):
print("我叫 %s,我是一个演说家,我演讲的主题是 %s" % (self.name, self.topic))
# 多重继承
class sample(speaker, student):
a = ''
def __init__(self, n, a, w, g, t):
student.__init__(self, n, a, w, g)
speaker.__init__(self, n, t)
test = sample("Tim", 25, 80, 4, "Python")
test.speak() # 方法名同,默认调用的是在括号中排前地父类的方法
- 如果父类A和父类B中,有一个同名的方法,那么通过子类去调用的时候,调用哪个?
# -*- coding: utf-8 -*-
"""
__author__ = 小小明-代码实体
"""
# 类定义
class base(object):
def test(self):
print('----base test----')
class A(base):
def test(self):
print('----A test----')
# 定义一个父类
class B(base):
def test(self):
print('----B test----')
# 定义一个子类,继承自A、B
class C(A,B):
pass
obj_C = C()
obj_C.test()
print(C.__mro__) #可以查看C类的对象搜索方法时的先后顺序
总结&&重点:
- 多继承优先级:从左往右,优先级由高到低。
- 如果继承的两个父类有同样的方法,那么会选择优先级高的方法,即子类会优先使用最先被继承的方法。
1.1.7 方法重写
-
对于父类的方法,只要它不符合子类模拟的实物的行为,都可对其进行重写。为此,可在子
类中定义一个这样的方法,即它与要重写的父类方法同名。这样,Python将不会考虑这个父类方
法,而只关注你在子类中定义的相应方法。 -
多继承中super调用所有父类的被重写的方法。super本质是用mor算法(后面会讲这是啥玩意)的顺序调用的。
-
父类也称为基类。
# -*- coding: utf-8 -*-
"""
__author__ = 小小明-代码实体
"""
class Parent: # 定义父类
def myMethod(self):
print('调用父类方法')
class Child(Parent): # 定义子类
def myMethod(self):
print('调用子类方法')
c = Child() # 子类实例
c.myMethod() # 子类调用重写方法
super(Child, c).myMethod() # 用子类对象调用父类已被覆盖的方法
1.1.8 结合一个代码讲一下
# -*- coding: utf-8 -*-
"""
__author__ = 孤寒者
"""
class Base:
def play(self):
print('这是Base')
class A(Base):
def play(self):
print('这是A')
super().play() # 这里调用的是Base
class B(Base):
def play(self):
print('这是B')
super().play() # 这里调用的是Base
class C(A, B):
# 当子类继承父类之后,如果子类不想使用父类的方法,可以通过重写来覆盖父类的方法
def play(self):
print('这是C')
# #重写父类方法之后,如果又需要使用父类的方法:
# B().play() #第一种方法 实例化,再使用方法
# A.play(self) #第二种方法 类名使用这个方法
super().play() # 这里调用的是A
c = C()
c.play()
print(C.__mro__) # 可以通过调用类的__mro__属性或者mro方法来查看类的继承关系
# 输出为(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Base'>, <class 'object'>)
# super可以使用父类的方法;在父类中也可以使用super函数;要满足mro算法规则
# 根据mro的顺序来决定调用的是谁
# 在python3中,类被创建时会自动创建方法解析顺序mro
1.1.9 类属性与方法
类的私有属性
__private_attrs
:两个下划线开头,声明该属性为私有,不能在类的外部被使用或直接访问。在类内部的方法中使用时self.__private_attrs
。
类的方法
- 在类的内部,使用def关键字可以为类定义一个方法,与一般函数定义不同,类方法必须包含参数self,且为第一个参数。
类的私有方法
__private_method
:两个下划线开头,声明该方法为私有方法,不能在类地外部调用。在类的内部调用self.__private_methods
。
# -*- coding: utf-8 -*-
"""
__author__ = 小小明-代码实体
"""
class JustCounter:
__secretCount = 0 # 私有变量
publicCount = 0 # 公开变量
def count(self):
self.__secretCount += 1
self.publicCount += 1
print(self.__secretCount)
counter = JustCounter()
counter.count()
counter.count()
print(counter.publicCount)
print(counter.__secretCount) # 报错,实例不能访问私有变量
- Python不允许实例化的类访问私有数据,但你可以使用
object._className__attrName
访问属性,但是强烈建议不要这么干,因为不同版本的Python解释器可能会把__attrName
改成不同的变量名。 - 将如下代码替换以上代码的最后一行代码:
print(counter._JustCounter__secretCount)
-
__foo__
: 定义的是内置方法,类似__init__()
之类的。 -
_foo
: 以单下划线开头的表示的是 protected 类型的变量
即保护类型只能允许其本身与子类进行访问,不能用于 from module import * -
__foo
: 双下划线的表示的是私有类型(private)的变量, 只能是允许这个类本身进行访问了。
1.1.10 getattr、setattr及hasattr
仅仅把属性和方法列出来是不够的,配合getattr()、setattr()以及hasattr(),我们可以直接操作一个对象的状态:
# -*- coding: utf-8 -*-
"""
__author__ = 小小明-代码实体
"""
class MyObject(object):
def __init__(self):
self.x = 9
def power(self):
return self.x * self.x
obj = MyObject()
紧接着,可以测试该对象的属性:
>>> hasattr(obj, 'x') # 有属性'x'吗?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有属性'y'吗?
False
>>> setattr(obj, 'y', 19) # 设置一个属性'y'
>>> hasattr(obj, 'y') # 有属性'y'吗?
True
>>> getattr(obj, 'y') # 获取属性'y'
19
>>> obj.y # 获取属性'y'
19
如果试图获取不存在的属性,会抛出AttributeError的错误:
>>> getattr(obj, 'z') # 获取属性'z'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MyObject' object has no attribute 'z'
可以传入一个default参数,如果属性不存在,就返回默认值:
>>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
404
也可以获得对象的方法:
>>> hasattr(obj, 'power') # 有属性'power'吗?
True
>>> getattr(obj, 'power') # 获取属性'power'
<bound method MyObject.power of <__main__.MyObject object at 0x108ca35d0>>
>>> fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn
>>> fn # fn指向obj.power
<bound method MyObject.power of <__main__.MyObject object at 0x108ca35d0>>
>>> fn() # 调用fn()与调用obj.power()是一样的
81
通过内置的一系列函数,我们可以对任意一个Python对象进行剖析,拿到其内部的数据。要注意的是,只有在不知道对象信息的时候,我们才会去获取对象信息。如果可以直接写:
sum = obj.x + obj.y
就不要写:
sum = getattr(obj, 'x') + getattr(obj, 'y')
一个正确的用法的例子如下:
def readImage(fp):
if hasattr(fp, 'read'):
return readData(fp)
return None
- 假设我们希望从文件流fp中读取图像,我们首先要判断该fp对象是否存在read方法,如果存在,则该对象是一个流,如果不存在,则无法读取。hasattr()就派上了用场。
- 请注意,在Python这类动态语言中,有read()方法,不代表该fp对象就是一个文件流,它也可能是网络流,也可能是内存中的一个字节流,但只要read()方法返回的是有效的图像数据,就不影响读取图像的功能。
1.1.11 讲一下super()函数
这个玩意非常常见又非常常用,但是又不知道怎么讲更容易理解,如果我直接拿个项目代码来讲,大家可能有点接受不了,后来突然就想到了《Python编程从入门到实战》这本书里的这部分,讲的很透彻,都是很基础的知识来讲解的,所以就贴出来:
1.1.12 导入类
-
Python允许你将类存储在模块中,然后在主程序中导入所需的模块。
-
将上述Car 类存储在一个名为car.py的模块中,使用该模块的程序都必须使用更具体的文件名,如my_car.py,其中只包含 Car 类的代码。
我们包含了一个模块级文档字符串(在该模块的最上面),对该模块的内容做了简要的描述。你应为自己
创建的每个模块都编写文档字符串。
下面来创建另一个文件——my_car.py,在其中导入 Car 类并创建其实例:
from car import Car #从一个只包含一个类的模块中导入这个类
from car import ElectricCar #从一个包含多个类的模块中导入一个类
from car import Car, ElectricCar #从一个有多个类的模块中导入多个类
import car #导入整个模块 我们使用语法 module_name.class_name 访问需要的类
from module_name import * #导入模块中的所有类 (不建议使用,如果要用,推荐使用导入整个模块)
import 语句让Python打开模块 car ,并导入其中的 Car 类。这样我们就可以使用 Car 类了,
就像它是在这个文件中定义的一样。
以上是关于29.Python面向对象类:主要讲初始化方法__init__,垃圾回收,继承&多继承,方法重写,super()函数的主要内容,如果未能解决你的问题,请参考以下文章