python之__init__()、__call__()、__str__()、__del()__和__all__,以及pytorch的nn.Module的forward函数在实例化的时候不需要被调用。

python基础语言之__init__()、call()、str()、__del()__和__all__的用法及讲解,以及为什么pytorch的nn.Module的forward函数在实例化的时候不需要被调用?

下面的所有内容都来自于这些链接:
1、python基础语言之__init__()call()str()和__all__的用法及讲解:https://blog.csdn.net/lixiaosenlin/article/details/91040112
2、Python中的__init__()和__call__()函数:https://blog.csdn.net/yaokai_assultmaster/article/details/70256621
3、Python del()方法:销毁对象:http://c.biancheng.net/view/2371.html
4、为什么pytorch的nn.Module的forward函数在实例化的时候不需要被调用?:https://zhuanlan.zhihu.com/p/392233393

Python init()

在Python中,init()函数的意义等同于类的构造器(同理,del()等同于类的析构函数)。因此,init()方法的作用是初始化一个类的实例。

class Test():
    
    def __init__(self):
        print("我是Test的构造函数,我将在类被实例化时调用")
 
t = Test()#此时会输出“我是Test的构造函数,我将在类被实例化时调用”

构造函数将会在类被实例化时调用,也就是说不管在哪里只要遇到Test(),那么类的构造函数就会被调用。
因为类在被初始化时会调用构造函数,所以一般情况下,构造函数会用于初始化一些类的基本数据所用。

Python call()

这个方法是python中的一个特殊方法,虽然不是很常见但有时候也会用得到。通常情况下我们在调用一个函数的时候,都是通过函数名()的形式来调用,如果调用的是类中的函数则是由类的对象名称.函数名()进行调用,这类函数被称为可调用对象。所有的函数都是可调用对象,同样一个类的实例也可以变成一个可调用对象。只需要实现这个特殊的函数__call__()即可通过类的实例名()的形式来使用。

class Test():
    def __call__(self):
        print('我是一个特殊的方法,可以让类变成可调用对象')
 
#调用方式1
t = Test()
t()
 
#调用方式2
Test()()
 
#输出结果
'我是一个特殊的方法,可以让类变成可调用对象'

Python str()

这个函数也是python中比较常见的函数,其主要用来返回一个对象的描述信息,比如一个类有name,age,address等属性,那么就可以通过定义__str__()函数来返回这个类的描述信息。用法就是直接使用这个类的对象即可,一般用于打印输出等。

class Test():
    def __init__(self,name,age,addr):
        self.name = name
        self.age = age
        self.address = addr
    
    def __str__(self):
        print("我可以输出对象的描述信息")
        return '我的名字是:%s,我的年龄是:%s,我的地址:%s' % (self.name,self,age,self.address)
 
 
#调用
t = Test('alvin',28,'hangzhoushi')
print(t)
 
#输出结果:
"我可以输出对象的描述信息"
"我的名字是:alvin,我的年龄是:28,我的地址:hangzhoushi"

Python del()

del() 方法,功能正好和 init() 相反,其用来销毁实例化对象。
事实上在编写程序时,如果之前创建的类实例化对象后续不再使用,最好在适当位置手动将其销毁,释放其占用的内存空间(整个过程称为垃圾回收(简称GC))。

大多数情况下,Python 开发者不需要手动进行垃圾回收,因为 Python 有自动的垃圾回收机制(下面会讲),能自动将不需要使用的实例对象进行销毁。

无论是手动销毁,还是 Python 自动帮我们销毁,都会调用 del() 方法。举个例子:

class CLanguage:
    def __init__(self):
        print("调用 __init__() 方法构造对象")
    def __del__(self):
        print("调用__del__() 销毁对象,释放其空间")
clangs = CLanguage()
del clangs

程序运行结果为:
在这里插入图片描述

但是,读者千万不要误认为,只要为该实例对象调用 del() 方法,该对象所占用的内存空间就会被释放。举个例子:

class CLanguage:
    def __init__(self):
        print("调用 __init__() 方法构造对象")
    def __del__(self):
        print("调用__del__() 销毁对象,释放其空间")
clangs = CLanguage()
#添加一个引用clangs对象的实例对象
cl = clangs
del clangs
print("***********")

程序运行结果为:
在这里插入图片描述
注意,最后一行输出信息,是程序执行即将结束时调用 del() 方法输出的。

可以看到,当程序中有其它变量(比如这里的 cl)引用该实例对象时,即便手动调用 del() 方法,该方法也不会立即执行。这和 Python 的垃圾回收机制的实现有关。

Python 采用自动引用计数(简称 ARC)的方式实现垃圾回收机制。该方法的核心思想是:每个 Python 对象都会配置一个计数器,初始 Python 实例对象的计数器值都为 0,如果有变量引用该实例对象,其计数器的值会加 1,依次类推;反之,每当一个变量取消对该实例对象的引用,计数器会减 1。如果一个 Python 对象的的计数器值为 0,则表明没有变量引用该 Python 对象,即证明程序不再需要它,此时 Python 就会自动调用 del() 方法将其回收。

