29.Python面向对象类:主要讲初始化方法__init__,垃圾回收,继承&多继承,方法重写,super()函数

Posted 孤寒者

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了29.Python面向对象类:主要讲初始化方法__init__,垃圾回收,继承&多继承,方法重写,super()函数相关的知识,希望对你有一定的参考价值。

目录:

每篇前言:


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中继承中的一些特点:

  1. 在继承中父类的构造(__init__()方法)不会被自动调用,它需要在其子类的构造中亲自专门调用。
  2. 在调用父类的方法时,需要加上父类的类名前缀,且需要带上self参数变量。区别于在类中调用普通函数时并不需要带上self参数
  3. Python总是首先查找对应类型的方法,如果它不能在派生类中找到对应的方法,它才开始到基类中逐个查找。(先在本类中查找调用的方法,找不到才去基类中找)。
  4. 如果在继承元组中列了一个以上的类,那么它就被称作"多重继承" 。

代码实战:

# -*- 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. 多继承优先级:从左往右,优先级由高到低。
  2. 如果继承的两个父类有同样的方法,那么会选择优先级高的方法,即子类会优先使用最先被继承的方法。

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()函数的主要内容,如果未能解决你的问题,请参考以下文章

29.Python面向对象类:主要讲初始化方法__init__,垃圾回收,继承&多继承,方法重写,super()函数

七面向对象之单例设计模式

Python面向对象 | 类的成员

面向对象day07:类的属性-继承-经典类

Python面向对象之单例模式

Python面向对象:一篇文章掌握面向对象的基础