以上面程序中的 clangs 为例,实际上构建 clangs 实例对象的过程分为 2 步,先使用 CLanguage() 调用该类中的 init() 方法构造出一个该类的对象(将其称为 C,计数器为 0),并立即用 clangs 这个变量作为所建实例对象的引用( C 的计数器值 + 1)。在此基础上,又有一个 clang 变量引用 clangs(其实相当于引用 CLanguage(),此时 C 的计数器再 +1 ),这时如果调用del clangs语句,只会导致 C 的计数器减 1(值变为 1),因为 C 的计数器值不为 0,因此 C 不会被销毁(不会执行 del() 方法)。

如果在上面程序结尾,添加如下语句:

del cl
print("-----------")

则程序的执行结果为:
在这里插入图片描述
可以看到,当执行 del cl 语句时,其应用的对象实例对象 C 的计数器继续 -1(变为 0),对于计数器为 0 的实例对象,Python 会自动将其视为垃圾进行回收。

需要额外说明的是,如果我们重写子类的 del() 方法(父类为非 object 的类),则必须显式调用父类的 del() 方法,这样才能保证在回收子类对象时,其占用的资源(可能包含继承自父类的部分资源)能被彻底释放。为了说明这一点,这里举一个反例:

class CLanguage:
    def __del__(self):
        print("调用父类 __del__() 方法")

class cl(CLanguage):
    def __del__(self):
        print("调用子类 __del__() 方法")
c = cl()
del c

程序运行结果为:
在这里插入图片描述

Python all

上面讲到的三个函数都是在类的内部定义实现的是类级别的,接下来要讲的这个不是一个函数而是一个列表类型的属性,它也不是定义在类的内部,而是模块级别的,定义在模块中的。

当我们导入一个模块的时候,有如下几种方式:

import xxx
from xxx import *
from xxx import x,xx

1、将整个xxx模块导入,然后用模块名xxx.xx去使用模块中的对象
2、将模块xxx中所有的对象全部导入,然后直接使用,这样的缺点就是一旦后面导入的模块跟前面导入的模块中有重名的对象,那么前面导入的将会被覆盖掉
3、只导入模块xxx中的x和xx两个对象,然后直接使用

有时候我们定义好了一个模块,有的方法或属性只想在本模块内部使用不想让外部导入,这个时候该怎么办呢?__all__就派上用场了。只需要在模块中定义一个名为__all__的列表,然后把要开放的对象即可以被外部导入使用的对象,把对象名以字符串的形式添加到该列表中即可,这样外部就只能导入和使用在列表中定义过的对象,其它的是不能导入和使用的。

#test.py模块
__all__ = ["test1","name"]
 
 
def test1():
    print("我是test1函数")
 
 
name = '我们是模块名'
 
def test2():
    print("我是test2函数,是内部函数不想让外部使用")

如上示例,不管外部是通过 import test还是通过from test import *来导入,那么可使用的对象只有test1函数和name属性,而test2是不能被外部导入和使用的

为什么pytorch的nn.Module的forward函数在实例化的时候不需要被调用?

先放出一段代码

import torch.nn as nn

# 创建一个Model类, 这个模型的功能就是给输入的数加上1
class Model(nn.Module):
    def __init__(self):
        super().__init__()

    def forward(self, input):
        output = input + 1
        print(output)

model = Model()         # 实例化
input = torch.tensor(1) # 输入为1
model(input)            # 输出为2

上面代码运行结果为
在这里插入图片描述
可以看到输出是2。我们实例化一个Model类叫做model,我们输入1,通过forward函数,所以输出=输入 + 1 = 2。

但是奇怪的一点是:我们没有用model.forward(torch.tensor(1))来传入参数1,我们直接用model(torch.tensor(1))。所以1是如何输入给forward函数的呢?答案藏在__call__函数里。

类似__call__(), init()格式是__name__() 这样的方法(函数)在python里叫做魔术方法(magic method)。魔术方法与普通方法的区别就在于,魔术方法不用调用自动执行,而普通方法需要通过调用来执行。

关于__call__()和 init()的区别见下面回答:
1、https://www.geeksforgeeks.org/call-in-python/
2、https://stackoverflow.com/questions/9663562/what-is-the-difference-between-init-and-call

回到forward函数,为什么没有通过model.forward(torch.tensor(1))传入参数给forward函数,而仅仅使用model(torch.tensor(1))却把参数1传给了forward函数呢?可以猜到,在Model继承的父类nn.Module里应该有一个__call__()直接自动调用了forward函数,而在Model这个nn.Module的子类里我们又重写了forward函数,所以我们间接地通过父类nn.Module里的__call__()调用了子类Model里的forward函数。上面阐述的利用__call__()核心可以用下面例子理解

class Person:
    def __call__(self, param):
        print('__call__ 被自动调用了')
        print('传入参数的类型是:{}   值为: {}'.format(type(param), param))

        res = self.forward(param) 
        return res

    def forward(self, input_):
        print('forward 函数被调用了')

        print('forward函数里, 传入参数类型是:{}  值为: {}'.format(type(input_), input_))
        return input_

Jason = Person()
input = Jason("60分")
print("Jason传入的参数是:", input)

输出为:
在这里插入图片描述

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值